diff --git a/lib/compress.js b/lib/compress.js index 17c3bdb3..7dbb9290 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -291,7 +291,7 @@ merge(Compressor.prototype, { self.transform(tt); }); - AST_Node.DEFMETHOD("reset_opt_flags", function(compressor) { + AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { var reduce_vars = compressor.option("reduce_vars"); var unused = compressor.option("unused"); // Stack of look-up tables to keep track of whether a `SymbolDef` has been @@ -322,12 +322,18 @@ merge(Compressor.prototype, { d.fixed = false; } else if (d.fixed) { var value = node.fixed_value(); - if (unused && !compressor.exposed(d)) { - d.single_use = value - && d.references.length == 1 - && loop_ids[d.id] === in_loop - && d.scope === node.scope - && value.is_constant_expression(); + if (unused && !compressor.exposed(d) && value && d.references.length == 1) { + if (value instanceof AST_Lambda) { + d.single_use = d.scope === node.scope + && !(d.orig[0] instanceof AST_SymbolFunarg) + || value.is_constant_expression(node.scope); + } else { + d.single_use = d.scope === node.scope + && loop_ids[d.id] === in_loop + && value.is_constant_expression(); + } + } else { + d.single_use = false; } if (is_modified(node, value, 0, is_immutable(value))) { if (d.single_use) { @@ -390,6 +396,10 @@ merge(Compressor.prototype, { } else { d.fixed = node; mark(d, true); + if (unused && d.references.length == 1) { + d.single_use = d.scope === d.references[0].scope + || node.is_constant_expression(d.references[0].scope); + } } var save_ids = safe_ids; safe_ids = Object.create(null); @@ -542,6 +552,7 @@ merge(Compressor.prototype, { } return true; } + return def.fixed instanceof AST_Defun; } function safe_to_assign(def, value) { @@ -580,7 +591,10 @@ merge(Compressor.prototype, { } function is_immutable(value) { - return value && (value.is_constant() || value instanceof AST_Lambda); + if (!value) return false; + return value.is_constant() + || value instanceof AST_Lambda + || value instanceof AST_This; } function read_property(obj, key) { @@ -608,7 +622,7 @@ merge(Compressor.prototype, { || !immutable && parent instanceof AST_Call && parent.expression === node - && (!(value instanceof AST_Function) || value.contains_this())) { + && (!(value instanceof AST_Function) || value.contains_this(parent))) { return true; } else if (parent instanceof AST_Array || parent instanceof AST_Object) { return is_modified(parent, parent, level + 1); @@ -848,6 +862,7 @@ merge(Compressor.prototype, { function collapse(statements, compressor) { var scope = compressor.find_parent(AST_Scope).get_defun_scope(); if (scope.uses_eval || scope.uses_with) return statements; + var args; var candidates = []; var stat_index = statements.length; while (--stat_index >= 0) { @@ -868,7 +883,7 @@ merge(Compressor.prototype, { var one_off = lhs instanceof AST_Symbol && lhs.definition().references.length == 1; var side_effects = value_has_side_effects(candidate); var hit = candidate.name instanceof AST_SymbolFunarg; - var abort = false, replaced = false; + var abort = false, replaced = false, can_replace = !args || !hit; var tt = new TreeTransformer(function(node, descend) { if (abort) return node; // Skip nodes before `candidate` as quickly as possible @@ -895,7 +910,8 @@ merge(Compressor.prototype, { return node; } // Replace variable with assignment when found - if (!(node instanceof AST_SymbolDeclaration) + if (can_replace + && !(node instanceof AST_SymbolDeclaration) && !is_lhs(node, parent) && lhs.equivalent_to(node)) { CHANGED = replaced = abort = true; @@ -946,6 +962,12 @@ merge(Compressor.prototype, { // Skip (non-executed) functions and (leading) default case in switch statements if (node instanceof AST_Default || node instanceof AST_Scope) return node; }); + if (!can_replace) { + for (var j = compressor.self().argnames.lastIndexOf(candidate.__name || candidate.name) + 1; j < args.length; j++) { + args[j].transform(tt); + } + can_replace = true; + } for (var i = stat_index; !abort && i < statements.length; i++) { statements[i].transform(tt); } @@ -991,9 +1013,16 @@ merge(Compressor.prototype, { })) { var fn_strict = compressor.has_directive("use strict"); if (fn_strict && fn.body.indexOf(fn_strict) < 0) fn_strict = false; + var len = fn.argnames.length; + args = iife.args.slice(len); var names = Object.create(null); - for (var i = fn.argnames.length; --i >= 0;) { + for (var i = len; --i >= 0;) { var sym = fn.argnames[i]; + var arg = iife.args[i]; + args.unshift(make_node(AST_VarDef, sym, { + name: sym, + value: arg + })); if (sym.name in names) continue; names[sym.name] = true; if (sym instanceof AST_Expansion) { @@ -1007,9 +1036,9 @@ merge(Compressor.prototype, { elements: elements }) })); + candidates[0].__name = sym; } } else { - var arg = iife.args[i]; if (!arg) arg = make_node(AST_Undefined, sym).transform(compressor); else if (has_overlapping_symbol(fn, arg, fn_strict)) arg = null; if (arg) candidates.unshift(make_node(AST_VarDef, sym, { @@ -2265,18 +2294,22 @@ merge(Compressor.prototype, { } def(AST_Node, return_false); def(AST_Constant, return_true); - def(AST_Function, function(){ + def(AST_Lambda, function(scope){ var self = this; var result = true; self.walk(new TreeWalker(function(node) { if (!result) return true; if (node instanceof AST_SymbolRef) { var def = node.definition(); - if (self.enclosed.indexOf(def) >= 0 - && self.variables.get(def.name) !== def) { + if (member(def, self.enclosed) + && !self.variables.has(def.name)) { + if (scope) { + var scope_def = scope.find_variable(node); + if (def.undeclared ? !scope_def : scope_def === def) return true; + } result = false; - return true; } + return true; } })); return result; @@ -2439,8 +2472,11 @@ merge(Compressor.prototype, { }); return true; } - if (assign_as_unused(node) instanceof AST_SymbolRef && scope === self - && !is_ref_of(node.left, AST_SymbolBlockDeclaration)) { + var sym; + if (scope === self + && (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); return true; } @@ -2526,9 +2562,11 @@ merge(Compressor.prototype, { } } if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) { - var keep = (node.name.definition().id in in_use_ids) || !drop_funcs && node.name.definition().global; + var def = node.name.definition(); + var keep = (def.id in in_use_ids) || !drop_funcs && def.global; if (!keep) { compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name)); + drop_decl(def, node.name); return make_node(AST_EmptyStatement, node); } return node; @@ -2553,7 +2591,7 @@ merge(Compressor.prototype, { if (var_defs.length > 1 && !def.value) { compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); remove(var_defs, def); - remove(sym.orig, def.name); + drop_decl(sym, def.name); return; } } @@ -2586,7 +2624,7 @@ merge(Compressor.prototype, { } else { compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name)); } - remove(sym.orig, def.name); + drop_decl(sym, def.name); } }); if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) { @@ -2595,7 +2633,7 @@ merge(Compressor.prototype, { var def = tail.pop(); compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name)); remove(var_defs, def); - remove(def.name.definition().orig, def.name); + drop_decl(def.name.definition(), def.name); side_effects.unshift(make_node(AST_Assign, def, { operator: "=", left: make_node(AST_SymbolRef, def.name, def.name), @@ -2627,8 +2665,7 @@ merge(Compressor.prototype, { var def = assign_as_unused(node); if (def instanceof AST_SymbolRef && !((def = def.definition()).id in in_use_ids) - && (drop_vars || !def.global) - && self.variables.get(def.name) === def) { + && (drop_vars || !def.global)) { if (node instanceof AST_Assign) { return maintain_this_binding(parent, node, node.right.transform(tt)); } @@ -2685,6 +2722,14 @@ merge(Compressor.prototype, { col : sym.start.col }; } + + function drop_decl(def, decl) { + remove(def.orig, decl); + if (!def.orig.length) { + def.scope.functions.del(def.name); + def.scope.variables.del(def.name); + } + } } ); self.transform(tt); @@ -4456,46 +4501,53 @@ merge(Compressor.prototype, { if (compressor.option("unused") && fixed && d.references.length == 1 - && (d.single_use || is_func_expr(fixed) - && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) - && !d.scope.uses_eval - && compressor.find_parent(AST_Scope) === fixed.parent_scope)) { + && d.single_use) { var value = fixed.optimize(compressor); return value === fixed ? fixed.clone(true) : value; } - if (compressor.option("evaluate") && fixed) { - if (d.should_replace === undefined) { - var init = fixed.evaluate(compressor); - if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) { - init = make_node_from_constant(init, fixed); - var value_length = init.optimize(compressor).print_to_string().length; - var fn; - if (has_symbol_ref(fixed)) { - fn = function() { - var result = init.optimize(compressor); - return result === init ? result.clone(true) : result; - }; - } else { - value_length = Math.min(value_length, fixed.print_to_string().length); - fn = function() { - var result = best_of_expression(init.optimize(compressor), fixed); - return result === init || result === fixed ? result.clone(true) : result; - }; - } - var name_length = d.name.length; - var overhead = 0; - if (compressor.option("unused") && !compressor.exposed(d)) { - overhead = (name_length + 2 + value_length) / d.references.length; - } - d.should_replace = value_length <= name_length + overhead ? fn : false; - } else { - d.should_replace = false; + if (fixed && d.should_replace === undefined) { + var init; + if (fixed instanceof AST_This) { + if (!(d.orig[0] instanceof AST_SymbolFunarg) + && all(d.references, function(ref) { + return d.scope === ref.scope; + })) { + init = fixed; + } + } else { + var ev = fixed.evaluate(compressor); + if (ev !== fixed && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp))) { + init = make_node_from_constant(ev, fixed); } } - if (d.should_replace) { - return d.should_replace(); + if (init) { + var value_length = init.optimize(compressor).print_to_string().length; + var fn; + if (has_symbol_ref(fixed)) { + fn = function() { + var result = init.optimize(compressor); + return result === init ? result.clone(true) : result; + }; + } else { + value_length = Math.min(value_length, fixed.print_to_string().length); + fn = function() { + var result = best_of_expression(init.optimize(compressor), fixed); + return result === init || result === fixed ? result.clone(true) : result; + }; + } + var name_length = d.name.length; + var overhead = 0; + if (compressor.option("unused") && !compressor.exposed(d)) { + overhead = (name_length + 2 + value_length) / d.references.length; + } + d.should_replace = value_length <= name_length + overhead ? fn : false; + } else { + d.should_replace = false; } } + if (d.should_replace) { + return d.should_replace(); + } } return self; @@ -4832,11 +4884,11 @@ merge(Compressor.prototype, { } } if (is_lhs(self, compressor.parent())) return self; - if (compressor.option("properties") && key !== prop) { - var node = self.flatten_object(property, compressor); - if (node) { - expr = self.expression = node.expression; - prop = self.property = node.property; + if (key !== prop) { + var sub = self.flatten_object(property, compressor); + if (sub) { + expr = self.expression = sub.expression; + prop = self.property = sub.property; } } if (compressor.option("properties") && compressor.option("side_effects") @@ -4882,7 +4934,8 @@ merge(Compressor.prototype, { return self; }); - AST_Lambda.DEFMETHOD("contains_this", function() { + AST_Lambda.DEFMETHOD("contains_this", function(grandparent) { + if (grandparent instanceof AST_New) return false; var result; var self = this; self.walk(new TreeWalker(function(node) { @@ -4894,6 +4947,7 @@ merge(Compressor.prototype, { }); AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) { + if (!compressor.option("properties")) return; var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 6; var expr = this.expression; if (expr instanceof AST_Object) { @@ -4907,7 +4961,7 @@ merge(Compressor.prototype, { })) break; var value = prop.value; if ((value instanceof AST_Accessor || value instanceof AST_Function) - && value.contains_this()) break; + && value.contains_this(compressor.parent())) break; return make_node(AST_Sub, this, { expression: make_node(AST_Array, expr, { elements: props.map(function(prop) { @@ -4957,10 +5011,8 @@ merge(Compressor.prototype, { } } if (is_lhs(self, compressor.parent())) return self; - if (compressor.option("properties")) { - var node = self.flatten_object(self.property, compressor); - if (node) return node.optimize(compressor); - } + var sub = self.flatten_object(self.property, compressor); + if (sub) return sub.optimize(compressor); var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); diff --git a/package.json b/package.json index 2b30d251..46740497 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.1.6", + "version": "3.1.7", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 38dadb0f..5939943b 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3223,3 +3223,69 @@ conditional_2: { } expect_stdout: "5 5" } + +issue_2425_1: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var a = 8; + (function(b) { + b.toString(); + })(--a, a |= 10); + console.log(a); + } + expect: { + var a = 8; + (function(b) { + b.toString(); + })(--a, a |= 10); + console.log(a); + } + expect_stdout: "15" +} + +issue_2425_2: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var a = 8; + (function(b, c) { + b.toString(); + })(--a, a |= 10); + console.log(a); + } + expect: { + var a = 8; + (function(b, c) { + b.toString(); + })(--a, a |= 10); + console.log(a); + } + expect_stdout: "15" +} + +issue_2425_3: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var a = 8; + (function(b, b) { + b.toString(); + })(--a, a |= 10); + console.log(a); + } + expect: { + var a = 8; + (function(b, b) { + (a |= 10).toString(); + })(--a); + console.log(a); + } + expect_stdout: "15" +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 0a88220d..e6cc94fe 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1294,11 +1294,11 @@ issue_2063: { } } -issue_2105: { +issue_2105_1: { options = { collapse_vars: true, inline: true, - passes: 3, + passes: 2, reduce_vars: true, side_effects: true, unused: true, @@ -1324,17 +1324,50 @@ issue_2105: { }); } expect: { - (function() { - var quux = function() { + ({ + prop: function() { + console.log; console.log("PASS"); - }; - return { - prop: function() { - console.log; - quux(); + } + }).prop(); + } + expect_stdout: "PASS" +} + +issue_2105_2: { + options = { + collapse_vars: true, + inline: true, + passes: 2, + properties: true, + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + unsafe: true, + unused: true, + } + input: { + !function(factory) { + factory(); + }( function() { + return function(fn) { + fn()().prop(); + }( function() { + function bar() { + var quux = function() { + console.log("PASS"); + }, foo = function() { + console.log; + quux(); + }; + return { prop: foo }; } - }; - })().prop(); + return bar; + } ); + }); + } + expect: { + console.log("PASS"); } expect_stdout: "PASS" } diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index 2c9bc155..b2925af6 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -483,3 +483,31 @@ hoist_function_with_call: { } expect_stdout: "Foo true 10 20" } + +new_this: { + options = { + evaluate: true, + hoist_props: true, + inline: true, + passes: 2, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + f: function(a) { + this.b = a; + } + }; + console.log(new o.f(o.a).b, o.b); + } + expect: { + console.log(new function(a) { + this.b = a; + }(1).b, 2); + } + expect_stdout: "1 2" +} diff --git a/test/compress/properties.js b/test/compress/properties.js index b69ee692..410352f9 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1282,3 +1282,22 @@ computed_property: { ] node_version: ">=4" } + +new_this: { + options = { + properties: true, + side_effects: true, + } + input: { + new { + f: function(a) { + this.a = a; + } + }.f(42); + } + expect: { + new function(a) { + this.a = a; + }(42); + } +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index aca453b8..f0b1264f 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -3537,3 +3537,250 @@ escaped_prop: { } expect_stdout: "2" } + +issue_2420_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function run() { + var self = this; + if (self.count++) + self.foo(); + else + self.bar(); + } + var o = { + count: 0, + foo: function() { console.log("foo"); }, + bar: function() { console.log("bar"); }, + }; + run.call(o); + run.call(o); + } + expect: { + function run() { + if (this.count++) + this.foo(); + else + this.bar(); + } + var o = { + count: 0, + foo: function() { console.log("foo"); }, + bar: function() { console.log("bar"); }, + }; + run.call(o); + run.call(o); + } + expect_stdout: [ + "bar", + "foo", + ] +} + +issue_2420_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + var that = this; + if (that.bar) + that.foo(); + else + !function(that, self) { + console.log(this === that, self === this, that === self); + }(that, this); + } + f.call({ + bar: 1, + foo: function() { console.log("foo", this.bar); }, + }); + f.call({}); + } + expect: { + function f() { + if (this.bar) + this.foo(); + else + !function(that, self) { + console.log(this === that, self === this, that === self); + }(this, this); + } + f.call({ + bar: 1, + foo: function() { console.log("foo", this.bar); }, + }); + f.call({}); + } + expect_stdout: [ + "foo 1", + "false false true", + ] +} + +issue_2423_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function c() { return 1; } + function p() { console.log(c()); } + p(); + p(); + } + expect: { + function p() { console.log(function() { return 1; }()); } + p(); + p(); + } + expect_stdout: [ + "1", + "1", + ] +} + +issue_2423_2: { + options = { + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function c() { return 1; } + function p() { console.log(c()); } + p(); + p(); + } + expect: { + function p() { console.log(1); } + p(); + p(); + } + expect_stdout: [ + "1", + "1", + ] +} + +issue_2423_3: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function c() { return 1; } + function p() { console.log(c()); } + p(); + } + expect: { + (function() { console.log(function() { return 1; }()); })(); + } + expect_stdout: "1" +} + +issue_2423_4: { + options = { + inline: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + function c() { return 1; } + function p() { console.log(c()); } + p(); + } + expect: { + console.log(1); + } + expect_stdout: "1" +} + +issue_2423_5: { + options = { + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + function x() { + y(); + } + function y() { + console.log(1); + } + function z() { + function y() { + console.log(2); + } + x(); + } + z(); + z(); + } + expect: { + function z() { + console.log(1); + } + z(); + z(); + } + expect_stdout: [ + "1", + "1", + ] +} + +issue_2423_6: { + options = { + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + function x() { + y(); + } + function y() { + console.log(1); + } + function z() { + function y() { + console.log(2); + } + x(); + y(); + } + z(); + z(); + } + expect: { + function z(){ + console.log(1); + console.log(2); + } + z(); + z(); + } + expect_stdout: [ + "1", + "2", + "1", + "2", + ] +} diff --git a/test/ufuzz.js b/test/ufuzz.js index e38ffa2f..578103e8 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -162,6 +162,7 @@ var VALUES = [ '"object"', '"number"', '"function"', + 'this', ]; var BINARY_OPS_NO_COMMA = [ @@ -349,10 +350,10 @@ function createParams() { return params.join(', '); } -function createArgs() { +function createArgs(recurmax, stmtDepth, canThrow) { var args = []; for (var n = rng(4); --n >= 0;) { - args.push(createValue()); + args.push(rng(2) ? createValue() : createExpression(recurmax - 1, COMMA_OK, stmtDepth, canThrow)); } return args.join(', '); } @@ -390,9 +391,10 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { VAR_NAMES.length = namesLenBefore; - if (noDecl) s = 'var ' + createVarName(MANDATORY) + ' = ' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ');'; + if (noDecl) s = 'var ' + createVarName(MANDATORY) + ' = ' + s; // avoid "function statements" (decl inside statements) - else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');'; + else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name; + s += '(' + createArgs(recurmax, stmtDepth, canThrow) + ');'; return s; } @@ -626,6 +628,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: case p++: return createValue(); + case p++: + case p++: + return getVarName(); case p++: return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); case p++: