From dee5a27516cb574dda5fc3d23a64344f0ea654b6 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 6 May 2017 16:15:43 +0800 Subject: [PATCH] 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 --- lib/compress.js | 341 +++++++++++---------- test/compress/collapse_vars.js | 527 ++++++++++++++++++++++++++++++++- test/compress/drop-unused.js | 33 +++ test/compress/issue-1609.js | 9 +- 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; })(); })(); } -- 2.34.1