fix corner cases with `new.target` (#4784)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 16 Mar 2021 06:34:36 +0000 (06:34 +0000)
committerGitHub <noreply@github.com>
Tue, 16 Mar 2021 06:34:36 +0000 (14:34 +0800)
lib/ast.js
lib/compress.js
lib/parse.js
test/compress/functions.js
test/ufuzz/index.js

index e77ec09..22942be 100644 (file)
@@ -55,9 +55,10 @@ function DEFNODE(type, props, methods, base) {
     props.forEach(function(prop) {
         code.push("this.", prop, "=props.", prop, ";");
     });
+    code.push("}");
     var proto = base && new base;
     if (proto && proto.initialize || methods && methods.initialize) code.push("this.initialize();");
-    code.push("}}");
+    code.push("}");
     var ctor = new Function(code.join(""))();
     if (proto) {
         ctor.prototype = proto;
@@ -1818,6 +1819,9 @@ var AST_This = DEFNODE("This", null, {
 
 var AST_NewTarget = DEFNODE("NewTarget", null, {
     $documentation: "The `new.target` symbol",
+    initialize: function() {
+        this.name = "new.target";
+    },
     _validate: function() {
         if (this.name !== "new.target") throw new Error('name must be "new.target": ' + this.name);
     },
index da75677..2dc6fab 100644 (file)
@@ -916,7 +916,7 @@ merge(Compressor.prototype, {
             tw.find_parent(AST_Scope).may_call_this();
             var exp = this.expression;
             if (exp instanceof AST_LambdaExpression) {
-                var iife = !exp.name;
+                var iife = is_iife_single(this);
                 this.args.forEach(function(arg) {
                     arg.walk(tw);
                     if (arg instanceof AST_Spread) iife = false;
@@ -1591,6 +1591,19 @@ merge(Compressor.prototype, {
         return node instanceof AST_LambdaExpression ? !is_arrow(node) : is_iife_call(node);
     }
 
+    function is_iife_single(call) {
+        var exp = call.expression;
+        if (exp.name) return false;
+        if (!(call instanceof AST_New)) return true;
+        var found = false;
+        exp.walk(new TreeWalker(function(node) {
+            if (found) return true;
+            if (node instanceof AST_NewTarget) return found = true;
+            if (node instanceof AST_Scope && node !== exp) return true;
+        }));
+        return !found;
+    }
+
     function is_undeclared_ref(node) {
         return node instanceof AST_SymbolRef && node.definition().undeclared;
     }
@@ -2127,11 +2140,11 @@ merge(Compressor.prototype, {
                 var iife, fn = compressor.self();
                 if (fn instanceof AST_LambdaExpression
                     && !is_generator(fn)
-                    && !fn.name
                     && !fn.uses_arguments
                     && !fn.pinned()
                     && (iife = compressor.parent()) instanceof AST_Call
                     && iife.expression === fn
+                    && is_iife_single(iife)
                     && all(iife.args, function(arg) {
                         return !(arg instanceof AST_Spread);
                     })) {
index aaaa714..a7202cc 100644 (file)
@@ -1737,28 +1737,23 @@ function parse($TEXT, options) {
     var new_ = function(allow_calls) {
         var start = S.token;
         expect_token("operator", "new");
+        var call;
         if (is("punc", ".") && is_token(peek(), "name", "target")) {
             next();
             next();
-            return new AST_NewTarget({
-                name: "new.target",
-                start: start,
-                end: prev(),
-            });
-        }
-        var newexp = expr_atom(false), args;
-        if (is("punc", "(")) {
-            next();
-            args = expr_list(")", !options.strict);
+            call = new AST_NewTarget();
         } else {
-            args = [];
+            var exp = expr_atom(false), args;
+            if (is("punc", "(")) {
+                next();
+                args = expr_list(")", !options.strict);
+            } else {
+                args = [];
+            }
+            call = new AST_New({ expression: exp, args: args });
         }
-        var call = new AST_New({
-            start      : start,
-            expression : newexp,
-            args       : args,
-            end        : prev()
-        });
+        call.start = start;
+        call.end = prev();
         return subscripts(call, allow_calls);
     };
 
index 682b584..68ff709 100644 (file)
@@ -5753,22 +5753,102 @@ issue_4725_2: {
     node_version: ">=4"
 }
 
-new_target: {
+new_target_1: {
     input: {
-        console.log(typeof new function() {
-            return new.target;
-        }, function() {
+        new function f() {
+            console.log(new.target === f);
+        }();
+        console.log(function() {
             return new.target;
         }());
     }
     expect: {
-        console.log(typeof new function() {
-            return new.target;
-        }(), function() {
+        new function f() {
+            console.log(new.target === f);
+        }();
+        console.log(function() {
             return new.target;
         }());
     }
-    expect_stdout: "function undefined"
+    expect_stdout: [
+        "true",
+        "undefined",
+    ]
+    node_version: ">=6"
+}
+
+new_target_2: {
+    input: {
+        new function(a) {
+            if (!new.target)
+                console.log("FAIL");
+            else if (a)
+                console.log("PASS");
+            else
+                new new.target(new.target.length);
+        }();
+    }
+    expect: {
+        new function(a) {
+            if (!new.target)
+                console.log("FAIL");
+            else if (a)
+                console.log("PASS");
+            else
+                new new.target(new.target.length);
+        }();
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+new_target_collapse_vars: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        new function(a) {
+            if (a)
+                console.log("PASS");
+            else
+                new new.target(new.target.length);
+        }(0);
+    }
+    expect: {
+        new function(a) {
+            if (a)
+                console.log("PASS");
+            else
+                new new.target(new.target.length);
+        }(0);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+new_target_reduce_vars: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+    }
+    input: {
+        new function(a) {
+            if (a)
+                console.log("PASS");
+            else
+                new new.target(new.target.length);
+        }(0);
+    }
+    expect: {
+        new function(a) {
+            if (a)
+                console.log("PASS");
+            else
+                new new.target(new.target.length);
+        }(0);
+    }
+    expect_stdout: "PASS"
     node_version: ">=6"
 }
 
index c5a2e75..bc9e62a 100644 (file)
@@ -149,6 +149,7 @@ var SUPPORT = function(matrix) {
     for_of: "for (var a of []);",
     generator: "function* f(){}",
     let: "let a;",
+    new_target: "function f() { new.target; }",
     nullish: "0 ?? 0",
     rest: "var [...a] = [];",
     rest_object: "var {...a} = {};",
@@ -1401,13 +1402,16 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
             createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
                 s.push(
                     instantiate + makeFunction(name) + "(" + createParams(save_async, save_generator) + "){",
-                    strictMode(),
-                    defns()
+                    strictMode()
                 );
+                var add_new_target = SUPPORT.new_target && VALUES.indexOf("new.target") < 0;
+                if (add_new_target) VALUES.push("new.target");
+                s.push(defns());
                 if (instantiate) for (var i = rng(4); --i >= 0;) {
                     s.push((in_class ? "if (this) " : "") + createThisAssignment(recurmax, stmtDepth, canThrow));
                 }
                 s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
+                if (add_new_target) VALUES.splice(VALUES.indexOf("new.target"), 1);
             });
             generator = save_generator;
             async = save_async;