fix & improve function argument compression (#1584)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 9 Mar 2017 11:11:05 +0000 (19:11 +0800)
committerGitHub <noreply@github.com>
Thu, 9 Mar 2017 11:11:05 +0000 (19:11 +0800)
- one-use function call => IIFE should take `eval()` & `arguments` into account
- if unused parameter cannot be eliminated, replace it with `0`

fixes #1583

lib/compress.js
test/compress/drop-unused.js
test/compress/reduce_vars.js

index f6b76ec..3964636 100644 (file)
@@ -281,6 +281,9 @@ merge(Compressor.prototype, {
                 if (node instanceof AST_Function
                     && (iife = tw.parent()) instanceof AST_Call
                     && iife.expression === node) {
+                    // Virtually turn IIFE parameters into variable definitions:
+                    //   (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
+                    // So existing transformation rules can work on them.
                     node.argnames.forEach(function(arg, i) {
                         var d = arg.definition();
                         d.fixed = iife.args[i] || make_node(AST_Undefined, iife);
@@ -1810,10 +1813,12 @@ merge(Compressor.prototype, {
                         node.name = null;
                     }
                     if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
-                        if (!compressor.option("keep_fargs")) {
-                            for (var a = node.argnames, i = a.length; --i >= 0;) {
-                                var sym = a[i];
-                                if (!(sym.definition().id in in_use_ids)) {
+                        var trim = !compressor.option("keep_fargs");
+                        for (var a = node.argnames, i = a.length; --i >= 0;) {
+                            var sym = a[i];
+                            if (!(sym.definition().id in in_use_ids)) {
+                                sym.__unused = true;
+                                if (trim) {
                                     a.pop();
                                     compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", {
                                         name : sym.name,
@@ -1822,7 +1827,9 @@ merge(Compressor.prototype, {
                                         col  : sym.start.col
                                     });
                                 }
-                                else break;
+                            }
+                            else {
+                                trim = false;
                             }
                         }
                     }
@@ -2609,6 +2616,9 @@ merge(Compressor.prototype, {
                 exp = def.fixed;
                 if (compressor.option("unused")
                     && def.references.length == 1
+                    && !(def.scope.uses_arguments
+                        && def.orig[0] instanceof AST_SymbolFunarg)
+                    && !def.scope.uses_eval
                     && compressor.find_parent(AST_Scope) === def.scope) {
                     self.expression = exp;
                 }
@@ -2617,16 +2627,26 @@ merge(Compressor.prototype, {
         if (compressor.option("unused")
             && exp instanceof AST_Function
             && !exp.uses_arguments
-            && !exp.uses_eval
-            && self.args.length > exp.argnames.length) {
-            var end = exp.argnames.length;
-            for (var i = end, len = self.args.length; i < len; i++) {
-                var node = self.args[i].drop_side_effect_free(compressor);
-                if (node) {
-                    self.args[end++] = node;
+            && !exp.uses_eval) {
+            var pos = 0, last = 0;
+            for (var i = 0, len = self.args.length; i < len; i++) {
+                var trim = i >= exp.argnames.length;
+                if (trim || exp.argnames[i].__unused) {
+                    var node = self.args[i].drop_side_effect_free(compressor);
+                    if (node) {
+                        self.args[pos++] = node;
+                    } else if (!trim) {
+                        self.args[pos++] = make_node(AST_Number, self.args[i], {
+                            value: 0
+                        });
+                        continue;
+                    }
+                } else {
+                    self.args[pos++] = self.args[i];
                 }
+                last = pos;
             }
-            self.args.length = end;
+            self.args.length = last;
         }
         if (compressor.option("unsafe")) {
             if (exp instanceof AST_SymbolRef && exp.undeclared()) {
index 728557a..9f3bf77 100644 (file)
@@ -761,3 +761,33 @@ assign_chain: {
         }
     }
 }
+
+issue_1583: {
+    options = {
+        keep_fargs: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        function m(t) {
+            (function(e) {
+                t = e();
+            })(function() {
+                return (function(a) {
+                    return a;
+                })(function(a) {});
+            });
+        }
+    }
+    expect: {
+        function m(t) {
+            (function(e) {
+                t = (function() {
+                    return (function(a) {
+                        return a;
+                    })(function(a) {});
+                })();
+            })();
+        }
+    }
+}
index 10dc9d9..734ce4e 100644 (file)
@@ -1144,3 +1144,111 @@ double_reference: {
         }
     }
 }
+
+iife_arguments_1: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(x) {
+            console.log(x() === arguments[0]);
+        })(function f() {
+            return f;
+        });
+    }
+    expect: {
+        (function(x) {
+            console.log(x() === arguments[0]);
+        })(function f() {
+            return f;
+        });
+    }
+}
+
+iife_arguments_2: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function() {
+            var x = function f() {
+                return f;
+            };
+            console.log(x() === arguments[0]);
+        })();
+    }
+    expect: {
+        (function() {
+            console.log(function f() {
+                return f;
+            }() === arguments[0]);
+        })();
+    }
+}
+
+iife_eval_1: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(x) {
+            console.log(x() === eval("x"));
+        })(function f() {
+            return f;
+        });
+    }
+    expect: {
+        (function(x) {
+            console.log(x() === eval("x"));
+        })(function f() {
+            return f;
+        });
+    }
+}
+
+iife_eval_2: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function() {
+            var x = function f() {
+                return f;
+            };
+            console.log(x() === eval("x"));
+        })();
+    }
+    expect: {
+        (function() {
+            var x = function f() {
+                return f;
+            };
+            console.log(x() === eval("x"));
+        })();
+    }
+}
+
+iife_func_side_effects: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(a, b, c) {
+            return b();
+        })(x(), function() {
+            return y();
+        }, z());
+    }
+    expect: {
+        (function(a, b, c) {
+            return function() {
+                return y();
+            }();
+        })(x(), 0, z());
+    }
+}