aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2021-02-23 21:57:11 +0000
committerGitHub <noreply@github.com>2021-02-24 05:57:11 +0800
commitc88566034756eb17c4ff563901b3a1c95b63f788 (patch)
tree24e376db8595b8152b2685708ee08b7f9e194758 /lib
parentd68d155f93a355a9f6f0451150186b7fad8c58b8 (diff)
downloadtracifyjs-c88566034756eb17c4ff563901b3a1c95b63f788.tar.gz
tracifyjs-c88566034756eb17c4ff563901b3a1c95b63f788.zip
support nullish coalescing operator (#4678)
Diffstat (limited to 'lib')
-rw-r--r--lib/compress.js46
-rw-r--r--lib/output.js4
-rw-r--r--lib/parse.js4
3 files changed, 41 insertions, 13 deletions
diff --git a/lib/compress.js b/lib/compress.js
index dcd48fa8..7f9a8dd5 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -2182,6 +2182,7 @@ merge(Compressor.prototype, {
var lazy = lazy_op[expr.operator];
if (unused
&& lazy
+ && expr.operator != "??"
&& expr.right instanceof AST_Assign
&& expr.right.operator == "="
&& !(expr.right.left instanceof AST_Destructured)) {
@@ -3840,7 +3841,7 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_string", func);
});
- var lazy_op = makePredicate("&& ||");
+ var lazy_op = makePredicate("&& || ??");
(function(def) {
function to_node(value, orig) {
@@ -4286,6 +4287,9 @@ merge(Compressor.prototype, {
switch (this.operator) {
case "&&" : result = left && right; break;
case "||" : result = left || right; break;
+ case "??" :
+ result = left == null ? right : left;
+ break;
case "|" : result = left | right; break;
case "&" : result = left & right; break;
case "^" : result = left ^ right; break;
@@ -7130,6 +7134,7 @@ merge(Compressor.prototype, {
node = this.clone();
node.right = right.drop_side_effect_free(compressor);
}
+ if (this.operator == "??") return node;
return (first_in_statement ? best_of_statement : best_of_expression)(node, make_node(AST_Binary, this, {
operator: node.operator == "&&" ? "||" : "&&",
left: node.left.negate(compressor, first_in_statement),
@@ -9159,7 +9164,7 @@ merge(Compressor.prototype, {
// (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.operator == "&&" || node.operator == "||")) return;
if (!(node.right instanceof AST_Assign)) return;
if (node.right.operator != "=") return;
if (!(node.right.left instanceof AST_SymbolRef)) return;
@@ -9680,22 +9685,41 @@ merge(Compressor.prototype, {
}).optimize(compressor);
}
break;
+ case "??":
+ var nullish = true;
case "||":
- var ll = fuzzy_eval(self.left);
- if (!ll) {
- AST_Node.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
+ var ll = fuzzy_eval(self.left, nullish);
+ if (nullish ? ll == null : !ll) {
+ AST_Node.warn("Condition left of {operator} always {value} [{file}:{line},{col}]", {
+ operator: self.operator,
+ value: nullish ? "nulish" : "false",
+ file: self.start.file,
+ line: self.start.line,
+ col: self.start.col,
+ });
return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
} else if (!(ll instanceof AST_Node)) {
- AST_Node.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
+ AST_Node.warn("Condition left of {operator} always {value} [{file}:{line},{col}]", {
+ operator: self.operator,
+ value: nullish ? "defined" : "true",
+ file: self.start.file,
+ line: self.start.line,
+ col: self.start.col,
+ });
return maintain_this_binding(compressor, parent, compressor.self(), self.left).optimize(compressor);
}
var rr = self.right.evaluate(compressor);
if (!rr) {
if (in_bool || parent.operator == "||" && parent.left === compressor.self()) {
- AST_Node.warn("Dropping side-effect-free || [{file}:{line},{col}]", self.start);
+ AST_Node.warn("Dropping side-effect-free {operator} [{file}:{line},{col}]", {
+ operator: self.operator,
+ file: self.start.file,
+ line: self.start.line,
+ col: self.start.col,
+ });
return self.left.optimize(compressor);
}
- } else if (!(rr instanceof AST_Node)) {
+ } else if (!nullish && !(rr instanceof AST_Node)) {
if (in_bool) {
AST_Node.warn("Boolean || always true [{file}:{line},{col}]", self.start);
return make_sequence(self, [
@@ -9705,7 +9729,7 @@ merge(Compressor.prototype, {
} else self.truthy = true;
}
// x && true || y ---> x ? true : y
- if (self.left.operator == "&&") {
+ if (!nullish && self.left.operator == "&&") {
var lr = self.left.right.is_truthy() || self.left.right.evaluate(compressor, true);
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
condition: self.left.left,
@@ -10047,9 +10071,9 @@ merge(Compressor.prototype, {
});
}
- function fuzzy_eval(node) {
+ function fuzzy_eval(node, nullish) {
if (node.truthy) return true;
- if (node.falsy) return false;
+ if (node.falsy && !nullish) return false;
if (node.is_truthy()) return true;
return node.evaluate(compressor, true);
}
diff --git a/lib/output.js b/lib/output.js
index 592717b0..49164359 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -753,7 +753,9 @@ function OutputStream(options) {
if (p instanceof AST_Binary) {
var po = p.operator, pp = PRECEDENCE[po];
var so = this.operator, sp = PRECEDENCE[so];
- return pp > sp || (pp == sp && this === p[po == "**" ? "left" : "right"]);
+ return pp > sp
+ || po == "??" && (so == "&&" || so == "||")
+ || (pp == sp && this === p[po == "**" ? "left" : "right"]);
}
// (foo && bar)()
if (p instanceof AST_Call) return p.expression === this;
diff --git a/lib/parse.js b/lib/parse.js
index 26c618ef..2cb30409 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -107,7 +107,8 @@ var OPERATORS = makePredicate([
"^=",
"&=",
"&&",
- "||"
+ "||",
+ "??",
]);
var NEWLINE_CHARS = "\n\r\u2028\u2029";
@@ -662,6 +663,7 @@ var PRECEDENCE = function(a, ret) {
}
return ret;
}([
+ ["??"],
["||"],
["&&"],
["|"],