diff --git a/README.md b/README.md index f0a64a76..fab8eda0 100644 --- a/README.md +++ b/README.md @@ -653,6 +653,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `booleans` -- various optimizations for boolean context, for example `!!a ? b : c → a ? b : c` +- `typeofs` -- default `true`. Transforms `typeof foo == "undefined"` into + `foo === void 0`. Note: recommend to set this value to `false` for IE10 and + earlier versions due to known issues. + - `loops` -- optimizations for `do`, `while` and `for` loops when we can statically determine the condition @@ -897,7 +901,6 @@ when this flag is on: - `new Object()` → `{}` - `String(exp)` or `exp.toString()` → `"" + exp` - `new Object/RegExp/Function/Error/Array (...)` → we discard the `new` -- `typeof foo == "undefined"` → `foo === void 0` - `void 0` → `undefined` (if there is a variable named "undefined" in scope; we do it because the variable name will be mangled, typically reduced to a single character) @@ -1050,3 +1053,29 @@ in total it's a bit more than just using UglifyJS's own parser. [acorn]: https://github.com/ternjs/acorn [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k + +### Uglify Fast Minify Mode + +It's not well known, but variable and function name mangling accounts for +95% of the size reduction in minified code for most javascript - not +elaborate code transforms. One can simply disable `compress` to speed up +Uglify builds by 3 to 4 times. In this fast `mangle`-only mode Uglify has +comparable minify speeds and gzip sizes to +[`butternut`](https://www.npmjs.com/package/butternut): + +| d3.js | minify size | gzip size | minify time (seconds) | +| --- | ---: | ---: | ---: | +| original | 451,131 | 108,733 | - | +| uglify-js@3.0.23 mangle=false, compress=false | 316,600 | 85,245 | 0.73 | +| uglify-js@3.0.23 mangle=true, compress=false | 220,216 | 72,730 | 1.21 | +| Butternut 0.4.6 | 217,568 | 72,738 | 1.81 | +| uglify-js@3.0.23 mangle=true, compress=true | 212,511 | 71,560 | 4.64 | + +To enable fast minify mode from the CLI use: +``` +uglifyjs file.js -m +``` +To enable fast minify mode with the API use: +```js +UglifyJS.minify(code, { compress: false, mangle: true }); +``` diff --git a/lib/ast.js b/lib/ast.js index 1ac58bae..d2be08a8 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -1160,7 +1160,7 @@ TreeWalker.prototype = { if (!ret && descend) { descend.call(node); } - this.pop(node); + this.pop(); return ret; }, parent: function(n) { @@ -1179,8 +1179,8 @@ TreeWalker.prototype = { } this.stack.push(node); }, - pop: function(node) { - this.stack.pop(); + pop: function() { + var node = this.stack.pop(); if (node instanceof AST_Lambda || node instanceof AST_Class) { this.directives = Object.getPrototypeOf(this.directives); } diff --git a/lib/compress.js b/lib/compress.js index 07d3b4b5..88d66c51 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -82,6 +82,7 @@ function Compressor(options, false_by_default) { switches : !false_by_default, top_retain : null, toplevel : !!(options && options["top_retain"]), + typeofs : !false_by_default, unsafe : false, unsafe_comps : false, unsafe_Func : false, @@ -850,16 +851,20 @@ merge(Compressor.prototype, { function has_overlapping_symbol(fn, arg) { var found = false; - arg.walk(new TreeWalker(function(node) { + var tw = new TreeWalker(function(node) { if (found) return true; if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { var s = node.definition().scope; if (s !== scope) while (s = s.parent_scope) { if (s === scope) return true; } - found = true; + return found = true; } - })); + if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { + return found = true; + } + }); + arg.walk(tw); return found; } @@ -874,13 +879,17 @@ merge(Compressor.prototype, { && all(iife.args, function(arg) { return !(arg instanceof AST_Expansion); })) { - fn.argnames.forEach(function(sym, i) { + var names = Object.create(null); + for (var i = fn.argnames.length; --i >= 0;) { + var sym = fn.argnames[i]; + if (sym.name in names) continue; + names[sym.name] = true; if (sym instanceof AST_Expansion) { var elements = iife.args.slice(i); if (all(elements, function(arg) { return !has_overlapping_symbol(fn, arg); })) { - candidates.push(make_node(AST_VarDef, sym, { + candidates.unshift(make_node(AST_VarDef, sym, { name: sym.expression, value: make_node(AST_Array, iife, { elements: elements @@ -891,12 +900,12 @@ merge(Compressor.prototype, { var arg = iife.args[i]; if (!arg) arg = make_node(AST_Undefined, sym); else if (has_overlapping_symbol(fn, arg)) arg = null; - if (arg) candidates.push(make_node(AST_VarDef, sym, { + if (arg) candidates.unshift(make_node(AST_VarDef, sym, { name: sym, value: arg })); } - }); + } } } @@ -1268,21 +1277,21 @@ merge(Compressor.prototype, { for (var i = 0, len = statements.length; i < len; i++) { var stat = statements[i]; if (prev) { - if (stat instanceof AST_For) { - try { - prev.body.walk(new TreeWalker(function(node){ - if (node instanceof AST_Binary && node.operator == "in") - throw cons_seq; - })); - if (stat.init && !(stat.init instanceof AST_Definitions)) { - stat.init = cons_seq(stat.init); + if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) { + var abort = false; + prev.body.walk(new TreeWalker(function(node) { + if (abort || node instanceof AST_Scope) return true; + if (node instanceof AST_Binary && node.operator == "in") { + abort = true; + return true; } - else if (!stat.init) { + })); + if (!abort) { + if (stat.init) stat.init = cons_seq(stat.init); + else { stat.init = prev.body.drop_side_effect_free(compressor); n--; } - } catch(ex) { - if (ex !== cons_seq) throw ex; } } else if (stat instanceof AST_If) { @@ -1613,13 +1622,8 @@ merge(Compressor.prototype, { // descendant of AST_Node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; - try { - var val = this._eval(compressor); - return !val || val instanceof RegExp || typeof val != "object" ? val : this; - } catch(ex) { - if (ex !== def) throw ex; - return this; - } + var val = this._eval(compressor); + return !val || val instanceof RegExp || typeof val != "object" ? val : this; }); var unaryPrefix = makePredicate("! ~ - + void"); AST_Node.DEFMETHOD("is_constant", function(){ @@ -1665,34 +1669,33 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); - def(AST_Lambda, function(){ - throw def; - }); - def(AST_Class, function() { - throw def; - }); + def(AST_Lambda, return_this); + def(AST_Class, return_this); function ev(node, compressor) { if (!compressor) throw new Error("Compressor must be passed"); return node._eval(compressor); }; - def(AST_Node, function(){ - throw def; // not constant - }); + def(AST_Node, return_this); def(AST_Constant, function(){ return this.getValue(); }); def(AST_TemplateString, function() { - if (this.segments.length !== 1) throw def; + if (this.segments.length !== 1) return this; return this.segments[0].value; }); def(AST_Array, function(compressor){ if (compressor.option("unsafe")) { - return this.elements.map(function(element) { - return ev(element, compressor); - }); + var elements = []; + for (var i = 0, len = this.elements.length; i < len; i++) { + var element = this.elements[i]; + var value = ev(element, compressor); + if (element === value) return this; + elements.push(value); + } + return elements; } - throw def; + return this; }); def(AST_Object, function(compressor){ if (compressor.option("unsafe")) { @@ -1704,164 +1707,263 @@ merge(Compressor.prototype, { key = key.name; } else if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { - throw def; + return this; } val[key] = ev(prop.value, compressor); + if (val[key] === prop.value) return this; } return val; } - throw def; + return this; }); def(AST_UnaryPrefix, function(compressor){ - var e = this.expression; + // Function would be evaluated to an array and so typeof would + // incorrectly return 'object'. Hence making is a special case. + if (this.operator == "typeof" && is_func_expr(this.expression)) { + return typeof function(){}; + } + var e = ev(this.expression, compressor); + if (e === this.expression) return this; switch (this.operator) { - case "!": return !ev(e, compressor); + case "!": return !e; case "typeof": - // Function would be evaluated to an array and so typeof would - // incorrectly return 'object'. Hence making is a special case. - if (is_func_expr(e)) return typeof function(){}; - - e = ev(e, compressor); - // typeof returns "object" or "function" on different platforms // so cannot evaluate reliably - if (e instanceof RegExp) throw def; - + if (e instanceof RegExp) return this; return typeof e; - case "void": return void ev(e, compressor); - case "~": return ~ev(e, compressor); - case "-": return -ev(e, compressor); - case "+": return +ev(e, compressor); + case "void": return void e; + case "~": return ~e; + case "-": return -e; + case "+": return +e; } - throw def; + return this; }); - def(AST_Binary, function(c){ - var left = this.left, right = this.right, result; + def(AST_Binary, function(compressor){ + var left = ev(this.left, compressor); + if (left === this.left) return this; + var right = ev(this.right, compressor); + if (right === this.right) return this; + var result; switch (this.operator) { - case "&&" : result = ev(left, c) && ev(right, c); break; - case "||" : result = ev(left, c) || ev(right, c); break; - case "|" : result = ev(left, c) | ev(right, c); break; - case "&" : result = ev(left, c) & ev(right, c); break; - case "^" : result = ev(left, c) ^ ev(right, c); break; - case "+" : result = ev(left, c) + ev(right, c); break; - case "*" : result = ev(left, c) * ev(right, c); break; - case "**" : result = Math.pow(ev(left, c), ev(right, c)); break; - case "/" : result = ev(left, c) / ev(right, c); break; - case "%" : result = ev(left, c) % ev(right, c); break; - case "-" : result = ev(left, c) - ev(right, c); break; - case "<<" : result = ev(left, c) << ev(right, c); break; - case ">>" : result = ev(left, c) >> ev(right, c); break; - case ">>>" : result = ev(left, c) >>> ev(right, c); break; - case "==" : result = ev(left, c) == ev(right, c); break; - case "===" : result = ev(left, c) === ev(right, c); break; - case "!=" : result = ev(left, c) != ev(right, c); break; - case "!==" : result = ev(left, c) !== ev(right, c); break; - case "<" : result = ev(left, c) < ev(right, c); break; - case "<=" : result = ev(left, c) <= ev(right, c); break; - case ">" : result = ev(left, c) > ev(right, c); break; - case ">=" : result = ev(left, c) >= ev(right, c); break; + case "&&" : result = left && right; break; + case "||" : result = left || right; break; + case "|" : result = left | right; break; + case "&" : result = left & right; break; + case "^" : result = left ^ right; break; + case "+" : result = left + right; break; + case "*" : result = left * right; break; + case "**" : result = Math.pow(left, right); break; + case "/" : result = left / right; break; + case "%" : result = left % right; break; + case "-" : result = left - right; break; + case "<<" : result = left << right; break; + case ">>" : result = left >> right; break; + case ">>>" : result = left >>> right; break; + case "==" : result = left == right; break; + case "===" : result = left === right; break; + case "!=" : result = left != right; break; + case "!==" : result = left !== right; break; + case "<" : result = left < right; break; + case "<=" : result = left <= right; break; + case ">" : result = left > right; break; + case ">=" : result = left >= right; break; default: - throw def; + return this; } - if (isNaN(result) && c.find_parent(AST_With)) { + if (isNaN(result) && compressor.find_parent(AST_With)) { // leave original expression as is - throw def; + return this; } return result; }); def(AST_Conditional, function(compressor){ - return ev(this.condition, compressor) - ? ev(this.consequent, compressor) - : ev(this.alternative, compressor); + var condition = ev(this.condition, compressor); + if (condition === this.condition) return this; + var node = condition ? this.consequent : this.alternative; + var value = ev(node, compressor); + return value === node ? this : value; }); def(AST_SymbolRef, function(compressor){ - if (!compressor.option("reduce_vars") || this._evaluating) throw def; - this._evaluating = true; - try { - var fixed = this.fixed_value(); - if (!fixed) throw def; - var value = ev(fixed, compressor); - if (!HOP(fixed, "_eval")) fixed._eval = function() { - return value; - }; - if (value && typeof value == "object" && this.definition().escaped) throw def; - return value; - } finally { - this._evaluating = false; + if (!compressor.option("reduce_vars")) return this; + var fixed = this.fixed_value(); + if (!fixed) return this; + this._eval = return_this; + var value = ev(fixed, compressor); + if (value === fixed) { + delete this._eval; + return this; } + if (!HOP(fixed, "_eval")) fixed._eval = function() { + return value; + }; + if (value && typeof value == "object" && this.definition().escaped) { + delete this._eval; + return this; + } + this._eval = fixed._eval; + return value; }); + var global_objs = { + Array: Array, + Boolean: Boolean, + Math: Math, + Number: Number, + RegExp: RegExp, + Object: Object, + String: String, + }; + function convert_to_predicate(obj) { + for (var key in obj) { + obj[key] = makePredicate(obj[key]); + } + } + var static_values = { + Math: [ + "E", + "LN10", + "LN2", + "LOG2E", + "LOG10E", + "PI", + "SQRT1_2", + "SQRT2", + ], + Number: [ + "MAX_VALUE", + "MIN_VALUE", + "NaN", + "NEGATIVE_INFINITY", + "POSITIVE_INFINITY", + ], + }; + convert_to_predicate(static_values); def(AST_PropAccess, function(compressor){ if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === this.property) return this; } - var val = ev(this.expression, compressor); - if (val && HOP(val, key)) { - return val[key]; + var exp = this.expression; + var val; + if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (!(static_values[exp.name] || return_false)(key)) return this; + val = global_objs[exp.name]; + } else { + val = ev(exp, compressor); + if (!val || val === exp || !HOP(val, key)) return this; } + return val[key]; } - throw def; + return this; }); var object_fns = [ - 'constructor', - 'toString', - 'valueOf', + "constructor", + "toString", + "valueOf", ]; var native_fns = { - Array: makePredicate([ - 'indexOf', - 'join', - 'lastIndexOf', - 'slice', - ].concat(object_fns)), - Boolean: makePredicate(object_fns), - Number: makePredicate([ - 'toExponential', - 'toFixed', - 'toPrecision', - ].concat(object_fns)), - RegExp: makePredicate([ - 'test', - ].concat(object_fns)), - String: makePredicate([ - 'charAt', - 'charCodeAt', - 'concat', - 'indexOf', - 'italics', - 'lastIndexOf', - 'match', - 'replace', - 'search', - 'slice', - 'split', - 'substr', - 'substring', - 'trim', - ].concat(object_fns)), + Array: [ + "indexOf", + "join", + "lastIndexOf", + "slice", + ].concat(object_fns), + Boolean: object_fns, + Number: [ + "toExponential", + "toFixed", + "toPrecision", + ].concat(object_fns), + RegExp: [ + "test", + ].concat(object_fns), + String: [ + "charAt", + "charCodeAt", + "concat", + "indexOf", + "italics", + "lastIndexOf", + "match", + "replace", + "search", + "slice", + "split", + "substr", + "substring", + "trim", + ].concat(object_fns), }; + convert_to_predicate(native_fns); + var static_fns = { + Array: [ + "isArray", + ], + Math: [ + "abs", + "acos", + "asin", + "atan", + "ceil", + "cos", + "exp", + "floor", + "log", + "round", + "sin", + "sqrt", + "tan", + "atan2", + "pow", + "max", + "min" + ], + Number: [ + "isFinite", + "isNaN", + ], + Object: [ + "keys", + "getOwnPropertyNames", + ], + String: [ + "fromCharCode", + ], + }; + convert_to_predicate(static_fns); def(AST_Call, function(compressor){ var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { var key = exp.property; if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === exp.property) return this; } - var val = ev(exp.expression, compressor); - if ((val && native_fns[val.constructor.name] || return_false)(key)) { - return val[key].apply(val, this.args.map(function(arg) { - return ev(arg, compressor); - })); + var val; + var e = exp.expression; + if (e instanceof AST_SymbolRef && e.undeclared()) { + if (!(static_fns[e.name] || return_false)(key)) return this; + val = global_objs[e.name]; + } else { + val = ev(e, compressor); + if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this; } + var args = []; + for (var i = 0, len = this.args.length; i < len; i++) { + var arg = this.args[i]; + var value = ev(arg, compressor); + if (arg === value) return this; + args.push(value); + } + return val[key].apply(val, args); } - throw def; - }); - def(AST_New, function(compressor){ - throw def; + return this; }); + def(AST_New, return_this); })(function(node, func){ node.DEFMETHOD("_eval", func); }); @@ -3782,7 +3884,8 @@ merge(Compressor.prototype, { case "==": case "!=": // "undefined" == typeof x => undefined === x - if (self.left instanceof AST_String + if (compressor.option("typeofs") + && self.left instanceof AST_String && self.left.value == "undefined" && self.right instanceof AST_UnaryPrefix && self.right.operator == "typeof") { @@ -4510,6 +4613,17 @@ merge(Compressor.prototype, { return self; }); + AST_Lambda.DEFMETHOD("contains_this", function() { + var result; + var self = this; + self.walk(new TreeWalker(function(node) { + if (result) return true; + if (node instanceof AST_This) return result = true; + if (node !== self && node instanceof AST_Scope && !(node instanceof AST_Arrow)) return true; + })); + return result; + }); + OPT(AST_Dot, function(self, compressor){ var def = self.resolve_defines(compressor); if (def) { @@ -4524,6 +4638,20 @@ merge(Compressor.prototype, { }) }).optimize(compressor); } + if (compressor.option("unsafe") && self.expression instanceof AST_Object) { + var values = self.expression.properties; + for (var i = values.length; --i >= 0;) { + if (values[i].key === prop) { + var value = values[i].value; + if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) { + var obj = self.expression.clone(); + obj.properties = obj.properties.slice(); + obj.properties.splice(i, 1); + return make_sequence(self, [ obj, value ]).optimize(compressor); + } + } + } + } if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot && self.expression.property == "prototype") { @@ -4656,17 +4784,6 @@ merge(Compressor.prototype, { return self; }); - AST_Lambda.DEFMETHOD("contains_this", function() { - var result; - var self = this; - self.walk(new TreeWalker(function(node) { - if (result) return true; - if (node instanceof AST_This) return result = true; - if (node !== self && node instanceof AST_Scope && !(node instanceof AST_Arrow)) return true; - })); - return result; - }); - OPT(AST_ConciseMethod, function(self, compressor){ // p(){return x;} ---> p:()=>x if (compressor.option("arrows") diff --git a/lib/output.js b/lib/output.js index 69ff89fd..7df56a66 100644 --- a/lib/output.js +++ b/lib/output.js @@ -745,14 +745,15 @@ function OutputStream(options) { // parens around it too, otherwise the call will be // interpreted as passing the arguments to the upper New // expression. - try { - this.walk(new TreeWalker(function(node){ - if (node instanceof AST_Call) throw p; - })); - } catch(ex) { - if (ex !== p) throw ex; - return true; - } + var parens = false; + this.walk(new TreeWalker(function(node) { + if (parens || node instanceof AST_Scope) return true; + if (node instanceof AST_Call) { + parens = true; + return true; + } + })); + return parens; } }); @@ -1369,19 +1370,17 @@ function OutputStream(options) { }); function parenthesize_for_noin(node, output, noin) { - if (!noin) node.print(output); - else try { - // need to take some precautions here: - // https://github.com/mishoo/UglifyJS2/issues/60 - node.walk(new TreeWalker(function(node){ - if (node instanceof AST_Binary && node.operator == "in") - throw output; - })); - node.print(output); - } catch(ex) { - if (ex !== output) throw ex; - node.print(output, true); - } + var parens = false; + // need to take some precautions here: + // https://github.com/mishoo/UglifyJS2/issues/60 + if (noin) node.walk(new TreeWalker(function(node) { + if (parens || node instanceof AST_Scope) return true; + if (node instanceof AST_Binary && node.operator == "in") { + parens = true; + return true; + } + })); + node.print(output, parens); }; DEFPRINT(AST_VarDef, function(self, output){ diff --git a/lib/transform.js b/lib/transform.js index 0d52199e..014a82e4 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker; if (y !== undefined) x = y; } } - tw.pop(this); + tw.pop(); return x; }); }; diff --git a/package.json b/package.json index beb745fc..5ef15bd1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.0.23", + "version": "3.0.24", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index aa9a2e41..7fe337d2 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2400,3 +2400,89 @@ issue_2187_3: { } expect_stdout: "1" } + +issue_2203_1: { + options = { + collapse_vars: true, + unused: true, + } + input: { + a = "FAIL"; + console.log({ + a: "PASS", + b: function() { + return function(c) { + return c.a; + }((String, (Object, this))); + } + }.b()); + } + expect: { + a = "FAIL"; + console.log({ + a: "PASS", + b: function() { + return function(c) { + return c.a; + }((String, (Object, this))); + } + }.b()); + } + expect_stdout: "PASS" +} + +issue_2203_2: { + options = { + collapse_vars: true, + unused: true, + } + input: { + a = "PASS"; + console.log({ + a: "FAIL", + b: function() { + return function(c) { + return c.a; + }((String, (Object, function() { + return this; + }()))); + } + }.b()); + } + expect: { + a = "PASS"; + console.log({ + a: "FAIL", + b: function() { + return function(c) { + return (String, (Object, function() { + return this; + }())).a; + }(); + } + }.b()); + } + expect_stdout: "PASS" +} + +duplicate_argname: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function f() { return "PASS"; } + console.log(function(a, a) { + f++; + return a; + }("FAIL", f())); + } + expect: { + function f() { return "PASS"; } + console.log(function(a, a) { + f++; + return a; + }("FAIL", f())); + } + expect_stdout: "PASS" +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index f084fda8..67e4ab38 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -344,22 +344,26 @@ unsafe_constant: { unsafe_object: { options = { - evaluate : true, - unsafe : true + evaluate: true, + reduce_vars: true, + toplevel: true, + unsafe: true, } input: { + var o = { a: 1 }; console.log( - ({a:1}) + 1, - ({a:1}).a + 1, - ({a:1}).b + 1, - ({a:1}).a.b + 1 + o + 1, + o.a + 1, + o.b + 1, + o.a.b + 1 ); } expect: { + var o = { a: 1 }; console.log( - ({a:1}) + 1, + o + 1, 2, - ({a:1}).b + 1, + o.b + 1, 1..b + 1 ); } @@ -368,22 +372,26 @@ unsafe_object: { unsafe_object_nested: { options = { - evaluate : true, - unsafe : true + evaluate: true, + reduce_vars: true, + toplevel: true, + unsafe: true, } input: { + var o = { a: { b: 1 } }; console.log( - ({a:{b:1}}) + 1, - ({a:{b:1}}).a + 1, - ({a:{b:1}}).b + 1, - ({a:{b:1}}).a.b + 1 + o + 1, + o.a + 1, + o.b + 1, + o.a.b + 1 ); } expect: { + var o = { a: { b: 1 } }; console.log( - ({a:{b:1}}) + 1, - ({a:{b:1}}).a + 1, - ({a:{b:1}}).b + 1, + o + 1, + o.a + 1, + o.b + 1, 2 ); } @@ -392,21 +400,25 @@ unsafe_object_nested: { unsafe_object_complex: { options = { - evaluate : true, - unsafe : true + evaluate: true, + reduce_vars: true, + toplevel: true, + unsafe: true, } input: { + var o = { a: { b: 1 }, b: 1 }; console.log( - ({a:{b:1},b:1}) + 1, - ({a:{b:1},b:1}).a + 1, - ({a:{b:1},b:1}).b + 1, - ({a:{b:1},b:1}).a.b + 1 + o + 1, + o.a + 1, + o.b + 1, + o.a.b + 1 ); } expect: { + var o = { a: { b: 1 }, b: 1 }; console.log( - ({a:{b:1},b:1}) + 1, - ({a:{b:1},b:1}).a + 1, + o + 1, + o.a + 1, 2, 2 ); @@ -416,22 +428,26 @@ unsafe_object_complex: { unsafe_object_repeated: { options = { - evaluate : true, - unsafe : true + evaluate: true, + reduce_vars: true, + toplevel: true, + unsafe: true, } input: { + var o = { a: { b: 1 }, a: 1 }; console.log( - ({a:{b:1},a:1}) + 1, - ({a:{b:1},a:1}).a + 1, - ({a:{b:1},a:1}).b + 1, - ({a:{b:1},a:1}).a.b + 1 + o + 1, + o.a + 1, + o.b + 1, + o.a.b + 1 ); } expect: { + var o = { a: { b: 1 }, a: 1 }; console.log( - ({a:{b:1},a:1}) + 1, + o + 1, 2, - ({a:{b:1},a:1}).b + 1, + o.b + 1, 1..b + 1 ); } @@ -480,9 +496,9 @@ unsafe_function: { expect: { console.log( ({a:{b:1},b:function(){}}) + 1, - ({a:{b:1},b:function(){}}).a + 1, - ({a:{b:1},b:function(){}}).b + 1, - ({a:{b:1},b:function(){}}).a.b + 1 + ({b:function(){}}, {b:1}) + 1, + ({a:{b:1}}, function(){}) + 1, + ({b:function(){}}, {b:1}).b + 1 ); } expect_stdout: true @@ -730,8 +746,8 @@ unsafe_prototype_function: { var d = ({toString: 0}) + ""; var e = (({valueOf: 0}) + "")[2]; var f = (({toString: 0}) + "")[2]; - var g = ({valueOf: 0}).valueOf(); - var h = "" + ({toString: 0}); + var g = ({}, 0)(); + var h = ({}, 0)(); } } @@ -1162,3 +1178,75 @@ string_charCodeAt: { } expect_stdout: "NaN" } + +issue_2207_1: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(String.fromCharCode(65)); + console.log(Math.max(3, 6, 2, 7, 3, 4)); + console.log(Math.cos(1.2345)); + console.log(Math.cos(1.2345) - Math.sin(4.321)); + console.log(Math.pow(Math.PI, Math.E - Math.LN10)); + } + expect: { + console.log("A"); + console.log(7); + console.log(Math.cos(1.2345)); + console.log(1.2543732512566947); + console.log(1.6093984514472044); + } + expect_stdout: true +} + +issue_2207_2: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Math.E); + console.log(Math.LN10); + console.log(Math.LN2); + console.log(Math.LOG2E); + console.log(Math.LOG10E); + console.log(Math.PI); + console.log(Math.SQRT1_2); + console.log(Math.SQRT2); + } + expect: { + console.log(Math.E); + console.log(Math.LN10); + console.log(Math.LN2); + console.log(Math.LOG2E); + console.log(Math.LOG10E); + console.log(Math.PI); + console.log(Math.SQRT1_2); + console.log(Math.SQRT2); + } + expect_stdout: true +} + +issue_2207_3: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Number.MAX_VALUE); + console.log(Number.MIN_VALUE); + console.log(Number.NaN); + console.log(Number.NEGATIVE_INFINITY); + console.log(Number.POSITIVE_INFINITY); + } + expect: { + console.log(Number.MAX_VALUE); + console.log(5e-324); + console.log(NaN); + console.log(-1/0); + console.log(1/0); + } + expect_stdout: true +} diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js index dd8cd52f..11946007 100644 --- a/test/compress/global_defs.js +++ b/test/compress/global_defs.js @@ -37,6 +37,7 @@ object: { VALUE: 42, }, }, + side_effects: true, unsafe: true, } input: { @@ -140,9 +141,9 @@ mixed: { console.log(CONFIG); } expect_warnings: [ - 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:126,22]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]', - 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', + 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:128,22]', + 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:130,8]', ] } diff --git a/test/compress/issue-1446.js b/test/compress/issue-1446.js index cad1ae57..30062c7d 100644 --- a/test/compress/issue-1446.js +++ b/test/compress/issue-1446.js @@ -1,6 +1,7 @@ typeof_eq_undefined: { options = { - comparisons: true + comparisons: true, + typeofs: true, } input: { var a = typeof b != "undefined"; @@ -24,6 +25,7 @@ typeof_eq_undefined_ie8: { options = { comparisons: true, ie8: true, + typeofs: true, } input: { var a = typeof b != "undefined"; @@ -45,7 +47,8 @@ typeof_eq_undefined_ie8: { undefined_redefined: { options = { - comparisons: true + comparisons: true, + typeofs: true, } input: { function f(undefined) { @@ -58,7 +61,8 @@ undefined_redefined: { undefined_redefined_mangle: { options = { - comparisons: true + comparisons: true, + typeofs: true, } mangle = {} input: { diff --git a/test/compress/loops.js b/test/compress/loops.js index 4d354bcf..9461a78d 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -480,3 +480,17 @@ do_switch: { } while (false); } } + +in_parenthesis_1: { + input: { + for (("foo" in {});0;); + } + expect_exact: 'for(("foo"in{});0;);' +} + +in_parenthesis_2: { + input: { + for ((function(){ "foo" in {}; });0;); + } + expect_exact: 'for(function(){"foo"in{}};0;);' +} diff --git a/test/compress/new.js b/test/compress/new.js index 2364a16e..b2fbc0c9 100644 --- a/test/compress/new.js +++ b/test/compress/new.js @@ -98,3 +98,19 @@ new_with_assignement_expression: { new y([a, b] = [3, 4]); } } + +dot_parenthesis_1: { + input: { + console.log(new (Math.random().constructor) instanceof Number); + } + expect_exact: "console.log(new(Math.random().constructor)instanceof Number);" + expect_stdout: "true" +} + +dot_parenthesis_2: { + input: { + console.log(typeof new function(){Math.random()}.constructor); + } + expect_exact: "console.log(typeof new function(){Math.random()}.constructor);" + expect_stdout: "function" +} diff --git a/test/compress/properties.js b/test/compress/properties.js index c0c80ba2..796bf48f 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -659,3 +659,116 @@ accessor_this: { expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);' expect_stdout: "1 2 2" } + +issue_2208_1: { + options = { + inline: true, + side_effects: true, + unsafe: true, + } + input: { + console.log({ + p: function() { + return 42; + } + }.p()); + } + expect: { + console.log(42); + } + expect_stdout: "42" +} + +issue_2208_2: { + options = { + inline: true, + side_effects: true, + unsafe: true, + } + input: { + console.log({ + a: 42, + p: function() { + return this.a; + } + }.p()); + } + expect: { + console.log({ + a: 42, + p: function() { + return this.a; + } + }.p()); + } + expect_stdout: "42" +} + +issue_2208_3: { + options = { + inline: true, + side_effects: true, + unsafe: true, + } + input: { + a = 42; + console.log({ + p: function() { + return function() { + return this.a; + }(); + } + }.p()); + } + expect: { + a = 42; + console.log(function() { + return this.a; + }()); + } + expect_stdout: "42" +} + +issue_2208_4: { + options = { + inline: true, + side_effects: true, + unsafe: true, + } + input: { + function foo() {} + console.log({ + a: foo(), + p: function() { + return 42; + } + }.p()); + } + expect: { + function foo() {} + console.log((foo(), function() { + return 42; + })()); + } + expect_stdout: "42" +} + +issue_2208_5: { + options = { + inline: true, + side_effects: true, + unsafe: true, + } + input: { + console.log({ + p: "FAIL", + p: function() { + return 42; + } + }.p()); + } + expect: { + console.log(42); + } + expect_stdout: "42" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 2734f5dd..7364cc4d 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1469,6 +1469,7 @@ issue_1670_1: { reduce_vars: true, side_effects: true, switches: true, + typeofs: true, unused: true, } input: { @@ -1532,6 +1533,7 @@ issue_1670_3: { reduce_vars: true, side_effects: true, switches: true, + typeofs: true, unused: true, } input: { diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 2752f290..829c10fe 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -176,6 +176,11 @@ for_sequences: { // 4 x = (foo in bar); for (y = 5; false;); + // 5 + x = function() { + foo in bar; + }; + for (y = 5; false;); } expect: { // 1 @@ -188,6 +193,10 @@ for_sequences: { // 4 x = (foo in bar); for (y = 5; false;); + // 5 + for (x = function() { + foo in bar; + }, y = 5; false;); } }