From: Alex Lam S.L Date: Fri, 29 Nov 2019 09:45:49 +0000 (+0800) Subject: enhance `collapse_vars` (#3613) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=1b61a81b5d6c9489466fe4707c1b01810a01d03a;p=UglifyJS.git enhance `collapse_vars` (#3613) --- diff --git a/lib/compress.js b/lib/compress.js index 52c79a73..34700d4a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1116,7 +1116,7 @@ merge(Compressor.prototype, { var args; var candidates = []; var stat_index = statements.length; - var scanner = new TreeTransformer(function(node) { + var scanner = new TreeTransformer(function(node, descend) { if (abort) return node; // Skip nodes before `candidate` as quickly as possible if (!hit) { @@ -1151,11 +1151,16 @@ merge(Compressor.prototype, { return node; } if (is_lhs(node, parent)) { - if (value_def) replaced++; + if (value_def && !hit_rhs) { + assign_used = true; + replaced++; + } + return node; + } else if (value_def) { + if (!hit_rhs) replaced++; return node; } else { replaced++; - if (value_def) return node; } CHANGED = abort = true; AST_Node.info("Collapsing {name} [{file}:{line},{col}]", { @@ -1188,6 +1193,14 @@ merge(Compressor.prototype, { stop_after = node; if (node instanceof AST_Scope) abort = true; } + // Scan but don't replace inside getter/setter + if (node instanceof AST_Accessor) { + var replace = can_replace; + can_replace = false; + descend(node, scanner); + can_replace = replace; + return node; + } return handle_custom_scan_order(node); }, function(node) { if (abort) return; @@ -1200,9 +1213,28 @@ merge(Compressor.prototype, { if (!hit) { if (node !== hit_stack[hit_index]) return node; hit_index++; - if (hit_index < hit_stack.length) return; - hit = true; - return node; + switch (hit_stack.length - hit_index) { + case 0: + hit = true; + if (assign_used) return node; + if (node instanceof AST_VarDef) return node; + def.replaced++; + var parent = multi_replacer.parent(); + if (parent instanceof AST_Sequence && parent.tail_node() !== node) { + value_def.replaced++; + return MAP.skip; + } + return get_rvalue(candidate); + case 1: + if (!assign_used && node.body === candidate) { + hit = true; + def.replaced++; + value_def.replaced++; + return null; + } + default: + return; + } } // Replace variable when found if (node instanceof AST_SymbolRef @@ -1215,7 +1247,8 @@ 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; - }); + }, patch_sequence); + var force_single; while (--stat_index >= 0) { // Treat parameters as collapsible in IIFE, i.e. // function(a, b){ ... }(x()); @@ -1248,7 +1281,10 @@ merge(Compressor.prototype, { } : side_effects_external : return_false; var funarg = candidate.name instanceof AST_SymbolFunarg; var hit = funarg; - var abort = false, replaced = 0, can_replace = !args || !hit; + var abort = false; + var replaced = 0; + var assign_used = false; + var can_replace = !args || !hit; if (!can_replace) { for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { args[j].transform(scanner); @@ -1260,20 +1296,22 @@ merge(Compressor.prototype, { } if (value_def) { var def = lhs.definition(); - if (abort) { - var referenced = def.references.length - def.replaced; - if (candidate instanceof AST_Assign) referenced--; - if (referenced > replaced) { - replaced = false; - } else { - abort = false; - } + var referenced = def.references.length - def.replaced; + if (candidate instanceof AST_Assign) referenced--; + if (replaced && referenced == replaced) { + abort = false; + } else if (candidate instanceof AST_Assign) { + candidates.push(hit_stack); + force_single = true; + continue; + } else { + replaced = false; } - if (!abort) { + if (replaced) { hit_index = 0; hit = funarg; for (var i = stat_index; !abort && i < statements.length; i++) { - statements[i].transform(multi_replacer); + if (!statements[i].transform(multi_replacer)) statements.splice(i--, 1); } value_def.single_use = false; } @@ -1502,9 +1540,12 @@ merge(Compressor.prototype, { function find_stop(node, level) { var parent = scanner.parent(level); - if (parent instanceof AST_Array) return node; + if (parent instanceof AST_Array) return value_def ? find_stop(parent, level + 1) : node; if (parent instanceof AST_Assign) return node; - if (parent instanceof AST_Binary) return node; + if (parent instanceof AST_Binary) { + if (!value_def || parent.left !== node) return node; + return find_stop(parent, level + 1); + } if (parent instanceof AST_Call) return node; if (parent instanceof AST_Case) return node; if (parent instanceof AST_Conditional) return node; @@ -1512,7 +1553,9 @@ merge(Compressor.prototype, { if (parent instanceof AST_Exit) return node; if (parent instanceof AST_If) return node; if (parent instanceof AST_IterationStatement) return node; - if (parent instanceof AST_ObjectKeyVal) return node; + if (parent instanceof AST_ObjectKeyVal) { + return value_def ? find_stop(scanner.parent(level + 1), level + 2) : node; + } if (parent instanceof AST_PropAccess) return node; if (parent instanceof AST_Sequence) { return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1); @@ -1557,8 +1600,11 @@ merge(Compressor.prototype, { return null; } - function mangleable_var(var_def) { - var value = var_def.value; + function mangleable_var(value) { + if (force_single) { + force_single = false; + return; + } if (!(value instanceof AST_SymbolRef)) return; var def = value.definition(); if (def.undeclared) return; @@ -1573,11 +1619,21 @@ merge(Compressor.prototype, { var referenced = def.references.length - def.replaced; var declared = def.orig.length - def.eliminated; if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg) - || (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) { + || (referenced > 1 ? mangleable_var(expr.value) : !compressor.exposed(def))) { return make_node(AST_SymbolRef, expr.name, expr.name); } + } else if (expr instanceof AST_Assign) { + var lhs = expr.left; + if (expr.operator == "=" && lhs instanceof AST_SymbolRef) { + var def = lhs.definition(); + if (def.references[0] === lhs) { + var referenced = def.references.length - def.replaced; + if (referenced > 1) mangleable_var(expr.right); + } + } + return lhs; } else { - return expr[expr instanceof AST_Assign ? "left" : "expression"]; + return expr.expression; } } @@ -1689,20 +1745,15 @@ merge(Compressor.prototype, { node.value = null; return node; } - var parent = this.parent(); - if (!parent) return in_list ? MAP.skip : null; - if (parent instanceof AST_Sequence) return MAP.skip; - var value = expr; - do { - value = get_rvalue(value); - } while (value instanceof AST_Assign); - return value; - }, function(node) { - if (node instanceof AST_Sequence) switch (node.expressions.length) { - case 0: return null; - case 1: return node.expressions[0]; - } - })); + return in_list ? MAP.skip : null; + }, patch_sequence)); + } + + function patch_sequence(node) { + if (node instanceof AST_Sequence) switch (node.expressions.length) { + case 0: return null; + case 1: return node.expressions[0]; + } } function is_lhs_local(lhs) { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 175ab9c2..20bbfea2 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1348,8 +1348,7 @@ collapse_vars_array_3: { } expect: { function f(a) { - var b; - return [ b = a, b, b ]; + return [ a, a, a ]; } console.log(f().length); } @@ -1487,11 +1486,10 @@ collapse_vars_object_3: { } expect: { function f(a) { - var b; return { - p: b = a, - q: b, - r: b, + p: a, + q: a, + r: a, }; } console.log(f("PASS").r); @@ -6304,7 +6302,7 @@ assign_left: { } expect: { console.log(function(a, b) { - (b = a).p.q = "PASS"; + a.p.q = "PASS"; return a.p.q; }({p: {}})); } @@ -6317,12 +6315,12 @@ sub_property: { } input: { console.log(function(a, b) { - return a[(b = a, b.length - 1)]; + return a[b = a, b.length - 1]; }([ "FAIL", "PASS" ])); } expect: { console.log(function(a, b) { - return a[(b = a).length - 1]; + return a[a.length - 1]; }([ "FAIL", "PASS" ])); } expect_stdout: "PASS" @@ -6754,7 +6752,7 @@ local_value_replacement: { } expect: { function f(a, b) { - (a = b) && g(a); + b && g(b); } function g(c) { console.log(c); @@ -6805,3 +6803,165 @@ array_in_object_2: { } expect_stdout: "1 1" } + +array_in_conditional: { + options = { + collapse_vars: true, + } + input: { + var a = 1, b = 2, c; + console.log(c && [ b = a ], a, b); + } + expect: { + var a = 1, b = 2, c; + console.log(c && [ b = a ], a, b); + } + expect_stdout: "undefined 1 2" +} + +object_in_conditional: { + options = { + collapse_vars: true, + } + input: { + var a = 1, b = 2, c; + console.log(c && { + p: b = a + }, a, b); + } + expect: { + var a = 1, b = 2, c; + console.log(c && { + p: b = a + }, a, b); + } + expect_stdout: "undefined 1 2" +} + +sequence_in_iife_1: { + options = { + collapse_vars: true, + } + input: { + var a = "foo", b = 42; + (function() { + var c = (b = a, b); + })(); + console.log(a, b); + } + expect: { + var a = "foo", b = 42; + (function() { + var c = b = a; + })(); + console.log(a, b); + } + expect_stdout: "foo foo" +} + +sequence_in_iife_2: { + options = { + collapse_vars: true, + inline: true, + passes: 2, + side_effects: true, + unused: true, + } + input: { + var a = "foo", b = 42; + (function() { + var c = (b = a, b); + })(); + console.log(a, b); + } + expect: { + var a = "foo", b = 42; + console.log(a, a); + } + expect_stdout: "foo foo" +} + +retain_assign: { + options = { + collapse_vars: true, + } + input: { + var a = 42, b, c = "FAIL"; + b = a; + b++ && (c = "PASS"); + console.log(c); + } + expect: { + var a = 42, b, c = "FAIL"; + b = a; + b++ && (c = "PASS"); + console.log(c); + } + expect_stdout: "PASS" +} + +getter_side_effect: { + options = { + collapse_vars: true, + } + input: { + var c = "FAIL"; + (function(a) { + var b; + (b = a) && { + get foo() { + a = 0; + } + }.foo; + b && (c = "PASS"); + })(42); + console.log(c); + } + expect: { + var c = "FAIL"; + (function(a) { + var b; + (b = a) && { + get foo() { + a = 0; + } + }.foo; + b && (c = "PASS"); + })(42); + console.log(c); + } + expect_stdout: "PASS" +} + +setter_side_effect: { + options = { + collapse_vars: true, + } + input: { + var c = "FAIL"; + (function(a) { + var b; + (b = a) && ({ + set foo(v) { + a = v; + } + }.foo = 0); + b && (c = "PASS"); + })(42); + console.log(c); + } + expect: { + var c = "FAIL"; + (function(a) { + var b; + (b = a) && ({ + set foo(v) { + a = v; + } + }.foo = 0); + b && (c = "PASS"); + })(42); + console.log(c); + } + expect_stdout: "PASS" +}