enhance `collapse_vars` (#3616)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sat, 30 Nov 2019 18:31:04 +0000 (02:31 +0800)
committerGitHub <noreply@github.com>
Sat, 30 Nov 2019 18:31:04 +0000 (02:31 +0800)
lib/compress.js
test/compress/collapse_vars.js

index 34700d4..6a76fc3 100644 (file)
@@ -1147,7 +1147,7 @@ merge(Compressor.prototype, {
                     && (scan_lhs && lhs.equivalent_to(node)
                         || scan_rhs && (hit_rhs = scan_rhs(node, this)))) {
                     if (stop_if_hit && (hit_rhs || !lhs_local || !replace_all)) {
-                        abort = true;
+                        if (!hit_rhs || !value_def) abort = true;
                         return node;
                     }
                     if (is_lhs(node, parent)) {
@@ -1541,14 +1541,27 @@ merge(Compressor.prototype, {
             function find_stop(node, level) {
                 var parent = scanner.parent(level);
                 if (parent instanceof AST_Array) return value_def ? find_stop(parent, level + 1) : node;
-                if (parent instanceof AST_Assign) return node;
+                if (parent instanceof AST_Assign) {
+                    if (!value_def) return node;
+                    if (lhs.equivalent_to(parent.left)) return node;
+                    if (get_rvalue(candidate).equivalent_to(parent.left)) return node;
+                    return find_stop(parent, level + 1);
+                }
                 if (parent instanceof AST_Binary) {
-                    if (!value_def || parent.left !== node) return node;
+                    if (!value_def) return node;
+                    if (lazy_op[parent.operator] && parent.left !== node) {
+                        var grandparent = scanner.parent(level + 1);
+                        if (!(grandparent instanceof AST_Binary)) return node;
+                        if (grandparent.operator != parent.operator) return node;
+                    }
                     return find_stop(parent, level + 1);
                 }
-                if (parent instanceof AST_Call) return node;
+                if (parent instanceof AST_Call) return value_def ? parent : node;
                 if (parent instanceof AST_Case) return node;
-                if (parent instanceof AST_Conditional) return node;
+                if (parent instanceof AST_Conditional) {
+                    if (!value_def || parent.condition !== node) return node;
+                    return find_stop(parent, level + 1);
+                }
                 if (parent instanceof AST_Definitions) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_Exit) return node;
                 if (parent instanceof AST_If) return node;
@@ -1562,7 +1575,10 @@ merge(Compressor.prototype, {
                 }
                 if (parent instanceof AST_SimpleStatement) return find_stop_unused(parent, level + 1);
                 if (parent instanceof AST_Switch) return node;
-                if (parent instanceof AST_Unary) return node;
+                if (parent instanceof AST_Unary) {
+                    if (parent.operator == "delete") return node;
+                    return value_def ? find_stop(parent, level + 1) : node;
+                }
                 if (parent instanceof AST_VarDef) return node;
                 return null;
             }
index 20bbfea..75dddb0 100644 (file)
@@ -6965,3 +6965,330 @@ setter_side_effect: {
     }
     expect_stdout: "PASS"
 }
+
+substitution_assign: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            f1 = b = a;
+            console.log(a, b);
+        }
+        function f2(a, b) {
+            a = 1 + (b = a);
+            console.log(a, b);
+        }
+        function f3(a, b) {
+            b = 1 + (b = a);
+            console.log(a, b);
+        }
+        f1(42, "foo");
+        f2(42, "foo");
+        f3(42, "foo");
+    }
+    expect: {
+        function f1(a, b) {
+            f1 = a;
+            console.log(a, a);
+        }
+        function f2(a, b) {
+            a = 1 + (b = a);
+            console.log(a, b);
+        }
+        function f3(a, b) {
+            b = 1 + (b = a);
+            console.log(a, b);
+        }
+        f1(42, "foo");
+        f2(42, "foo");
+        f3(42, "foo");
+    }
+    expect_stdout: [
+        "42 42",
+        "43 42",
+        "42 43",
+    ]
+}
+
+substitution_arithmetic: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            console.log((b = a) + a, b);
+        }
+        function f2(a, b) {
+            console.log(a - (b = a), b);
+        }
+        function f3(a, b) {
+            console.log(a / (b = a) + b, b);
+        }
+        f1(42, "foo");
+        f2(42, "foo");
+        f3(42, "foo");
+    }
+    expect: {
+        function f1(a, b) {
+            console.log(a + a, a);
+        }
+        function f2(a, b) {
+            console.log(a - a, a);
+        }
+        function f3(a, b) {
+            console.log(a / a + a, a);
+        }
+        f1(42, "foo");
+        f2(42, "foo");
+        f3(42, "foo");
+    }
+    expect_stdout: [
+        "84 42",
+        "0 42",
+        "43 42",
+    ]
+}
+
+substitution_logical_1: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            console.log((b = a) && a, b);
+        }
+        function f2(a, b) {
+            console.log(a && (b = a), b);
+        }
+        f1(42, "foo");
+        f1(null, true);
+        f2(42, "foo");
+        f2(null, true);
+    }
+    expect: {
+        function f1(a, b) {
+            console.log(a && a, a);
+        }
+        function f2(a, b) {
+            console.log(a && (b = a), b);
+        }
+        f1(42, "foo");
+        f1(null, true);
+        f2(42, "foo");
+        f2(null, true);
+    }
+    expect_stdout: [
+        "42 42",
+        "null null",
+        "42 42",
+        "null true"
+    ]
+}
+
+substitution_logical_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            console.log((b = a) && a && b);
+        }
+        function f2(a, b) {
+            console.log((b = a) && a || b);
+        }
+        function f3(a, b) {
+            console.log((b = a) || a && b);
+        }
+        function f4(a, b) {
+            console.log((b = a) || a || b);
+        }
+        f1(42, "foo");
+        f1(null, true);
+        f2(42, "foo");
+        f2(null, true);
+        f3(42, "foo");
+        f3(null, true);
+        f4(42, "foo");
+        f4(null, true);
+    }
+    expect: {
+        function f1(a, b) {
+            console.log(a && a && a);
+        }
+        function f2(a, b) {
+            console.log(a && a || a);
+        }
+        function f3(a, b) {
+            console.log(a || a && a);
+        }
+        function f4(a, b) {
+            console.log(a || a || a);
+        }
+        f1(42, "foo");
+        f1(null, true);
+        f2(42, "foo");
+        f2(null, true);
+        f3(42, "foo");
+        f3(null, true);
+        f4(42, "foo");
+        f4(null, true);
+    }
+    expect_stdout: [
+        "42",
+        "null",
+        "42",
+        "null",
+        "42",
+        "null",
+        "42",
+        "null",
+    ]
+}
+
+substitution_logical_3: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            console.log(a && (b = a) && b);
+        }
+        function f2(a, b) {
+            console.log(a && (b = a) || b);
+        }
+        function f3(a, b) {
+            console.log(a || (b = a) && b);
+        }
+        function f4(a, b) {
+            console.log(a || (b = a) || b);
+        }
+        f1(42, "foo");
+        f1(null, true);
+        f2(42, "foo");
+        f2(null, true);
+        f3(42, "foo");
+        f3(null, true);
+        f4(42, "foo");
+        f4(null, true);
+    }
+    expect: {
+        function f1(a, b) {
+            console.log(a && a && a);
+        }
+        function f2(a, b) {
+            console.log(a && (b = a) || b);
+        }
+        function f3(a, b) {
+            console.log(a || a && a);
+        }
+        function f4(a, b) {
+            console.log(a || a || a);
+        }
+        f1(42, "foo");
+        f1(null, true);
+        f2(42, "foo");
+        f2(null, true);
+        f3(42, "foo");
+        f3(null, true);
+        f4(42, "foo");
+        f4(null, true);
+    }
+    expect_stdout: [
+        "42",
+        "null",
+        "42",
+        "true",
+        "42",
+        "null",
+        "42",
+        "null",
+    ]
+}
+
+substitution_conditional: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            console.log((b = a) ? a : b, a, b);
+        }
+        function f2(a, b) {
+            console.log(a ? b = a : b, a, b);
+        }
+        function f3(a, b) {
+            console.log(a ? a : b = a, a, b);
+        }
+        f1("foo", "bar");
+        f1(null, true);
+        f2("foo", "bar");
+        f2(null, true);
+        f3("foo", "bar");
+        f3(null, true);
+    }
+    expect: {
+        function f1(a, b) {
+            console.log(a ? a : a, a, a);
+        }
+        function f2(a, b) {
+            console.log(a ? b = a : b, a, b);
+        }
+        function f3(a, b) {
+            console.log(a ? a : b = a, a, b);
+        }
+        f1("foo", "bar");
+        f1(null, true);
+        f2("foo", "bar");
+        f2(null, true);
+        f3("foo", "bar");
+        f3(null, true);
+    }
+    expect_stdout: [
+        "foo foo foo",
+        "null null null",
+        "foo foo foo",
+        "true null true",
+        "foo foo bar",
+        "null null null",
+    ]
+}
+
+substitution_unary: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function f1(a, b) {
+            console.log(typeof (b = a), a, b);
+        }
+        function f2(a, b) {
+            console.log(void (b = a), a, b);
+        }
+        function f3(a, b) {
+            console.log(delete (b = a), a, b);
+        }
+        f1(42, "foo");
+        f2(42, "foo");
+        f3(42, "foo");
+    }
+    expect: {
+        function f1(a, b) {
+            console.log(typeof a, a, a);
+        }
+        function f2(a, b) {
+            console.log(void a, a, a);
+        }
+        function f3(a, b) {
+            console.log(delete (b = a), a, b);
+        }
+        f1(42, "foo");
+        f2(42, "foo");
+        f3(42, "foo");
+    }
+    expect_stdout: [
+        "number 42 42",
+        "undefined 42 42",
+        "true 42 42",
+    ]
+}