enhance collapse_vars (#1862)
- extend expression types
- `a++`
- `a=x;`
- extend scan range
- `for(init;;);`
- `switch(expr){case expr:}`
- `a = x; a = a || y;`
- terminate upon `debugger;`
closes #1821
fixes #27
fixes #315
fixes #1858
This commit is contained in:
357
lib/compress.js
357
lib/compress.js
@@ -512,6 +512,10 @@ merge(Compressor.prototype, {
|
||||
return fixed();
|
||||
});
|
||||
|
||||
function is_lhs_read_only(lhs) {
|
||||
return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda;
|
||||
}
|
||||
|
||||
function find_variable(compressor, name) {
|
||||
var scope, i = 0;
|
||||
while (scope = compressor.parent(i++)) {
|
||||
@@ -643,174 +647,210 @@ merge(Compressor.prototype, {
|
||||
statements = join_consecutive_vars(statements, compressor);
|
||||
}
|
||||
if (compressor.option("collapse_vars")) {
|
||||
statements = collapse_single_use_vars(statements, compressor);
|
||||
statements = collapse(statements, compressor);
|
||||
}
|
||||
} while (CHANGED && max_iter-- > 0);
|
||||
|
||||
return statements;
|
||||
|
||||
function collapse_single_use_vars(statements, compressor) {
|
||||
// Iterate statements backwards looking for a statement with a var/const
|
||||
// declaration immediately preceding it. Grab the rightmost var definition
|
||||
// and if it has exactly one reference then attempt to replace its reference
|
||||
// in the statement with the var value and then erase the var definition.
|
||||
|
||||
// Search from right to left for assignment-like expressions:
|
||||
// - `var a = x;`
|
||||
// - `a = x;`
|
||||
// - `++a`
|
||||
// For each candidate, scan from left to right for first usage, then try
|
||||
// to fold assignment into the site for compression.
|
||||
// Will not attempt to collapse assignments into or past code blocks
|
||||
// which are not sequentially executed, e.g. loops and conditionals.
|
||||
function collapse(statements, compressor) {
|
||||
var scope = compressor.find_parent(AST_Scope);
|
||||
var stat_index;
|
||||
var prev_stat_index;
|
||||
var def_stat_index;
|
||||
var stat;
|
||||
var var_defs;
|
||||
var var_defs_index;
|
||||
for (stat_index = statements.length; --stat_index >= 0;) {
|
||||
stat = statements[stat_index];
|
||||
// Scan variable definitions from right to left.
|
||||
if (stat instanceof AST_Definitions) {
|
||||
prev_stat_index = stat_index;
|
||||
var_defs = stat.definitions;
|
||||
for (def_stat_index = var_defs.length; --def_stat_index >= 1;) {
|
||||
stat = var_defs[def_stat_index];
|
||||
scan_var_defs(def_stat_index);
|
||||
if (scope.uses_eval || scope.uses_with) return statements;
|
||||
var candidates = [];
|
||||
var stat_index = statements.length;
|
||||
while (--stat_index >= 0) {
|
||||
extract_candidates(statements[stat_index]);
|
||||
while (candidates.length > 0) {
|
||||
var candidate = candidates.pop();
|
||||
var lhs = get_lhs(candidate);
|
||||
if (!lhs || is_lhs_read_only(lhs)) continue;
|
||||
var lvalues = get_lvalues(candidate);
|
||||
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
|
||||
var side_effects = value_has_side_effects(candidate);
|
||||
var hit = false, abort = false, replaced = false;
|
||||
var tt = new TreeTransformer(function(node, descend) {
|
||||
if (abort) return node;
|
||||
// Skip nodes before `candidate` as quickly as possible
|
||||
if (!hit) {
|
||||
if (node === candidate) {
|
||||
hit = true;
|
||||
return node;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Stop immediately if these node types are encountered
|
||||
var parent = tt.parent();
|
||||
if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
|
||||
|| node instanceof AST_Debugger
|
||||
|| node instanceof AST_IterationStatement && !(node instanceof AST_For)
|
||||
|| node instanceof AST_SymbolRef && node.undeclared()
|
||||
|| node instanceof AST_Try
|
||||
|| node instanceof AST_With
|
||||
|| parent instanceof AST_For && node !== parent.init) {
|
||||
abort = true;
|
||||
return node;
|
||||
}
|
||||
// Replace variable with assignment when found
|
||||
if (!(node instanceof AST_SymbolDeclaration)
|
||||
&& !is_lhs(node, parent)
|
||||
&& lhs.equivalent_to(node)) {
|
||||
CHANGED = replaced = abort = true;
|
||||
compressor.info("Collapsing {name} [{file}:{line},{col}]", {
|
||||
name: node.print_to_string(),
|
||||
file: node.start.file,
|
||||
line: node.start.line,
|
||||
col: node.start.col
|
||||
});
|
||||
if (candidate instanceof AST_UnaryPostfix) {
|
||||
return make_node(AST_UnaryPrefix, candidate, candidate);
|
||||
}
|
||||
if (candidate instanceof AST_VarDef) {
|
||||
var def = candidate.name.definition();
|
||||
if (def.references.length == 1 && (!def.global || compressor.toplevel(def))) {
|
||||
return maintain_this_binding(parent, node, candidate.value);
|
||||
}
|
||||
return make_node(AST_Assign, candidate, {
|
||||
operator: "=",
|
||||
left: make_node(AST_SymbolRef, candidate.name, candidate.name),
|
||||
right: candidate.value
|
||||
});
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
// These node types have child nodes that execute sequentially,
|
||||
// but are otherwise not safe to scan into or beyond them.
|
||||
var sym;
|
||||
if (node instanceof AST_Call
|
||||
|| node instanceof AST_Exit
|
||||
|| node instanceof AST_PropAccess
|
||||
|| node instanceof AST_SymbolRef
|
||||
&& (lvalues[node.name]
|
||||
|| side_effects && !references_in_scope(node.definition()))
|
||||
|| (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues
|
||||
|| parent instanceof AST_Binary
|
||||
&& (parent.operator == "&&" || parent.operator == "||")
|
||||
|| parent instanceof AST_Case
|
||||
|| parent instanceof AST_Conditional
|
||||
|| parent instanceof AST_For
|
||||
|| parent instanceof AST_If) {
|
||||
if (!(node instanceof AST_Scope)) descend(node, tt);
|
||||
abort = true;
|
||||
return node;
|
||||
}
|
||||
// Skip (non-executed) functions and (leading) default case in switch statements
|
||||
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
|
||||
});
|
||||
for (var i = stat_index; !abort && i < statements.length; i++) {
|
||||
statements[i].transform(tt);
|
||||
}
|
||||
} else if (stat_index > 0) {
|
||||
// The variable definition must precede a statement.
|
||||
prev_stat_index = stat_index - 1;
|
||||
var prev_stat = statements[prev_stat_index];
|
||||
if (!(prev_stat instanceof AST_Definitions)) continue;
|
||||
var_defs = prev_stat.definitions;
|
||||
scan_var_defs(var_defs.length);
|
||||
if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return statements;
|
||||
|
||||
function scan_var_defs(end_pos) {
|
||||
var var_names_seen = Object.create(null);
|
||||
var side_effects_encountered = false;
|
||||
var lvalues_encountered = false;
|
||||
var lvalues = Object.create(null);
|
||||
for (var_defs_index = end_pos; --var_defs_index >= 0;) {
|
||||
var var_decl = var_defs[var_defs_index];
|
||||
// `drop_unused()` shuffles variables without values to the top,
|
||||
// so we can terminate upon first sighting as an optimization.
|
||||
if (var_decl.value == null) break;
|
||||
var var_name = var_decl.name.name;
|
||||
|
||||
// Bail if we've seen a var definition of same name before.
|
||||
if (var_name in var_names_seen) break;
|
||||
var_names_seen[var_name] = true;
|
||||
|
||||
// Only interested in non-constant values.
|
||||
if (var_decl.value.is_constant()) continue;
|
||||
|
||||
// Only interested in cases with just one reference to the variable.
|
||||
var def = var_decl.name.definition();
|
||||
if (def.references.length !== 1
|
||||
|| var_name == "arguments"
|
||||
|| def.global && !compressor.toplevel(def)) {
|
||||
side_effects_encountered = true;
|
||||
continue;
|
||||
}
|
||||
var ref = def.references[0];
|
||||
|
||||
// Don't replace ref if eval() or with statement in scope.
|
||||
if (ref.scope.uses_eval || ref.scope.uses_with) break;
|
||||
|
||||
// Restrict var replacement to constants if side effects encountered.
|
||||
if (side_effects_encountered |= lvalues_encountered) continue;
|
||||
|
||||
var value_has_side_effects = var_decl.value.has_side_effects(compressor);
|
||||
// Non-constant single use vars can only be replaced in same scope.
|
||||
if (ref.scope !== scope) {
|
||||
side_effects_encountered |= value_has_side_effects;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect lvalues in var value.
|
||||
var tw = new TreeWalker(function(node){
|
||||
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
|
||||
lvalues[node.name] = lvalues_encountered = true;
|
||||
}
|
||||
function extract_candidates(expr) {
|
||||
if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor)
|
||||
|| expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) {
|
||||
candidates.push(expr);
|
||||
} else if (expr instanceof AST_Sequence) {
|
||||
expr.expressions.forEach(extract_candidates);
|
||||
} else if (expr instanceof AST_Definitions) {
|
||||
expr.definitions.forEach(function(var_def) {
|
||||
if (var_def.value) candidates.push(var_def);
|
||||
});
|
||||
var_decl.value.walk(tw);
|
||||
} else if (expr instanceof AST_SimpleStatement) {
|
||||
extract_candidates(expr.body);
|
||||
} else if (expr instanceof AST_For && expr.init) {
|
||||
extract_candidates(expr.init);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the non-constant single use var in statement if side effect free.
|
||||
var unwind = false;
|
||||
var tt = new TreeTransformer(
|
||||
function preorder(node) {
|
||||
if (unwind || node instanceof AST_Scope && node !== scope) return node;
|
||||
var parent = tt.parent();
|
||||
if (node instanceof AST_Try
|
||||
|| node instanceof AST_With
|
||||
|| node instanceof AST_Case
|
||||
|| node instanceof AST_IterationStatement
|
||||
|| (parent instanceof AST_If && node !== parent.condition)
|
||||
|| (parent instanceof AST_Conditional && node !== parent.condition)
|
||||
|| (node instanceof AST_SymbolRef
|
||||
&& value_has_side_effects
|
||||
&& !are_references_in_scope(node.definition(), scope))
|
||||
|| (parent instanceof AST_Binary
|
||||
&& (parent.operator == "&&" || parent.operator == "||")
|
||||
&& node === parent.right)
|
||||
|| (parent instanceof AST_Switch && node !== parent.expression)) {
|
||||
return side_effects_encountered = unwind = true, node;
|
||||
}
|
||||
function are_references_in_scope(def, scope) {
|
||||
if (def.orig.length === 1
|
||||
&& def.orig[0] instanceof AST_SymbolDefun) return true;
|
||||
if (def.scope !== scope) return false;
|
||||
var refs = def.references;
|
||||
for (var i = 0, len = refs.length; i < len; i++) {
|
||||
if (refs[i].scope !== scope) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
function postorder(node) {
|
||||
if (unwind) return node;
|
||||
if (node === ref)
|
||||
return unwind = true, replace_var(var_decl, node, tt.parent(), false);
|
||||
if (side_effects_encountered |= node.has_side_effects(compressor))
|
||||
return unwind = true, node;
|
||||
if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
|
||||
side_effects_encountered = true;
|
||||
return unwind = true, node;
|
||||
}
|
||||
function get_lhs(expr) {
|
||||
if (expr instanceof AST_VarDef) {
|
||||
var def = expr.name.definition();
|
||||
if (def.orig.length > 1
|
||||
|| def.references.length == 1 && (!def.global || compressor.toplevel(def))) {
|
||||
return make_node(AST_SymbolRef, expr.name, expr.name);
|
||||
}
|
||||
} else {
|
||||
return expr[expr instanceof AST_Assign ? "left" : "expression"];
|
||||
}
|
||||
}
|
||||
|
||||
function get_symbol(node) {
|
||||
while (node instanceof AST_PropAccess) node = node.expression;
|
||||
return node;
|
||||
}
|
||||
|
||||
function get_lvalues(expr) {
|
||||
var lvalues = Object.create(null);
|
||||
if (expr instanceof AST_Unary) return lvalues;
|
||||
var scope;
|
||||
var tw = new TreeWalker(function(node, descend) {
|
||||
if (node instanceof AST_Scope) {
|
||||
var save_scope = scope;
|
||||
descend();
|
||||
scope = save_scope;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_SymbolRef || node instanceof AST_PropAccess) {
|
||||
var sym = get_symbol(node);
|
||||
if (sym instanceof AST_SymbolRef) {
|
||||
lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent());
|
||||
}
|
||||
);
|
||||
stat.transform(tt);
|
||||
}
|
||||
}
|
||||
|
||||
function is_lvalue(node, parent) {
|
||||
return node instanceof AST_SymbolRef && is_lhs(node, parent);
|
||||
}
|
||||
|
||||
function replace_var(var_decl, node, parent, is_constant) {
|
||||
if (is_lvalue(node, parent)) return node;
|
||||
|
||||
// Remove var definition and return its value to the TreeTransformer to replace.
|
||||
var value = maintain_this_binding(parent, node, var_decl.value);
|
||||
var_decl.value = null;
|
||||
|
||||
var_defs.splice(var_defs_index, 1);
|
||||
def_stat_index--;
|
||||
if (var_defs.length === 0) {
|
||||
statements.splice(prev_stat_index, 1);
|
||||
stat_index--;
|
||||
}
|
||||
// Further optimize statement after substitution.
|
||||
stat.reset_opt_flags(compressor);
|
||||
|
||||
compressor.info("Collapsing {type} {name} [{file}:{line},{col}]", {
|
||||
type: is_constant ? "constant" : "variable",
|
||||
name: var_decl.name.name,
|
||||
file: node.start.file,
|
||||
line: node.start.line,
|
||||
col: node.start.col
|
||||
}
|
||||
});
|
||||
expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw);
|
||||
return lvalues;
|
||||
}
|
||||
|
||||
function lhs_or_def(node) {
|
||||
if (node instanceof AST_VarDef) return node.value && node.name;
|
||||
return is_lhs(node.left, node);
|
||||
}
|
||||
|
||||
function remove_candidate(expr) {
|
||||
var found = false;
|
||||
return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
|
||||
if (found) return node;
|
||||
if (node === expr) {
|
||||
found = true;
|
||||
if (node instanceof AST_VarDef) {
|
||||
remove(node.name.definition().orig, node.name);
|
||||
}
|
||||
return in_list ? MAP.skip : null;
|
||||
}
|
||||
}, function(node) {
|
||||
if (node instanceof AST_Sequence) switch (node.expressions.length) {
|
||||
case 0: return null;
|
||||
case 1: return node.expressions[0];
|
||||
}
|
||||
if (node instanceof AST_Definitions && node.definitions.length == 0
|
||||
|| node instanceof AST_SimpleStatement && !node.body) {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function value_has_side_effects(expr) {
|
||||
if (expr instanceof AST_Unary) return false;
|
||||
return expr[expr instanceof AST_Assign ? "right" : "value"].has_side_effects(compressor);
|
||||
}
|
||||
|
||||
function references_in_scope(def) {
|
||||
if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) return true;
|
||||
if (def.scope !== scope) return false;
|
||||
return def.references.every(function(ref) {
|
||||
return ref.scope === scope;
|
||||
});
|
||||
CHANGED = true;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2022,7 +2062,8 @@ merge(Compressor.prototype, {
|
||||
var var_defs = var_defs_by_id.get(sym.id);
|
||||
if (var_defs.length > 1 && !def.value) {
|
||||
compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
|
||||
var_defs.splice(var_defs.indexOf(def), 1);
|
||||
remove(var_defs, def);
|
||||
remove(sym.orig, def.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2055,6 +2096,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);
|
||||
}
|
||||
});
|
||||
if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) {
|
||||
@@ -2062,7 +2104,8 @@ merge(Compressor.prototype, {
|
||||
if (var_defs.length > 1) {
|
||||
var def = tail.pop();
|
||||
compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
|
||||
var_defs.splice(var_defs.indexOf(def), 1);
|
||||
remove(var_defs, def);
|
||||
remove(def.name.definition().orig, def.name);
|
||||
side_effects.unshift(make_node(AST_Assign, def, {
|
||||
operator: "=",
|
||||
left: make_node(AST_SymbolRef, def.name, def.name),
|
||||
@@ -3130,9 +3173,7 @@ merge(Compressor.prototype, {
|
||||
&& (left.operator == "++" || left.operator == "--")) {
|
||||
left = left.expression;
|
||||
} else left = null;
|
||||
if (!left ||
|
||||
left instanceof AST_SymbolRef
|
||||
&& left.definition().orig[0] instanceof AST_SymbolLambda) {
|
||||
if (!left || is_lhs_read_only(left)) {
|
||||
expressions[++i] = cdr;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -68,11 +68,10 @@ collapse_vars_side_effects_1: {
|
||||
log(x, s.charAt(i++), y, 7);
|
||||
}
|
||||
function f4() {
|
||||
var log = console.log.bind(console),
|
||||
i = 10,
|
||||
var i = 10,
|
||||
x = i += 2,
|
||||
y = i += 3;
|
||||
log(x, i += 4, y, i);
|
||||
console.log.bind(console)(x, i += 4, y, i);
|
||||
}
|
||||
f1(), f2(), f3(), f4();
|
||||
}
|
||||
@@ -671,8 +670,8 @@ collapse_vars_lvalues: {
|
||||
function f4(x) { var a = (x -= 3); return x + a; }
|
||||
function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; }
|
||||
function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); }
|
||||
function f7(x) { var w = e1(), c = e2() - x; return (w = x) - c; }
|
||||
function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); }
|
||||
function f7(x) { var w = e1(); return (w = x) - (e2() - x); }
|
||||
function f8(x) { var w = e1(); return (w = x) - (e2() - x); }
|
||||
function f9(x) { var w = e1(); return e2() - x - (w = x); }
|
||||
}
|
||||
}
|
||||
@@ -703,8 +702,8 @@ collapse_vars_lvalues_drop_assign: {
|
||||
function f4(x) { var a = (x -= 3); return x + a; }
|
||||
function f5(x) { e1(); var v = e2(), c = v = --x; return x - c; }
|
||||
function f6(x) { e1(), e2(); return --x - x; }
|
||||
function f7(x) { e1(); var c = e2() - x; return x - c; }
|
||||
function f8(x) { e1(); var v = e2(); return x - (v - x); }
|
||||
function f7(x) { e1(); return x - (e2() - x); }
|
||||
function f8(x) { e1(); return x - (e2() - x); }
|
||||
function f9(x) { e1(); return e2() - x - x; }
|
||||
}
|
||||
}
|
||||
@@ -1047,10 +1046,9 @@ collapse_vars_object: {
|
||||
}
|
||||
expect: {
|
||||
function f0(x, y) {
|
||||
var z = x + y;
|
||||
return {
|
||||
get b() { return 7; },
|
||||
r: z
|
||||
r: x + y
|
||||
};
|
||||
}
|
||||
function f1(x, y) {
|
||||
@@ -1677,3 +1675,514 @@ var_defs: {
|
||||
}
|
||||
expect_stdout: "97"
|
||||
}
|
||||
|
||||
assignment: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
var a;
|
||||
a = x;
|
||||
return a;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f() {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for_init: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f(x, y) {
|
||||
var a = x;
|
||||
var b = y;
|
||||
for (a; b;);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f(x, y) {
|
||||
var b = y;
|
||||
for (x; b;);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_case: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f(x, y, z) {
|
||||
var a = x();
|
||||
var b = y();
|
||||
var c = z;
|
||||
switch (a) {
|
||||
default: d();
|
||||
case b: e();
|
||||
case c: f();
|
||||
}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f(x, y, z) {
|
||||
var c = z;
|
||||
switch (x()) {
|
||||
default: d();
|
||||
case y(): e();
|
||||
case c: f();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issue_27: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
(function(jQuery) {
|
||||
var $;
|
||||
$ = jQuery;
|
||||
$("body").addClass("foo");
|
||||
})(jQuery);
|
||||
}
|
||||
expect: {
|
||||
(function(jQuery) {
|
||||
jQuery("body").addClass("foo");
|
||||
})(jQuery);
|
||||
}
|
||||
}
|
||||
|
||||
modified: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f1(b) {
|
||||
var a = b;
|
||||
return b + a;
|
||||
}
|
||||
function f2(b) {
|
||||
var a = b;
|
||||
return b++ + a;
|
||||
}
|
||||
function f3(b) {
|
||||
var a = b++;
|
||||
return b + a;
|
||||
}
|
||||
function f4(b) {
|
||||
var a = b++;
|
||||
return b++ + a;
|
||||
}
|
||||
function f5(b) {
|
||||
var a = function() {
|
||||
return b;
|
||||
}();
|
||||
return b++ + a;
|
||||
}
|
||||
console.log(f1(1), f2(1), f3(1), f4(1), f5(1));
|
||||
}
|
||||
expect: {
|
||||
function f1(b) {
|
||||
return b + b;
|
||||
}
|
||||
function f2(b) {
|
||||
var a = b;
|
||||
return b++ + a;
|
||||
}
|
||||
function f3(b) {
|
||||
var a = b++;
|
||||
return b + a;
|
||||
}
|
||||
function f4(b) {
|
||||
var a = b++;
|
||||
return b++ + a;
|
||||
}
|
||||
function f5(b) {
|
||||
var a = function() {
|
||||
return b;
|
||||
}();
|
||||
return b++ + a;
|
||||
}
|
||||
console.log(f1(1), f2(1), f3(1), f4(1), f5(1));
|
||||
}
|
||||
expect_stdout: "2 2 3 3 2"
|
||||
}
|
||||
|
||||
issue_1858: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
pure_getters: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(x) {
|
||||
var a = {}, b = a.b = x;
|
||||
return a.b + b;
|
||||
}(1));
|
||||
}
|
||||
expect: {
|
||||
console.log(function(x) {
|
||||
var a = {}, b = a.b = x;
|
||||
return a.b + b;
|
||||
}(1));
|
||||
}
|
||||
expect_stdout: "2"
|
||||
}
|
||||
|
||||
anonymous_function: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function f(a) {
|
||||
f ^= 0;
|
||||
return f * a;
|
||||
}(1));
|
||||
}
|
||||
expect: {
|
||||
console.log(function f(a) {
|
||||
f ^= 0;
|
||||
return f * a;
|
||||
}(1));
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
side_effects_property: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = [];
|
||||
var b = 0;
|
||||
a[b++] = function() { return 42;};
|
||||
var c = a[b++]();
|
||||
console.log(c);
|
||||
}
|
||||
expect: {
|
||||
var a = [];
|
||||
var b = 0;
|
||||
a[b++] = function() { return 42;};
|
||||
var c = a[b++]();
|
||||
console.log(c);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
undeclared: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f(x, y) {
|
||||
var a;
|
||||
a = x;
|
||||
b = y;
|
||||
return b + a;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
function f(x, y) {
|
||||
var a;
|
||||
a = x;
|
||||
b = y;
|
||||
return b + a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref_scope: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function() {
|
||||
var a = 1, b = 2, c = 3;
|
||||
var a = c++, b = b /= a;
|
||||
return function() {
|
||||
return a;
|
||||
}() + b;
|
||||
}());
|
||||
}
|
||||
expect: {
|
||||
console.log(function() {
|
||||
var a = 1, b = 2, c = 3;
|
||||
b = b /= a = c++;
|
||||
return function() {
|
||||
return a;
|
||||
}() + b;
|
||||
}());
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
chained_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = 2;
|
||||
var a = 3 / a;
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = 3 / (a = 2);
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
chained_2: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
var a = 2;
|
||||
a = 3 / a;
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
a = 3 / (a = 2);
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
chained_3: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(a, b) {
|
||||
var c = a, c = b;
|
||||
b++;
|
||||
return c;
|
||||
}(1, 2));
|
||||
}
|
||||
expect: {
|
||||
console.log(function(a, b) {
|
||||
var c = a, c = b;
|
||||
b++;
|
||||
return c;
|
||||
}(1, 2));
|
||||
}
|
||||
expect_stdout: "2"
|
||||
}
|
||||
|
||||
boolean_binary_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = 1;
|
||||
a++;
|
||||
(function() {} || a || 3).toString();
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = 1;
|
||||
a++;
|
||||
(function() {} || a || 3).toString();
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
boolean_binary_2: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var c = 0;
|
||||
c += 1;
|
||||
(function() {
|
||||
c = 1 + c;
|
||||
} || 9).toString();
|
||||
console.log(c);
|
||||
}
|
||||
expect: {
|
||||
var c = 0;
|
||||
c += 1;
|
||||
(function() {
|
||||
c = 1 + c;
|
||||
} || 9).toString();
|
||||
console.log(c);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
inner_lvalues: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a, b = 10;
|
||||
var a = (--b || a || 3).toString(), c = --b + -a;
|
||||
console.log(null, a, b);
|
||||
}
|
||||
expect: {
|
||||
var a, b = 10;
|
||||
var a = (--b || a || 3).toString(), c = --b + -a;
|
||||
console.log(null, a, b);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
double_def: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = x, a = a && y;
|
||||
a();
|
||||
}
|
||||
expect: {
|
||||
var a = x;
|
||||
(a = a && y)();
|
||||
}
|
||||
}
|
||||
|
||||
toplevel_single_reference: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
for (var b in x) {
|
||||
var a = b;
|
||||
b(a);
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
for (var b in x)
|
||||
b(a = b);
|
||||
}
|
||||
}
|
||||
|
||||
unused_orig: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
passes: 2,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = 1;
|
||||
console.log(function(b) {
|
||||
var a;
|
||||
var c = b;
|
||||
for (var d in c) {
|
||||
var a = c[0];
|
||||
return --b + a;
|
||||
}
|
||||
try {
|
||||
} catch (e) {
|
||||
--b + a;
|
||||
}
|
||||
a && a.NaN;
|
||||
}([2]), a);
|
||||
}
|
||||
expect: {
|
||||
var a = 1;
|
||||
console.log(function(b) {
|
||||
var c = b;
|
||||
for (var d in c) {
|
||||
var a = c[0];
|
||||
return --b + a;
|
||||
}
|
||||
a && a.NaN;
|
||||
}([2]), a);
|
||||
}
|
||||
expect_stdout: "3 1"
|
||||
}
|
||||
|
||||
issue_315: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
keep_fargs: false,
|
||||
reduce_vars: true,
|
||||
sequences: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(s) {
|
||||
var w, _i, _len, _ref, _results;
|
||||
_ref = s.trim().split(" ");
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
w = _ref[_i];
|
||||
_results.push(w.toLowerCase());
|
||||
}
|
||||
return _results;
|
||||
}("test"));
|
||||
}
|
||||
expect: {
|
||||
console.log(function() {
|
||||
var w, _i, _len, _ref, _results;
|
||||
for (_results = [], _i = 0, _len = (_ref = "test".trim().split(" ")).length; _i < _len ; _i++)
|
||||
w = _ref[_i], _results.push(w.toLowerCase());
|
||||
return _results;
|
||||
}());
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
lvalues_def: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = 0, b = 1;
|
||||
var a = b++, b = +function() {}();
|
||||
a && a[a++];
|
||||
console.log(a, b);
|
||||
}
|
||||
expect: {
|
||||
var a = 0, b = 1;
|
||||
var a = b++, b = +void 0;
|
||||
a && a[a++];
|
||||
console.log(a, b);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
compound_assignment: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
a = 1;
|
||||
a += a + 2;
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
a = 1;
|
||||
a += a + 2;
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: "4"
|
||||
}
|
||||
|
||||
@@ -1114,3 +1114,36 @@ issue_1838: {
|
||||
"}",
|
||||
]
|
||||
}
|
||||
|
||||
var_catch_toplevel: {
|
||||
options = {
|
||||
conditionals: true,
|
||||
negate_iife: true,
|
||||
reduce_vars: true,
|
||||
side_effects: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
a--;
|
||||
try {
|
||||
a++;
|
||||
} catch(a) {
|
||||
if (a) var a;
|
||||
var a = 10;
|
||||
}
|
||||
}
|
||||
f();
|
||||
}
|
||||
expect: {
|
||||
!function() {
|
||||
a--;
|
||||
try {
|
||||
a++;
|
||||
} catch(a) {
|
||||
var a;
|
||||
}
|
||||
}();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@ chained_evaluation_1: {
|
||||
expect: {
|
||||
(function() {
|
||||
(function() {
|
||||
var c;
|
||||
c = f(1);
|
||||
c.bar = 1;
|
||||
f(1).bar = 1;
|
||||
})();
|
||||
})();
|
||||
}
|
||||
@@ -46,9 +44,8 @@ chained_evaluation_2: {
|
||||
expect: {
|
||||
(function() {
|
||||
(function() {
|
||||
var c, b = "long piece of string";
|
||||
c = f(b);
|
||||
c.bar = b;
|
||||
var b = "long piece of string";
|
||||
f(b).bar = b;
|
||||
})();
|
||||
})();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user