enhance `collapse_vars` (#3611)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 27 Nov 2019 19:57:10 +0000 (03:57 +0800)
committerGitHub <noreply@github.com>
Wed, 27 Nov 2019 19:57:10 +0000 (03:57 +0800)
lib/compress.js
test/compress/collapse_vars.js

index 19fba0b..686a8d4 100644 (file)
@@ -1415,7 +1415,9 @@ merge(Compressor.prototype, {
 
             function extract_candidates(expr) {
                 hit_stack.push(expr);
-                if (expr instanceof AST_Assign) {
+                if (expr instanceof AST_Array) {
+                    expr.elements.forEach(extract_candidates);
+                } else if (expr instanceof AST_Assign) {
                     candidates.push(hit_stack.slice());
                     extract_candidates(expr.left);
                     extract_candidates(expr.right);
@@ -1462,6 +1464,14 @@ merge(Compressor.prototype, {
                     if (expr.alternative && !(expr.alternative instanceof AST_Block)) {
                         extract_candidates(expr.alternative);
                     }
+                } else if (expr instanceof AST_Object) {
+                    expr.properties.forEach(function(prop) {
+                        if (prop instanceof AST_ObjectKeyVal) {
+                            hit_stack.push(prop);
+                            extract_candidates(prop.value);
+                            hit_stack.pop();
+                        }
+                    });
                 } else if (expr instanceof AST_Sequence) {
                     expr.expressions.forEach(extract_candidates);
                 } else if (expr instanceof AST_SimpleStatement) {
@@ -1492,6 +1502,7 @@ merge(Compressor.prototype, {
 
             function find_stop(node, level) {
                 var parent = scanner.parent(level);
+                if (parent instanceof AST_Array) return node;
                 if (parent instanceof AST_Assign) return node;
                 if (parent instanceof AST_Binary) return node;
                 if (parent instanceof AST_Call) return node;
@@ -1501,6 +1512,7 @@ merge(Compressor.prototype, {
                 if (parent instanceof AST_Exit) return node;
                 if (parent instanceof AST_If) return node;
                 if (parent instanceof AST_IterationStatement) return node;
+                if (parent instanceof AST_ObjectKeyVal) return node;
                 if (parent instanceof AST_PropAccess) return node;
                 if (parent instanceof AST_Sequence) {
                     return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
@@ -1516,6 +1528,7 @@ merge(Compressor.prototype, {
                 var parent = scanner.parent(level);
                 if (is_last_node(node, parent)) return node;
                 if (in_conditional(node, parent)) return node;
+                if (parent instanceof AST_Array) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_Assign) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_Binary) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_Call) return find_stop_unused(parent, level + 1);
@@ -1525,6 +1538,7 @@ merge(Compressor.prototype, {
                 if (parent instanceof AST_Exit) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_If) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_IterationStatement) return node;
+                if (parent instanceof AST_ObjectKeyVal) return find_stop_unused(scanner.parent(level + 1), level + 2);
                 if (parent instanceof AST_PropAccess) {
                     var exp = parent.expression;
                     if (exp === node) return find_stop_unused(parent, level + 1);
@@ -1668,20 +1682,21 @@ merge(Compressor.prototype, {
                 var found = false;
                 return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
                     if (found) return node;
+                    if (node instanceof AST_Scope) return node;
                     if (node !== expr && node.body !== expr) return;
+                    found = true;
                     if (node instanceof AST_VarDef) {
-                        found = true;
                         node.value = null;
                         return node;
                     }
-                    if (in_list) {
-                        found = true;
-                        return MAP.skip;
-                    }
-                    if (!this.parent()) {
-                        found = true;
-                        return null;
-                    }
+                    var parent = this.parent();
+                    if (!parent) return in_list ? MAP.skip : null;
+                    if (parent instanceof AST_Sequence) return MAP.skip;
+                    var value = expr;
+                    do {
+                        value = get_rvalue(value);
+                    } while (value instanceof AST_Assign);
+                    return value;
                 }, function(node) {
                     if (node instanceof AST_Sequence) switch (node.expressions.length) {
                       case 0: return null;
index 0f1e59a..175ab9c 100644 (file)
@@ -669,7 +669,7 @@ collapse_vars_throw: {
     expect_stdout: "13"
 }
 
-collapse_vars_switch: {
+collapse_vars_switch_1: {
     options = {
         booleans: true,
         collapse_vars: true,
@@ -721,6 +721,31 @@ collapse_vars_switch: {
     }
 }
 
+collapse_vars_switch_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var c = 0;
+        (function(b) {
+            switch (b && [ b = 0, (c++, 0) ]) {
+              case c = 1 + c:
+            }
+        })();
+        console.log(c);
+    }
+    expect: {
+        var c = 0;
+        (function(b) {
+            switch (b && [ b = 0, (c++, 0) ]) {
+              case c = 1 + c:
+            }
+        })();
+        console.log(c);
+    }
+    expect_stdout: "1"
+}
+
 collapse_vars_assignment: {
     options = {
         booleans: true,
@@ -1234,7 +1259,7 @@ collapse_vars_try: {
     }
 }
 
-collapse_vars_array: {
+collapse_vars_array_1: {
     options = {
         booleans: true,
         collapse_vars: true,
@@ -1280,7 +1305,58 @@ collapse_vars_array: {
     }
 }
 
-collapse_vars_object: {
+collapse_vars_array_2: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(a) {
+            var b;
+            return [ (b = a, b.g()) ];
+        }
+        console.log(f({
+            g: function() {
+                return "PASS";
+            }
+        })[0]);
+    }
+    expect: {
+        function f(a) {
+            return [ a.g() ];
+        }
+        console.log(f({
+            g: function() {
+                return "PASS";
+            }
+        })[0]);
+    }
+    expect_stdout: "PASS"
+}
+
+collapse_vars_array_3: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(a) {
+            var b;
+            return [ b = a, b, b ];
+        }
+        console.log(f().length);
+    }
+    expect: {
+        function f(a) {
+            var b;
+            return [ b = a, b, b ];
+        }
+        console.log(f().length);
+    }
+    expect_stdout: "3"
+}
+
+collapse_vars_object_1: {
     options = {
         booleans: true,
         collapse_vars: true,
@@ -1360,6 +1436,69 @@ collapse_vars_object: {
     }
 }
 
+collapse_vars_object_2: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(a) {
+            var b;
+            return {
+                p: (b = a, b.g())
+            };
+        }
+        console.log(f({
+            g: function() {
+                return "PASS";
+            }
+        }).p);
+    }
+    expect: {
+        function f(a) {
+            return {
+                p: a.g()
+            };
+        }
+        console.log(f({
+            g: function() {
+                return "PASS";
+            }
+        }).p);
+    }
+    expect_stdout: "PASS"
+}
+
+collapse_vars_object_3: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(a) {
+            var b;
+            return {
+                p: b = a,
+                q: b,
+                r: b,
+            };
+        }
+        console.log(f("PASS").r);
+    }
+    expect: {
+        function f(a) {
+            var b;
+            return {
+                p: b = a,
+                q: b,
+                r: b,
+            };
+        }
+        console.log(f("PASS").r);
+    }
+    expect_stdout: "PASS"
+}
+
 collapse_vars_eval_and_with: {
     options = {
         booleans: true,
@@ -6624,3 +6763,45 @@ local_value_replacement: {
     }
     expect_stdout: "PASS"
 }
+
+array_in_object_1: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 2;
+        console.log({
+            p: [ a, a-- ],
+            q: a,
+        }.q, a);
+    }
+    expect: {
+        var a = 2;
+        console.log({
+            p: [ a, a-- ],
+            q: a,
+        }.q, a);
+    }
+    expect_stdout: "1 1"
+}
+
+array_in_object_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 2;
+        console.log({
+            p: [ a, (a--, 42) ],
+            q: a,
+        }.q, a);
+    }
+    expect: {
+        var a = 2;
+        console.log({
+            p: [ a, 42 ],
+            q: --a,
+        }.q, a);
+    }
+    expect_stdout: "1 1"
+}