From 80fc8625476f8ce3aada091251072359b350a990 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 3 Oct 2022 01:01:23 +0100 Subject: [PATCH] enhance `assignments`, `booleans` & `conditionals` (#5691) --- lib/compress.js | 94 +++++++++++++++++++++-------------- test/compress/assignments.js | 39 +++++++++++++++ test/compress/booleans.js | 44 ++++++++++++++++ test/compress/comparisons.js | 5 ++ test/compress/conditionals.js | 2 +- test/compress/if_return.js | 38 +++++++------- 6 files changed, 164 insertions(+), 58 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3b1c1416d2..0c2a7e27f3 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -11589,12 +11589,18 @@ Compressor.prototype.compress = function(node) { if (node instanceof AST_Unary) return true; } + function extract_lhs(node) { + if (node instanceof AST_Assign) return node.left; + if (node instanceof AST_Sequence) return extract_lhs(node.tail_node()); + if (node instanceof AST_UnaryPrefix && UNARY_POSTFIX[node.operator]) return node.expression; + return node; + } + function repeatable(compressor, node) { if (node instanceof AST_Dot) return repeatable(compressor, node.expression); if (node instanceof AST_Sub) { return repeatable(compressor, node.expression) && repeatable(compressor, node.property); } - if (node instanceof AST_Symbol) return true; return !node.has_side_effects(compressor); } @@ -11631,31 +11637,32 @@ Compressor.prototype.compress = function(node) { if (seq !== self) return seq.optimize(compressor); } if (compressor.option("assignments") && lazy_op[self.operator]) { + var lhs = extract_lhs(self.left); var right = self.right; // a || (a = x) ---> a = a || x - // a && (a = x) ---> a = a && x - if (self.left instanceof AST_SymbolRef + // (a = x) && (a = y) ---> a = (a = x) && y + if (lhs instanceof AST_SymbolRef && right instanceof AST_Assign && right.operator == "=" - && self.left.equals(right.left)) { - var left = right.left.clone(); + && lhs.equals(right.left)) { + lhs = lhs.clone(); var assign = make_node(AST_Assign, self, { operator: "=", - left: left, + left: lhs, right: make_node(AST_Binary, self, { operator: self.operator, left: self.left, right: right.right, }), }); - if (left.fixed) { - left.fixed = function() { + if (lhs.fixed) { + lhs.fixed = function() { return assign.right; }; - left.fixed.assigns = [ assign ]; + lhs.fixed.assigns = [ assign ]; } - var def = left.definition(); - def.references.push(left); + var def = lhs.definition(); + def.references.push(lhs); def.replaced++; return assign.optimize(compressor); } @@ -11710,31 +11717,32 @@ Compressor.prototype.compress = function(node) { case "||": // void 0 !== x && null !== x ---> null != x // void 0 === x || null === x ---> null == x - var lhs = self.left; - if (lhs.operator == self.operator) lhs = lhs.right; - var expr = lhs.right; - if (expr instanceof AST_Assign && expr.operator == "=") expr = expr.left; - if (lhs instanceof AST_Binary - && lhs.operator == (self.operator == "&&" ? "!==" : "===") - && self.right instanceof AST_Binary - && lhs.operator == self.right.operator - && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null - || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor)) - && !expr.has_side_effects(compressor) - && expr.equals(self.right.right)) { - lhs.operator = lhs.operator.slice(0, -1); - lhs.left = make_node(AST_Null, self); - return self.left; + var left = self.left; + if (!(left instanceof AST_Binary)) break; + if (left.operator != (self.operator == "&&" ? "!==" : "===")) break; + if (!(self.right instanceof AST_Binary)) break; + if (left.operator != self.right.operator) break; + if (is_undefined(left.left, compressor) && self.right.left instanceof AST_Null + || left.left instanceof AST_Null && is_undefined(self.right.left, compressor)) { + var expr = left.right; + if (expr instanceof AST_Assign && expr.operator == "=") expr = expr.left; + if (expr.has_side_effects(compressor)) break; + if (!expr.equals(self.right.right)) break; + left.operator = left.operator.slice(0, -1); + left.left = make_node(AST_Null, self); + return left; } break; } var in_bool = false; var parent = compressor.parent(); if (compressor.option("booleans")) { - var lhs = self.left; + var lhs = extract_lhs(self.left); if (lazy_op[self.operator] && !lhs.has_side_effects(compressor)) { + // a || a ---> a + // (a = x) && a --> a = x if (lhs.equals(self.right)) { - return maintain_this_binding(parent, compressor.self(), lhs).optimize(compressor); + return maintain_this_binding(parent, compressor.self(), self.left).optimize(compressor); } mark_duplicate_condition(compressor, lhs); } @@ -11782,13 +11790,22 @@ Compressor.prototype.compress = function(node) { case ">=": reverse("<="); break; } } - // x && (y && z) ---> x && y && z - // x || (y || z) ---> x || y || z - if (compressor.option("conditionals") - && lazy_op[self.operator] - && self.right instanceof AST_Binary - && self.operator == self.right.operator) { - swap_chain(self, compressor); + if (compressor.option("conditionals") && lazy_op[self.operator]) { + if (self.left instanceof AST_Binary && self.operator == self.left.operator) { + var before = make_node(AST_Binary, self, { + operator: self.operator, + left: self.left.right, + right: self.right, + }); + var after = before.optimize(compressor); + if (before !== after) { + self.left = self.left.left; + self.right = after; + } + } + // x && (y && z) ---> x && y && z + // x || (y || z) ---> x || y || z + if (self.right instanceof AST_Binary && self.operator == self.right.operator) swap_chain(self, compressor); } if (compressor.option("strings") && self.operator == "+") { // "foo" + 42 + "" ---> "foo" + 42 @@ -12881,15 +12898,16 @@ Compressor.prototype.compress = function(node) { } var consequent = self.consequent; var alternative = self.alternative; - if (repeatable(compressor, condition)) { + var cond_lhs = extract_lhs(condition); + if (repeatable(compressor, cond_lhs)) { // x ? x : y ---> x || y - if (condition.equals(consequent)) return make_node(AST_Binary, self, { + if (cond_lhs.equals(consequent)) return make_node(AST_Binary, self, { operator: "||", left: condition, right: alternative, }).optimize(compressor); // x ? y : x ---> x && y - if (condition.equals(alternative)) return make_node(AST_Binary, self, { + if (cond_lhs.equals(alternative)) return make_node(AST_Binary, self, { operator: "&&", left: condition, right: consequent, diff --git a/test/compress/assignments.js b/test/compress/assignments.js index 9bd102e157..beae023073 100644 --- a/test/compress/assignments.js +++ b/test/compress/assignments.js @@ -290,6 +290,45 @@ increment_decrement_2: { expect_stdout: "42" } +lazily_chained_assignments: { + options = { + assignments: true, + collapse_vars: true, + conditionals: true, + unused: true, + } + input: { + function f(a) { + if (a = console.log("foo")) + a = console.log("bar"); + return a; + } + function g(b) { + if (b = console.log("baz")) + ; + else + b = console.log("moo"); + return b; + } + console.log(f(), g()); + } + expect: { + function f(a) { + return console.log("foo") && console.log("bar"); + } + function g(b) { + return console.log("baz") || console.log("moo"); + } + console.log(f(), g()); + } + expect_stdout: [ + "foo", + "baz", + "moo", + "undefined undefined", + ] +} + issue_3375_1: { options = { assignments: true, diff --git a/test/compress/booleans.js b/test/compress/booleans.js index 92680682eb..aabb380576 100644 --- a/test/compress/booleans.js +++ b/test/compress/booleans.js @@ -80,6 +80,25 @@ de_morgan_1c: { expect_stdout: "true" } +de_morgan_1d: { + options = { + booleans: true, + } + input: { + function f(a) { + return (a = false) || a; + } + console.log(f(null), f(42)); + } + expect: { + function f(a) { + return a = !1; + } + console.log(f(null), f(42)); + } + expect_stdout: "false false" +} + de_morgan_2a: { options = { booleans: true, @@ -181,6 +200,31 @@ de_morgan_2d: { ] } +de_morgan_2e: { + options = { + booleans: true, + conditionals: true, + } + input: { + function f(a, b) { + return (a && b) && b; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect: { + function f(a, b) { + return a && b; + } + console.log(f(null), f(null, {})); + console.log(f(42), f(42, {})); + } + expect_stdout: [ + "null null", + "undefined {}", + ] +} + de_morgan_3a: { options = { booleans: true, diff --git a/test/compress/comparisons.js b/test/compress/comparisons.js index 66602efc81..d187c2d768 100644 --- a/test/compress/comparisons.js +++ b/test/compress/comparisons.js @@ -76,12 +76,14 @@ self_comparison_1: { comparisons: true, } input: { + var a, b; a === a; a !== b; b.c === a.c; b.c !== b.c; } expect: { + var a, b; a == a; a !== b; b.c === a.c; @@ -275,6 +277,7 @@ issue_2857_3: { issue_2857_4: { options = { comparisons: true, + conditionals: true, } input: { function f(a, p) { @@ -305,6 +308,7 @@ issue_2857_4: { issue_2857_5: { options = { comparisons: true, + conditionals: true, } input: { function f(a, p) { @@ -528,6 +532,7 @@ nullish_assign: { nullish_chain: { options = { comparisons: true, + conditionals: true, } input: { var a; diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 2be0b44933..e5c903ed86 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -3028,7 +3028,7 @@ issue_5673_2: { expect: { var a = "PASS"; console.log(function(b) { - return ((b = a) || (b = a)) && b; + return a || (b = a) && b; }()); } expect_stdout: "PASS" diff --git a/test/compress/if_return.js b/test/compress/if_return.js index d993ca22c8..fc077d31aa 100644 --- a/test/compress/if_return.js +++ b/test/compress/if_return.js @@ -976,33 +976,33 @@ nested_if_return: { if_return: true, } input: { - function f() { - if (A) { - if (B) - return B; - if (C) - return D; - if (E) - return F; - if (G) - return H; - if (I) { - if (J) - return K; + function f(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { + if (a) { + if (b) + return b; + if (c) + return d; + if (e) + return f; + if (g) + return h; + if (i) { + if (j) + return k; return; } - if (L) { - if (M) + if (l) { + if (m) return; - return N; + return n; } } } } expect: { - function f() { - if (A) - return B || (C ? D : E ? F : G ? H : I ? J ? K : void 0 : L && !M ? N : void 0); + function f(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { + if (a) + return b || (c ? d : e ? f : g ? h : i ? j ? k : void 0 : l && !m ? n : void 0); } } }