aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/compress.js341
-rw-r--r--test/compress/collapse_vars.js527
-rw-r--r--test/compress/drop-unused.js33
-rw-r--r--test/compress/issue-1609.js9
4 files changed, 745 insertions, 165 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 931b8d6f..bf6a40d4 100644
--- a/lib/compress.js
+++ b/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];
+ 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);
+ });
+ } else if (expr instanceof AST_SimpleStatement) {
+ extract_candidates(expr.body);
+ } else if (expr instanceof AST_For && expr.init) {
+ extract_candidates(expr.init);
+ }
+ }
- // Don't replace ref if eval() or with statement in scope.
- if (ref.scope.uses_eval || ref.scope.uses_with) break;
+ 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"];
+ }
+ }
- // Restrict var replacement to constants if side effects encountered.
- if (side_effects_encountered |= lvalues_encountered) continue;
+ function get_symbol(node) {
+ while (node instanceof AST_PropAccess) node = node.expression;
+ return node;
+ }
- 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;
+ 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;
}
-
- // 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;
- }
- });
- var_decl.value.walk(tw);
-
- // 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;
- }
+ 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);
- }
+ }
+ });
+ expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw);
+ return lvalues;
}
- function is_lvalue(node, parent) {
- return node instanceof AST_SymbolRef && is_lhs(node, parent);
+ function lhs_or_def(node) {
+ if (node instanceof AST_VarDef) return node.value && node.name;
+ return is_lhs(node.left, node);
}
- function replace_var(var_decl, node, parent, is_constant) {
- if (is_lvalue(node, parent)) return 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;
+ }
+ }));
+ }
- // 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;
+ 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);
+ }
- 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
+ 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;
}
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index a4c1f9e6..4215cebe 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -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"
+}
diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js
index c702cfaf..ddf90bfa 100644
--- a/test/compress/drop-unused.js
+++ b/test/compress/drop-unused.js
@@ -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;
+ }
+ }();
+ }
+}
diff --git a/test/compress/issue-1609.js b/test/compress/issue-1609.js
index da4b54a2..dffa54a5 100644
--- a/test/compress/issue-1609.js
+++ b/test/compress/issue-1609.js
@@ -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;
})();
})();
}