aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2021-01-24 01:51:18 +0000
committerGitHub <noreply@github.com>2021-01-24 09:51:18 +0800
commit8bfd891c09edfb54d1b93010487ee68ad64a457c (patch)
tree947c02f460b927b4a0843e3f0955a5636621a067
parentef9f7ca3e7c7f71df440645f782b3a7da4646d9b (diff)
downloadtracifyjs-8bfd891c09edfb54d1b93010487ee68ad64a457c.tar.gz
tracifyjs-8bfd891c09edfb54d1b93010487ee68ad64a457c.zip
support BigInt literals (#4583)
-rw-r--r--lib/ast.js17
-rw-r--r--lib/compress.js282
-rw-r--r--lib/output.js32
-rw-r--r--lib/parse.js22
-rw-r--r--lib/propmangle.js2
-rw-r--r--test/compress/arrays.js5
-rw-r--r--test/compress/bigint.js46
-rw-r--r--test/compress/issue-269.js2
-rw-r--r--test/compress/numbers.js2
-rw-r--r--test/ufuzz/index.js7
10 files changed, 246 insertions, 171 deletions
diff --git a/lib/ast.js b/lib/ast.js
index ab2559c2..1a09d4de 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -249,7 +249,7 @@ var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_s
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
functions: "[Object/S] like `variables`, but only lists function declarations",
parent_scope: "[AST_Scope?/S] link to the parent scope",
- variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
+ variables: "[Object/S] a map of name ---> SymbolDef for all variables/functions defined in this scope",
},
clone: function(deep) {
var node = this._clone(deep);
@@ -472,7 +472,7 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", {
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$documentation: "The toplevel scope",
$propdoc: {
- globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
+ globals: "[Object/S] a map of name ---> SymbolDef for all undeclared names",
},
wrap: function(name) {
var body = this.body;
@@ -1440,6 +1440,19 @@ var AST_Number = DEFNODE("Number", "value", {
},
_validate: function() {
if (typeof this.value != "number") throw new Error("value must be number");
+ if (!isFinite(this.value)) throw new Error("value must be finite");
+ if (this.value < 0) throw new Error("value cannot be negative");
+ },
+}, AST_Constant);
+
+var AST_BigInt = DEFNODE("BigInt", "value", {
+ $documentation: "A BigInt literal",
+ $propdoc: {
+ value: "[string] the numeric representation",
+ },
+ _validate: function() {
+ if (typeof this.value != "string") throw new Error("value must be string");
+ if (this.value[0] == "-") throw new Error("value cannot be negative");
},
}, AST_Constant);
diff --git a/lib/compress.js b/lib/compress.js
index f4441775..6b8a7ef9 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -729,7 +729,7 @@ merge(Compressor.prototype, {
if (aborts) push(tw);
reset_variables(tw, compressor, fn);
// Virtually turn IIFE parameters into variable definitions:
- // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
+ // (function(a,b) {...})(c,d) ---> (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
var safe = !fn.uses_arguments || tw.has_directive("use strict");
fn.argnames.forEach(function(arg, i) {
@@ -2742,7 +2742,7 @@ merge(Compressor.prototype, {
var in_bool = stat.body.in_bool || next instanceof AST_Return && next.in_bool;
//---
// pretty silly case, but:
- // if (foo()) return; return; => foo(); return;
+ // if (foo()) return; return; ---> foo(); return;
if (!value && !stat.alternative
&& (in_lambda && !next || next instanceof AST_Return && !next.value)) {
CHANGED = true;
@@ -2752,7 +2752,7 @@ merge(Compressor.prototype, {
continue;
}
//---
- // if (foo()) return x; return y; => return foo() ? x : y;
+ // if (foo()) return x; return y; ---> return foo() ? x : y;
if (!stat.alternative && next instanceof AST_Return) {
CHANGED = true;
stat = stat.clone();
@@ -2762,7 +2762,7 @@ merge(Compressor.prototype, {
continue;
}
//---
- // if (foo()) return x; [ return ; ] => return foo() ? x : undefined;
+ // if (foo()) return x; [ return ; ] ---> return foo() ? x : undefined;
if (!stat.alternative && !next && in_lambda && (in_bool || value && multiple_if_returns)) {
CHANGED = true;
stat = stat.clone();
@@ -2773,7 +2773,7 @@ merge(Compressor.prototype, {
continue;
}
//---
- // if (a) return b; if (c) return d; e; => return a ? b : c ? d : void e;
+ // if (a) return b; if (c) return d; e; ---> return a ? b : c ? d : void e;
//
// if sequences is not enabled, this can lead to an endless loop (issue #866).
// however, with sequences on this helps producing slightly better output for
@@ -3970,6 +3970,7 @@ merge(Compressor.prototype, {
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
def(AST_Accessor, return_this);
+ def(AST_BigInt, return_this);
def(AST_Node, return_this);
def(AST_Constant, function() {
return this.value;
@@ -4172,7 +4173,7 @@ merge(Compressor.prototype, {
&& typeof result == "number"
&& (this.operator == "+" || this.operator == "-")) {
var digits = Math.max(0, decimals(left), decimals(right));
- // 53-bit significand => 15.95 decimal places
+ // 53-bit significand ---> 15.95 decimal places
if (digits < 16) return +result.toFixed(digits);
}
return result;
@@ -7944,6 +7945,7 @@ merge(Compressor.prototype, {
if (compressor.option("unsafe")) {
if (is_undeclared_ref(exp)) switch (exp.name) {
case "Array":
+ // Array(n) ---> [ , , ... , ]
if (self.args.length == 1) {
var first = self.args[0];
if (first instanceof AST_Number) try {
@@ -7951,9 +7953,7 @@ merge(Compressor.prototype, {
if (length > 6) break;
var elements = Array(length);
for (var i = 0; i < length; i++) elements[i] = make_node(AST_Hole, self);
- return make_node(AST_Array, self, {
- elements: elements
- });
+ return make_node(AST_Array, self, { elements: elements });
} catch (ex) {
AST_Node.warn("Invalid array length: {length} [{file}:{line},{col}]", {
length: length,
@@ -7965,81 +7965,90 @@ merge(Compressor.prototype, {
}
if (!first.is_boolean(compressor) && !first.is_string(compressor)) break;
}
- return make_node(AST_Array, self, {
- elements: self.args
- });
+ // Array(...) ---> [ ... ]
+ return make_node(AST_Array, self, { elements: self.args });
case "Object":
- if (self.args.length == 0) {
- return make_node(AST_Object, self, {
- properties: []
- });
- }
+ // Object() ---> {}
+ if (self.args.length == 0) return make_node(AST_Object, self, { properties: [] });
break;
case "String":
- if (self.args.length == 0) return make_node(AST_String, self, {
- value: ""
- });
- if (self.args.length <= 1) return make_node(AST_Binary, self, {
- left: self.args[0],
+ // String() ---> ""
+ if (self.args.length == 0) return make_node(AST_String, self, { value: "" });
+ // String(x) ---> "" + x
+ if (self.args.length == 1) return make_node(AST_Binary, self, {
operator: "+",
- right: make_node(AST_String, self, { value: "" })
+ left: make_node(AST_String, self, { value: "" }),
+ right: self.args[0],
}).optimize(compressor);
break;
case "Number":
- if (self.args.length == 0) return make_node(AST_Number, self, {
- value: 0
- });
+ // Number() ---> 0
+ if (self.args.length == 0) return make_node(AST_Number, self, { value: 0 });
+ // Number(x) ---> +("" + x)
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
- expression: self.args[0],
- operator: "+"
+ operator: "+",
+ expression: make_node(AST_Binary, self, {
+ operator: "+",
+ left: make_node(AST_String, self, { value: "" }),
+ right: self.args[0],
+ }),
}).optimize(compressor);
+ break;
case "Boolean":
- if (self.args.length == 0) return make_node(AST_False, self);
+ // Boolean() ---> false
+ if (self.args.length == 0) return make_node(AST_False, self).optimize(compressor);
+ // Boolean(x) ---> !!x
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
+ operator: "!",
expression: make_node(AST_UnaryPrefix, self, {
+ operator: "!",
expression: self.args[0],
- operator: "!"
}),
- operator: "!"
}).optimize(compressor);
break;
case "RegExp":
+ // attempt to convert RegExp(...) to literal
var params = [];
if (all(self.args, function(arg) {
var value = arg.evaluate(compressor);
params.unshift(value);
return arg !== value;
- })) {
- try {
- return best_of(compressor, self, make_node(AST_RegExp, self, {
- value: RegExp.apply(RegExp, params),
- }));
- } catch (ex) {
- AST_Node.warn("Error converting {expr} [{file}:{line},{col}]", {
- expr: self,
- file: self.start.file,
- line: self.start.line,
- col: self.start.col,
- });
- }
+ })) try {
+ return best_of(compressor, self, make_node(AST_RegExp, self, {
+ value: RegExp.apply(RegExp, params),
+ }));
+ } catch (ex) {
+ AST_Node.warn("Error converting {expr} [{file}:{line},{col}]", {
+ expr: self,
+ file: self.start.file,
+ line: self.start.line,
+ col: self.start.col,
+ });
}
break;
- } else if (exp instanceof AST_Dot) switch(exp.property) {
+ } else if (exp instanceof AST_Dot) switch (exp.property) {
case "toString":
+ // x.toString() ---> "" + x
if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
return make_node(AST_Binary, self, {
- left: make_node(AST_String, self, { value: "" }),
operator: "+",
- right: exp.expression
+ left: make_node(AST_String, self, { value: "" }),
+ right: exp.expression,
}).optimize(compressor);
}
break;
case "join":
- if (exp.expression instanceof AST_Array) EXIT: {
- var separator;
- if (self.args.length > 0) {
- separator = self.args[0].evaluate(compressor);
- if (separator === self.args[0]) break EXIT; // not a constant
+ if (exp.expression instanceof AST_Array && self.args.length < 2) EXIT: {
+ var separator = self.args[0];
+ // [].join() ---> ""
+ // [].join(x) ---> (x, "")
+ if (exp.expression.elements.length == 0) return separator ? make_sequence(self, [
+ separator,
+ make_node(AST_String, self, { value: "" }),
+ ]).optimize(compressor) : make_node(AST_String, self, { value: "" });
+ if (separator) {
+ separator = separator.evaluate(compressor);
+ if (separator instanceof AST_Node) break EXIT; // not a constant
}
var elements = [];
var consts = [];
@@ -8050,45 +8059,46 @@ merge(Compressor.prototype, {
} else {
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
- value: consts.join(separator)
+ value: consts.join(separator),
}));
consts.length = 0;
}
elements.push(el);
}
});
- if (consts.length > 0) {
- elements.push(make_node(AST_String, self, {
- value: consts.join(separator)
- }));
- }
- if (elements.length == 0) return make_node(AST_String, self, { value: "" });
+ if (consts.length > 0) elements.push(make_node(AST_String, self, {
+ value: consts.join(separator),
+ }));
+ // [ x ].join() ---> "" + x
+ // [ x ].join(".") ---> "" + x
+ // [ 1, 2, 3 ].join() ---> "1,2,3"
+ // [ 1, 2, 3 ].join(".") ---> "1.2.3"
if (elements.length == 1) {
- if (elements[0].is_string(compressor)) {
- return elements[0];
- }
+ if (elements[0].is_string(compressor)) return elements[0];
return make_node(AST_Binary, elements[0], {
- operator : "+",
- left : make_node(AST_String, self, { value: "" }),
- right : elements[0]
+ operator: "+",
+ left: make_node(AST_String, self, { value: "" }),
+ right: elements[0],
});
}
+ // [ 1, 2, a, 3 ].join("") ---> "12" + a + "3"
if (separator == "") {
var first;
- if (elements[0].is_string(compressor)
- || elements[1].is_string(compressor)) {
+ if (elements[0].is_string(compressor) || elements[1].is_string(compressor)) {
first = elements.shift();
} else {
first = make_node(AST_String, self, { value: "" });
}
return elements.reduce(function(prev, el) {
return make_node(AST_Binary, el, {
- operator : "+",
- left : prev,
- right : el
+ operator: "+",
+ left: prev,
+ right: el,
});
}, first).optimize(compressor);
}
+ // [ x, "foo", "bar", y ].join() ---> [ x, "foo,bar", y ].join()
+ // [ x, "foo", "bar", y ].join("-") ---> [ x, "foo-bar", y ].join("-")
// need this awkward cloning to not affect original element
// best_of will decide which one to get through.
var node = self.clone();
@@ -8152,7 +8162,7 @@ merge(Compressor.prototype, {
if (compressor.option("unsafe_Function")
&& is_undeclared_ref(exp)
&& exp.name == "Function") {
- // new Function() => function(){}
+ // new Function() ---> function(){}
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
@@ -8780,8 +8790,8 @@ merge(Compressor.prototype, {
return self;
});
- // (a = b, x && a = c) => a = x ? c : b
- // (a = b, x || a = c) => a = x ? b : c
+ // (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;
@@ -8912,7 +8922,7 @@ merge(Compressor.prototype, {
} else if (compressor.in_boolean_context()) switch (op) {
case "!":
if (exp instanceof AST_UnaryPrefix && exp.operator == "!") {
- // !!foo => foo, if we're in boolean context
+ // !!foo ---> foo, if we're in boolean context
return exp.expression;
}
if (exp instanceof AST_Binary) {
@@ -9077,8 +9087,8 @@ merge(Compressor.prototype, {
}
if (compressor.option("assignments") && lazy_op[self.operator]) {
var assign = self.right;
- // a || (a = x) => a = a || x
- // a && (a = x) => a = a && x
+ // a || (a = x) ---> a = a || x
+ // a && (a = x) ---> a = a && x
if (self.left instanceof AST_SymbolRef
&& assign instanceof AST_Assign
&& assign.operator == "="
@@ -9108,11 +9118,11 @@ merge(Compressor.prototype, {
// XXX: intentionally falling down to the next case
case "==":
case "!=":
- // void 0 == x => null == x
+ // void 0 == x ---> null == x
if (!is_strict_comparison && is_undefined(self.left, compressor)) {
self.left = make_node(AST_Null, self.left);
}
- // "undefined" == typeof x => undefined === x
+ // "undefined" == typeof x ---> undefined === x
else if (compressor.option("typeofs")
&& self.left instanceof AST_String
&& self.left.value == "undefined"
@@ -9126,7 +9136,7 @@ merge(Compressor.prototype, {
if (self.operator.length == 2) self.operator += "=";
}
}
- // obj !== obj => false
+ // obj !== obj ---> false
else if (self.left instanceof AST_SymbolRef
&& self.right instanceof AST_SymbolRef
&& self.left.definition() === self.right.definition()
@@ -9136,8 +9146,8 @@ merge(Compressor.prototype, {
break;
case "&&":
case "||":
- // void 0 !== x && null !== x => null != x
- // void 0 === x || null === x => null == x
+ // 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;
@@ -9214,8 +9224,8 @@ merge(Compressor.prototype, {
case ">=": reverse("<="); break;
}
}
- // x && (y && z) => x && y && z
- // x || (y || z) => x || y || z
+ // 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
@@ -9223,19 +9233,19 @@ merge(Compressor.prototype, {
swap_chain();
}
if (compressor.option("strings") && self.operator == "+") {
- // "foo" + 42 + "" => "foo" + 42
+ // "foo" + 42 + "" ---> "foo" + 42
if (self.right instanceof AST_String
&& self.right.value == ""
&& self.left.is_string(compressor)) {
return self.left.optimize(compressor);
}
- // "" + ("foo" + 42) => "foo" + 42
+ // "" + ("foo" + 42) ---> "foo" + 42
if (self.left instanceof AST_String
&& self.left.value == ""
&& self.right.is_string(compressor)) {
return self.right.optimize(compressor);
}
- // "" + 42 + "foo" => 42 + "foo"
+ // "" + 42 + "foo" ---> 42 + "foo"
if (self.left instanceof AST_Binary
&& self.left.operator == "+"
&& self.left.left instanceof AST_String
@@ -9244,8 +9254,8 @@ merge(Compressor.prototype, {
self.left = self.left.right;
return self.optimize(compressor);
}
- // "x" + (y + "z") => "x" + y + "z"
- // x + ("y" + z) => x + "y" + z
+ // "x" + (y + "z") ---> "x" + y + "z"
+ // x + ("y" + z) ---> x + "y" + z
if (self.right instanceof AST_Binary
&& self.operator == self.right.operator
&& (self.left.is_string(compressor) && self.right.is_string(compressor)
@@ -9281,7 +9291,7 @@ merge(Compressor.prototype, {
return self.left.optimize(compressor);
}
}
- // (x || false) && y => x ? y : false
+ // (x || false) && y ---> x ? y : false
if (self.left.operator == "||") {
var lr = self.left.right.evaluate(compressor, true);
if (!lr) return make_node(AST_Conditional, self, {
@@ -9315,7 +9325,7 @@ merge(Compressor.prototype, {
]).optimize(compressor);
} else self.truthy = true;
}
- // x && true || y => x ? true : y
+ // x && true || y ---> x ? true : y
if (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, {
@@ -9326,7 +9336,7 @@ merge(Compressor.prototype, {
}
break;
case "+":
- // "foo" + ("bar" + x) => "foobar" + x
+ // "foo" + ("bar" + x) ---> "foobar" + x
if (self.left instanceof AST_Constant
&& self.right instanceof AST_Binary
&& self.right.operator == "+"
@@ -9342,7 +9352,7 @@ merge(Compressor.prototype, {
right: self.right.right
});
}
- // (x + "foo") + "bar" => x + "foobar"
+ // (x + "foo") + "bar" ---> x + "foobar"
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator == "+"
@@ -9358,7 +9368,7 @@ merge(Compressor.prototype, {
})
});
}
- // a + -b => a - b
+ // a + -b ---> a - b
if (self.right instanceof AST_UnaryPrefix
&& self.right.operator == "-"
&& self.left.is_number(compressor)) {
@@ -9369,7 +9379,7 @@ merge(Compressor.prototype, {
});
break;
}
- // -a + b => b - a
+ // -a + b ---> b - a
if (self.left instanceof AST_UnaryPrefix
&& self.left.operator == "-"
&& reversible()
@@ -9381,7 +9391,7 @@ merge(Compressor.prototype, {
});
break;
}
- // (a + b) + 3 => 3 + (a + b)
+ // (a + b) + 3 ---> 3 + (a + b)
if (compressor.option("unsafe_math")
&& self.left instanceof AST_Binary
&& PRECEDENCE[self.left.operator] == PRECEDENCE[self.operator]
@@ -9402,7 +9412,7 @@ merge(Compressor.prototype, {
break;
}
case "-":
- // a - -b => a + b
+ // a - -b ---> a + b
if (self.right instanceof AST_UnaryPrefix
&& self.right.operator == "-"
&& self.left.is_number(compressor)
@@ -9417,8 +9427,8 @@ merge(Compressor.prototype, {
case "*":
case "/":
associative = compressor.option("unsafe_math");
- // +a - b => a - b
- // a - +b => a - b
+ // +a - b ---> a - b
+ // a - +b ---> a - b
if (self.operator != "+") [ "left", "right" ].forEach(function(operand) {
var node = self[operand];
if (node instanceof AST_UnaryPrefix && node.operator == "+") {
@@ -9431,7 +9441,7 @@ merge(Compressor.prototype, {
case "&":
case "|":
case "^":
- // a + +b => +b + a
+ // a + +b ---> +b + a
if (self.operator != "-"
&& self.operator != "/"
&& (self.left.is_boolean(compressor) || self.left.is_number(compressor))
@@ -9453,7 +9463,7 @@ merge(Compressor.prototype, {
}
}
if (!associative || !self.is_number(compressor)) break;
- // a + (b + c) => (a + b) + c
+ // a + (b + c) ---> (a + b) + c
if (self.right instanceof AST_Binary
&& self.right.operator != "%"
&& PRECEDENCE[self.right.operator] == PRECEDENCE[self.operator]
@@ -9485,8 +9495,8 @@ merge(Compressor.prototype, {
});
}
}
- // (2 * n) * 3 => 6 * n
- // (n + 2) + 3 => n + 5
+ // (2 * n) * 3 ---> 6 * n
+ // (n + 2) + 3 ---> n + 5
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator != "%"
@@ -9511,7 +9521,7 @@ merge(Compressor.prototype, {
}
if (!(parent instanceof AST_UnaryPrefix && parent.operator == "delete")) {
if (self.left instanceof AST_Number && !self.right.is_constant()) switch (self.operator) {
- // 0 + n => n
+ // 0 + n ---> n
case "+":
if (self.left.value == 0) {
if (self.right.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
@@ -9521,7 +9531,7 @@ merge(Compressor.prototype, {
if (self.right.is_number(compressor) && !self.right.is_negative_zero()) return self.right;
}
break;
- // 1 * n => n
+ // 1 * n ---> n
case "*":
if (self.left.value == 1) {
return self.right.is_number(compressor) ? self.right : make_node(AST_UnaryPrefix, self, {
@@ -9532,7 +9542,7 @@ merge(Compressor.prototype, {
break;
}
if (self.right instanceof AST_Number && !self.left.is_constant()) switch (self.operator) {
- // n + 0 => n
+ // n + 0 ---> n
case "+":
if (self.right.value == 0) {
if (self.left.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
@@ -9542,7 +9552,7 @@ merge(Compressor.prototype, {
if (self.left.is_number(compressor) && !self.left.is_negative_zero()) return self.left;
}
break;
- // n - 0 => n
+ // n - 0 ---> n
case "-":
if (self.right.value == 0) {
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
@@ -9551,7 +9561,7 @@ merge(Compressor.prototype, {
}).optimize(compressor);
}
break;
- // n / 1 => n
+ // n / 1 ---> n
case "/":
if (self.right.value == 1) {
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
@@ -9674,16 +9684,16 @@ merge(Compressor.prototype, {
function is_indexOf_match_pattern() {
switch (self.operator) {
case "<=":
- // 0 <= array.indexOf(string) => !!~array.indexOf(string)
+ // 0 <= array.indexOf(string) ---> !!~array.indexOf(string)
return indexRight && self.left instanceof AST_Number && self.left.value == 0;
case "<":
- // array.indexOf(string) < 0 => !~array.indexOf(string)
+ // array.indexOf(string) < 0 ---> !~array.indexOf(string)
if (indexLeft && self.right instanceof AST_Number && self.right.value == 0) return true;
- // -1 < array.indexOf(string) => !!~array.indexOf(string)
+ // -1 < array.indexOf(string) ---> !!~array.indexOf(string)
case "==":
case "!=":
- // -1 == array.indexOf(string) => !~array.indexOf(string)
- // -1 != array.indexOf(string) => !!~array.indexOf(string)
+ // -1 == array.indexOf(string) ---> !~array.indexOf(string)
+ // -1 != array.indexOf(string) ---> !!~array.indexOf(string)
if (!indexRight) return false;
return self.left instanceof AST_Number && self.left.value == -1
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
@@ -10037,7 +10047,7 @@ merge(Compressor.prototype, {
if (self.right.left instanceof AST_SymbolRef
&& self.right.left.name == self.left.name
&& ASSIGN_OPS[self.right.operator]) {
- // x = x - 2 => x -= 2
+ // x = x - 2 ---> x -= 2
return make_node(AST_Assign, self, {
operator: self.right.operator + "=",
left: self.left,
@@ -10048,7 +10058,7 @@ merge(Compressor.prototype, {
&& self.right.right.name == self.left.name
&& ASSIGN_OPS_COMMUTATIVE[self.right.operator]
&& !self.right.left.has_side_effects(compressor)) {
- // x = 2 & x => x &= 2
+ // x = 2 & x ---> x &= 2
return make_node(AST_Assign, self, {
operator: self.right.operator + "=",
left: self.left,
@@ -10122,13 +10132,13 @@ merge(Compressor.prototype, {
var consequent = self.consequent;
var alternative = self.alternative;
if (repeatable(compressor, condition)) {
- // x ? x : y => x || y
+ // x ? x : y ---> x || y
if (condition.equivalent_to(consequent)) return make_node(AST_Binary, self, {
operator: "||",
left: condition,
right: alternative,
}).optimize(compressor);
- // x ? y : x => x && y
+ // x ? y : x ---> x && y
if (condition.equivalent_to(alternative)) return make_node(AST_Binary, self, {
operator: "&&",
left: condition,
@@ -10162,17 +10172,17 @@ merge(Compressor.prototype, {
});
}
}
- // x ? y : y => x, y
+ // x ? y : y ---> x, y
if (consequent.equivalent_to(alternative)) return make_sequence(self, [
condition,
consequent
]).optimize(compressor);
- // x ? y.p : z.p => (x ? y : z).p
- // x ? y(a) : z(a) => (x ? y : z)(a)
- // x ? y.f(a) : z.f(a) => (x ? y : z).f(a)
+ // x ? y.p : z.p ---> (x ? y : z).p
+ // x ? y(a) : z(a) ---> (x ? y : z)(a)
+ // x ? y.f(a) : z.f(a) ---> (x ? y : z).f(a)
var combined = combine_tail(consequent, alternative, true);
if (combined) return combined;
- // x ? y(a) : y(b) => y(x ? a : b)
+ // x ? y(a) : y(b) ---> y(x ? a : b)
var arg_index;
if (consequent instanceof AST_Call
&& alternative.TYPE == consequent.TYPE
@@ -10195,7 +10205,7 @@ merge(Compressor.prototype, {
});
return node;
}
- // x ? (y ? a : b) : b => x && y ? a : b
+ // x ? (y ? a : b) : b ---> x && y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.alternative.equivalent_to(alternative)) {
return make_node(AST_Conditional, self, {
@@ -10208,7 +10218,7 @@ merge(Compressor.prototype, {
alternative: alternative
});
}
- // x ? (y ? a : b) : a => !x || y ? a : b
+ // x ? (y ? a : b) : a ---> !x || y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.consequent.equivalent_to(alternative)) {
return make_node(AST_Conditional, self, {
@@ -10221,7 +10231,7 @@ merge(Compressor.prototype, {
alternative: consequent.alternative
});
}
- // x ? a : (y ? a : b) => x || y ? a : b
+ // x ? a : (y ? a : b) ---> x || y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equivalent_to(alternative.consequent)) {
return make_node(AST_Conditional, self, {
@@ -10234,7 +10244,7 @@ merge(Compressor.prototype, {
alternative: alternative.alternative
});
}
- // x ? b : (y ? a : b) => !x && y ? a : b
+ // x ? b : (y ? a : b) ---> !x && y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equivalent_to(alternative.alternative)) {
return make_node(AST_Conditional, self, {
@@ -10247,7 +10257,7 @@ merge(Compressor.prototype, {
alternative: consequent
});
}
- // x ? (a, c) : (b, c) => x ? a : b, c
+ // x ? (a, c) : (b, c) ---> x ? a : b, c
if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence)
&& consequent.tail_node().equivalent_to(alternative.tail_node())) {
return make_sequence(self, [
@@ -10259,7 +10269,7 @@ merge(Compressor.prototype, {
consequent.tail_node()
]).optimize(compressor);
}
- // x ? y && a : a => (!x || y) && a
+ // x ? y && a : a ---> (!x || y) && a
if (consequent instanceof AST_Binary
&& consequent.operator == "&&"
&& consequent.right.equivalent_to(alternative)) {
@@ -10273,7 +10283,7 @@ merge(Compressor.prototype, {
right: alternative
}).optimize(compressor);
}
- // x ? y || a : a => x && y || a
+ // x ? y || a : a ---> x && y || a
if (consequent instanceof AST_Binary
&& consequent.operator == "||"
&& consequent.right.equivalent_to(alternative)) {
@@ -10287,7 +10297,7 @@ merge(Compressor.prototype, {
right: alternative
}).optimize(compressor);
}
- // x ? a : y && a => (x || y) && a
+ // x ? a : y && a ---> (x || y) && a
if (alternative instanceof AST_Binary
&& alternative.operator == "&&"
&& alternative.right.equivalent_to(consequent)) {
@@ -10301,7 +10311,7 @@ merge(Compressor.prototype, {
right: consequent
}).optimize(compressor);
}
- // x ? a : y || a => !x && y || a
+ // x ? a : y || a ---> !x && y || a
if (alternative instanceof AST_Binary
&& alternative.operator == "||"
&& alternative.right.equivalent_to(consequent)) {
@@ -10318,10 +10328,10 @@ merge(Compressor.prototype, {
var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
if (is_true(consequent)) {
if (is_false(alternative)) {
- // c ? true : false => !!c
+ // c ? true : false ---> !!c
return booleanize(condition);
}
- // c ? true : x => !!c || x
+ // c ? true : x ---> !!c || x
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(condition),
@@ -10330,10 +10340,10 @@ merge(Compressor.prototype, {
}
if (is_false(consequent)) {
if (is_true(alternative)) {
- // c ? false : true => !c
+ // c ? false : true ---> !c
return booleanize(condition.negate(compressor));
}
- // c ? false : x => !c && x
+ // c ? false : x ---> !c && x
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(condition.negate(compressor)),
@@ -10341,7 +10351,7 @@ merge(Compressor.prototype, {
});
}
if (is_true(alternative)) {
- // c ? x : true => !c || x
+ // c ? x : true ---> !c || x
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(condition.negate(compressor)),
@@ -10349,7 +10359,7 @@ merge(Compressor.prototype, {
});
}
if (is_false(alternative)) {
- // c ? x : false => !!c && x
+ // c ? x : false ---> !!c && x
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(condition),
diff --git a/lib/output.js b/lib/output.js
index ae068462..f2748e9e 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -689,32 +689,32 @@ function OutputStream(options) {
PARENS(AST_Sequence, function(output) {
var p = output.parent();
- // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
+ // [ 1, (2, 3), 4 ] ---> [ 1, 3, 4 ]
return p instanceof AST_Array
- // () => (foo, bar)
+ // () ---> (foo, bar)
|| is_arrow(p) && p.value === this
// await (foo, bar)
|| p instanceof AST_Await
- // 1 + (2, 3) + 4 ==> 8
+ // 1 + (2, 3) + 4 ---> 8
|| p instanceof AST_Binary
// new (foo, bar) or foo(1, (2, 3), 4)
|| p instanceof AST_Call
// (false, true) ? (a = 10, b = 20) : (c = 30)
- // ==> 20 (side effect, set a := 10 and b := 20)
+ // ---> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional
- // [ a = (1, 2) ] = [] ==> a == 2
+ // [ a = (1, 2) ] = [] ---> a == 2
|| p instanceof AST_DefaultValue
- // { [(1, 2)]: 3 }[2] ==> 3
- // { foo: (1, 2) }.foo ==> 2
+ // { [(1, 2)]: 3 }[2] ---> 3
+ // { foo: (1, 2) }.foo ---> 2
|| p instanceof AST_DestructuredKeyVal
|| p instanceof AST_ObjectProperty
- // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
+ // (1, {foo:2}).foo or (1, {foo:2})["foo"] ---> 2
|| p instanceof AST_PropAccess && p.expression === this
// ...(foo, bar, baz)
|| p instanceof AST_Spread
// !(foo, bar, baz)
|| p instanceof AST_Unary
- // var a = (1, 2), b = a + a; ==> b == 4
+ // var a = (1, 2), b = a + a; ---> b == 4
|| p instanceof AST_VarDef;
});
@@ -777,14 +777,10 @@ function OutputStream(options) {
});
PARENS(AST_Number, function(output) {
+ if (!output.option("galio")) return false;
+ // https://github.com/mishoo/UglifyJS/pull/1009
var p = output.parent();
- if (p instanceof AST_PropAccess && p.expression === this) {
- var value = this.value;
- // https://github.com/mishoo/UglifyJS/issues/115
- return value < 0
- // https://github.com/mishoo/UglifyJS/pull/1009
- || output.option("galio") && /^0/.test(make_num(value));
- }
+ return p instanceof AST_PropAccess && p.expression === this && /^0/.test(make_num(this.value));
});
function needs_parens_assign_cond(self, output) {
@@ -807,8 +803,8 @@ function OutputStream(options) {
});
PARENS(AST_Assign, function(output) {
if (needs_parens_assign_cond(this, output)) return true;
- // v8 parser bug => workaround
- // f([1], [a] = []) => f([1], ([a] = []))
+ // v8 parser bug ---> workaround
+ // f([1], [a] = []) ---> f([1], ([a] = []))
if (output.option("v8")) return this.left instanceof AST_Destructured;
// ({ p: a } = o);
if (this.left instanceof AST_DestructuredObject) return needs_parens_obj(output);
diff --git a/lib/parse.js b/lib/parse.js
index 4fd8a019..64307a95 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -280,9 +280,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}
function read_while(pred) {
- var ret = "", ch, i = 0;
- while ((ch = peek()) && pred(ch, i++))
- ret += next();
+ var ret = "", ch;
+ while ((ch = peek()) && pred(ch)) ret += next();
return ret;
}
@@ -292,16 +291,14 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function read_num(prefix) {
var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
- var num = read_while(function(ch, i) {
+ var num = read_while(function(ch) {
var code = ch.charCodeAt(0);
switch (code) {
case 120: case 88: // xX
return has_x ? false : (has_x = true);
case 101: case 69: // eE
return has_x ? true : has_e ? false : (has_e = after_e = true);
- case 45: // -
- return after_e || (i == 0 && !prefix);
- case 43: // +
+ case 43: case 45: // +-
return after_e;
case (after_e = false, 46): // .
return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false;
@@ -315,8 +312,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
num = num.replace(has_x ? /([1-9a-f]|.0)_(?=[0-9a-f])/gi : /([1-9]|.0)_(?=[0-9])/gi, "$1");
}
var valid = parse_js_number(num);
- if (!isNaN(valid)) return token("num", valid);
- parse_error("Invalid syntax: " + num);
+ if (isNaN(valid)) parse_error("Invalid syntax: " + num);
+ if (has_dot || has_e || peek() != "n") return token("num", valid);
+ return token("bigint", num.toLowerCase() + next());
}
function read_escaped_char(in_string) {
@@ -635,7 +633,7 @@ var PRECEDENCE = function(a, ret) {
["*", "/", "%"]
], {});
-var ATOMIC_START_TOKEN = makePredicate("atom num regexp string");
+var ATOMIC_START_TOKEN = makePredicate("atom bigint num regexp string");
/* -----[ Parser ]----- */
@@ -783,6 +781,7 @@ function parse($TEXT, options) {
semicolon();
return dir ? new AST_Directive(body) : new AST_SimpleStatement({ body: body });
case "num":
+ case "bigint":
case "regexp":
case "operator":
case "atom":
@@ -1361,6 +1360,9 @@ function parse($TEXT, options) {
case "num":
ret = new AST_Number({ start: tok, end: tok, value: tok.value });
break;
+ case "bigint":
+ ret = new AST_BigInt({ start: tok, end: tok, value: tok.value });
+ break;
case "string":
ret = new AST_String({
start : tok,
diff --git a/lib/propmangle.js b/lib/propmangle.js
index 80797599..254780f9 100644
--- a/lib/propmangle.js
+++ b/lib/propmangle.js
@@ -228,7 +228,7 @@ function mangle_properties(ast, options) {
var mangled = cache.get(name);
if (!mangled) {
if (debug) {
- // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
+ // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo ---> o._$foo$NNN_.
var debug_mangled = "_$" + name + "$" + debug_suffix + "_";
if (can_mangle(debug_mangled)) mangled = debug_mangled;
}
diff --git a/test/compress/arrays.js b/test/compress/arrays.js
index 9569c4fa..fff7e9b6 100644
--- a/test/compress/arrays.js
+++ b/test/compress/arrays.js
@@ -13,9 +13,10 @@ holes_and_undefined: {
}
}
-constant_join: {
+constant_join_1: {
options = {
evaluate: true,
+ side_effects: true,
strings: true,
unsafe: true,
}
@@ -57,7 +58,7 @@ constant_join: {
var c5 = [ boo() + bar() + "foo", 1, 2, 3, "bar", bar() + "foo" ].join();
var c6 = [ "1,2,,,foo,bar", baz() ].join();
var d = "foo-3bar-baz";
- var e = [].join(foo + bar);
+ var e = (foo, bar, "");
var f = "";
var g = "";
}
diff --git a/test/compress/bigint.js b/test/compress/bigint.js
new file mode 100644
index 00000000..7ee225ce
--- /dev/null
+++ b/test/compress/bigint.js
@@ -0,0 +1,46 @@
+arithmetic: {
+ input: {
+ console.log(((1n + 0x2n) * (0o3n - -4n)) >> (5n - 6n));
+ }
+ expect_exact: "console.log((1n+0x2n)*(0o3n- -4n)>>5n-6n);"
+ expect_stdout: "42n"
+ node_version: ">=10"
+}
+
+minus_dot: {
+ input: {
+ console.log(typeof -42n.toString(), typeof (-42n).toString());
+ }
+ expect_exact: "console.log(typeof-42n.toString(),typeof(-42n).toString());"
+ expect_stdout: "number string"
+ node_version: ">=10"
+}
+
+evaluate: {
+ options = {
+ evaluate: true,
+ unsafe: true,
+ }
+ input: {
+ console.log((0xDEAD_BEEFn).toString(16));
+ }
+ expect: {
+ console.log(0xdeadbeefn.toString(16));
+ }
+ expect_stdout: "deadbeef"
+ node_version: ">=10"
+}
+
+Number: {
+ options = {
+ unsafe: true,
+ }
+ input: {
+ console.log(Number(-0xfeed_dead_beef_badn));
+ }
+ expect: {
+ console.log(+("" + -0xfeed_dead_beef_badn));
+ }
+ expect_stdout: "-1148098955808013200"
+ node_version: ">=10"
+}
diff --git a/test/compress/issue-269.js b/test/compress/issue-269.js
index 2adc5017..3e0c4437 100644
--- a/test/compress/issue-269.js
+++ b/test/compress/issue-269.js
@@ -17,7 +17,7 @@ issue_269_1: {
expect: {
var x = {};
console.log(
- x + "", +x, !!x,
+ "" + x, +("" + x), !!x,
"", 0, false
);
}
diff --git a/test/compress/numbers.js b/test/compress/numbers.js
index 5a7000cd..994cd5cf 100644
--- a/test/compress/numbers.js
+++ b/test/compress/numbers.js
@@ -338,7 +338,7 @@ evaluate_3: {
console.log(1 + Number(x) + 2);
}
expect: {
- console.log(+x + 3);
+ console.log(+("" + x) + 3);
}
}
diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js
index 0a6de2a3..a9676fc0 100644
--- a/test/ufuzz/index.js
+++ b/test/ufuzz/index.js
@@ -134,6 +134,7 @@ var SUPPORT = function(matrix) {
}({
arrow: "a => 0;",
async: "async function f(){}",
+ bigint: "42n",
catch_omit_var: "try {} catch {}",
computed_key: "({[0]: 0});",
const_block: "var a; { const a = 0; }",
@@ -188,6 +189,12 @@ var VALUES = [
'"function"',
"this",
];
+if (SUPPORT.bigint) VALUES = VALUES.concat([
+ "!0o644n",
+ "([3n][0] > 2)",
+ "(-42n).toString()",
+ "Number(0XDEADn << 16n | 0xbeefn)",
+]);
var BINARY_OPS = [
" + ", // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)