diff --git a/lib/compress.js b/lib/compress.js index 1c02bfc7..293905da 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -680,11 +680,13 @@ merge(Compressor.prototype, { } if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right || parent instanceof AST_Call && node !== parent.expression - || parent instanceof AST_Return && node === parent.value && node.scope !== d.scope + || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope || parent instanceof AST_VarDef && node === parent.value) { d.escaped = true; return; - } else if (parent instanceof AST_Array) { + } else if (parent instanceof AST_Array + || parent instanceof AST_Conditional && node !== parent.condition + || parent instanceof AST_Sequence && node === parent.tail_node()) { mark_escaped(d, scope, parent, parent, level + 1); } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { var obj = tw.parent(level + 1); @@ -1298,10 +1300,19 @@ merge(Compressor.prototype, { var stat = statements[i]; var next = statements[i + 1]; - if (in_lambda && stat instanceof AST_Return && !stat.value && !next) { - CHANGED = true; - statements.length--; - continue; + if (in_lambda && !next && stat instanceof AST_Return) { + if (!stat.value) { + CHANGED = true; + statements.length--; + continue; + } + if (stat.value instanceof AST_UnaryPrefix && stat.value.operator == "void") { + CHANGED = true; + statements[i] = make_node(AST_SimpleStatement, stat, { + body: stat.value.expression + }); + continue; + } } if (stat instanceof AST_If) { @@ -1388,9 +1399,16 @@ merge(Compressor.prototype, { && prev instanceof AST_If && prev.body instanceof AST_Return && i + 2 == statements.length && next instanceof AST_SimpleStatement) { CHANGED = true; - statements.push(make_node(AST_Return, next, { - value: null - }).transform(compressor)); + stat = stat.clone(); + stat.alternative = make_node(AST_BlockStatement, next, { + body: [ + next, + make_node(AST_Return, next, { + value: null + }) + ] + }); + statements.splice(i, 2, stat.transform(compressor)); continue; } } @@ -2627,6 +2645,21 @@ merge(Compressor.prototype, { // pass 3: we should drop declarations not in_use var tt = new TreeTransformer( function before(node, descend, in_list) { + var parent = tt.parent(); + if (drop_vars) { + var sym = assign_as_unused(node); + if (sym instanceof AST_SymbolRef + && !((sym = sym.definition()).id in in_use_ids) + && (drop_vars || !sym.global)) { + if (node instanceof AST_Assign) { + return maintain_this_binding(parent, node, node.right.transform(tt)); + } + return make_node(AST_Number, node, { + value: 0 + }); + } + } + if (scope !== self) return; if (node.name && (!compressor.option("keep_classnames") && node instanceof AST_ClassExpression || !compressor.option("keep_fnames") && node instanceof AST_Function)) { @@ -2672,9 +2705,7 @@ merge(Compressor.prototype, { def.eliminated++; return make_node(AST_EmptyStatement, node); } - return node; } - var parent = tt.parent(); if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) { var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var); // place uninitialized names at the start @@ -2757,19 +2788,6 @@ merge(Compressor.prototype, { }); } } - if (drop_vars) { - var def = assign_as_unused(node); - if (def instanceof AST_SymbolRef - && !((def = def.definition()).id in in_use_ids) - && (drop_vars || !def.global)) { - if (node instanceof AST_Assign) { - return maintain_this_binding(parent, node, node.right.transform(tt)); - } - return make_node(AST_Number, node, { - value: 0 - }); - } - } // certain combination of unused name + side effect leads to: // https://github.com/mishoo/UglifyJS2/issues/44 // https://github.com/mishoo/UglifyJS2/issues/1830 @@ -2807,8 +2825,13 @@ merge(Compressor.prototype, { return MAP.splice(node.body); } } - if (node instanceof AST_Scope && node !== self) + if (node instanceof AST_Scope) { + var save_scope = scope; + scope = node; + descend(node, this); + scope = save_scope; return node; + } function template(sym) { return { @@ -2824,8 +2847,7 @@ merge(Compressor.prototype, { function scan_ref_scoped(node, descend) { var sym; - if (scope === self - && (sym = assign_as_unused(node)) instanceof AST_SymbolRef + if ((sym = assign_as_unused(node)) instanceof AST_SymbolRef && !is_ref_of(node.left, AST_SymbolBlockDeclaration) && self.variables.get(sym.name) === sym.definition()) { if (node instanceof AST_Assign) node.right.walk(tw); @@ -3674,12 +3696,12 @@ merge(Compressor.prototype, { var simple_args = all(self.args, function(arg) { return !(arg instanceof AST_Expansion); }); + if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) { + fn = fn.fixed_value(); + } if (compressor.option("unused") && simple_args - && (is_func_expr(fn) - || compressor.option("reduce_vars") - && fn instanceof AST_SymbolRef - && is_func_expr(fn = fn.fixed_value())) + && is_func_expr(fn) && !fn.uses_arguments && !fn.uses_eval) { var pos = 0, last = 0; @@ -3930,15 +3952,16 @@ merge(Compressor.prototype, { return make_sequence(self, args).optimize(compressor); } } - if (is_func_expr(exp) && !exp.is_generator && !exp.async) { + if (is_func_expr(fn) && !fn.is_generator && !fn.async) { if (compressor.option("inline") + && exp === fn && simple_args - && !exp.name - && !exp.uses_arguments - && !exp.uses_eval - && (exp.body instanceof AST_Node || exp.body.length == 1) - && !exp.contains_this() - && all(exp.argnames, function(arg) { + && !fn.name + && !fn.uses_arguments + && !fn.uses_eval + && (fn.body instanceof AST_Node || fn.body.length == 1) + && !fn.contains_this() + && all(fn.argnames, function(arg) { if (arg instanceof AST_Expansion) return arg.expression.__unused; return arg.__unused; }) @@ -3957,7 +3980,7 @@ merge(Compressor.prototype, { return make_sequence(self, args).optimize(compressor); } } - if (compressor.option("side_effects") && !(exp.body instanceof AST_Node) && all(exp.body, is_empty)) { + if (compressor.option("side_effects") && !(fn.body instanceof AST_Node) && all(fn.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); return make_sequence(self, args).optimize(compressor); } @@ -4930,20 +4953,22 @@ merge(Compressor.prototype, { }); } // x ? y(a) : y(b) --> y(x ? a : b) + var arg_index; if (consequent instanceof AST_Call && alternative.TYPE === consequent.TYPE - && consequent.args.length == 1 - && alternative.args.length == 1 - && !(consequent.args[0] instanceof AST_Expansion) - && !(alternative.args[0] instanceof AST_Expansion) + && consequent.args.length > 0 + && consequent.args.length == alternative.args.length && consequent.expression.equivalent_to(alternative.expression) - && !consequent.expression.has_side_effects(compressor)) { - consequent.args[0] = make_node(AST_Conditional, self, { + && !self.condition.has_side_effects(compressor) + && !consequent.expression.has_side_effects(compressor) + && typeof (arg_index = single_arg_diff()) == "number") { + var node = consequent.clone(); + node.args[arg_index] = make_node(AST_Conditional, self, { condition: self.condition, - consequent: consequent.args[0], - alternative: alternative.args[0] + consequent: consequent.args[arg_index], + alternative: alternative.args[arg_index] }); - return consequent; + return node; } // x?y?z:a:a --> x&&y?z:a if (consequent instanceof AST_Conditional @@ -5040,6 +5065,22 @@ merge(Compressor.prototype, { && node.expression instanceof AST_Constant && node.expression.getValue()); } + + function single_arg_diff() { + var a = consequent.args; + var b = alternative.args; + for (var i = 0, len = a.length; i < len; i++) { + if (a[i] instanceof AST_Expansion) return; + if (!a[i].equivalent_to(b[i])) { + if (b[i] instanceof AST_Expansion) return; + for (var j = i + 1; j < len; j++) { + if (a[j] instanceof AST_Expansion) return; + if (!a[j].equivalent_to(b[j])) return; + } + return i; + } + } + } }); OPT(AST_Boolean, function(self, compressor){ diff --git a/package.json b/package.json index cec1080e..d3bb8336 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.2.1", + "version": "3.2.2", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 89c05263..143ece4a 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -166,22 +166,24 @@ cond_1: { conditionals: true }; input: { - var do_something; // if undeclared it's assumed to have side-effects - if (some_condition()) { - do_something(x); - } else { - do_something(y); - } - if (some_condition()) { - side_effects(x); - } else { - side_effects(y); + function foo(do_something, some_condition) { + if (some_condition) { + do_something(x); + } else { + do_something(y); + } + if (some_condition) { + side_effects(x); + } else { + side_effects(y); + } } } expect: { - var do_something; - do_something(some_condition() ? x : y); - some_condition() ? side_effects(x) : side_effects(y); + function foo(do_something, some_condition) { + do_something(some_condition ? x : y); + some_condition ? side_effects(x) : side_effects(y); + } } } @@ -190,16 +192,18 @@ cond_2: { conditionals: true }; input: { - var x, FooBar; - if (some_condition()) { - x = new FooBar(1); - } else { - x = new FooBar(2); + function foo(x, FooBar, some_condition) { + if (some_condition) { + x = new FooBar(1); + } else { + x = new FooBar(2); + } } } expect: { - var x, FooBar; - x = new FooBar(some_condition() ? 1 : 2); + function foo(x, FooBar, some_condition) { + x = new FooBar(some_condition ? 1 : 2); + } } } @@ -605,6 +609,42 @@ cond_8c: { } } +cond_9: { + options = { + conditionals: true, + } + input: { + function f(x, y) { + g() ? x(1) : x(2); + x ? (y || x)() : (y || x)(); + x ? y(a, b) : y(d, b, c); + x ? y(a, b, c) : y(a, b, c); + x ? y(a, b, c) : y(a, b, f); + x ? y(a, b, c) : y(a, e, c); + x ? y(a, b, c) : y(a, e, f); + x ? y(a, b, c) : y(d, b, c); + x ? y(a, b, c) : y(d, b, f); + x ? y(a, b, c) : y(d, e, c); + x ? y(a, b, c) : y(d, e, f); + } + } + expect: { + function f(x, y) { + g() ? x(1) : x(2); + x, (y || x)(); + x ? y(a, b) : y(d, b, c); + x, y(a, b, c); + y(a, b, x ? c : f); + y(a, x ? b : e, c); + x ? y(a, b, c) : y(a, e, f); + y(x ? a : d, b, c); + x ? y(a, b, c) : y(d, b, f); + x ? y(a, b, c) : y(d, e, c); + x ? y(a, b, c) : y(d, e, f); + } + } +} + ternary_boolean_consequent: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, @@ -1115,3 +1155,51 @@ issue_2535_2: { "false", ] } + +issue_2560: { + options = { + conditionals: true, + inline: true, + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function log(x) { + console.log(x); + } + function foo() { + return log; + } + function bar() { + if (x !== (x = foo())) { + x(1); + } else { + x(2); + } + } + var x = function() { + console.log("init"); + }; + bar(); + bar(); + } + expect: { + function log(x) { + console.log(x); + } + function bar() { + x !== (x = log) ? x(1) : x(2); + } + var x = function() { + console.log("init"); + }; + bar(); + bar(); + } + expect_stdout: [ + "1", + "2", + ] +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index c64cc054..c9cee798 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -260,7 +260,9 @@ keep_fnames: { } drop_assign: { - options = { unused: true }; + options = { + unused: true, + } input: { function f1() { var a; @@ -281,7 +283,7 @@ drop_assign: { var a; return function() { a = 1; - } + }; } } expect: { @@ -298,16 +300,17 @@ drop_assign: { return 1; } function f5() { - var a; return function() { - a = 1; - } + 1; + }; } } } keep_assign: { - options = { unused: "keep_assign" }; + options = { + unused: "keep_assign", + } input: { function f1() { var a; @@ -328,7 +331,7 @@ keep_assign: { var a; return function() { a = 1; - } + }; } } expect: { @@ -351,19 +354,22 @@ keep_assign: { var a; return function() { a = 1; - } + }; } } } drop_toplevel_funcs: { - options = { toplevel: "funcs", unused: true }; + options = { + toplevel: "funcs", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -379,13 +385,16 @@ drop_toplevel_funcs: { } drop_toplevel_vars: { - options = { toplevel: "vars", unused: true }; + options = { + toplevel: "vars", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -393,11 +402,10 @@ drop_toplevel_vars: { console.log(b = 3); } expect: { - var c = g; function f(d) { return function() { - c = 2; - } + 2; + }; } 2; function g() {} @@ -407,13 +415,17 @@ drop_toplevel_vars: { } drop_toplevel_vars_fargs: { - options = { keep_fargs: false, toplevel: "vars", unused: true }; + options = { + keep_fargs: false, + toplevel: "vars", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -421,11 +433,10 @@ drop_toplevel_vars_fargs: { console.log(b = 3); } expect: { - var c = g; function f() { return function() { - c = 2; - } + 2; + }; } 2; function g() {} @@ -435,13 +446,16 @@ drop_toplevel_vars_fargs: { } drop_toplevel_all: { - options = { toplevel: true, unused: true }; + options = { + toplevel: true, + unused: true + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -455,13 +469,16 @@ drop_toplevel_all: { } drop_toplevel_retain: { - options = { top_retain: "f,a,o", unused: true }; + options = { + top_retain: "f,a,o", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -469,26 +486,28 @@ drop_toplevel_retain: { console.log(b = 3); } expect: { - var a, c = g; + var a; function f(d) { return function() { - c = 2; - } + 2; + }; } a = 2; - function g() {} console.log(3); } } drop_toplevel_retain_array: { - options = { top_retain: [ "f", "a", "o" ], unused: true }; + options = { + top_retain: [ "f", "a", "o" ], + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -496,26 +515,28 @@ drop_toplevel_retain_array: { console.log(b = 3); } expect: { - var a, c = g; + var a; function f(d) { return function() { - c = 2; - } + 2; + }; } a = 2; - function g() {} console.log(3); } } drop_toplevel_retain_regex: { - options = { top_retain: /^[fao]$/, unused: true }; + options = { + top_retain: /^[fao]$/, + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -523,26 +544,29 @@ drop_toplevel_retain_regex: { console.log(b = 3); } expect: { - var a, c = g; + var a; function f(d) { return function() { - c = 2; - } + 2; + }; } a = 2; - function g() {} console.log(3); } } drop_toplevel_all_retain: { - options = { toplevel: true, top_retain: "f,a,o", unused: true }; + options = { + toplevel: true, + top_retain: "f,a,o", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -550,26 +574,29 @@ drop_toplevel_all_retain: { console.log(b = 3); } expect: { - var a, c = g; + var a; function f(d) { return function() { - c = 2; - } + 2; + }; } a = 2; - function g() {} console.log(3); } } drop_toplevel_funcs_retain: { - options = { toplevel: "funcs", top_retain: "f,a,o", unused: true }; + options = { + toplevel: "funcs", + top_retain: "f,a,o", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -581,7 +608,7 @@ drop_toplevel_funcs_retain: { function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -590,13 +617,17 @@ drop_toplevel_funcs_retain: { } drop_toplevel_vars_retain: { - options = { toplevel: "vars", top_retain: "f,a,o", unused: true }; + options = { + toplevel: "vars", + top_retain: "f,a,o", + unused: true, + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -604,11 +635,11 @@ drop_toplevel_vars_retain: { console.log(b = 3); } expect: { - var a, c = g; + var a; function f(d) { return function() { - c = 2; - } + 2; + }; } a = 2; function g() {} @@ -618,13 +649,16 @@ drop_toplevel_vars_retain: { } drop_toplevel_keep_assign: { - options = { toplevel: true, unused: "keep_assign" }; + options = { + toplevel: true, + unused: "keep_assign", + } input: { var a, b = 1, c = g; function f(d) { return function() { c = 2; - } + }; } a = 2; function g() {} @@ -866,11 +900,11 @@ issue_1583: { expect: { function m(t) { (function(e) { - t = function() { + (function() { return (function(a) { return function(a) {}; })(); - }(); + })(); })(); } } diff --git a/test/compress/functions.js b/test/compress/functions.js index 471c27d1..563507f6 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -652,3 +652,23 @@ issue_2531_3: { } expect_stdout: "Greeting: Hello" } + +empty_body: { + options = { + reduce_vars: true, + side_effects: true, + } + input: { + function f() { + function noop() {} + noop(); + return noop; + } + } + expect: { + function f() { + function noop() {} + return noop; + } + } +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 6b0d7a54..078080e3 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -4906,3 +4906,243 @@ issue_2455: { } } } + +issue_2560_1: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function main() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("FAIL"); + else + console.log("PASS"); + } + function baz(s) { + return s ? foo : bar; + } + function foo() {} + function bar() {} + main(); + } + expect: { + function baz(s) { + return s ? foo : bar; + } + function foo() {} + function bar() {} + (function() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("FAIL"); + else + console.log("PASS"); + })(); + } + expect_stdout: "PASS" +} + +issue_2560_2: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function main() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("FAIL"); + else + console.log("PASS"); + } + function baz() { + return foo, bar; + } + function foo() {} + function bar() {} + main(); + } + expect: { + function baz() { + return function() {}, bar; + } + function bar() {} + (function() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("FAIL"); + else + console.log("PASS"); + })(); + } + expect_stdout: "PASS" +} + +issue_2560_3: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function main() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("FAIL"); + else + console.log("PASS"); + } + function baz() { + try { + throw foo; + } catch (bar) { + return bar; + } + } + function foo() {} + main(); + } + expect: { + function baz() { + try { + throw foo; + } catch (bar) { + return bar; + } + } + function foo() {} + (function() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("FAIL"); + else + console.log("PASS"); + })(); + } + expect_stdout: "PASS" +} + +issue_2560_4: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function main() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("PASS"); + else + console.log("FAIL"); + } + function baz(s) { + function foo() {} + function bar() {} + return s ? foo : bar; + } + main(); + } + expect: { + function baz(s) { + return s ? function() {} : function() {}; + } + (function() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("PASS"); + else + console.log("FAIL"); + })(); + } + expect_stdout: "PASS" +} + +issue_2560_5: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function main() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("PASS"); + else + console.log("FAIL"); + } + function baz() { + function foo() {} + function bar() {} + return foo, bar; + } + main(); + } + expect: { + function baz() { + return function() {}, function() {}; + } + (function() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("PASS"); + else + console.log("FAIL"); + })(); + } + expect_stdout: "PASS" +} + +issue_2560_6: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function main() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("PASS"); + else + console.log("FAIL"); + } + function baz() { + function foo() {} + try { + throw foo; + } catch (bar) { + return bar; + } + } + main(); + } + expect: { + function baz() { + try { + throw function() {}; + } catch (bar) { + return bar; + } + } + (function() { + var thing = baz(); + if (thing !== (thing = baz())) + console.log("PASS"); + else + console.log("FAIL"); + })(); + } + expect_stdout: "PASS" +} diff --git a/test/compress/return_undefined.js b/test/compress/return_undefined.js index 9662aa51..4d2b4257 100644 --- a/test/compress/return_undefined.js +++ b/test/compress/return_undefined.js @@ -122,3 +122,25 @@ return_undefined: { } } } + +return_void: { + options = { + if_return: true, + inline: true, + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g() { + h(); + } + return g(); + } + } + expect: { + function f() { + h(); + } + } +}