diff --git a/lib/compress.js b/lib/compress.js index 44b27584..918e7ba2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -326,6 +326,7 @@ merge(Compressor.prototype, { } else { def.fixed = false; } + def.recursive_refs = 0; def.references = []; def.should_replace = undefined; def.single_use = undefined; @@ -383,7 +384,7 @@ merge(Compressor.prototype, { return compressor.option("unused") && !def.scope.uses_eval && !def.scope.uses_with - && def.references.length == 1 + && def.references.length - def.recursive_refs == 1 && tw.loop_ids[def.id] === tw.in_loop; } @@ -669,7 +670,9 @@ merge(Compressor.prototype, { d.fixed = false; } else if (d.fixed) { value = this.fixed_value(); - if (value && !compressor.exposed(d) && ref_once(tw, compressor, d)) { + if (value instanceof AST_Lambda && recursive_ref(tw, d)) { + d.recursive_refs++; + } else if (value && !compressor.exposed(d) && ref_once(tw, compressor, d)) { d.single_use = value instanceof AST_Lambda || value instanceof AST_Class || d.scope === this.scope && value.is_constant_expression(); @@ -1820,15 +1823,8 @@ merge(Compressor.prototype, { return this.operator == "void"; }); def(AST_Binary, function(compressor) { - switch (this.operator) { - case "&&": - return this.left._dot_throw(compressor); - case "||": - return this.left._dot_throw(compressor) - && this.right._dot_throw(compressor); - default: - return false; - } + return (this.operator == "&&" || this.operator == "||") + && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor)); }) def(AST_Assign, function(compressor) { return this.operator == "=" @@ -2813,15 +2809,15 @@ merge(Compressor.prototype, { var tw = new TreeWalker(function(node, descend){ if (node === self) return; if (node instanceof AST_Defun || node instanceof AST_DefClass) { + var node_def = node.name.definition(); var in_export = tw.parent() instanceof AST_Export; if (in_export || !drop_funcs && scope === self) { - var node_def = node.name.definition(); if (node_def.global && !(node_def.id in in_use_ids)) { in_use_ids[node_def.id] = true; in_use.push(node_def); } } - initializations.add(node.name.name, node); + initializations.add(node_def.id, node); return true; // don't go in nested scopes } if (node instanceof AST_SymbolFunarg && scope === self) { @@ -2851,7 +2847,7 @@ merge(Compressor.prototype, { def.walk(tw); destructuring_value = destructuring_cache; } else { - initializations.add(def.name.name, def.value); + initializations.add(def.name.definition().id, def.value); } if (def.value.has_side_effects(compressor)) { def.value.walk(tw); @@ -2870,13 +2866,10 @@ merge(Compressor.prototype, { // initialization code to figure out if it uses other // symbols (that may not be in_use). tw = new TreeWalker(scan_ref_scoped); - for (var i = 0; i < in_use.length; ++i) { - in_use[i].orig.forEach(function(decl){ - // undeclared globals will be instanceof AST_SymbolRef - var init = initializations.get(decl.name); - if (init) init.forEach(function(init){ - init.walk(tw); - }); + for (var i = 0; i < in_use.length; i++) { + var init = initializations.get(in_use[i].id); + if (init) init.forEach(function(init) { + init.walk(tw); }); } // pass 3: we should drop declarations not in_use @@ -3942,9 +3935,10 @@ merge(Compressor.prototype, { if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) { fn = fn.fixed_value(); } + var is_func = fn instanceof AST_Lambda; if (compressor.option("unused") && simple_args - && is_func_expr(fn) + && is_func && !fn.uses_arguments && !fn.uses_eval) { var pos = 0, last = 0; @@ -4131,7 +4125,7 @@ merge(Compressor.prototype, { if (func instanceof AST_SymbolRef) { func = func.fixed_value(); } - if (func instanceof AST_Function && !func.contains_this()) { + if (func instanceof AST_Lambda && !func.contains_this()) { return make_sequence(this, [ self.args[0], make_node(AST_Call, self, { @@ -4208,7 +4202,7 @@ merge(Compressor.prototype, { } } } - var stat = is_func_expr(fn) && fn.body; + var stat = is_func && fn.body; if (stat instanceof AST_Node) { stat = make_node(AST_Return, stat, { value: stat @@ -4223,7 +4217,7 @@ merge(Compressor.prototype, { return make_sequence(self, args).optimize(compressor); } } - if (is_func_expr(fn) && !fn.is_generator && !fn.async) { + if (is_func && !fn.is_generator && !fn.async) { var def, value, scope, level = -1; if (compressor.option("inline") && simple_args @@ -4272,26 +4266,39 @@ merge(Compressor.prototype, { return self; function can_flatten_args(fn) { - var catches = Object.create(null); + var catches = Object.create(null), defs; do { scope = compressor.parent(++level); - if (scope instanceof AST_SymbolRef) { - scope = scope.fixed_value(); - } else if (scope instanceof AST_Catch) { + if (scope instanceof AST_Catch) { catches[scope.argname.name] = true; + } else if (scope instanceof AST_IterationStatement) { + defs = []; + } else if (scope instanceof AST_SymbolRef) { + if (scope.fixed_value() instanceof AST_Scope) return false; } } while (!(scope instanceof AST_Scope) || scope instanceof AST_Arrow); var safe_to_inject = compressor.toplevel.vars || !(scope instanceof AST_Toplevel); - return all(fn.argnames, function(arg) { - if (arg instanceof AST_DefaultAssign) return arg.left.__unused; + for (var i = 0, len = fn.argnames.length; i < len; i++) { + var arg = fn.argnames[i]; + if (arg instanceof AST_DefaultAssign) { + if (arg.left.__unused) continue; + return false; + } if (arg instanceof AST_Destructuring) return false; - if (arg instanceof AST_Expansion) return arg.expression.__unused; - return arg.__unused - || safe_to_inject - && !catches[arg.name] - && !identifier_atom(arg.name) - && !scope.var_names()[arg.name]; - }); + if (arg instanceof AST_Expansion) { + if (arg.expression.__unused) continue; + return false; + } + if (arg.__unused) continue; + if (!safe_to_inject + || catches[arg.name] + || identifier_atom(arg.name) + || scope.var_names()[arg.name]) { + return false; + } + if (defs) defs.push(arg.definition()); + } + return !defs || defs.length == 0 || !is_reachable(stat, defs); } function flatten_args(fn) { @@ -4969,23 +4976,18 @@ merge(Compressor.prototype, { && is_lhs(self, compressor.parent()) !== self) { var d = self.definition(); var fixed = self.fixed_value(); - if (fixed instanceof AST_DefClass) { - d.fixed = fixed = make_node(AST_ClassExpression, fixed, fixed); - } - if (fixed instanceof AST_Defun) { - d.fixed = fixed = make_node(AST_Function, fixed, fixed); - } - if (d.single_use && (is_func_expr(fixed) || fixed instanceof AST_ClassExpression)) { + var single_use = d.single_use; + if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { if (d.scope !== self.scope - && (!compressor.option("reduce_funcs") && is_func_expr(fixed) + && (!compressor.option("reduce_funcs") && fixed instanceof AST_Lambda || d.escaped == 1 || fixed.inlined)) { - d.single_use = false; + single_use = false; } else if (recursive_ref(compressor, d)) { - d.single_use = false; + single_use = false; } else if (d.scope !== self.scope || d.orig[0] instanceof AST_SymbolFunarg) { - d.single_use = fixed.is_constant_expression(self.scope); - if (d.single_use == "f") { + single_use = fixed.is_constant_expression(self.scope); + if (single_use == "f") { var scope = self.scope; do { if (scope instanceof AST_Defun || is_func_expr(scope)) { @@ -4995,9 +4997,36 @@ merge(Compressor.prototype, { } } } - if (d.single_use && fixed) { - var value = fixed.optimize(compressor); - return value === fixed ? fixed.clone(true) : value; + if (single_use && fixed) { + if (fixed instanceof AST_DefClass) { + fixed = make_node(AST_ClassExpression, fixed, fixed); + } + if (fixed instanceof AST_Defun) { + fixed = make_node(AST_Function, fixed, fixed); + } + var value; + if (d.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) { + value = fixed.clone(true); + var defun_def = value.name.definition(); + var lambda_def = value.variables.get(value.name.name); + var name = lambda_def && lambda_def.orig[0]; + if (!(name instanceof AST_SymbolLambda)) { + name = make_node(AST_SymbolLambda, value.name, value.name); + name.scope = value; + value.name = name; + lambda_def = value.def_function(name); + } + value.walk(new TreeWalker(function(node) { + if (node instanceof AST_SymbolRef && node.definition() === defun_def) { + node.thedef = lambda_def; + lambda_def.references.push(node); + } + })); + } else { + value = fixed.optimize(compressor); + if (value === fixed) value = fixed.clone(true); + } + return value; } if (fixed && d.should_replace === undefined) { var init; @@ -5119,18 +5148,42 @@ merge(Compressor.prototype, { return self; }); + function is_reachable(node, defs) { + var reachable = false; + var find_ref = new TreeWalker(function(node) { + if (reachable) return true; + if (node instanceof AST_SymbolRef && member(node.definition(), defs)) { + return reachable = true; + } + }); + var scan_scope = new TreeWalker(function(node) { + if (reachable) return true; + if (node instanceof AST_Scope) { + var parent = scan_scope.parent(); + if (!(parent instanceof AST_Call && parent.expression === node)) { + node.walk(find_ref); + } + return true; + } + }); + node.walk(scan_scope); + return reachable; + } + var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; var ASSIGN_OPS_COMMUTATIVE = [ '*', '|', '^', '&' ]; OPT(AST_Assign, function(self, compressor){ + var def; if (compressor.option("dead_code") && self.left instanceof AST_SymbolRef - && self.left.definition().scope === compressor.find_parent(AST_Lambda)) { + && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) { var level = 0, node, parent = self; do { node = parent; parent = compressor.parent(level++); if (parent instanceof AST_Exit) { if (in_try(level, parent instanceof AST_Throw)) break; + if (is_reachable(self, [ def ])) break; if (self.operator == "=") return self.right; return make_node(AST_Binary, self, { operator: self.operator.slice(0, -1), diff --git a/package.json b/package.json index ebf73b68..767814be 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.3.2", + "version": "3.3.3", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index f2ef18df..a819ac95 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -4180,10 +4180,9 @@ recursive_function_replacement: { console.log(f(c)); } expect: { - function f(n) { - return x(y(f(n))); - } - console.log(f(c)); + console.log(function n(o) { + return x(y(n(o))); + }(c)); } } diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index c25fe1ce..425ae1ec 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -1047,3 +1047,30 @@ issue_2597: { } expect_stdout: "PASS" } + +issue_2666: { + options = { + dead_code: true, + } + input: { + function f(a) { + return a = { + p: function() { + return a; + } + }; + } + console.log(typeof f().p()); + } + expect: { + function f(a) { + return a = { + p: function() { + return a; + } + }; + } + console.log(typeof f().p()); + } + expect_stdout: "object" +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 2886d52f..fda27a38 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1786,3 +1786,112 @@ issue_2418_5: { (function f() {}); } } + +defun_lambda_same_name: { + options = { + toplevel: true, + unused: true, + } + input: { + function f(n) { + return n ? n * f(n - 1) : 1; + } + console.log(function f(n) { + return n ? n * f(n - 1) : 1; + }(5)); + } + expect: { + console.log(function f(n) { + return n ? n * f(n - 1) : 1; + }(5)); + } + expect_stdout: "120" +} + +issue_2660_1: { + options = { + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = 2; + function f(b) { + return b && f() || a--; + } + f(1); + console.log(a); + } + expect: { + var a = 2; + (function f(b) { + return b && f() || a--; + })(1); + console.log(a); + } + expect_stdout: "1" +} + +issue_2660_2: { + options = { + collapse_vars: true, + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = 1; + function f(b) { + b && f(); + --a, a.toString(); + } + f(); + console.log(a); + } + expect: { + var a = 1; + (function f(b) { + b && f(), + (--a).toString(); + })(), + console.log(a); + } + expect_stdout: "0" +} + +issue_2665: { + options = { + evaluate: true, + inline: true, + keep_fargs: false, + passes: 2, + reduce_funcs: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + typeofs: true, + unused: true, + } + input: { + var a = 1; + function g() { + a-- && g(); + } + typeof h == "function" && h(); + function h() { + typeof g == "function" && g(); + } + console.log(a); + } + expect: { + var a = 1; + !function g() { + a-- && g(); + }(); + console.log(a); + } + expect_stdout: "-1" +} diff --git a/test/compress/functions.js b/test/compress/functions.js index 1ba9b3dc..e8f1ea4c 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -1378,11 +1378,10 @@ issue_2630_3: { expect: { var x = 2, a = 1; (function() { - function f1(a) { + (function f1(a) { f2(); --x >= 0 && f1({}); - } - f1(a++); + })(a++); function f2() { a++; } @@ -1549,7 +1548,7 @@ issue_2647_3: { node_version: ">=4" } -recursive_inline: { +recursive_inline_1: { options = { inline: true, reduce_funcs: true, @@ -1572,3 +1571,203 @@ recursive_inline: { } expect: {} } + +recursive_inline_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function f(n) { + return n ? n * f(n - 1) : 1; + } + console.log(f(5)); + } + expect: { + console.log(function f(n) { + return n ? n * f(n - 1) : 1; + }(5)); + } + expect_stdout: "120" +} + +issue_2657: { + options = { + inline: true, + reduce_vars: true, + sequences: true, + unused: true, + } + input: { + "use strict"; + console.log(function f() { + return h; + function g(b) { + return b || b(); + } + function h(a) { + g(a); + return a; + } + }()(42)); + } + expect: { + "use strict"; + console.log(function(a) { + return b = a, b || b(), a; + var b; + }(42)); + } + expect_stdout: "42" +} + +issue_2663_1: { + options = { + inline: true, + reduce_vars: true, + unused: true, + } + input: { + (function() { + var i, o = {}; + function createFn(j) { + return function() { + console.log(j); + }; + } + for (i in { a: 1, b: 2, c: 3 }) + o[i] = createFn(i); + for (i in o) + o[i](); + })(); + } + expect: { + (function() { + var i, o = {}; + function createFn(j) { + return function() { + console.log(j); + }; + } + for (i in { a: 1, b: 2, c: 3 }) + o[i] = createFn(i); + for (i in o) + o[i](); + })(); + } + expect_stdout: [ + "a", + "b", + "c", + ] +} + +issue_2663_2: { + options = { + inline: true, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + (function() { + var i; + function fn(j) { + return function() { + console.log(j); + }(); + } + for (i in { a: 1, b: 2, c: 3 }) + fn(i); + })(); + } + expect: { + (function() { + var i; + for (i in { a: 1, b: 2, c: 3 }) + j = i, console.log(j); + var j; + })(); + } + expect_stdout: [ + "a", + "b", + "c", + ] +} + +issue_2663_3: { + options = { + inline: true, + reduce_vars: true, + unused: true, + } + input: { + (function () { + var outputs = [ + { type: 0, target: null, eventName: "ngSubmit", propName: null }, + { type: 0, target: null, eventName: "submit", propName: null }, + { type: 0, target: null, eventName: "reset", propName: null }, + ]; + function listenToElementOutputs(outputs) { + var handlers = []; + for (var i = 0; i < outputs.length; i++) { + var output = outputs[i]; + var handleEventClosure = renderEventHandlerClosure(output.eventName); + handlers.push(handleEventClosure) + } + var target, name; + return handlers; + } + function renderEventHandlerClosure(eventName) { + return function () { + return console.log(eventName); + }; + } + listenToElementOutputs(outputs).forEach(function (handler) { + return handler() + }); + })(); + } + expect: { + (function() { + function renderEventHandlerClosure(eventName) { + return function() { + return console.log(eventName); + }; + } + (function(outputs) { + var handlers = []; + for (var i = 0; i < outputs.length; i++) { + var output = outputs[i]; + var handleEventClosure = renderEventHandlerClosure(output.eventName); + handlers.push(handleEventClosure); + } + return handlers; + })([ { + type: 0, + target: null, + eventName: "ngSubmit", + propName: null + }, { + type: 0, + target: null, + eventName: "submit", + propName: null + }, { + type: 0, + target: null, + eventName: "reset", + propName: null + } ]).forEach(function(handler) { + return handler(); + }); + })(); + } + expect_stdout: [ + "ngSubmit", + "submit", + "reset", + ] +} diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 9379c609..43c37070 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -735,3 +735,35 @@ issue_2313_7: { expect_stdout: "2 1" node_version: ">=6" } + +issue_2678: { + options = { + pure_getters: "strict", + side_effects: true, + } + input: { + var a = 1, c = "FAIL"; + (function f() { + (a-- && f()).p; + return { + get p() { + c = "PASS"; + } + }; + })(); + console.log(c); + } + expect: { + var a = 1, c = "FAIL"; + (function f() { + (a-- && f()).p; + return { + get p() { + c = "PASS"; + } + }; + })(); + console.log(c); + } + expect_stdout: "PASS" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 5253026f..b99cc261 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1355,10 +1355,9 @@ defun_inline_1: { function f() { return function(b) { return b; - }(2) + h(); - function h() { + }(2) + function h() { return h(); - } + }(); } } } @@ -1382,12 +1381,11 @@ defun_inline_2: { } expect: { function f() { - function h() { - return h(); - } return function(b) { return b; - }(2) + h(); + }(2) + function h() { + return h(); + }(); } } } diff --git a/test/ufuzz.js b/test/ufuzz.js index 14b8f114..1589d5f1 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -329,7 +329,8 @@ function createTopLevelCode() { rng(2) == 0 ? createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0) : createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, DEFUN_OK, CANNOT_THROW, 0), - 'console.log(null, a, b, c);' // preceding `null` makes for a cleaner output (empty string still shows up etc) + // preceding `null` makes for a cleaner output (empty string still shows up etc) + 'console.log(null, a, b, c, Infinity, NaN, undefined);' ].join('\n'); } @@ -635,6 +636,8 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: case p++: return getVarName(); + case p++: + return getVarName() + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); case p++: return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); case p++: