aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2021-06-22 17:35:50 +0100
committerGitHub <noreply@github.com>2021-06-23 00:35:50 +0800
commit7c5b6f349e5818a13ccb93c88b79a607d236bd0b (patch)
tree007faa7ceb09f56de573d644e7ad47143d058b13
parente9c902b0449a291469ddaee04dc745cf4706560d (diff)
downloadtracifyjs-7c5b6f349e5818a13ccb93c88b79a607d236bd0b.tar.gz
tracifyjs-7c5b6f349e5818a13ccb93c88b79a607d236bd0b.zip
enhance `booleans` (#5022)
closes #5021
-rw-r--r--lib/compress.js107
-rw-r--r--test/benchmark.js2
-rw-r--r--test/compress/booleans.js377
-rw-r--r--test/compress/collapse_vars.js2
-rw-r--r--test/compress/conditionals.js27
-rw-r--r--test/compress/join_vars.js2
-rw-r--r--test/compress/nullish.js152
7 files changed, 639 insertions, 30 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 52979c92..0d98e456 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -8223,35 +8223,86 @@ merge(Compressor.prototype, {
}
}
+ function fuzzy_eval(compressor, node, nullish) {
+ if (node.truthy) return true;
+ if (node.falsy && !nullish) return false;
+ if (node.is_truthy()) return true;
+ return node.evaluate(compressor, true);
+ }
+
+ function mark_duplicate_condition(compressor, node) {
+ for (var level = 0, child = compressor.self(), parent; ; child = parent) {
+ parent = compressor.parent(level++);
+ if (parent instanceof AST_Binary) {
+ var op = parent.operator;
+ if (!lazy_op[op]) return;
+ var left = parent.left;
+ if (left === child) continue;
+ if (node.equivalent_to(left)) node[op == "&&" ? "truthy" : "falsy"] = true;
+ } else if (parent instanceof AST_Conditional) {
+ var cond = parent.condition;
+ if (cond === child) continue;
+ if (node.equivalent_to(cond)) switch (child) {
+ case parent.consequent:
+ node.truthy = true;
+ break;
+ case parent.alternative:
+ node.falsy = true;
+ break;
+ }
+ } else if (parent instanceof AST_Exit) {
+ break;
+ } else if (parent instanceof AST_If) {
+ break;
+ } else if (parent instanceof AST_Sequence) {
+ if (parent.expressions[0] === child) continue;
+ } else if (parent instanceof AST_SimpleStatement) {
+ break;
+ }
+ return;
+ }
+ while (true) {
+ child = parent;
+ parent = compressor.parent(level++);
+ if (parent instanceof AST_BlockStatement) {
+ if (parent.body[0] === child) continue;
+ } else if (parent instanceof AST_If) {
+ var cond = parent.condition;
+ if (cond === child) continue;
+ if (node.equivalent_to(cond)) switch (child) {
+ case parent.body:
+ node.truthy = true;
+ break;
+ case parent.alternative:
+ node.falsy = true;
+ break;
+ }
+ }
+ return;
+ }
+ }
+
OPT(AST_If, function(self, compressor) {
if (is_empty(self.alternative)) self.alternative = null;
if (!compressor.option("conditionals")) return self;
+ if (compressor.option("booleans")) mark_duplicate_condition(compressor, self.condition);
// if condition can be statically determined, warn and drop
// one of the blocks. note, statically determined implies
// “has no side effects”; also it doesn't work for cases like
// `x && true`, though it probably should.
if (compressor.option("dead_code")) {
- var cond = self.condition.evaluate(compressor);
- if (cond instanceof AST_Node) {
- cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
- }
+ var cond = fuzzy_eval(compressor, self.condition);
if (!cond) {
AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
- var body = [
- make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- }),
- ];
+ var body = [ make_node(AST_SimpleStatement, self.condition, { body: self.condition }) ];
extract_declarations_from_unreachable_code(compressor, self.body, body);
if (self.alternative) body.push(self.alternative);
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
} else if (!(cond instanceof AST_Node)) {
AST_Node.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
var body = [
- make_node(AST_SimpleStatement, self.condition, {
- body: self.condition
- }),
+ make_node(AST_SimpleStatement, self.condition, { body: self.condition }),
self.body,
];
if (self.alternative) extract_declarations_from_unreachable_code(compressor, self.alternative, body);
@@ -8275,9 +8326,7 @@ merge(Compressor.prototype, {
var body_exprs = sequencesize(self.body, body, var_defs, refs);
var alt_exprs = sequencesize(self.alternative, body, var_defs, refs);
if (body_exprs && alt_exprs) {
- if (var_defs.length > 0) body.push(make_node(AST_Var, self, {
- definitions: var_defs
- }));
+ if (var_defs.length > 0) body.push(make_node(AST_Var, self, { definitions: var_defs }));
if (body_exprs.length == 0) {
body.push(make_node(AST_SimpleStatement, self.condition, {
body: alt_exprs.length > 0 ? make_node(AST_Binary, self, {
@@ -10221,7 +10270,18 @@ merge(Compressor.prototype, {
}
break;
}
- var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
+ var in_bool = false;
+ var parent = compressor.parent();
+ if (compressor.option("booleans")) {
+ var lhs = self.left;
+ if (lazy_op[self.operator] && !lhs.has_side_effects(compressor)) {
+ if (lhs.equivalent_to(self.right)) {
+ return maintain_this_binding(compressor, parent, compressor.self(), lhs).optimize(compressor);
+ }
+ mark_duplicate_condition(compressor, lhs);
+ }
+ in_bool = compressor.in_boolean_context();
+ }
if (in_bool) switch (self.operator) {
case "+":
var ll = self.left.evaluate(compressor);
@@ -10255,7 +10315,6 @@ merge(Compressor.prototype, {
}
break;
}
- var parent = compressor.parent();
if (compressor.option("comparisons") && self.is_boolean(compressor)) {
if (!(parent instanceof AST_Binary) || parent instanceof AST_Assign) {
var negated = best_of(compressor, self, make_node(AST_UnaryPrefix, self, {
@@ -10313,7 +10372,7 @@ merge(Compressor.prototype, {
var associative = true;
switch (self.operator) {
case "&&":
- var ll = fuzzy_eval(self.left);
+ var ll = fuzzy_eval(compressor, self.left);
if (!ll) {
AST_Node.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor, parent, compressor.self(), self.left).optimize(compressor);
@@ -10349,7 +10408,7 @@ merge(Compressor.prototype, {
case "??":
var nullish = true;
case "||":
- var ll = fuzzy_eval(self.left, nullish);
+ var ll = fuzzy_eval(compressor, self.left, nullish);
if (nullish ? ll == null : !ll) {
AST_Node.warn("Condition left of {operator} always {value} [{file}:{line},{col}]", {
operator: self.operator,
@@ -10732,13 +10791,6 @@ merge(Compressor.prototype, {
});
}
- function fuzzy_eval(node, nullish) {
- if (node.truthy) return true;
- if (node.falsy && !nullish) return false;
- if (node.is_truthy()) return true;
- return node.evaluate(compressor, true);
- }
-
function is_indexFn(node) {
return node.TYPE == "Call"
&& node.expression instanceof AST_Dot
@@ -11286,7 +11338,8 @@ merge(Compressor.prototype, {
return make_sequence(self, expressions);
}
if (!compressor.option("conditionals")) return self;
- var condition = self.condition.is_truthy() || self.condition.evaluate(compressor, true);
+ if (compressor.option("booleans")) mark_duplicate_condition(compressor, self.condition);
+ var condition = fuzzy_eval(compressor, self.condition);
if (!condition) {
AST_Node.warn("Condition always false [{file}:{line},{col}]", self.start);
return make_sequence(self, [ self.condition, self.alternative ]).optimize(compressor);
diff --git a/test/benchmark.js b/test/benchmark.js
index 74ac84be..5bc7a5ce 100644
--- a/test/benchmark.js
+++ b/test/benchmark.js
@@ -16,7 +16,7 @@ var urls = [
"https://code.angularjs.org/1.7.8/angular.js",
"https://unpkg.com/mathjs@6.2.3/dist/math.js",
"https://unpkg.com/react@15.3.2/dist/react.js",
- "https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.js",
+ "https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.js",
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js",
"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js",
"https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.12.2/ember.prod.js",
diff --git a/test/compress/booleans.js b/test/compress/booleans.js
index 779f8d8c..cd860f96 100644
--- a/test/compress/booleans.js
+++ b/test/compress/booleans.js
@@ -29,6 +29,383 @@ iife_boolean_context: {
]
}
+de_morgan_1a: {
+ options = {
+ booleans: true,
+ }
+ input: {
+ function f(a) {
+ return a || a;
+ }
+ console.log(f(null), f(42));
+ }
+ expect: {
+ function f(a) {
+ return a;
+ }
+ console.log(f(null), f(42));
+ }
+ expect_stdout: "null 42"
+}
+
+de_morgan_1b: {
+ options = {
+ booleans: true,
+ }
+ input: {
+ function f(a) {
+ return a && a;
+ }
+ console.log(f(null), f(42));
+ }
+ expect: {
+ function f(a) {
+ return a;
+ }
+ console.log(f(null), f(42));
+ }
+ expect_stdout: "null 42"
+}
+
+de_morgan_1c: {
+ options = {
+ booleans: true,
+ }
+ input: {
+ console.log(delete (NaN && NaN));
+ }
+ expect: {
+ console.log(delete (0, NaN));
+ }
+ expect_stdout: "true"
+}
+
+de_morgan_2a: {
+ options = {
+ booleans: true,
+ conditionals: true,
+ }
+ input: {
+ function f(a, b) {
+ return a || (a || 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: [
+ "undefined {}",
+ "42 42",
+ ]
+}
+
+de_morgan_2b: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b) {
+ return a || (a && b);
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect: {
+ function f(a, b) {
+ return a;
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect_stdout: [
+ "null null",
+ "42 42",
+ ]
+}
+
+de_morgan_2c: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b) {
+ return a && (a || b);
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect: {
+ function f(a, b) {
+ return a;
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect_stdout: [
+ "null null",
+ "42 42",
+ ]
+}
+
+de_morgan_2d: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a, b) {
+ return a && (a && 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,
+ conditionals: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a || ((a || b) || c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a || b || c;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "undefined {} true true",
+ "42 42 42 42",
+ ]
+}
+
+de_morgan_3b: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a || ((a || b) && c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a || b && c;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "false false undefined {}",
+ "42 42 42 42",
+ ]
+}
+
+de_morgan_3c: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a || ((a && b) || c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a || c;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "undefined {} undefined {}",
+ "42 42 42 42",
+ ]
+}
+
+de_morgan_3d: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a || ((a && b) && c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "null null null null",
+ "42 42 42 42",
+ ]
+}
+
+de_morgan_3e: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a && ((a || b) || c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "null null null null",
+ "42 42 42 42",
+ ]
+}
+
+de_morgan_3f: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a && ((a || b) && c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a && c;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "null null null null",
+ "undefined {} undefined {}",
+ ]
+}
+
+de_morgan_3g: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a && ((a && b) || c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a && (b || c);
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "null null null null",
+ "undefined {} true true",
+ ]
+}
+
+de_morgan_3h: {
+ options = {
+ booleans: true,
+ conditionals: true,
+ }
+ input: {
+ function f(a, b, c) {
+ return a && ((a && b) && c);
+ }
+ console.log(f(null, false), f(null, false, {}), f(null, true), f(null, true, {}));
+ console.log(f(42, false), f(42, false, {}), f(42, true), f(42, true, {}));
+ }
+ expect: {
+ function f(a, b, c) {
+ return a && b && c;
+ }
+ console.log(f(null, !1), f(null, !1, {}), f(null, !0), f(null, !0, {}));
+ console.log(f(42, !1), f(42, !1, {}), f(42, !0), f(42, !0, {}));
+ }
+ expect_stdout: [
+ "null null null null",
+ "false false undefined {}",
+ ]
+}
+
+conditional_chain: {
+ options = {
+ booleans: true,
+ conditionals: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b) {
+ return a ? a : b ? b : 42;
+ }
+ console.log(f("PASS", "FAIL"));
+ }
+ expect: {
+ function f(a, b) {
+ return a || b || 42;
+ }
+ console.log(f("PASS", "FAIL"));
+ }
+ expect_stdout: "PASS"
+}
+
issue_3465_1: {
options = {
booleans: true,
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index 30dc0248..50db51b0 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -8864,7 +8864,7 @@ issue_4732_1: {
expect: {
var a = 0;
(function(b) {
- (b = a++) && (b && console.log("PASS"));
+ (b = a++) && console.log("PASS");
})(a++);
}
expect_stdout: "PASS"
diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js
index 4187c152..84559c32 100644
--- a/test/compress/conditionals.js
+++ b/test/compress/conditionals.js
@@ -822,6 +822,33 @@ cond_13: {
}
}
+cond_14: {
+ options = {
+ booleans: true,
+ conditionals: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a) {
+ if (a)
+ if (a)
+ console.log("PASS");
+ else
+ console.log("FAIL");
+ }
+ f(null);
+ f(42);
+ }
+ expect: {
+ function f(a) {
+ a && console.log("PASS");
+ }
+ f(null);
+ f(42);
+ }
+ expect_stdout: "PASS"
+}
+
ternary_boolean_consequent: {
options = {
booleans: true,
diff --git a/test/compress/join_vars.js b/test/compress/join_vars.js
index 89251266..65e18f88 100644
--- a/test/compress/join_vars.js
+++ b/test/compress/join_vars.js
@@ -1015,7 +1015,7 @@ issue_3856: {
console.log(function() {
(function() {
var a, b;
- if (a) return !!a;
+ if (a) return a, 1;
for (a = 0; !console;);
return 0;
})();
diff --git a/test/compress/nullish.js b/test/compress/nullish.js
index a8526cac..27f6aa5a 100644
--- a/test/compress/nullish.js
+++ b/test/compress/nullish.js
@@ -110,6 +110,158 @@ conditional_assignment_4: {
node_version: ">=14"
}
+de_morgan_1: {
+ options = {
+ booleans: true,
+ }
+ input: {
+ function f(a) {
+ return a ?? a;
+ }
+ console.log(f(null), f(42));
+ }
+ expect: {
+ function f(a) {
+ return a;
+ }
+ console.log(f(null), f(42));
+ }
+ expect_stdout: "null 42"
+ node_version: ">=14"
+}
+
+de_morgan_2a: {
+ options = {
+ booleans: true,
+ conditionals: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b) {
+ return a || (a ?? b);
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect: {
+ function f(a, b) {
+ return a || (a ?? b);
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect_stdout: [
+ "undefined {}",
+ "42 42",
+ ]
+ node_version: ">=14"
+}
+
+de_morgan_2b: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b) {
+ return a && (a ?? b);
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect: {
+ function f(a, b) {
+ return a;
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect_stdout: [
+ "null null",
+ "42 42",
+ ]
+ node_version: ">=14"
+}
+
+de_morgan_2c: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ side_effects: true,
+ }
+ input: {
+ function f(a, b) {
+ return a ?? (a || 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: [
+ "undefined {}",
+ "42 42",
+ ]
+ node_version: ">=14"
+}
+
+de_morgan_2d: {
+ options = {
+ booleans: true,
+ evaluate: true,
+ }
+ input: {
+ function f(a, b) {
+ return a ?? (a && b);
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect: {
+ function f(a, b) {
+ return a;
+ }
+ console.log(f(null), f(null, {}));
+ console.log(f(42), f(42, {}));
+ }
+ expect_stdout: [
+ "null null",
+ "42 42",
+ ]
+ node_version: ">=14"
+}
+
+de_morgan_2e: {
+ options = {
+ booleans: true,
+ conditionals: true,
+ }
+ input: {
+ function f(a, b) {
+ return a ?? (a ?? 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: [
+ "undefined {}",
+ "42 42",
+ ]
+ node_version: ">=14"
+}
+
issue_4679: {
options = {
comparisons: true,