aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2020-04-20 02:42:13 +0100
committerGitHub <noreply@github.com>2020-04-20 09:42:13 +0800
commita2fc32c64b52ec5582c70e7e93bc9e6bb45eac3f (patch)
tree3db60627c91031d66c4937f1c5a042769984c09a
parent88504ab86995b33b1c497e44ef314ab54fd57355 (diff)
downloadtracifyjs-a2fc32c64b52ec5582c70e7e93bc9e6bb45eac3f.tar.gz
tracifyjs-a2fc32c64b52ec5582c70e7e93bc9e6bb45eac3f.zip
enhance `conditionals` (#3805)
-rw-r--r--lib/compress.js85
-rw-r--r--test/compress/conditionals.js93
-rw-r--r--test/compress/join_vars.js93
3 files changed, 265 insertions, 6 deletions
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"
+}