From: Alex Lam S.L Date: Mon, 20 Apr 2020 01:42:13 +0000 (+0100) Subject: enhance `conditionals` (#3805) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=a2fc32c64b52ec5582c70e7e93bc9e6bb45eac3f;p=UglifyJS.git enhance `conditionals` (#3805) --- diff --git a/lib/compress.js b/lib/compress.js index e40e1d2e..ef032b3f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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) { diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index b78f7267..26f6b73e 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -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" +} diff --git a/test/compress/join_vars.js b/test/compress/join_vars.js index 078ba351..a4e0936d 100644 --- a/test/compress/join_vars.js +++ b/test/compress/join_vars.js @@ -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" +}