enhance collapse_vars (#3613)
This commit is contained in:
129
lib/compress.js
129
lib/compress.js
@@ -1116,7 +1116,7 @@ merge(Compressor.prototype, {
|
|||||||
var args;
|
var args;
|
||||||
var candidates = [];
|
var candidates = [];
|
||||||
var stat_index = statements.length;
|
var stat_index = statements.length;
|
||||||
var scanner = new TreeTransformer(function(node) {
|
var scanner = new TreeTransformer(function(node, descend) {
|
||||||
if (abort) return node;
|
if (abort) return node;
|
||||||
// Skip nodes before `candidate` as quickly as possible
|
// Skip nodes before `candidate` as quickly as possible
|
||||||
if (!hit) {
|
if (!hit) {
|
||||||
@@ -1151,11 +1151,16 @@ merge(Compressor.prototype, {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
if (is_lhs(node, parent)) {
|
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;
|
return node;
|
||||||
} else {
|
} else {
|
||||||
replaced++;
|
replaced++;
|
||||||
if (value_def) return node;
|
|
||||||
}
|
}
|
||||||
CHANGED = abort = true;
|
CHANGED = abort = true;
|
||||||
AST_Node.info("Collapsing {name} [{file}:{line},{col}]", {
|
AST_Node.info("Collapsing {name} [{file}:{line},{col}]", {
|
||||||
@@ -1188,6 +1193,14 @@ merge(Compressor.prototype, {
|
|||||||
stop_after = node;
|
stop_after = node;
|
||||||
if (node instanceof AST_Scope) abort = true;
|
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);
|
return handle_custom_scan_order(node);
|
||||||
}, function(node) {
|
}, function(node) {
|
||||||
if (abort) return;
|
if (abort) return;
|
||||||
@@ -1200,9 +1213,28 @@ merge(Compressor.prototype, {
|
|||||||
if (!hit) {
|
if (!hit) {
|
||||||
if (node !== hit_stack[hit_index]) return node;
|
if (node !== hit_stack[hit_index]) return node;
|
||||||
hit_index++;
|
hit_index++;
|
||||||
if (hit_index < hit_stack.length) return;
|
switch (hit_stack.length - hit_index) {
|
||||||
hit = true;
|
case 0:
|
||||||
return node;
|
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
|
// Replace variable when found
|
||||||
if (node instanceof AST_SymbolRef
|
if (node instanceof AST_SymbolRef
|
||||||
@@ -1215,7 +1247,8 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
// Skip (non-executed) functions and (leading) default case in switch statements
|
// Skip (non-executed) functions and (leading) default case in switch statements
|
||||||
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
|
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
|
||||||
});
|
}, patch_sequence);
|
||||||
|
var force_single;
|
||||||
while (--stat_index >= 0) {
|
while (--stat_index >= 0) {
|
||||||
// Treat parameters as collapsible in IIFE, i.e.
|
// Treat parameters as collapsible in IIFE, i.e.
|
||||||
// function(a, b){ ... }(x());
|
// function(a, b){ ... }(x());
|
||||||
@@ -1248,7 +1281,10 @@ merge(Compressor.prototype, {
|
|||||||
} : side_effects_external : return_false;
|
} : side_effects_external : return_false;
|
||||||
var funarg = candidate.name instanceof AST_SymbolFunarg;
|
var funarg = candidate.name instanceof AST_SymbolFunarg;
|
||||||
var hit = funarg;
|
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) {
|
if (!can_replace) {
|
||||||
for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
|
for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
|
||||||
args[j].transform(scanner);
|
args[j].transform(scanner);
|
||||||
@@ -1260,20 +1296,22 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
if (value_def) {
|
if (value_def) {
|
||||||
var def = lhs.definition();
|
var def = lhs.definition();
|
||||||
if (abort) {
|
var referenced = def.references.length - def.replaced;
|
||||||
var referenced = def.references.length - def.replaced;
|
if (candidate instanceof AST_Assign) referenced--;
|
||||||
if (candidate instanceof AST_Assign) referenced--;
|
if (replaced && referenced == replaced) {
|
||||||
if (referenced > replaced) {
|
abort = false;
|
||||||
replaced = false;
|
} else if (candidate instanceof AST_Assign) {
|
||||||
} else {
|
candidates.push(hit_stack);
|
||||||
abort = false;
|
force_single = true;
|
||||||
}
|
continue;
|
||||||
|
} else {
|
||||||
|
replaced = false;
|
||||||
}
|
}
|
||||||
if (!abort) {
|
if (replaced) {
|
||||||
hit_index = 0;
|
hit_index = 0;
|
||||||
hit = funarg;
|
hit = funarg;
|
||||||
for (var i = stat_index; !abort && i < statements.length; i++) {
|
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;
|
value_def.single_use = false;
|
||||||
}
|
}
|
||||||
@@ -1502,9 +1540,12 @@ merge(Compressor.prototype, {
|
|||||||
|
|
||||||
function find_stop(node, level) {
|
function find_stop(node, level) {
|
||||||
var parent = scanner.parent(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_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_Call) return node;
|
||||||
if (parent instanceof AST_Case) return node;
|
if (parent instanceof AST_Case) return node;
|
||||||
if (parent instanceof AST_Conditional) 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_Exit) return node;
|
||||||
if (parent instanceof AST_If) return node;
|
if (parent instanceof AST_If) return node;
|
||||||
if (parent instanceof AST_IterationStatement) 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_PropAccess) return node;
|
||||||
if (parent instanceof AST_Sequence) {
|
if (parent instanceof AST_Sequence) {
|
||||||
return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
|
return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
|
||||||
@@ -1557,8 +1600,11 @@ merge(Compressor.prototype, {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mangleable_var(var_def) {
|
function mangleable_var(value) {
|
||||||
var value = var_def.value;
|
if (force_single) {
|
||||||
|
force_single = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!(value instanceof AST_SymbolRef)) return;
|
if (!(value instanceof AST_SymbolRef)) return;
|
||||||
var def = value.definition();
|
var def = value.definition();
|
||||||
if (def.undeclared) return;
|
if (def.undeclared) return;
|
||||||
@@ -1573,11 +1619,21 @@ merge(Compressor.prototype, {
|
|||||||
var referenced = def.references.length - def.replaced;
|
var referenced = def.references.length - def.replaced;
|
||||||
var declared = def.orig.length - def.eliminated;
|
var declared = def.orig.length - def.eliminated;
|
||||||
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
|
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);
|
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 {
|
} else {
|
||||||
return expr[expr instanceof AST_Assign ? "left" : "expression"];
|
return expr.expression;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1689,20 +1745,15 @@ merge(Compressor.prototype, {
|
|||||||
node.value = null;
|
node.value = null;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
var parent = this.parent();
|
return in_list ? MAP.skip : null;
|
||||||
if (!parent) return in_list ? MAP.skip : null;
|
}, patch_sequence));
|
||||||
if (parent instanceof AST_Sequence) return MAP.skip;
|
}
|
||||||
var value = expr;
|
|
||||||
do {
|
function patch_sequence(node) {
|
||||||
value = get_rvalue(value);
|
if (node instanceof AST_Sequence) switch (node.expressions.length) {
|
||||||
} while (value instanceof AST_Assign);
|
case 0: return null;
|
||||||
return value;
|
case 1: return node.expressions[0];
|
||||||
}, function(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) {
|
function is_lhs_local(lhs) {
|
||||||
|
|||||||
@@ -1348,8 +1348,7 @@ collapse_vars_array_3: {
|
|||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
function f(a) {
|
function f(a) {
|
||||||
var b;
|
return [ a, a, a ];
|
||||||
return [ b = a, b, b ];
|
|
||||||
}
|
}
|
||||||
console.log(f().length);
|
console.log(f().length);
|
||||||
}
|
}
|
||||||
@@ -1487,11 +1486,10 @@ collapse_vars_object_3: {
|
|||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
function f(a) {
|
function f(a) {
|
||||||
var b;
|
|
||||||
return {
|
return {
|
||||||
p: b = a,
|
p: a,
|
||||||
q: b,
|
q: a,
|
||||||
r: b,
|
r: a,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.log(f("PASS").r);
|
console.log(f("PASS").r);
|
||||||
@@ -6304,7 +6302,7 @@ assign_left: {
|
|||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
console.log(function(a, b) {
|
console.log(function(a, b) {
|
||||||
(b = a).p.q = "PASS";
|
a.p.q = "PASS";
|
||||||
return a.p.q;
|
return a.p.q;
|
||||||
}({p: {}}));
|
}({p: {}}));
|
||||||
}
|
}
|
||||||
@@ -6317,12 +6315,12 @@ sub_property: {
|
|||||||
}
|
}
|
||||||
input: {
|
input: {
|
||||||
console.log(function(a, b) {
|
console.log(function(a, b) {
|
||||||
return a[(b = a, b.length - 1)];
|
return a[b = a, b.length - 1];
|
||||||
}([ "FAIL", "PASS" ]));
|
}([ "FAIL", "PASS" ]));
|
||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
console.log(function(a, b) {
|
console.log(function(a, b) {
|
||||||
return a[(b = a).length - 1];
|
return a[a.length - 1];
|
||||||
}([ "FAIL", "PASS" ]));
|
}([ "FAIL", "PASS" ]));
|
||||||
}
|
}
|
||||||
expect_stdout: "PASS"
|
expect_stdout: "PASS"
|
||||||
@@ -6754,7 +6752,7 @@ local_value_replacement: {
|
|||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
function f(a, b) {
|
function f(a, b) {
|
||||||
(a = b) && g(a);
|
b && g(b);
|
||||||
}
|
}
|
||||||
function g(c) {
|
function g(c) {
|
||||||
console.log(c);
|
console.log(c);
|
||||||
@@ -6805,3 +6803,165 @@ array_in_object_2: {
|
|||||||
}
|
}
|
||||||
expect_stdout: "1 1"
|
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"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user