support BigInt literals (#4583)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 24 Jan 2021 01:51:18 +0000 (01:51 +0000)
committerGitHub <noreply@github.com>
Sun, 24 Jan 2021 01:51:18 +0000 (09:51 +0800)
lib/ast.js
lib/compress.js
lib/output.js
lib/parse.js
lib/propmangle.js
test/compress/arrays.js
test/compress/bigint.js [new file with mode: 0644]
test/compress/issue-269.js
test/compress/numbers.js
test/ufuzz/index.js

index ab2559c..1a09d4d 100644 (file)
@@ -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);
 
index f444177..6b8a7ef 100644 (file)
@@ -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),
index ae06846..f2748e9 100644 (file)
@@ -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);
index 4fd8a01..64307a9 100644 (file)
@@ -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,
index 8079759..254780f 100644 (file)
@@ -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;
             }
index 9569c4f..fff7e9b 100644 (file)
@@ -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 (file)
index 0000000..7ee225c
--- /dev/null
@@ -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"
+}
index 2adc501..3e0c443 100644 (file)
@@ -17,7 +17,7 @@ issue_269_1: {
     expect: {
         var x = {};
         console.log(
-            x + "", +x, !!x,
+            "" + x, +("" + x), !!x,
             "", 0, false
         );
     }
index 5a7000c..994cd5c 100644 (file)
@@ -338,7 +338,7 @@ evaluate_3: {
         console.log(1 + Number(x) + 2);
     }
     expect: {
-        console.log(+x + 3);
+        console.log(+("" + x) + 3);
     }
 }
 
index 0a6de2a..a9676fc 100644 (file)
@@ -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)