enhance `arguments` (#3193)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 21 Jun 2018 06:10:37 +0000 (14:10 +0800)
committeralexlamsl <alexlamsl@gmail.com>
Sat, 23 Jun 2018 20:00:21 +0000 (04:00 +0800)
fixes #3192

lib/compress.js
lib/utils.js
test/compress/arguments.js
test/compress/drop-unused.js

index 56c8fc4..0268e07 100644 (file)
@@ -518,14 +518,15 @@ merge(Compressor.prototype, {
             var sym = node.left;
             if (!(sym instanceof AST_SymbolRef)) return;
             var d = sym.definition();
+            var safe = safe_to_assign(tw, d, sym.scope, node.right);
+            d.assignments++;
+            if (!safe) return;
             var fixed = d.fixed;
             if (!fixed && node.operator != "=") return;
-            if (!safe_to_assign(tw, d, sym.scope, node.right)) return;
             var eq = node.operator == "=";
             var value = eq ? node.right : node;
             if (is_modified(compressor, tw, node, value, 0)) return;
             d.references.push(sym);
-            d.assignments++;
             if (!eq) d.chained = true;
             d.fixed = eq ? function() {
                 return node.right;
@@ -657,7 +658,7 @@ merge(Compressor.prototype, {
                 // So existing transformation rules can work on them.
                 node.argnames.forEach(function(arg, i) {
                     var d = arg.definition();
-                    if (!node.uses_arguments && d.fixed === undefined) {
+                    if (d.fixed === undefined && (!node.uses_arguments || tw.has_directive("use strict"))) {
                         d.fixed = function() {
                             return iife.args[i] || make_node(AST_Undefined, iife);
                         };
@@ -760,11 +761,12 @@ merge(Compressor.prototype, {
             var exp = node.expression;
             if (!(exp instanceof AST_SymbolRef)) return;
             var d = exp.definition();
+            var safe = safe_to_assign(tw, d, exp.scope, true);
+            d.assignments++;
+            if (!safe) return;
             var fixed = d.fixed;
             if (!fixed) return;
-            if (!safe_to_assign(tw, d, exp.scope, true)) return;
             d.references.push(exp);
-            d.assignments++;
             d.chained = true;
             d.fixed = function() {
                 return make_node(AST_Binary, node, {
@@ -3289,6 +3291,15 @@ merge(Compressor.prototype, {
         // this scope (not in nested scopes).
         var scope = this;
         var tw = new TreeWalker(function(node, descend) {
+            if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
+                node.argnames.forEach(function(argname) {
+                    var def = argname.definition();
+                    if (!(def.id in in_use_ids)) {
+                        in_use_ids[def.id] = true;
+                        in_use.push(def);
+                    }
+                });
+            }
             if (node === self) return;
             if (node instanceof AST_Defun) {
                 var node_def = node.name.definition();
@@ -3376,8 +3387,7 @@ merge(Compressor.prototype, {
                     // any declarations with same name will overshadow
                     // name of this anonymous function and can therefore
                     // never be used anywhere
-                    if (!(def.id in in_use_ids) || def.orig.length > 1)
-                        node.name = null;
+                    if (!(def.id in in_use_ids) || def.orig.length > 1) node.name = null;
                 }
                 if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
                     var trim = !compressor.option("keep_fargs");
@@ -3389,8 +3399,7 @@ merge(Compressor.prototype, {
                                 a.pop();
                                 compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
                             }
-                        }
-                        else {
+                        } else {
                             trim = false;
                         }
                     }
@@ -6208,6 +6217,39 @@ merge(Compressor.prototype, {
                 }
             }
         }
+        var fn;
+        if (compressor.option("arguments")
+            && expr instanceof AST_SymbolRef
+            && expr.name == "arguments"
+            && expr.definition().orig.length == 1
+            && (fn = expr.scope) instanceof AST_Lambda
+            && prop instanceof AST_Number) {
+            var index = prop.getValue();
+            var argname = fn.argnames[index];
+            if (argname && compressor.has_directive("use strict")) {
+                var def = argname.definition();
+                if (!compressor.option("reduce_vars") || def.assignments || def.orig.length > 1) {
+                    argname = null;
+                }
+            } else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) {
+                while (index >= fn.argnames.length) {
+                    argname = make_node(AST_SymbolFunarg, fn, {
+                        name: fn.make_var_name("argument_" + fn.argnames.length),
+                        scope: fn
+                    });
+                    fn.argnames.push(argname);
+                    fn.enclosed.push(fn.def_variable(argname));
+                }
+            }
+            if (argname && find_if(function(node) {
+                return node.name === argname.name;
+            }, fn.argnames) === argname) {
+                var sym = make_node(AST_SymbolRef, self, argname);
+                sym.reference({});
+                delete argname.__unused;
+                return sym;
+            }
+        }
         if (is_lhs(self, compressor.parent())) return self;
         if (key !== prop) {
             var sub = self.flatten_object(property, compressor);
@@ -6251,31 +6293,6 @@ merge(Compressor.prototype, {
                 });
             }
         }
-        var fn;
-        if (compressor.option("arguments")
-            && expr instanceof AST_SymbolRef
-            && expr.name == "arguments"
-            && expr.definition().orig.length == 1
-            && (fn = expr.scope) instanceof AST_Lambda
-            && prop instanceof AST_Number) {
-            var index = prop.getValue();
-            var argname = fn.argnames[index];
-            if (!argname && !compressor.option("keep_fargs")) {
-                while (index >= fn.argnames.length) {
-                    argname = make_node(AST_SymbolFunarg, fn, {
-                        name: fn.make_var_name("argument_" + fn.argnames.length),
-                        scope: fn
-                    });
-                    fn.argnames.push(argname);
-                    fn.enclosed.push(fn.def_variable(argname));
-                }
-            }
-            if (argname) {
-                var sym = make_node(AST_SymbolRef, self, argname);
-                sym.reference({});
-                return sym;
-            }
-        }
         var ev = self.evaluate(compressor);
         if (ev !== self) {
             ev = make_node_from_constant(ev, self).optimize(compressor);
index ca4b2d4..7a51fb8 100644 (file)
@@ -52,9 +52,7 @@ function member(name, array) {
 }
 
 function find_if(func, array) {
-    for (var i = 0, n = array.length; i < n; ++i) {
-        if (func(array[i])) return array[i];
-    }
+    for (var i = array.length; --i >= 0;) if (func(array[i])) return array[i];
 }
 
 function repeat_string(str, i) {
index e8cc690..0485744 100644 (file)
@@ -5,7 +5,8 @@ replace_index: {
         properties: true,
     }
     input: {
-        console.log(arguments && arguments[0]);
+        var arguments = [];
+        console.log(arguments[0]);
         (function() {
             console.log(arguments[1], arguments["1"], arguments["foo"]);
         })("bar", 42);
@@ -21,7 +22,8 @@ replace_index: {
         })("bar", 42);
     }
     expect: {
-        console.log(arguments && arguments[0]);
+        var arguments = [];
+        console.log(arguments[0]);
         (function() {
             console.log(arguments[1], arguments[1], arguments.foo);
         })("bar", 42);
@@ -45,6 +47,37 @@ replace_index: {
     ]
 }
 
+replace_index_strict: {
+    options = {
+        arguments: true,
+        evaluate: true,
+        properties: true,
+        reduce_vars: true,
+    }
+    input: {
+        "use strict";
+        (function() {
+            console.log(arguments[1], arguments["1"], arguments["foo"]);
+        })("bar", 42);
+        (function(a, b) {
+            console.log(arguments[1], arguments["1"], arguments["foo"]);
+        })("bar", 42);
+    }
+    expect: {
+        "use strict";
+        (function() {
+            console.log(arguments[1], arguments[1], arguments.foo);
+        })("bar", 42);
+        (function(a, b) {
+            console.log(b, b, arguments.foo);
+        })("bar", 42);
+    }
+    expect_stdout: [
+        "42 42 undefined",
+        "42 42 undefined",
+    ]
+}
+
 replace_index_keep_fargs: {
     options = {
         arguments: true,
@@ -53,7 +86,8 @@ replace_index_keep_fargs: {
         properties: true,
     }
     input: {
-        console.log(arguments && arguments[0]);
+        var arguments = [];
+        console.log(arguments[0]);
         (function() {
             console.log(arguments[1], arguments["1"], arguments["foo"]);
         })("bar", 42);
@@ -69,7 +103,8 @@ replace_index_keep_fargs: {
         })("bar", 42);
     }
     expect: {
-        console.log(arguments && arguments[0]);
+        var arguments = [];
+        console.log(arguments[0]);
         (function(argument_0, argument_1) {
             console.log(argument_1, argument_1, arguments.foo);
         })("bar", 42);
@@ -93,6 +128,38 @@ replace_index_keep_fargs: {
     ]
 }
 
+replace_index_keep_fargs_strict: {
+    options = {
+        arguments: true,
+        evaluate: true,
+        keep_fargs: false,
+        properties: true,
+        reduce_vars: true,
+    }
+    input: {
+        "use strict";
+        (function() {
+            console.log(arguments[1], arguments["1"], arguments["foo"]);
+        })("bar", 42);
+        (function(a, b) {
+            console.log(arguments[1], arguments["1"], arguments["foo"]);
+        })("bar", 42);
+    }
+    expect: {
+        "use strict";
+        (function(argument_0, argument_1) {
+            console.log(argument_1, argument_1, arguments.foo);
+        })("bar", 42);
+        (function(a, b) {
+            console.log(b, b, arguments.foo);
+        })("bar", 42);
+    }
+    expect_stdout: [
+        "42 42 undefined",
+        "42 42 undefined",
+    ]
+}
+
 modified: {
     options = {
         arguments: true,
@@ -101,8 +168,10 @@ modified: {
         (function(a, b) {
             var c = arguments[0];
             var d = arguments[1];
-            a = "foo";
+            var a = "foo";
             b++;
+            arguments[0] = "moo";
+            arguments[1] *= 2;
             console.log(a, b, c, d, arguments[0], arguments[1]);
         })("bar", 42);
     }
@@ -110,10 +179,61 @@ modified: {
         (function(a, b) {
             var c = a;
             var d = b;
-            a = "foo";
+            var a = "foo";
             b++;
+            a = "moo";
+            b *= 2;
             console.log(a, b, c, d, a, b);
         })("bar", 42);
     }
-    expect_stdout: "foo 43 bar 42 foo 43"
+    expect_stdout: "moo 86 bar 42 moo 86"
+}
+
+modified_strict: {
+    options = {
+        arguments: true,
+        reduce_vars: true,
+    }
+    input: {
+        "use strict";
+        (function(a, b) {
+            var c = arguments[0];
+            var d = arguments[1];
+            var a = "foo";
+            b++;
+            arguments[0] = "moo";
+            arguments[1] *= 2;
+            console.log(a, b, c, d, arguments[0], arguments[1]);
+        })("bar", 42);
+    }
+    expect: {
+        "use strict";
+        (function(a, b) {
+            var c = arguments[0];
+            var d = arguments[1];
+            var a = "foo";
+            b++;
+            arguments[0] = "moo";
+            arguments[1] *= 2;
+            console.log(a, b, c, d, arguments[0], arguments[1]);
+        })("bar", 42);
+    }
+    expect_stdout: "foo 43 bar 42 moo 84"
+}
+
+duplicate_argname: {
+    options = {
+        arguments: true,
+    }
+    input: {
+        (function(a, b, a) {
+            console.log(a, b, arguments[0], arguments[1], arguments[2]);
+        })("foo", 42, "bar");
+    }
+    expect: {
+        (function(a, b, a) {
+            console.log(a, b, arguments[0], b, a);
+        })("foo", 42, "bar");
+    }
+    expect_stdout: "bar 42 foo 42 bar"
 }
index 59990b5..e4daa4f 100644 (file)
@@ -1926,3 +1926,31 @@ issue_3146_4: {
     }
     expect_stdout: "PASS"
 }
+
+issue_3192: {
+    options = {
+        unused: true,
+    }
+    input: {
+        (function(a) {
+            console.log(a = "foo", arguments[0]);
+        })("bar");
+        (function(a) {
+            "use strict";
+            console.log(a = "foo", arguments[0]);
+        })("bar");
+    }
+    expect: {
+        (function(a) {
+            console.log(a = "foo", arguments[0]);
+        })("bar");
+        (function(a) {
+            "use strict";
+            console.log("foo", arguments[0]);
+        })("bar");
+    }
+    expect_stdout: [
+        "foo foo",
+        "foo bar",
+    ]
+}