diff --git a/README.md b/README.md index 9efa0c94..6dab6ff4 100644 --- a/README.md +++ b/README.md @@ -624,7 +624,7 @@ function uglify(ast, options, mangle) { // Compression uAST.figure_out_scope(); - uAST = uAST.transform(UglifyJS.Compressor(options)); + uAST = UglifyJS.Compressor(options).compress(uAST); // Mangling (optional) if (mangle) { @@ -868,7 +868,7 @@ toplevel.figure_out_scope() Like this: ```javascript var compressor = UglifyJS.Compressor(options); -var compressed_ast = toplevel.transform(compressor); +var compressed_ast = compressor.compress(toplevel); ``` The `options` can be missing. Available options are discussed above in diff --git a/lib/compress.js b/lib/compress.js index b107367d..c27b2b96 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -61,7 +61,7 @@ function Compressor(options, false_by_default) { booleans : !false_by_default, loops : !false_by_default, unused : !false_by_default, - toplevel : !!options["top_retain"], + toplevel : !!(options && options["top_retain"]), top_retain : null, hoist_funs : !false_by_default, keep_fargs : true, @@ -70,7 +70,7 @@ function Compressor(options, false_by_default) { if_return : !false_by_default, join_vars : !false_by_default, collapse_vars : !false_by_default, - reduce_vars : false, + reduce_vars : !false_by_default, cascade : !false_by_default, side_effects : !false_by_default, pure_getters : false, @@ -180,38 +180,105 @@ merge(Compressor.prototype, { AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ var reduce_vars = rescan && compressor.option("reduce_vars"); - var unsafe = compressor.option("unsafe"); + var safe_ids = []; + push(); var tw = new TreeWalker(function(node){ + if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { + node._squeezed = false; + node._optimized = false; + } if (reduce_vars) { if (node instanceof AST_Toplevel) node.globals.each(reset_def); if (node instanceof AST_Scope) node.variables.each(reset_def); if (node instanceof AST_SymbolRef) { var d = node.definition(); d.references.push(node); - if (!d.modified && (d.orig.length > 1 || isModified(node, 0))) { - d.modified = true; + if (!d.fixed || isModified(node, 0) || !is_safe(d)) { + d.fixed = false; } } - if (node instanceof AST_Call && node.expression instanceof AST_Function) { - node.expression.argnames.forEach(function(arg, i) { - arg.definition().init = node.args[i] || make_node(AST_Undefined, node); + if (node instanceof AST_VarDef) { + var d = node.name.definition(); + if (d.fixed === undefined) { + d.fixed = node.value || make_node(AST_Undefined, node); + mark_as_safe(d); + } else { + d.fixed = false; + } + } + var iife; + if (node instanceof AST_Function + && (iife = tw.parent()) instanceof AST_Call + && iife.expression === node) { + node.argnames.forEach(function(arg, i) { + var d = arg.definition(); + d.fixed = iife.args[i] || make_node(AST_Undefined, iife); + mark_as_safe(d); }); } - } - if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { - node._squeezed = false; - node._optimized = false; + if (node instanceof AST_If || node instanceof AST_DWLoop) { + node.condition.walk(tw); + push(); + node.body.walk(tw); + pop(); + if (node.alternative) { + push(); + node.alternative.walk(tw); + pop(); + } + return true; + } + if (node instanceof AST_LabeledStatement) { + push(); + node.body.walk(tw); + pop(); + return true; + } + if (node instanceof AST_For) { + if (node.init) node.init.walk(tw); + push(); + if (node.condition) node.condition.walk(tw); + node.body.walk(tw); + if (node.step) node.step.walk(tw); + pop(); + return true; + } + if (node instanceof AST_ForIn) { + if (node.init instanceof AST_SymbolRef) { + node.init.definition().fixed = false; + } + node.object.walk(tw); + push(); + node.body.walk(tw); + pop(); + return true; + } } }); this.walk(tw); + function mark_as_safe(def) { + safe_ids[safe_ids.length - 1][def.id] = true; + } + + function is_safe(def) { + for (var i = safe_ids.length, id = def.id; --i >= 0;) { + if (safe_ids[i][id]) return true; + } + } + + function push() { + safe_ids.push(Object.create(null)); + } + + function pop() { + safe_ids.pop(); + } + function reset_def(def) { - def.modified = false; + def.fixed = undefined; def.references = []; def.should_replace = undefined; - if (unsafe && def.init) { - def.init._evaluated = undefined; - } } function isModified(node, level) { @@ -988,12 +1055,6 @@ merge(Compressor.prototype, { def(AST_Conditional, function(compressor){ return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); }); - def(AST_Call, function(compressor){ - return compressor.option("unsafe") - && this.expression instanceof AST_SymbolRef - && this.expression.name == "String" - && this.expression.undeclared(); - }); })(function(node, func){ node.DEFMETHOD("is_string", func); }); @@ -1149,11 +1210,14 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); + // XXX: AST_Accessor and AST_Function both inherit from AST_Scope, + // which itself inherits from AST_Statement; however, they aren't + // really statements. This could bite in other places too. :-( + // Wish JS had multiple inheritance. + def(AST_Accessor, function(){ + throw def; + }); def(AST_Function, function(){ - // XXX: AST_Function inherits from AST_Scope, which itself - // inherits from AST_Statement; however, an AST_Function - // isn't really a statement. This could byte in other - // places too. :-( Wish JS had multiple inheritance. throw def; }); function ev(node, compressor) { @@ -1181,7 +1245,9 @@ merge(Compressor.prototype, { for (var i = 0, len = this.properties.length; i < len; i++) { var prop = this.properties[i]; var key = prop.key; - if (key instanceof AST_Node) { + if (key instanceof AST_Symbol) { + key = key.name; + } else if (key instanceof AST_Node) { key = ev(key, compressor); } if (typeof Object.prototype[key] === 'function') { @@ -1259,14 +1325,14 @@ merge(Compressor.prototype, { this._evaluating = true; try { var d = this.definition(); - if (compressor.option("reduce_vars") && !d.modified && d.init) { + if (compressor.option("reduce_vars") && d.fixed) { if (compressor.option("unsafe")) { - if (d.init._evaluated === undefined) { - d.init._evaluated = ev(d.init, compressor); + if (!HOP(d.fixed, "_evaluated")) { + d.fixed._evaluated = ev(d.fixed, compressor); } - return d.init._evaluated; + return d.fixed._evaluated; } - return ev(d.init, compressor); + return ev(d.fixed, compressor); } } finally { this._evaluating = false; @@ -2190,7 +2256,7 @@ merge(Compressor.prototype, { // here because they are only used in an equality comparison later on. self.condition = negated; var tmp = self.body; - self.body = self.alternative || make_node(AST_EmptyStatement); + self.body = self.alternative || make_node(AST_EmptyStatement, self); self.alternative = tmp; } if (is_empty(self.body) && is_empty(self.alternative)) { @@ -2416,6 +2482,20 @@ merge(Compressor.prototype, { }); OPT(AST_Call, function(self, compressor){ + if (compressor.option("unused") + && self.expression instanceof AST_Function + && !self.expression.uses_arguments + && !self.expression.uses_eval + && self.args.length > self.expression.argnames.length) { + var end = self.expression.argnames.length; + for (var i = end, len = self.args.length; i < len; i++) { + var node = self.args[i].drop_side_effect_free(compressor); + if (node) { + self.args[end++] = node; + } + } + self.args.length = end; + } if (compressor.option("unsafe")) { var exp = self.expression; if (exp instanceof AST_SymbolRef && exp.undeclared()) { @@ -2455,7 +2535,7 @@ merge(Compressor.prototype, { case "Boolean": if (self.args.length == 0) return make_node(AST_False, self); if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { - expression: make_node(AST_UnaryPrefix, null, { + expression: make_node(AST_UnaryPrefix, self, { expression: self.args[0], operator: "!" }), @@ -2627,6 +2707,12 @@ merge(Compressor.prototype, { } } } + if (self.args.length == 0 + && self.expression instanceof AST_Function + && self.expression.body[0] instanceof AST_Return + && self.expression.body[0].value.is_constant()) { + return self.expression.body[0].value; + } if (compressor.option("negate_iife") && compressor.parent() instanceof AST_SimpleStatement && is_iife_call(self)) { @@ -2890,7 +2976,7 @@ merge(Compressor.prototype, { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); return make_node(AST_Seq, self, { car: self.left, - cdr: make_node(AST_False) + cdr: make_node(AST_False, self) }).optimize(compressor); } if (ll.length > 1 && ll[1]) { @@ -2953,10 +3039,25 @@ merge(Compressor.prototype, { } } } - if (self.operator == "+" && self.right instanceof AST_String - && self.right.getValue() === "" && self.left instanceof AST_Binary - && self.left.operator == "+" && self.left.is_string(compressor)) { - return self.left; + if (self.operator == "+") { + if (self.right instanceof AST_String + && self.right.getValue() == "" + && self.left.is_string(compressor)) { + return self.left; + } + if (self.left instanceof AST_String + && self.left.getValue() == "" + && self.right.is_string(compressor)) { + return self.right; + } + if (self.left instanceof AST_Binary + && self.left.operator == "+" + && self.left.left instanceof AST_String + && self.left.left.getValue() == "" + && self.right.is_string(compressor)) { + self.left = self.left.right; + return self.transform(compressor); + } } if (compressor.option("evaluate")) { switch (self.operator) { @@ -3081,9 +3182,9 @@ merge(Compressor.prototype, { } if (compressor.option("evaluate") && compressor.option("reduce_vars")) { var d = self.definition(); - if (!d.modified && d.init) { + if (d.fixed) { if (d.should_replace === undefined) { - var init = d.init.evaluate(compressor); + var init = d.fixed.evaluate(compressor); if (init.length > 1) { var value = init[0].print_to_string().length; var name = d.name.length; diff --git a/lib/utils.js b/lib/utils.js index 46adfd4c..da663546 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -185,7 +185,7 @@ function push_uniq(array, el) { function string_template(text, props) { return text.replace(/\{(.+?)\}/g, function(str, p){ - return props[p]; + return props && props[p]; }); }; diff --git a/package.json b/package.json index 8bd00fa3..33bfc236 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.8.1", + "version": "2.8.4", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index d7432f3f..5f63488f 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1141,7 +1141,7 @@ collapse_vars_constants: { function f3(x) { var b = x.prop; sideeffect1(); - return b + (function() { return -9; })(); + return b + -9; } } } diff --git a/test/compress/concat-strings.js b/test/compress/concat-strings.js index d2503c6d..2f99375c 100644 --- a/test/compress/concat-strings.js +++ b/test/compress/concat-strings.js @@ -164,3 +164,53 @@ concat_6: { ); } } + +concat_7: { + input: { + console.log( + "" + 1, + "" + "1", + "" + 1 + 2, + "" + 1 + "2", + "" + "1" + 2, + "" + "1" + "2", + "" + (x += "foo") + ); + } + expect: { + console.log( + "" + 1, + "1", + "" + 1 + 2, + 1 + "2", + "1" + 2, + "1" + "2", + x += "foo" + ); + } +} + +concat_8: { + input: { + console.log( + 1 + "", + "1" + "", + 1 + 2 + "", + 1 + "2" + "", + "1" + 2 + "", + "1" + "2" + "", + (x += "foo") + "" + ); + } + expect: { + console.log( + 1 + "", + "1", + 1 + 2 + "", + 1 + "2", + "1" + 2, + "1" + "2", + x += "foo" + ); + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 6bed73fb..26b6e489 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -337,6 +337,32 @@ unsafe_object_repeated: { } } +unsafe_object_accessor: { + options = { + evaluate: true, + reduce_vars: true, + unsafe: true, + } + input: { + function f() { + var a = { + get b() {}, + set b() {} + }; + return {a:a}; + } + } + expect: { + function f() { + var a = { + get b() {}, + set b() {} + }; + return {a:a}; + } + } +} + unsafe_function: { options = { evaluate : true, @@ -620,6 +646,29 @@ call_args: { } } +call_args_drop_param: { + options = { + evaluate: true, + keep_fargs: false, + reduce_vars: true, + unused: true, + } + input: { + const a = 1; + console.log(a); + +function(a) { + return a; + }(a, b); + } + expect: { + const a = 1; + console.log(1); + +function() { + return 1; + }(b); + } +} + in_boolean_context: { options = { booleans: true, diff --git a/test/compress/functions.js b/test/compress/functions.js index 3a8701b7..d3d99b57 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -6,3 +6,71 @@ non_ascii_function_identifier_name: { } expect_exact: "function fooλ(δλ){}function λ(δλ){}(function λ(δλ){})();" } + +iifes_returning_constants_keep_fargs_true: { + options = { + keep_fargs : true, + side_effects : true, + evaluate : true, + unused : true, + dead_code : true, + conditionals : true, + comparisons : true, + booleans : true, + if_return : true, + join_vars : true, + reduce_vars : true, + cascade : true, + } + input: { + (function(){ return -1.23; }()); + console.log( function foo(){ return "okay"; }() ); + console.log( function foo(x, y, z){ return 123; }() ); + console.log( function(x, y, z){ return z; }() ); + console.log( function(x, y, z){ if (x) return y; return z; }(1, 2, 3) ); + console.log( function(x, y){ return x * y; }(2, 3) ); + console.log( function(x, y){ return x * y; }(2, 3, a(), b()) ); + } + expect: { + console.log("okay"); + console.log(123); + console.log(void 0); + console.log(function(x,y,z){return 2}(1,2,3)); + console.log(function(x,y){return 6}(2,3)); + console.log(function(x, y){return 6}(2,3,a(),b())); + } +} + +iifes_returning_constants_keep_fargs_false: { + options = { + keep_fargs : false, + side_effects : true, + evaluate : true, + unused : true, + dead_code : true, + conditionals : true, + comparisons : true, + booleans : true, + if_return : true, + join_vars : true, + reduce_vars : true, + cascade : true, + } + input: { + (function(){ return -1.23; }()); + console.log( function foo(){ return "okay"; }() ); + console.log( function foo(x, y, z){ return 123; }() ); + console.log( function(x, y, z){ return z; }() ); + console.log( function(x, y, z){ if (x) return y; return z; }(1, 2, 3) ); + console.log( function(x, y){ return x * y; }(2, 3) ); + console.log( function(x, y){ return x * y; }(2, 3, a(), b()) ); + } + expect: { + console.log("okay"); + console.log(123); + console.log(void 0); + console.log(2); + console.log(6); + console.log(function(){return 6}(a(),b())); + } +} diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 001795c5..f17ae206 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -38,10 +38,10 @@ negate_iife_3: { conditionals: true }; input: { - (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ return t })() ? console.log(true) : console.log(false); } expect: { - !function(){ return true }() ? console.log(false) : console.log(true); + !function(){ return t }() ? console.log(false) : console.log(true); } } @@ -51,10 +51,10 @@ negate_iife_3_off: { conditionals: true, }; input: { - (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ return t })() ? console.log(true) : console.log(false); } expect: { - !function(){ return true }() ? console.log(false) : console.log(true); + !function(){ return t }() ? console.log(false) : console.log(true); } } @@ -65,13 +65,13 @@ negate_iife_4: { sequences: true }; input: { - (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ return t })() ? console.log(true) : console.log(false); (function(){ console.log("something"); })(); } expect: { - !function(){ return true }() ? console.log(false) : console.log(true), function(){ + !function(){ return t }() ? console.log(false) : console.log(true), function(){ console.log("something"); }(); } @@ -86,7 +86,7 @@ sequence_off: { }; input: { function f() { - (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ return t })() ? console.log(true) : console.log(false); (function(){ console.log("something"); })(); @@ -95,19 +95,19 @@ sequence_off: { (function(){ console.log("something"); })(); - (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ return t })() ? console.log(true) : console.log(false); } } expect: { function f() { - !function(){ return true }() ? console.log(false) : console.log(true), function(){ + !function(){ return t }() ? console.log(false) : console.log(true), function(){ console.log("something"); }(); } function g() { (function(){ console.log("something"); - })(), function(){ return true }() ? console.log(true) : console.log(false); + })(), function(){ return t }() ? console.log(true) : console.log(false); } } } @@ -119,7 +119,7 @@ negate_iife_5: { conditionals: true, }; input: { - if ((function(){ return true })()) { + if ((function(){ return t })()) { foo(true); } else { bar(false); @@ -129,7 +129,7 @@ negate_iife_5: { })(); } expect: { - !function(){ return true }() ? bar(false) : foo(true), function(){ + !function(){ return t }() ? bar(false) : foo(true), function(){ console.log("something"); }(); } @@ -142,7 +142,7 @@ negate_iife_5_off: { conditionals: true, }; input: { - if ((function(){ return true })()) { + if ((function(){ return t })()) { foo(true); } else { bar(false); @@ -152,7 +152,7 @@ negate_iife_5_off: { })(); } expect: { - !function(){ return true }() ? bar(false) : foo(true), function(){ + !function(){ return t }() ? bar(false) : foo(true), function(){ console.log("something"); }(); } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 0ee201c0..e38c317b 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -470,3 +470,122 @@ multi_def_2: { var repeatLength = this.getBits(bitsLength) + bitsOffset; } } + +use_before_var: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + console.log(t); + var t = 1; + } + expect: { + console.log(t); + var t = 1; + } +} + +inner_var_if: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f(){ + return 0; + } + if (f()) + var t = 1; + if (!t) + console.log(t); + } + expect: { + function f(){ + return 0; + } + if (f()) + var t = 1; + if (!t) + console.log(t); + } +} + +inner_var_label: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f(){ + return 1; + } + l: { + if (f()) break l; + var t = 1; + } + console.log(t); + } + expect: { + function f(){ + return 1; + } + l: { + if (f()) break l; + var t = 1; + } + console.log(t); + } +} + +inner_var_for: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + var a = 1; + x(a, b, d); + for (var b = 2, c = 3; x(a, b, c, d); x(a, b, c, d)) { + var d = 4, e = 5; + x(a, b, c, d, e); + } + x(a, b, c, d, e) + } + expect: { + var a = 1; + x(1, b, d); + for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) { + var d = 4, e = 5; + x(1, b, 3, d, e); + } + x(1, b, 3, d, e); + } +} + +inner_var_for_in: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + var a = 1, b = 2; + for (b in (function() { + return x(a, b, c); + })()) { + var c = 3, d = 4; + x(a, b, c, d); + } + x(a, b, c, d); + } + expect: { + var a = 1, b = 2; + for (b in (function() { + return x(1, b, c); + })()) { + var c = 3, d = 4; + x(1, b, c, d); + } + x(1, b, c, d); + } +} diff --git a/test/mocha/minify.js b/test/mocha/minify.js index baac2a41..51c46b28 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -155,7 +155,7 @@ describe("minify", function() { assert.strictEqual(code, "// comment1 comment2\nbar();"); }); it("should not drop #__PURE__ hint if function is retained", function() { - var result = Uglify.minify("var a = /*#__PURE__*/(function(){return 1})();", { + var result = Uglify.minify("var a = /*#__PURE__*/(function(){ foo(); })();", { fromString: true, output: { comments: "all", @@ -163,7 +163,7 @@ describe("minify", function() { } }); var code = result.code; - assert.strictEqual(code, "var a=/*#__PURE__*/function(){return 1}();"); + assert.strictEqual(code, "var a=/*#__PURE__*/function(){foo()}();"); }) }); @@ -182,4 +182,13 @@ describe("minify", function() { }); }); + describe("Compressor", function() { + it("should be backward compatible with ast.transform(compressor)", function() { + var ast = Uglify.parse("function f(a){for(var i=0;i