enhance `conditionals` (#3805)
authorAlex Lam S.L <alexlamsl@gmail.com>
Mon, 20 Apr 2020 01:42:13 +0000 (02:42 +0100)
committerGitHub <noreply@github.com>
Mon, 20 Apr 2020 01:42:13 +0000 (09:42 +0800)
lib/compress.js
test/compress/conditionals.js
test/compress/join_vars.js

index e40e1d2..ef032b3 100644 (file)
@@ -2363,13 +2363,33 @@ merge(Compressor.prototype, {
                 exprs = exprs.slice(0, i + 1).concat(tail);
             }
             if (defn instanceof AST_Definitions) {
-                var def = defn.definitions[defn.definitions.length - 1];
-                if (trim_assigns(def.name, def.value, exprs)) trimmed = true;
-                if (join_var_assign(defn.definitions, exprs, keep || 0)) trimmed = true;
+                keep = keep || 0;
+                for (var i = defn.definitions.length; --i >= 0;) {
+                    var def = defn.definitions[i];
+                    if (!def.value) continue;
+                    if (trim_assigns(def.name, def.value, exprs)) trimmed = true;
+                    if (merge_conditional_assignments(def, exprs, keep)) trimmed = true;
+                    break;
+                }
+                if (join_var_assign(defn.definitions, exprs, keep)) trimmed = true;
             }
             return trimmed && exprs;
         }
 
+        function merge_conditional_assignments(var_def, exprs, keep) {
+            if (!compressor.option("conditionals")) return;
+            var trimmed = false;
+            var def = var_def.name.definition();
+            while (exprs.length > keep) {
+                var cond = to_conditional_assignment(compressor, def, var_def.value, exprs[0]);
+                if (!cond) break;
+                var_def.value = cond;
+                exprs.shift();
+                trimmed = true;
+            }
+            return trimmed;
+        }
+
         function join_var_assign(definitions, exprs, keep) {
             var trimmed = false;
             while (exprs.length > keep) {
@@ -6294,11 +6314,44 @@ merge(Compressor.prototype, {
         return self;
     });
 
+    // (a = b, x && a = c) => a = x ? c : b
+    // (a = b, x || a = c) => a = x ? b : c
+    function to_conditional_assignment(compressor, def, value, node) {
+        if (!(node instanceof AST_Binary)) return;
+        if (!lazy_op[node.operator]) return;
+        if (!(node.right instanceof AST_Assign)) return;
+        if (node.right.operator != "=") return;
+        if (!(node.right.left instanceof AST_SymbolRef)) return;
+        if (node.right.left.definition() !== def) return;
+        if (value.has_side_effects(compressor)) return;
+        if (!safe_from_assignment(node.left)) return;
+        if (!safe_from_assignment(node.right.right)) return;
+        def.replaced++;
+        return node.operator == "&&" ? make_node(AST_Conditional, node, {
+            condition: node.left,
+            consequent: node.right.right,
+            alternative: value
+        }) : make_node(AST_Conditional, node, {
+            condition: node.left,
+            consequent: value,
+            alternative: node.right.right
+        });
+
+        function safe_from_assignment(node) {
+            if (node.has_side_effects(compressor)) return;
+            var hit = false;
+            node.walk(new TreeWalker(function(node) {
+                if (hit) return true;
+                if (node instanceof AST_SymbolRef && node.definition() === def) return hit = true;
+            }));
+            return !hit;
+        }
+    }
+
     OPT(AST_Sequence, function(self, compressor) {
-        if (!compressor.option("side_effects")) return self;
-        var expressions = [];
-        filter_for_side_effects();
+        var expressions = filter_for_side_effects();
         var end = expressions.length - 1;
+        merge_conditional_assignments();
         trim_right_for_undefined();
         if (end == 0) {
             self = maintain_this_binding(compressor, compressor.parent(), compressor.self(), expressions[0]);
@@ -6309,6 +6362,8 @@ merge(Compressor.prototype, {
         return self;
 
         function filter_for_side_effects() {
+            if (!compressor.option("side_effects")) return self.expressions;
+            var expressions = [];
             var first = first_in_statement(compressor);
             var last = self.expressions.length - 1;
             self.expressions.forEach(function(expr, index) {
@@ -6318,9 +6373,11 @@ merge(Compressor.prototype, {
                     first = false;
                 }
             });
+            return expressions;
         }
 
         function trim_right_for_undefined() {
+            if (!compressor.option("side_effects")) return;
             while (end > 0 && is_undefined(expressions[end], compressor)) end--;
             if (end < expressions.length - 1) {
                 expressions[end] = make_node(AST_UnaryPrefix, self, {
@@ -6330,6 +6387,22 @@ merge(Compressor.prototype, {
                 expressions.length = end + 1;
             }
         }
+
+        function merge_conditional_assignments() {
+            if (!compressor.option("conditionals")) return;
+            for (var i = 0; i < end; i++) {
+                var assign = expressions[i];
+                if (!(assign instanceof AST_Assign)) continue;
+                if (assign.operator != "=") continue;
+                if (!(assign.left instanceof AST_SymbolRef)) continue;
+                var def = assign.left.definition();
+                var cond = to_conditional_assignment(compressor, def, assign.right, expressions[i + 1]);
+                if (!cond) continue;
+                assign.right = cond;
+                expressions.splice(i + 1, 1);
+                end--;
+            }
+        }
     });
 
     OPT(AST_UnaryPostfix, function(self, compressor) {
index b78f726..26f6b73 100644 (file)
@@ -1666,3 +1666,96 @@ issue_3668: {
     }
     expect_stdout: "undefined"
 }
+
+conditional_assignments_1: {
+    options = {
+        conditionals: true,
+        sequences: true,
+    }
+    input: {
+        function f(a, b, c, d) {
+            a = b;
+            if (c) a = d;
+            return a;
+        }
+        function g(a, b, c, d) {
+            a = b;
+            if (c); else a = d;
+            return a;
+        }
+        console.log(f(0, "FAIL", 1, "PASS"), g(0, "PASS", 1, "FAIL"));
+    }
+    expect: {
+        function f(a, b, c, d) {
+            return a = c ? d : b, a;
+        }
+        function g(a, b, c, d) {
+            return a = c ? b : d, a;
+        }
+        console.log(f(0, "FAIL", 1, "PASS"), g(0, "PASS", 1, "FAIL"));
+    }
+    expect_stdout: "PASS PASS"
+}
+
+conditional_assignments_2: {
+    options = {
+        conditionals: true,
+        sequences: true,
+    }
+    input: {
+        function f1(b, c, d) {
+            a = b;
+            if (c) a = d;
+            return a;
+        }
+        function f2(a, c, d) {
+            a = b;
+            if (c) a = d;
+            return a;
+        }
+        function f3(a, b, d) {
+            a = b;
+            if (c) a = d;
+            return a;
+        }
+        function f4(a, b, c) {
+            a = b;
+            if (c) a = d;
+            return a;
+        }
+    }
+    expect: {
+        function f1(b, c, d) {
+            return a = c ? d : b, a;
+        }
+        function f2(a, c, d) {
+            return a = b, c && (a = d), a;
+        }
+        function f3(a, b, d) {
+            return a = b, c && (a = d), a;
+        }
+        function f4(a, b, c) {
+            return a = b, c && (a = d), a;
+        }
+    }
+}
+
+conditional_assignments_3: {
+    options = {
+        conditionals: true,
+        sequences: true,
+    }
+    input: {
+        console.log(function(a, b) {
+            a = "PASS";
+            if (b) a = a;
+            return a;
+        }(0, 1));
+    }
+    expect: {
+        console.log(function(a, b) {
+            return a = "PASS", b && (a = a), a;
+        }(0, 1));
+    }
+    expect_stdout: "PASS"
+}
index 078ba35..a4e0936 100644 (file)
@@ -896,3 +896,96 @@ loop_body_3: {
         for (var a, b, c; x;);
     }
 }
+
+conditional_assignments_1: {
+    options = {
+        conditionals: true,
+        join_vars: true,
+        sequences: true,
+    }
+    input: {
+        function f(b, c, d) {
+            var a = b;
+            if (c) a = d;
+            return a;
+        }
+        function g(b, c, d) {
+            var a = b;
+            if (c); else a = d;
+            return a;
+        }
+        console.log(f("FAIL", 1, "PASS"), g("PASS", 1, "FAIL"));
+    }
+    expect: {
+        function f(b, c, d) {
+            var a = c ? d : b;
+            return a;
+        }
+        function g(b, c, d) {
+            var a = c ? b : d;
+            return a;
+        }
+        console.log(f("FAIL", 1, "PASS"), g("PASS", 1, "FAIL"));
+    }
+    expect_stdout: "PASS PASS"
+}
+
+conditional_assignments_2: {
+    options = {
+        conditionals: true,
+        join_vars: true,
+        sequences: true,
+    }
+    input: {
+        function f1(c, d) {
+            var a = b;
+            if (c) a = d;
+            return a;
+        }
+        function f2(b, d) {
+            var a = b;
+            if (c) a = d;
+            return a;
+        }
+        function f3(b, c) {
+            var a = b;
+            if (c) a = d;
+            return a;
+        }
+    }
+    expect: {
+        function f1(c, d) {
+            var a = b;
+            return c && (a = d), a;
+        }
+        function f2(b, d) {
+            var a = b;
+            return c && (a = d), a;
+        }
+        function f3(b, c) {
+            var a = b;
+            return c && (a = d), a;
+        }
+    }
+}
+
+conditional_assignments_3: {
+    options = {
+        conditionals: true,
+        sequences: true,
+    }
+    input: {
+        console.log(function(b) {
+            var a = "PASS";
+            if (b) a = a;
+            return a;
+        }(0, 1));
+    }
+    expect: {
+        console.log(function(b) {
+            var a = "PASS";
+            return b && (a = a), a;
+        }(0, 1));
+    }
+    expect_stdout: "PASS"
+}