From c6cfa04d10c648dc1ccdf7ac6369f4162f0a46dc Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 12 Nov 2017 22:31:47 +0800 Subject: [PATCH] allow symbol replacement on multiple occurrences (#2472) - all-or-nothing replacement - avoid unmangleable names fixes #2436 --- lib/compress.js | 71 +++++- lib/scope.js | 2 + test/compress/collapse_vars.js | 420 +++++++++++++++++++++++++++++++++ 3 files changed, 481 insertions(+), 12 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index fdf3a2b6..54ac3d75 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -796,8 +796,8 @@ merge(Compressor.prototype, { }); function drop_decl(def) { - def._eliminiated = (def._eliminiated || 0) + 1; - if (def.orig.length == def._eliminiated) { + def.eliminated++; + if (def.orig.length == def.eliminated) { def.scope.functions.del(def.name); def.scope.variables.del(def.name); } @@ -854,10 +854,14 @@ merge(Compressor.prototype, { // Locate symbols which may execute code outside of scanning range var lvalues = get_lvalues(candidate); if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; - var one_off = lhs instanceof AST_Symbol && lhs.definition().references.length == 1; + var replace_all = candidate.multiple; + if (!replace_all && lhs instanceof AST_SymbolRef) { + var def = lhs.definition(); + replace_all = def.references.length - def.replaced == 1; + } var side_effects = value_has_side_effects(candidate); var hit = candidate.name instanceof AST_SymbolFunarg; - var abort = false, replaced = false, can_replace = !args || !hit; + var abort = false, replaced = 0, can_replace = !args || !hit; var tt = new TreeTransformer(function(node, descend) { if (abort) return node; // Skip nodes before `candidate` as quickly as possible @@ -886,7 +890,8 @@ merge(Compressor.prototype, { && !(node instanceof AST_SymbolDeclaration) && !is_lhs(node, parent) && lhs.equivalent_to(node)) { - CHANGED = replaced = abort = true; + CHANGED = abort = true; + replaced++; compressor.info("Collapsing {name} [{file}:{line},{col}]", { name: node.print_to_string(), file: node.start.file, @@ -897,8 +902,13 @@ merge(Compressor.prototype, { return make_node(AST_UnaryPrefix, candidate, candidate); } if (candidate instanceof AST_VarDef) { + if (candidate.multiple) { + abort = false; + return node; + } var def = candidate.name.definition(); - if (def.references.length == 1 && !compressor.exposed(def)) { + if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) { + def.replaced++; return maintain_this_binding(parent, node, candidate.value); } return make_node(AST_Assign, candidate, { @@ -922,7 +932,7 @@ merge(Compressor.prototype, { || side_effects && !references_in_scope(node.definition())) || (sym = lhs_or_def(node)) && (sym instanceof AST_PropAccess || sym.name in lvalues) - || (side_effects || !one_off) + || (side_effects || !replace_all) && (parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Case || parent instanceof AST_Conditional @@ -935,7 +945,7 @@ merge(Compressor.prototype, { if (node instanceof AST_Default || node instanceof AST_Scope) return node; }); if (!can_replace) { - for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; j < args.length; j++) { + for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { args[j].transform(tt); } can_replace = true; @@ -943,6 +953,33 @@ merge(Compressor.prototype, { for (var i = stat_index; !abort && i < statements.length; i++) { statements[i].transform(tt); } + if (candidate.multiple) { + var def = candidate.name.definition(); + if (abort && def.references.length > replaced) replaced = false; + else { + abort = false; + hit = candidate.name instanceof AST_SymbolFunarg; + var value_def = candidate.value.definition(); + for (var i = stat_index; !abort && i < statements.length; i++) { + statements[i].transform(new TreeTransformer(function(node) { + if (abort) return node; + if (!hit) { + if (node === candidate) { + hit = true; + return node; + } + return; + } + if (node instanceof AST_SymbolRef && node.name == def.name) { + def.replaced++; + value_def.replaced--; + if (!--replaced) abort = true; + return candidate.value; + } + })); + } + } + } if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1); } } @@ -956,7 +993,7 @@ merge(Compressor.prototype, { && (iife = compressor.parent()) instanceof AST_Call && iife.expression === fn) { var fn_strict = compressor.has_directive("use strict"); - if (fn_strict && fn.body.indexOf(fn_strict) < 0) fn_strict = false; + if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false; var len = fn.argnames.length; args = iife.args.slice(len); var names = Object.create(null); @@ -1012,12 +1049,22 @@ merge(Compressor.prototype, { } } + function mangleable_var(expr) { + var value = expr.value; + if (!(value instanceof AST_SymbolRef)) return false; + if (value.name == "arguments") return false; + if (value.definition().undeclared) return false; + expr.multiple = true; + return true; + } + function get_lhs(expr) { if (expr instanceof AST_VarDef) { var def = expr.name.definition(); - if (def.orig.length - (def._eliminiated || 0) > 1 - && !(expr.name instanceof AST_SymbolFunarg) - || def.references.length == 1 && !compressor.exposed(def)) { + var declared = def.orig.length - def.eliminated; + var referenced = def.references.length - def.replaced; + if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg) + || (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) { return make_node(AST_SymbolRef, expr.name, expr.name); } } else { diff --git a/lib/scope.js b/lib/scope.js index 8e766a56..0d2a7aeb 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -46,8 +46,10 @@ function SymbolDef(scope, index, orig) { this.name = orig.name; this.orig = [ orig ]; + this.eliminated = 0; this.scope = scope; this.references = []; + this.replaced = 0; this.global = false; this.mangled_name = null; this.undeclared = false; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index d98dca95..402bd22b 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3098,3 +3098,423 @@ issue_2437: { }(); } } + +issue_2436_1: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + var o = { + a: 1, + b: 2, + }; + console.log({ + x: o.a, + y: o.b, + }); + } + expect_stdout: true +} + +issue_2436_2: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + o.a = 3; + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + o.a = 3; + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect_stdout: true +} + +issue_2436_3: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + o = { + a: 3, + b: 4, + }; + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + o = { + a: 3, + b: 4, + }; + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect_stdout: true +} + +issue_2436_4: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + var o; + }(o)); + } + expect: { + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }({ + a: 1, + b: 2, + })); + } + expect_stdout: true +} + +issue_2436_5: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(o) { + return { + x: o.a, + y: o.b, + }; + }(o)); + } + expect: { + console.log(function(o) { + return { + x: o.a, + y: o.b, + }; + }({ + a: 1, + b: 2, + })); + } + expect_stdout: true +} + +issue_2436_6: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + passes: 2, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + unsafe: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + console.log({ + x: 1, + y: 2, + }); + } + expect_stdout: true +} + +issue_2436_7: { + options = { + collapse_vars: true, + hoist_props: true, + inline: true, + passes: 3, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + console.log({ + x: 1, + y: 2, + }); + } + expect_stdout: true +} + +issue_2436_8: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect_stdout: true +} + +issue_2436_9: { + options = { + collapse_vars: true, + inline: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = console; + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect: { + var o = console; + console.log(function(c) { + return { + x: c.a, + y: c.b, + }; + }(o)); + } + expect_stdout: true +} + +issue_2436_10: { + options = { + collapse_vars: true, + inline: true, + pure_getters: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + function f(n) { + o = { b: 3 }; + return n; + } + console.log(function(c) { + return [ + c.a, + f(c.b), + c.b, + ]; + }(o).join(" ")); + } + expect: { + var o = { + a: 1, + b: 2, + }; + function f(n) { + o = { b: 3 }; + return n; + } + console.log(function(c) { + return [ + c.a, + f(c.b), + c.b, + ]; + }(o).join(" ")); + } + expect_stdout: "1 2 2" +} + +issue_2436_11: { + options = { + collapse_vars: true, + join_vars: true, + reduce_vars: true, + unused: true, + } + input: { + function matrix() {} + function isCollection() {} + function _randomDataForMatrix() {} + function _randomInt() {} + function f(arg1, arg2) { + if (isCollection(arg1)) { + var size = arg1; + var max = arg2; + var min = 0; + var res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt); + return size && true === size.isMatrix ? matrix(res) : res; + } else { + var min = arg1; + var max = arg2; + return _randomInt(min, max); + } + } + } + expect: { + function matrix() {} + function isCollection() {} + function _randomDataForMatrix() {} + function _randomInt() {} + function f(arg1, arg2) { + if (isCollection(arg1)) { + var size = arg1, max = arg2, min = 0, res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt); + return size && true === size.isMatrix ? matrix(res) : res; + } else { + return _randomInt(min = arg1, max = arg2); + } + } + } +} + +issue_2436_12: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function isUndefined() {} + function f() { + var viewValue = this.$$lastCommittedViewValue; + var modelValue = viewValue; + return isUndefined(modelValue) ? modelValue : null; + } + } + expect: { + function isUndefined() {} + function f() { + var modelValue = this.$$lastCommittedViewValue; + return isUndefined(modelValue) ? modelValue : null; + } + } +} -- 2.34.1