From 87a7426598c8a2053694b7f51089271134694e7d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 1 Jan 2022 21:40:43 +0000 Subject: [PATCH] enhance `inline` & `unused` (#5245) --- lib/compress.js | 494 +++++++++++++++++++++-------- test/compress/classes.js | 6 +- test/compress/const.js | 20 +- test/compress/default-values.js | 12 +- test/compress/functions.js | 533 +++++++++++++++++++++++++++++--- test/compress/issue-1704.js | 16 +- test/compress/issue-892.js | 7 +- test/compress/join_vars.js | 44 +++ test/compress/keep_fargs.js | 3 +- test/compress/let.js | 10 +- test/compress/nullish.js | 24 ++ test/compress/pure_getters.js | 5 +- test/compress/reduce_vars.js | 9 +- test/compress/sequences.js | 11 +- test/compress/spreads.js | 6 +- test/mocha/cli.js | 12 +- test/mocha/spidermonkey.js | 16 +- 17 files changed, 992 insertions(+), 236 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index ab930ffa..8e932730 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1859,7 +1859,7 @@ Compressor.prototype.compress = function(node) { if (!changed && last_changed == 3) break; } if (compressor.option("inline") >= 4) { - if (inline_last_iife(statements, compressor)) changed = 4; + if (inline_iife(statements, compressor)) changed = 4; if (!changed && last_changed == 4) break; } if (compressor.sequences_limit > 0) { @@ -3478,23 +3478,23 @@ Compressor.prototype.compress = function(node) { return statements.length != len; } - function inline_last_iife(statements, compressor) { - if (!in_lambda) return false; + function inline_iife(statements, compressor) { + var changed = false; var index = statements.length - 1; - var stat = statements[index]; - if (!(stat instanceof AST_SimpleStatement)) return false; - var body = stat.body; - if (body instanceof AST_UnaryPrefix) { - if (unary_side_effects[body.operator]) return false; - body = body.expression; + if (in_lambda && index >= 0) { + var inlined = statements[index].try_inline(compressor, block_scope); + if (inlined) { + statements[index--] = inlined; + changed = true; + } } - var inlined = make_node(AST_UnaryPrefix, stat, { - operator: "void", - expression: body, - }).try_inline(compressor, block_scope); - if (!inlined) return false; - statements[index] = inlined; - return true; + for (; index >= 0; index--) { + var inlined = statements[index].try_inline(compressor, block_scope, true, in_loop); + if (!inlined) continue; + statements[index] = inlined; + changed = true; + } + return changed; } function sequencesize(statements, compressor) { @@ -3803,6 +3803,7 @@ Compressor.prototype.compress = function(node) { if (stat.init) { prev.definitions = prev.definitions.concat(stat.init.definitions); } + stat = stat.clone(); defs = stat.init = prev; statements[j] = merge_defns(stat); changed = true; @@ -6775,7 +6776,7 @@ Compressor.prototype.compress = function(node) { var sym = def.name.definition(); var drop_sym = is_var ? can_drop_symbol(def.name) : is_safe_lexical(sym); if (!drop_sym || !drop_vars || sym.id in in_use_ids) { - if (value && indexOf_assign(sym, def) < 0) { + if (value && (indexOf_assign(sym, def) < 0 || self_assign(value.tail_node()))) { value = value.drop_side_effect_free(compressor); if (value) { AST_Node.warn("Side effects in definition of variable {name} [{file}:{line},{col}]", template(def.name)); @@ -6865,6 +6866,10 @@ Compressor.prototype.compress = function(node) { sym.eliminated++; } + function self_assign(ref) { + return ref instanceof AST_SymbolRef && ref.definition() === sym; + } + function assigned_once(fn, refs) { if (refs.length == 0) return fn === def.name.fixed_value(); return all(refs, function(ref) { @@ -9832,15 +9837,15 @@ Compressor.prototype.compress = function(node) { if (!has_default) has_default = 1; var arg = has_default == 1 && self.args[index]; if (arg && !is_undefined(arg)) has_default = 2; - if (has_arg_refs(argname.value)) return false; + if (has_arg_refs(fn, argname.value)) return false; argname = argname.name; } if (argname instanceof AST_Destructured) { has_destructured = true; - if (has_arg_refs(argname)) return false; + if (has_arg_refs(fn, argname)) return false; } return true; - }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn.rest)); + }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn, fn.rest)); var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor); if (can_inline && stat instanceof AST_Return) { var value = stat.value; @@ -9946,17 +9951,6 @@ Compressor.prototype.compress = function(node) { } return try_evaluate(compressor, self); - function has_arg_refs(node) { - var found = false; - node.walk(new TreeWalker(function(node) { - if (found) return true; - if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) { - return found = true; - } - })); - return found; - } - function make_void_lhs(orig) { return make_node(AST_Dot, orig, { expression: make_node(AST_Array, orig, { elements: [] }), @@ -10172,7 +10166,7 @@ Compressor.prototype.compress = function(node) { if (arg_used.has(name.name)) return false; if (var_exists(defined, name.name)) return false; if (!all(stat.enclosed, function(def) { - return def.scope === stat || !defined.has(def.name); + return def.scope === scope || def.scope === stat || !defined.has(def.name); })) return false; if (in_loop) in_loop.push(name.definition()); continue; @@ -10207,8 +10201,6 @@ Compressor.prototype.compress = function(node) { if (scope.init === child) continue; if (scope.object === child) continue; in_loop = []; - } else if (scope instanceof AST_SymbolRef) { - if (scope.fixed_value() instanceof AST_Scope) return false; } } while (!(scope instanceof AST_Scope)); insert = scope.body.indexOf(child) + 1; @@ -10270,7 +10262,9 @@ Compressor.prototype.compress = function(node) { if (value) expressions.push(value); } else { var symbol = make_node(AST_SymbolVar, name, name); - name.definition().orig.push(symbol); + var def = name.definition(); + def.orig.push(symbol); + def.eliminated++; if (name.unused !== undefined) { append_var(decls, expressions, symbol); if (value) expressions.push(value); @@ -10316,44 +10310,36 @@ Compressor.prototype.compress = function(node) { def.references.push(ref); var symbol = make_node(AST_SymbolVar, name, name); def.orig.push(symbol); + def.eliminated++; append_var(decls, expressions, symbol); } } - function flatten_var(name) { - var redef = name.definition().redefined(); - if (redef) { - name = name.clone(); - name.thedef = redef; - } - return name; - } - function flatten_vars(decls, expressions) { var args = [ insert, 0 ]; var decl_var = [], expr_var = [], expr_loop = [], exprs = []; - for (var i = 0; i < fn.body.length; i++) { - var stat = fn.body[i]; - if (stat instanceof AST_LambdaDefinition) { - if (in_loop) { - var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name)); - var def = name.definition(); - def.fixed = false; - def.orig.push(name); - append_var(decls, expressions, name, to_func_expr(stat, true)); - } else { - var def = stat.name.definition(); - scope.functions.set(def.name, def); - scope.variables.set(def.name, def); - scope.enclosed.push(def); - scope.var_names().set(def.name, true); - args.push(stat); - } - continue; - } + fn.body.filter(in_loop ? function(stat) { + if (!(stat instanceof AST_LambdaDefinition)) return true; + var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name)); + var def = name.definition(); + def.fixed = false; + def.orig.push(name); + def.eliminated++; + append_var(decls, expressions, name, to_func_expr(stat, true)); + return false; + } : function(stat) { + if (!(stat instanceof AST_LambdaDefinition)) return true; + var def = stat.name.definition(); + scope.functions.set(def.name, def); + scope.variables.set(def.name, def); + scope.enclosed.push(def); + scope.var_names().set(def.name, true); + args.push(stat); + return false; + }).forEach(function(stat) { if (!(stat instanceof AST_Var)) { if (stat instanceof AST_SimpleStatement) exprs.push(stat.body); - continue; + return; } for (var j = 0; j < stat.definitions.length; j++) { var var_def = stat.definitions[j]; @@ -10365,7 +10351,7 @@ Compressor.prototype.compress = function(node) { exprs = []; } append_var(decl_var, expr_var, name, value); - if (in_loop && !arg_used.has(name.name)) { + if (in_loop && !arg_used.has(name.name) && !fn.functions.has(name.name)) { var def = fn.variables.get(name.name); var sym = make_node(AST_SymbolRef, name, name); def.references.push(sym); @@ -10376,7 +10362,7 @@ Compressor.prototype.compress = function(node) { })); } } - } + }); [].push.apply(decls, decl_var); [].push.apply(expressions, expr_loop); [].push.apply(expressions, expr_var); @@ -10393,9 +10379,7 @@ Compressor.prototype.compress = function(node) { } var args = flatten_vars(decls, expressions); expressions.push(value); - if (decls.length) args.push(make_node(AST_Var, fn, { - definitions: decls - })); + if (decls.length) args.push(make_node(AST_Var, fn, { definitions: decls })); [].splice.apply(scope.body, args); fn.enclosed.forEach(function(def) { if (scope.var_names().has(def.name)) return; @@ -11809,9 +11793,7 @@ Compressor.prototype.compress = function(node) { 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; - } + if (node instanceof AST_SymbolRef && member(node.definition(), defs)) return reachable = true; }); var scan_scope = new TreeWalker(function(node) { if (reachable) return true; @@ -12825,12 +12807,72 @@ Compressor.prototype.compress = function(node) { } }); + function flatten_var(name) { + var redef = name.definition().redefined(); + if (redef) { + name = name.clone(); + name.thedef = redef; + } + return name; + } + + function has_arg_refs(fn, node) { + var found = false; + node.walk(new TreeWalker(function(node) { + if (found) return true; + if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) { + return found = true; + } + })); + return found; + } + (function(def) { def(AST_Node, noop); - def(AST_Await, function(compressor, scope) { - return this.expression.try_inline(compressor, scope); + def(AST_Assign, noop); + def(AST_Await, function(compressor, scope, no_return, in_loop) { + return this.expression.try_inline(compressor, scope, no_return, in_loop); }); - def(AST_Call, function(compressor, scope) { + def(AST_Binary, function(compressor, scope, no_return, in_loop) { + if (no_return === undefined) return; + var self = this; + var op = self.operator; + if (!lazy_op[op]) return; + var inlined = self.right.try_inline(compressor, scope, no_return, in_loop); + if (!inlined) return; + return make_node(AST_If, self, { + condition: make_condition(self.left), + body: inlined, + alternative: null, + }); + + function make_condition(cond) { + switch (op) { + case "&&": + return cond; + case "||": + return cond.negate(compressor); + case "??": + return make_node(AST_Binary, self, { + operator: "===", + left: make_node(AST_Undefined, self).transform(compressor), + right: cond, + }); + } + } + }); + def(AST_BlockStatement, function(compressor, scope, no_return, in_loop) { + if (no_return) return; + if (!this.variables) return; + var body = this.body; + var last = body.length - 1; + if (last < 0) return; + var inlined = body[last].try_inline(compressor, this, no_return, in_loop); + if (!inlined) return; + body[last] = inlined; + return this; + }); + def(AST_Call, function(compressor, scope, no_return, in_loop) { if (compressor.option("inline") < 4) return; var call = this; if (call.is_expr_pure(compressor)) return; @@ -12838,8 +12880,10 @@ Compressor.prototype.compress = function(node) { if (!(fn instanceof AST_LambdaExpression)) return; if (fn.name) return; if (fn.uses_arguments) return; - if (is_arrow(fn) && fn.value) return; if (is_generator(fn)) return; + var arrow = is_arrow(fn); + if (arrow && fn.value) return; + if (fn.body[0] instanceof AST_Directive) return; if (fn.contains_this()) return; if (!scope) scope = find_scope(compressor); var defined = new Dictionary(); @@ -12849,28 +12893,82 @@ Compressor.prototype.compress = function(node) { }); scope = scope.parent_scope; } - if (is_async(fn) && !(compressor.option("awaits") && is_async(scope))) return; + if (scope instanceof AST_Toplevel && fn.variables.size() > (arrow ? 0 : 1)) { + if (!compressor.toplevel.vars) return; + if (fn.functions.size() > 0 && !compressor.toplevel.funcs) return; + } + var async = is_async(fn); + if (async && !(compressor.option("awaits") && is_async(scope))) return; var names = scope.var_names(); + if (in_loop) in_loop = []; if (!fn.variables.all(function(def, name) { + if (in_loop) in_loop.push(def); if (!defined.has(name) && !names.has(name)) return true; if (name != "arguments") return false; if (scope.uses_arguments) return false; return def.references.length == def.replaced; })) return; - var safe = true; - fn.each_argname(function(argname) { - if (!all(argname.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolDefun); - })) safe = false; - }); - if (!safe) return; + if (in_loop && in_loop.length > 0 && is_reachable(fn, in_loop)) return; + var simple_argnames = true; + if (!all(fn.argnames, function(argname) { + var abort = false; + var tw = new TreeWalker(function(node) { + if (abort) return true; + if (node instanceof AST_DefaultValue) { + if (has_arg_refs(fn, node.value)) return abort = true; + node.name.walk(tw); + return true; + } + if (node instanceof AST_DestructuredKeyVal) { + if (node.key instanceof AST_Node && has_arg_refs(fn, node.key)) return abort = true; + node.value.walk(tw); + return true; + } + if (node instanceof AST_SymbolFunarg && !all(node.definition().orig, function(sym) { + return !(sym instanceof AST_SymbolDefun); + })) return abort = true; + }); + argname.walk(tw); + if (abort) return false; + if (!(argname instanceof AST_SymbolFunarg)) simple_argnames = false; + return true; + })) return; + if (fn.rest) { + if (has_arg_refs(fn, fn.rest)) return; + simple_argnames = false; + } + if (no_return && !all(fn.body, function(stat) { + var abort = false; + stat.walk(new TreeWalker(function(node) { + if (abort) return true; + if (async && node instanceof AST_Await || node instanceof AST_Return) return abort = true; + if (node instanceof AST_Scope && node !== fn) return true; + })); + return !abort; + })) return; if (!safe_from_await_yield(fn, avoid_await_yield(scope))) return; fn.functions.each(function(def, name) { scope.functions.set(name, def); }); + var body = []; fn.variables.each(function(def, name) { + names.set(name, true); + scope.enclosed.push(def); scope.variables.set(name, def); def.single_use = false; + if (!in_loop || fn.functions.has(name)) return; + if (def.references.length == def.replaced) return; + var sym = flatten_var(def.orig[0]); + if (sym.TYPE != "SymbolVar") return; + var ref = make_node(AST_SymbolRef, sym, sym); + sym.definition().references.push(ref); + body.push(make_node(AST_SimpleStatement, sym, { + body: make_node(AST_Assign, sym, { + operator: "=", + left: ref, + right: make_node(AST_Undefined, sym).transform(compressor), + }), + })); }); if (fn.variables.has("NaN")) scope.transform(new TreeTransformer(function(node) { if (node instanceof AST_NaN) return make_node(AST_Binary, node, { @@ -12879,12 +12977,28 @@ Compressor.prototype.compress = function(node) { right: make_node(AST_Number, node, { value: 0 }), }); })); - var body = [], defs = Object.create(null), syms = new Dictionary(); - if (fn.rest || !all(fn.argnames, function(argname) { - return argname instanceof AST_SymbolFunarg; - }) || !all(call.args, function(arg) { + var defs = Object.create(null), syms = new Dictionary(); + if (simple_argnames && all(call.args, function(arg) { return !(arg instanceof AST_Spread); })) { + var values = call.args.slice(); + fn.argnames.forEach(function(argname) { + var value = values.shift(); + if (argname.unused) { + if (value) body.push(make_node(AST_SimpleStatement, call, { body: value })); + return; + } + body.push(make_node(AST_Var, call, { + definitions: [ make_node(AST_VarDef, call, { + name: argname.convert_symbol(AST_SymbolVar, process), + value: value || make_node(AST_Undefined, call).transform(compressor), + }) ], + })); + }); + if (values.length) body.push(make_node(AST_SimpleStatement, call, { + body: make_sequence(call, values), + })); + } else { body.push(make_node(AST_Var, call, { definitions: [ make_node(AST_VarDef, call, { name: make_node(AST_DestructuredArray, call, { @@ -12897,37 +13011,37 @@ Compressor.prototype.compress = function(node) { value: make_node(AST_Array, call, { elements: call.args.slice() }), }) ], })); - } else { - var values = call.args.slice(); - fn.argnames.forEach(function(argname) { - var value = values.shift(); - if (argname.unused) { - if (value) body.push(make_node(AST_SimpleStatement, call, { body: value })); - return; - } - body.push(make_node(AST_Var, call, { - definitions: [ make_node(AST_VarDef, call, { - name: argname.convert_symbol(AST_SymbolVar, process), - value: value || make_node(AST_Undefined, call).optimize(compressor), - }) ], - })); - }); - if (values.length) body.push(make_node(AST_SimpleStatement, call, { - body: make_sequence(call, values), - })); } syms.each(function(orig, id) { - [].unshift.apply(defs[id].orig, orig); - }); - var inlined = make_node(AST_BlockStatement, call, { - body: body.concat(fn.body, make_node(AST_Return, call, { value: null })), - }); - if (is_async(fn)) scan_local_returns(inlined, function(node) { - var value = node.value; - if (!value) return; - if (is_undefined(value)) return; - node.value = make_node(AST_Await, call, { expression: value }); + var def = defs[id]; + [].unshift.apply(def.orig, orig); + def.eliminated += orig.length; }); + [].push.apply(body, in_loop ? fn.body.filter(function(stat) { + if (!(stat instanceof AST_LambdaDefinition)) return true; + var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name)); + var def = name.definition(); + def.fixed = false; + def.orig.push(name); + def.eliminated++; + body.push(make_node(AST_Var, stat, { + definitions: [ make_node(AST_VarDef, stat, { + name: name, + value: to_func_expr(stat, true), + }) ], + })); + return false; + }) : fn.body); + var inlined = make_node(AST_BlockStatement, call, { body: body }); + if (!no_return) { + if (async) scan_local_returns(inlined, function(node) { + var value = node.value; + if (!value) return; + if (is_undefined(value)) return; + node.value = make_node(AST_Await, call, { expression: value }); + }); + body.push(make_node(AST_Return, call, { value: null })); + } return inlined; function process(sym, argname) { @@ -12936,25 +13050,129 @@ Compressor.prototype.compress = function(node) { syms.add(def.id, sym); } }); - def(AST_New, noop); - def(AST_Sequence, function(compressor, scope) { - var inlined = this.tail_node().try_inline(compressor, scope); - if (inlined) return make_node(AST_BlockStatement, this, { - body: [ - make_node(AST_SimpleStatement, this, { - body: make_sequence(this, this.expressions.slice(0, -1)), - }), - inlined, - ], + def(AST_Conditional, function(compressor, scope, no_return, in_loop) { + var self = this; + var body = self.consequent.try_inline(compressor, scope, no_return, in_loop); + var alt = self.alternative.try_inline(compressor, scope, no_return, in_loop); + if (!body && !alt) return; + return make_node(AST_If, self, { + condition: self.condition, + body: body || make_body(self.consequent), + alternative: alt || make_body(self.alternative), }); + + function make_body(value) { + if (no_return) return make_node(AST_SimpleStatement, value, { body: value }); + return make_node(AST_Return, value, { value: value }); + } }); - def(AST_UnaryPrefix, function(compressor, scope) { + def(AST_For, function(compressor, scope, no_return, in_loop) { + var body = this.body.try_inline(compressor, scope, true, true); + if (body) this.body = body; + var inlined = this.init; + if (inlined) { + inlined = inlined.try_inline(compressor, scope, true, in_loop); + if (inlined) { + this.init = null; + inlined.body.push(this); + return inlined; + } + } + return body && this; + }); + def(AST_ForEnumeration, function(compressor, scope, no_return, in_loop) { + var body = this.body.try_inline(compressor, scope, true, true); + if (body) this.body = body; + var obj = this.object; + if (obj instanceof AST_Sequence) { + var inlined = inline_sequence(compressor, scope, true, in_loop, obj, 1); + if (inlined) { + this.object = obj.tail_node(); + inlined.body.push(this); + return inlined; + } + } + return body && this; + }); + def(AST_If, function(compressor, scope, no_return, in_loop) { + var body = this.body.try_inline(compressor, scope, no_return, in_loop); + if (body) this.body = body; + var alt = this.alternative; + if (alt) { + alt = alt.try_inline(compressor, scope, no_return, in_loop); + if (alt) this.alternative = alt; + } + var cond = this.condition; + if (cond instanceof AST_Sequence) { + var inlined = inline_sequence(compressor, scope, true, in_loop, cond, 1); + if (inlined) { + this.condition = cond.tail_node(); + inlined.body.push(this); + return inlined; + } + } + return (body || alt) && this; + }); + def(AST_IterationStatement, function(compressor, scope, no_return, in_loop) { + var body = this.body.try_inline(compressor, scope, true, true); + if (!body) return; + this.body = body; + return this; + }); + def(AST_LabeledStatement, function(compressor, scope, no_return, in_loop) { + var body = this.body.try_inline(compressor, scope, no_return, in_loop); + if (!body) return; + this.body = body; + return this; + }); + def(AST_New, noop); + function inline_sequence(compressor, scope, no_return, in_loop, node, skip) { + var body = [], exprs = node.expressions, no_ret = no_return; + for (var i = exprs.length - (skip || 0), j = i; --i >= 0; no_ret = true) { + var inlined = exprs[i].try_inline(compressor, scope, no_ret, in_loop); + if (!inlined) continue; + flush(); + body.push(inlined); + } + if (body.length == 0) return; + flush(); + if (!no_return && body[0] instanceof AST_SimpleStatement) { + body[0] = make_node(AST_Return, node, { value: body[0].body }); + } + return make_node(AST_BlockStatement, node, { body: body.reverse() }); + + function flush() { + if (j > i + 1) body.push(make_node(AST_SimpleStatement, node, { + body: make_sequence(node, exprs.slice(i + 1, j)), + })); + j = i; + } + } + def(AST_Sequence, function(compressor, scope, no_return, in_loop) { + return inline_sequence(compressor, scope, no_return, in_loop, this); + }); + def(AST_SimpleStatement, function(compressor, scope, no_return, in_loop) { + var body = this.body; + while (body instanceof AST_UnaryPrefix) { + var op = body.operator; + if (unary_side_effects[op]) break; + if (op == "void") break; + body = body.expression; + } + if (!no_return && !is_undefined(body)) body = make_node(AST_UnaryPrefix, this, { + operator: "void", + expression: body, + }); + return body.try_inline(compressor, scope, no_return || false, in_loop); + }); + def(AST_UnaryPrefix, function(compressor, scope, no_return, in_loop) { var self = this; var op = self.operator; if (unary_side_effects[op]) return; - var inlined = self.expression.try_inline(compressor, scope); + if (!no_return && op == "void") no_return = false; + var inlined = self.expression.try_inline(compressor, scope, no_return, in_loop); if (!inlined) return; - scan_local_returns(inlined, function(node) { + if (!no_return) scan_local_returns(inlined, function(node) { node.in_bool = false; var value = node.value; if (op == "void") { @@ -12968,7 +13186,21 @@ Compressor.prototype.compress = function(node) { }); return inlined; }); - def(AST_Yield, function(compressor, scope) { + def(AST_With, function(compressor, scope, no_return, in_loop) { + var body = this.body.try_inline(compressor, scope, no_return, in_loop); + if (body) this.body = body; + var exp = this.expression; + if (exp instanceof AST_Sequence) { + var inlined = inline_sequence(compressor, scope, true, in_loop, exp, 1); + if (inlined) { + this.expression = exp.tail_node(); + inlined.body.push(this); + return inlined; + } + } + return body && this; + }); + def(AST_Yield, function(compressor, scope, no_return, in_loop) { if (!compressor.option("yields")) return; if (!this.nested) return; var call = this.expression; @@ -12986,7 +13218,7 @@ Compressor.prototype.compress = function(node) { } call = call.clone(); call.expression = fn; - return call.try_inline(compressor, scope); + return call.try_inline(compressor, scope, no_return, in_loop); }); })(function(node, func) { node.DEFMETHOD("try_inline", func); diff --git a/test/compress/classes.js b/test/compress/classes.js index 0b4f622e..bad695f9 100644 --- a/test/compress/classes.js +++ b/test/compress/classes.js @@ -1646,10 +1646,8 @@ issue_4962_1: { })(function g() {}); } expect: { - (function g() {}), - void function() { - while (console.log(typeof g)); - }(); + (function g() {}); + while (console.log(typeof g)); } expect_stdout: "undefined" node_version: ">=12" diff --git a/test/compress/const.js b/test/compress/const.js index 83da5a4d..60c686c2 100644 --- a/test/compress/const.js +++ b/test/compress/const.js @@ -838,12 +838,10 @@ issue_4202: { expect: { { const o = {}; - (function() { - function f() { - o.p = 42; - } - f(f); - })(); + function f() { + o.p = 42; + } + f(f); console.log(o.p++); } } @@ -1287,12 +1285,10 @@ issue_4261_2: { expect: { { const a = 42; - (function() { - function g() { - while (void console.log(a)); - } - while (g()); - })(); + function g() { + while (void console.log(a)); + } + while (g()); } } expect_stdout: "42" diff --git a/test/compress/default-values.js b/test/compress/default-values.js index 53ebb194..3ad55f01 100644 --- a/test/compress/default-values.js +++ b/test/compress/default-values.js @@ -2027,13 +2027,11 @@ issue_5222: { f(); } expect: { - (function() { - do { - a = void 0, - [ a ] = []; - } while (console.log("PASS")); - var a; - })(); + do { + a = void 0, + [ a ] = []; + } while (console.log("PASS")); + var a; } expect_stdout: "PASS" node_version: ">=6" diff --git a/test/compress/functions.js b/test/compress/functions.js index 002db1b3..3cf48571 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -604,6 +604,254 @@ empty_body: { } } +inline_binary_and: { + options = { + inline: true, + } + input: { + console.log(function() { + (function() { + while (console.log("foo")); + return "bar"; + })() && (function() { + while (console.log("baz")); + return "moo"; + })(); + }()); + } + expect: { + console.log(function() { + if (function() { + while (console.log("foo")); + return "bar"; + }()) { + while (console.log("baz")); + return void "moo"; + return; + } + }()); + } + expect_stdout: [ + "foo", + "baz", + "undefined", + ] +} + +inline_binary_or: { + options = { + inline: true, + } + input: { + (function() { + while (console.log("foo")); + })() || (function() { + while (console.log("bar")); + })(); + } + expect: { + if (!function() { + while (console.log("foo")); + }()) + while (console.log("bar")); + } + expect_stdout: [ + "foo", + "bar", + ] +} + +inline_conditional: { + options = { + inline: true, + } + input: { + (function() { + while (console.log("foo")); + })() ? (function() { + while (console.log("bar")); + })() : (function() { + while (console.log("baz")); + })(); + } + expect: { + if (function() { + while (console.log("foo")); + }()) + while (console.log("bar")); + else + while (console.log("baz")); + } + expect_stdout: [ + "foo", + "baz", + ] +} + +inline_do: { + options = { + inline: true, + } + input: { + do (function() { + while (console.log("foo")); + })(); + while (function() { + while (console.log("bar")); + }()); + } + expect: { + do { + while (console.log("foo")); + } while (function() { + while (console.log("bar")); + }()); + } + expect_stdout: [ + "foo", + "bar", + ] +} + +inline_finally_return: { + options = { + inline: true, + } + input: { + console.log(function() { + try { + throw "FAIL"; + } finally { + return function() { + while (console.log("PASS")); + }(), 42; + } + }()); + } + expect: { + console.log(function() { + try { + throw "FAIL"; + } finally { + while (console.log("PASS")); + return 42; + } + }()); + } + expect_stdout: [ + "PASS", + "42", + ] +} + +inline_for_init: { + options = { + inline: true, + } + input: { + for (function() { + while (console.log("foo")); + }(); function() { + while (console.log("bar")); + }(); function() { + while (console.log("baz")); + }()) (function() { + while (console.log("moo")); + })(); + } + expect: { + while (console.log("foo")); + for (; function() { + while (console.log("bar")); + }(); function() { + while (console.log("baz")); + }()) { + while (console.log("moo")); + } + } + expect_stdout: [ + "foo", + "bar", + ] +} + +inline_for_object: { + options = { + inline: true, + } + input: { + for (var a in function() { + while (console.log("foo")); + }(), function() { + while (console.log("bar")); + }()) (function() { + while (console.log("baz")); + })(); + } + expect: { + while (console.log("foo")); + for (var a in function() { + while (console.log("bar")); + }()) { + while (console.log("baz")); + } + } + expect_stdout: [ + "foo", + "bar", + ] +} + +inline_if_else: { + options = { + inline: true, + } + input: { + if (function() { + while (console.log("foo")); + }(), function() { + while (console.log("bar")); + }()) (function() { + while (console.log("baz")); + })(); + else (function() { + while (console.log("moo")); + })(); + } + expect: { + while (console.log("foo")); + if (function() { + while (console.log("bar")); + }()) { + while (console.log("baz")); + } else { + while (console.log("moo")); + } + } + expect_stdout: [ + "foo", + "bar", + "moo", + ] +} + +inline_label: { + options = { + inline: true, + } + input: { + L: (function() { + while (console.log("PASS")); + })() + } + expect: { + L: { + while (console.log("PASS")); + } + } + expect_stdout: "PASS" +} + inline_loop_1: { options = { inline: true, @@ -679,6 +927,86 @@ inline_loop_4: { } } +inline_loop_5: { + options = { + inline: true, + toplevel: true, + } + input: { + for (var a in "foo") { + (function() { + function f() {} + var f; + console.log(typeof f, a - f); + })(); + } + } + expect: { + for (var a in "foo") + f = function() {}, + void console.log(typeof f, a - f); + var f; + } + expect_stdout: [ + "function NaN", + "function NaN", + "function NaN", + ] +} + +inline_loop_6: { + options = { + inline: true, + toplevel: true, + } + input: { + for (var a in "foo") { + (function() { + var f; + function f() {} + console.log(typeof f, a - f); + })(); + } + } + expect: { + for (var a in "foo") + f = function() {}, + void console.log(typeof f, a - f); + var f; + } + expect_stdout: [ + "function NaN", + "function NaN", + "function NaN", + ] +} + +inline_loop_7: { + options = { + inline: true, + toplevel: true, + } + input: { + for (var a = 0; a < 2; a++) { + (function() { + var b = b && b[console.log("FAIL")] || "PASS"; + while (console.log(b)); + })(); + } + } + expect: { + for (var a = 0; a < 2; a++) { + b = void 0; + var b = b && b[console.log("FAIL")] || "PASS"; + while (console.log(b)); + } + } + expect_stdout: [ + "PASS", + "PASS", + ] +} + inline_negate_iife: { options = { inline: true, @@ -699,6 +1027,111 @@ inline_negate_iife: { expect_stdout: "true" } +inline_return_binary: { + options = { + inline: true, + } + input: { + console.log(function() { + return function() { + while (console.log("foo")); + return "bar"; + }() || function() { + while (console.log("baz")); + return "moo"; + }(); + }()); + } + expect: { + console.log(function() { + while (console.log("foo")); + return "bar"; + }() || function() { + while (console.log("baz")); + return "moo"; + }()); + } + expect_stdout: [ + "foo", + "bar", + ] +} + +inline_return_conditional: { + options = { + inline: true, + } + input: { + console.log(function() { + return console ? "foo" : function() { + while (console.log("bar")); + return "baz"; + }(); + }()); + } + expect: { + console.log(function() { + if (console) + return "foo"; + else { + while (console.log("bar")); + return "baz"; + return; + } + }()); + } + expect_stdout: "foo" +} + +inline_while: { + options = { + inline: true, + } + input: { + while (function() { + while (console.log("foo")); + }()) (function() { + while (console.log("bar")); + })(); + } + expect: { + while (function() { + while (console.log("foo")); + }()) { + while (console.log("bar")); + } + } + expect_stdout: "foo" +} + +inline_with: { + options = { + inline: true, + } + input: { + with (+function() { + while (console.log("foo")); + }(), -function() { + while (console.log("bar")); + }()) ~function() { + while (console.log("baz")); + }(); + } + expect: { + while (console.log("foo")); + with (-function() { + while (console.log("bar")); + }()) { + while (console.log("baz")); + } + } + expect_stdout: [ + "foo", + "bar", + "baz", + ] +} + issue_2476: { options = { inline: true, @@ -820,16 +1253,14 @@ issue_2604_1: { } expect: { var a = "FAIL"; - (function() { - try { - throw 1; - } catch (b) { - (function(b) { - b && b(); - })(); - b && (a = "PASS"); - } - })(); + try { + throw 1; + } catch (b) { + (function(b) { + b && b(); + })(); + b && (a = "PASS"); + } console.log(a); } expect_stdout: "PASS" @@ -862,13 +1293,11 @@ issue_2604_2: { } expect: { var a = "FAIL"; - (function() { - try { - throw 1; - } catch (o) { - o && (a = "PASS"); - } - })(); + try { + throw 1; + } catch (o) { + o && (a = "PASS"); + } console.log(a); } expect_stdout: "PASS" @@ -1305,9 +1734,7 @@ issue_2630_1: { } expect: { var c = 0; - (function() { - while (void (c = 1 + ++c)); - })(), + while (void (c = 1 + ++c)); console.log(c); } expect_stdout: "2" @@ -1338,9 +1765,8 @@ issue_2630_2: { } expect: { var c = 0; - !function() { - while (void (c = 1 + (c += 1))); - }(), console.log(c); + while (void (c = 1 + (c += 1))); + console.log(c); } expect_stdout: "2" } @@ -1384,7 +1810,7 @@ issue_2630_3: { issue_2630_4: { options = { collapse_vars: true, - inline: true, + inline: 3, reduce_vars: true, side_effects: true, unused: true, @@ -1412,6 +1838,35 @@ issue_2630_4: { } issue_2630_5: { + options = { + collapse_vars: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + var x = 3, a = 1, b = 2; + (function() { + (function f1() { + while (--x >= 0 && f2()); + }()); + function f2() { + a++ + (b += a); + } + })(); + console.log(a); + } + expect: { + var x = 3, a = 1, b = 2; + while (--x >= 0 && void (b += ++a)); + console.log(a); + } + expect_stdout: "2" +} + +issue_2630_6: { options = { assignments: true, collapse_vars: true, @@ -4981,10 +5436,9 @@ issue_3833_2: { f(); } expect: { - (function(a) { - while (a); - console.log("PASS"); - })(); + var a = void 0; + while (a); + console.log("PASS"); } expect_stdout: "PASS" } @@ -5605,17 +6059,15 @@ issue_4261: { try { throw 42; } catch (e) { - (function() { - function g() { - // `ReferenceError: e is not defined` on Node.js v0.10 - while (void e.p); - } - while (console.log(g())); - })(); + function g() { + // `ReferenceError: e is not defined` on Node.js v4- + while (void e.p); + } + while (console.log(g())); } } expect_stdout: "undefined" - node_version: "<0.10 || >=0.12" + node_version: ">=6" } issue_4265: { @@ -5975,9 +6427,8 @@ issue_4659_3: { function f() { return a++; } - (function() { - while (!console); - })(f && a++); + f && a++; + while (!console); (function() { var a = console && a; })(); @@ -7122,9 +7573,7 @@ issue_5237: { } expect: { function f() { - (function() { - while (console.log(0/0)); - })(); + while (console.log(0/0)); var NaN = console && console.log(NaN); return; } diff --git a/test/compress/issue-1704.js b/test/compress/issue-1704.js index 12643c94..cb806a09 100644 --- a/test/compress/issue-1704.js +++ b/test/compress/issue-1704.js @@ -356,6 +356,7 @@ mangle_catch_redef_3: { try { throw 0; } catch (o) { + // prints "FAIL" if inlined on Node.js v4- (function() { function f() { o = "FAIL"; @@ -366,7 +367,8 @@ mangle_catch_redef_3: { console.log(o); } expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);' - expect_stdout: true + expect_stdout: "PASS" + node_version: ">=6" } mangle_catch_redef_3_toplevel: { @@ -379,6 +381,7 @@ mangle_catch_redef_3_toplevel: { try { throw 0; } catch (o) { + // prints "FAIL" if inlined on Node.js v4- (function() { function f() { o = "FAIL"; @@ -389,7 +392,8 @@ mangle_catch_redef_3_toplevel: { console.log(o); } expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);' - expect_stdout: true + expect_stdout: "PASS" + node_version: ">=6" } mangle_catch_redef_3_ie8: { @@ -402,6 +406,7 @@ mangle_catch_redef_3_ie8: { try { throw 0; } catch (o) { + // prints "FAIL" if inlined on Node.js v4- (function() { function f() { o = "FAIL"; @@ -412,7 +417,8 @@ mangle_catch_redef_3_ie8: { console.log(o); } expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);' - expect_stdout: true + expect_stdout: "PASS" + node_version: ">=6" } mangle_catch_redef_3_ie8_toplevel: { @@ -425,6 +431,7 @@ mangle_catch_redef_3_ie8_toplevel: { try { throw 0; } catch (o) { + // prints "FAIL" if inlined on Node.js v4- (function() { function f() { o = "FAIL"; @@ -435,5 +442,6 @@ mangle_catch_redef_3_ie8_toplevel: { console.log(o); } expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);' - expect_stdout: true + expect_stdout: "PASS" + node_version: ">=6" } diff --git a/test/compress/issue-892.js b/test/compress/issue-892.js index 93b42d70..53744a31 100644 --- a/test/compress/issue-892.js +++ b/test/compress/issue-892.js @@ -1,6 +1,4 @@ dont_mangle_arguments: { - mangle = { - }; options = { booleans: true, comparisons: true, @@ -21,12 +19,13 @@ dont_mangle_arguments: { side_effects: true, unused: true, } + mangle = {} input: { (function(){ var arguments = arguments, not_arguments = 9; console.log(not_arguments, arguments); - })(5,6,7); + })(5, 6, 7); } - expect_exact: "(function(){var arguments=arguments,o=9;console.log(o,arguments)})(5,6,7);" + expect_exact: "(function(){var arguments,o=9;console.log(o,arguments)})(5,6,7);" expect_stdout: true } diff --git a/test/compress/join_vars.js b/test/compress/join_vars.js index 3d6ec410..243517f1 100644 --- a/test/compress/join_vars.js +++ b/test/compress/join_vars.js @@ -570,6 +570,50 @@ inlined_assignments: { expect_stdout: "PASS" } +inilne_for: { + options = { + inline: true, + join_vars: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = function() { + for (; console.log("PASS");); + }; + a(); + } + expect: { + for (; console.log("PASS");); + } + expect_stdout: "PASS" +} + +inilne_var: { + options = { + inline: true, + join_vars: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + A = "PASS"; + var a = function() { + var b = A; + for (b in console.log(b)); + }; + a(); + } + expect: { + A = "PASS"; + var b = A; + for (b in console.log(b)); + } + expect_stdout: "PASS" +} + typescript_enum: { rename = true options = { diff --git a/test/compress/keep_fargs.js b/test/compress/keep_fargs.js index ea42ce2d..7c78ad20 100644 --- a/test/compress/keep_fargs.js +++ b/test/compress/keep_fargs.js @@ -1236,7 +1236,8 @@ issues_3267_1: { } expect: { !function() { - if (Object()) + var i = Object(); + if (i) return console.log("PASS"); throw "FAIL"; }(); diff --git a/test/compress/let.js b/test/compress/let.js index 3f4f75df..5ecef2e1 100644 --- a/test/compress/let.js +++ b/test/compress/let.js @@ -1054,12 +1054,10 @@ issue_4202: { "use strict"; { let o = {}; - (function() { - function f() { - o.p = 42; - } - f(f); - })(); + function f() { + o.p = 42; + } + f(f); console.log(o.p++); } } diff --git a/test/compress/nullish.js b/test/compress/nullish.js index 6ff4c3c7..be9c752d 100644 --- a/test/compress/nullish.js +++ b/test/compress/nullish.js @@ -262,6 +262,30 @@ de_morgan_2e: { node_version: ">=14" } +inline_binary_nullish: { + options = { + inline: true, + } + input: { + (function() { + while (console.log("foo")); + })() ?? (function() { + while (console.log("bar")); + })(); + } + expect: { + if (void 0 === function() { + while (console.log("foo")); + }()) + while (console.log("bar")); + } + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=14" +} + issue_4679: { options = { comparisons: true, diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 0ac15921..e38d5f24 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -1363,9 +1363,8 @@ issue_3490_1: { } expect: { var b = 42, c = "FAIL"; - if (function() { - var a; - }(), c = "PASS", b) while ("" == typeof d); + var a; + if (c = "PASS", b) while ("" == typeof d); console.log(c, b); } expect_stdout: "PASS 42" diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 0e232eee..4ae24544 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -41,10 +41,8 @@ reduce_vars: { } expect: { var A = 1; - (function() { - console.log(-3); - console.log(A - 5); - })(); + console.log(-3); + console.log(A - 5); (function f1() { var a = 2; console.log(a - 5); @@ -6690,7 +6688,8 @@ issues_3267_1: { } expect: { !function(x) { - if (Object()) + var i = Object(); + if (i) return console.log("PASS"); throw "FAIL"; }(); diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 556e2d70..efd8150d 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -1160,10 +1160,8 @@ issue_3490_1: { } expect: { var b = 42, c = "FAIL"; - if (function() { - var a; - a && a.p; - }(), c = "PASS", b) while ("" == typeof d); + var a; + if (a && a.p, c = "PASS", b) while ("" == typeof d); console.log(c, b); } expect_stdout: "PASS 42" @@ -1193,9 +1191,8 @@ issue_3490_2: { } expect: { var b = 42, c = "FAIL"; - for (function() { - var a; - }(), c = "PASS", b; "" == typeof d;); + var a; + for (c = "PASS", b; "" == typeof d;); console.log(c, b); } expect_stdout: "PASS 42" diff --git a/test/compress/spreads.js b/test/compress/spreads.js index dc77f2e6..13b12af8 100644 --- a/test/compress/spreads.js +++ b/test/compress/spreads.js @@ -197,10 +197,8 @@ do_inline_3: { })(); } expect: { - (function() { - var [] = [ ..."" ]; - while (console.log("PASS")); - })(); + var [] = [ ..."" ]; + while (console.log("PASS")); } expect_stdout: "PASS" node_version: ">=6" diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 9fb10a16..66fe3a4f 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -2,6 +2,7 @@ var assert = require("assert"); var exec = require("child_process").exec; var fs = require("fs"); var run_code = require("../sandbox").run_code; +var semver = require("semver"); var to_ascii = require("../node").to_ascii; function read(path) { @@ -12,10 +13,13 @@ describe("bin/uglifyjs", function() { var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs'; it("Should produce a functional build when using --self", function(done) { this.timeout(30000); - var command = uglifyjscmd + ' --self -cm --wrap WrappedUglifyJS'; - exec(command, { - maxBuffer: 1048576 - }, function(err, stdout) { + var command = [ + uglifyjscmd, + "--self", + semver.satisfies(process.version, "<=0.12") ? "-mc hoist_funs" : "-mc", + "--wrap WrappedUglifyJS", + ].join(" "); + exec(command, { maxBuffer: 1048576 }, function(err, stdout) { if (err) throw err; eval(stdout); assert.strictEqual(typeof WrappedUglifyJS, "object"); diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js index e99ce615..79b0f29f 100644 --- a/test/mocha/spidermonkey.js +++ b/test/mocha/spidermonkey.js @@ -1,14 +1,26 @@ var assert = require("assert"); var exec = require("child_process").exec; +var semver = require("semver"); var UglifyJS = require("../.."); describe("spidermonkey export/import sanity test", function() { it("Should produce a functional build when using --self with spidermonkey", function(done) { this.timeout(120000); var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; + var options = semver.satisfies(process.version, "<=0.12") ? "-mc hoist_funs" : "-mc"; var command = [ - uglifyjs + " --self -cm --wrap SpiderUglify -o spidermonkey", - uglifyjs + " -p spidermonkey -cm", + [ + uglifyjs, + "--self", + options, + "--wrap SpiderUglify", + "-o spidermonkey", + ].join(" "), + [ + uglifyjs, + "-p spidermonkey", + options, + ].join(" "), ].join(" | "); exec(command, { maxBuffer: 1048576 }, function(err, stdout) { if (err) throw err;