From: Alex Lam S.L Date: Sun, 24 Jan 2021 01:51:18 +0000 (+0000) Subject: support BigInt literals (#4583) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=8bfd891c09edfb54d1b93010487ee68ad64a457c;p=UglifyJS.git support BigInt literals (#4583) --- 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)