From ecc9f6b77093758d78a693a5ac4b6bcaf75e9a3f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 28 Nov 2017 13:08:40 +0800 Subject: [PATCH 01/17] drop assignment in `AST_VarDef.value` (#2522) fixes #2516 --- lib/compress.js | 59 ++++++++++++------------- test/compress/collapse_vars.js | 16 ++++--- test/compress/drop-unused.js | 80 ++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 37 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 22415f4d..b51fdfc7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1088,6 +1088,7 @@ merge(Compressor.prototype, { function get_lhs(expr) { if (expr instanceof AST_VarDef) { var def = expr.name.definition(); + if (!member(expr.name, def.orig)) return; var declared = def.orig.length - def.eliminated; var referenced = def.references.length - def.replaced; if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg) @@ -2434,47 +2435,18 @@ merge(Compressor.prototype, { }); return true; } - var sym; - if (scope === self - && (sym = assign_as_unused(node)) instanceof AST_SymbolRef - && self.variables.get(sym.name) === sym.definition()) { - if (node instanceof AST_Assign) node.right.walk(tw); - return true; - } - if (node instanceof AST_SymbolRef) { - var node_def = node.definition(); - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); - } - return true; - } - if (node instanceof AST_Scope) { - var save_scope = scope; - scope = node; - descend(); - scope = save_scope; - return true; - } + return scan_ref_scoped(node, descend); }); self.walk(tw); // pass 2: for every used symbol we need to walk its // initialization code to figure out if it uses other // symbols (that may not be in_use). + tw = new TreeWalker(scan_ref_scoped); for (var i = 0; i < in_use.length; ++i) { in_use[i].orig.forEach(function(decl){ // undeclared globals will be instanceof AST_SymbolRef var init = initializations.get(decl.name); if (init) init.forEach(function(init){ - var tw = new TreeWalker(function(node){ - if (node instanceof AST_SymbolRef) { - var node_def = node.definition(); - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); - } - } - }); init.walk(tw); }); }); @@ -2663,6 +2635,31 @@ merge(Compressor.prototype, { } ); self.transform(tt); + + function scan_ref_scoped(node, descend) { + var sym; + if (scope === self + && (sym = assign_as_unused(node)) instanceof AST_SymbolRef + && self.variables.get(sym.name) === sym.definition()) { + if (node instanceof AST_Assign) node.right.walk(tw); + return true; + } + if (node instanceof AST_SymbolRef) { + var node_def = node.definition(); + if (!(node_def.id in in_use_ids)) { + in_use_ids[node_def.id] = true; + in_use.push(node_def); + } + return true; + } + if (node instanceof AST_Scope) { + var save_scope = scope; + scope = node; + descend(); + scope = save_scope; + return true; + } + } }); AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){ diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 844d5b0f..f968ff20 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -705,7 +705,7 @@ collapse_vars_lvalues_drop_assign: { function f2(x) { var z = x, a = ++z; return z += a; } function f3(x) { var a = (x -= 3); return x + a; } function f4(x) { var a = (x -= 3); return x + a; } - function f5(x) { e1(); var v = e2(), c = v = --x; return x - c; } + function f5(x) { e1(), e2(); var c = --x; return x - c; } function f6(x) { e1(), e2(); return --x - x; } function f7(x) { e1(); return x - (e2() - x); } function f8(x) { e1(); return x - (e2() - x); } @@ -2386,6 +2386,7 @@ duplicate_argname: { issue_2298: { options = { collapse_vars: true, + passes: 2, reduce_funcs: true, reduce_vars: true, unused: true, @@ -3087,13 +3088,14 @@ issue_2437: { } expect: { !function() { - if (xhrDesc) - return result = !!(req = new XMLHttpRequest()).onreadystatechange, - Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}), + if (xhrDesc) { + var result = !!(req = new XMLHttpRequest()).onreadystatechange; + return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}), result; + } var req = new XMLHttpRequest(), detectFunc = function() {}; - req.onreadystatechange = detectFunc; - var result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; + req.onreadystatechange = detectFunc, + result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc, req.onreadystatechange = null; }(); } @@ -3522,6 +3524,7 @@ issue_2436_12: { issue_2436_13: { options = { collapse_vars: true, + passes: 2, reduce_vars: true, unused: true, } @@ -3622,6 +3625,7 @@ issue_2497: { issue_2506: { options = { collapse_vars: true, + passes: 2, reduce_vars: true, unused: true, } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 33241d67..eb6f9df4 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1299,3 +1299,83 @@ issue_2288: { } } } + +issue_2516_1: { + options = { + collapse_vars: true, + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + function foo() { + function qux(x) { + bar.call(null, x); + } + function bar(x) { + var FOUR = 4; + var trouble = x || never_called(); + var value = (FOUR - 1) * trouble; + console.log(value == 6 ? "PASS" : value); + } + Baz = qux; + } + var Baz; + foo(); + Baz(2); + } + expect: { + function foo() { + Baz = function(x) { + (function(x) { + var trouble = x || never_called(); + var value = (4 - 1) * trouble; + console.log(6 == value ? "PASS" : value); + }).call(null, x); + }; + } + var Baz; + foo(); + Baz(2); + } +} + +issue_2516_2: { + options = { + collapse_vars: true, + reduce_funcs: true, + reduce_vars: true, + passes: 2, + unused: true, + } + input: { + function foo() { + function qux(x) { + bar.call(null, x); + } + function bar(x) { + var FOUR = 4; + var trouble = x || never_called(); + var value = (FOUR - 1) * trouble; + console.log(value == 6 ? "PASS" : value); + } + Baz = qux; + } + var Baz; + foo(); + Baz(2); + } + expect: { + function foo() { + Baz = function(x) { + (function(x) { + var value = (4 - 1) * (x || never_called()); + console.log(6 == value ? "PASS" : value); + }).call(null, x); + }; + } + var Baz; + foo(); + Baz(2); + } +} From 32def5ebf5c9157937dcf802ce71fb9d1d3bc189 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 28 Nov 2017 14:02:39 +0800 Subject: [PATCH 02/17] improve synergy between `collapse_vars` & `unused` (#2521) --- lib/compress.js | 44 ++++++++++--------------------- test/compress/collapse_vars.js | 47 ++++++++++++++++++++++------------ test/compress/drop-unused.js | 2 +- test/compress/reduce_vars.js | 4 +-- 4 files changed, 48 insertions(+), 49 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index b51fdfc7..f49f0b24 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -800,14 +800,6 @@ merge(Compressor.prototype, { || compressor.option("unsafe") && global_names(this.name); }); - function drop_decl(def) { - def.eliminated++; - if (def.orig.length == def.eliminated) { - def.scope.functions.del(def.name); - def.scope.variables.del(def.name); - } - } - function is_identifier_atom(node) { return node instanceof AST_Infinity || node instanceof AST_NaN @@ -1138,7 +1130,6 @@ merge(Compressor.prototype, { if (node === expr || node.body === expr) { found = true; if (node instanceof AST_VarDef) { - drop_decl(node.name.definition()); node.value = null; return node; } @@ -2484,7 +2475,7 @@ merge(Compressor.prototype, { var def = node.name.definition(); if (!(def.id in in_use_ids)) { compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name)); - drop_decl(def); + def.eliminated++; return make_node(AST_EmptyStatement, node); } return node; @@ -2503,10 +2494,17 @@ merge(Compressor.prototype, { if (!drop_vars || sym.id in in_use_ids) { if (def.name instanceof AST_SymbolVar) { var var_defs = var_defs_by_id.get(sym.id); - if (var_defs.length > 1 && !def.value) { + if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) { compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); + if (def.value) { + merge_sequence(side_effects, make_node(AST_Assign, def, { + operator: "=", + left: make_node(AST_SymbolRef, def.name, def.name), + right: def.value + })); + } remove(var_defs, def); - drop_decl(sym); + sym.eliminated++; return; } } @@ -2539,25 +2537,9 @@ merge(Compressor.prototype, { } else { compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name)); } - drop_decl(sym); + sym.eliminated++; } }); - if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) { - var var_defs = var_defs_by_id.get(tail[0].name.definition().id); - 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)); - remove(var_defs, def); - side_effects.unshift(make_node(AST_Assign, def, { - operator: "=", - left: make_node(AST_SymbolRef, def.name, def.name), - right: def.value - })); - def = def.name.definition(); - drop_decl(def); - def.replaced--; - } - } if (head.length > 0 || tail.length > 0) { node.definitions = head.concat(tail); body.push(node); @@ -3385,7 +3367,9 @@ merge(Compressor.prototype, { })); if (reduce_vars) name.definition().fixed = false; } - drop_decl(def.name.definition()); + def = def.name.definition(); + def.eliminated++; + def.replaced--; return a; }, []); if (assignments.length == 0) return null; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index f968ff20..68313354 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2013,7 +2013,8 @@ chained_3: { } expect: { console.log(function(a, b) { - var c = 1, c = b; + var c = 1; + c = b; b++; return c; }(0, 2)); @@ -2081,7 +2082,7 @@ inner_lvalues: { expect_stdout: true } -double_def: { +double_def_1: { options = { collapse_vars: true, unused: true, @@ -2091,8 +2092,23 @@ double_def: { a(); } expect: { - var a = x; - (a = a && y)(); + var a; + (a = (a = x) && y)(); + } +} + +double_def_2: { + options = { + collapse_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = x, a = a && y; + a(); + } + expect: { + (x && y)(); } } @@ -2201,7 +2217,7 @@ lvalues_def: { } expect: { var a = 0, b = 1; - var a = b++, b = +void 0; + a = b++, b = +void 0; a && a[a++]; console.log(a, b); } @@ -3074,10 +3090,9 @@ issue_2437: { var result = !!req.onreadystatechange; Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', xhrDesc || {}); return result; - } - else { + } else { var req = new XMLHttpRequest(); - var detectFunc = function () { }; + var detectFunc = function () {}; req.onreadystatechange = detectFunc; var result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; req.onreadystatechange = null; @@ -3093,9 +3108,9 @@ issue_2437: { return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}), result; } - var req = new XMLHttpRequest(), detectFunc = function() {}; - req.onreadystatechange = detectFunc, - result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc, + var req, detectFunc = function() {}; + (req = new XMLHttpRequest()).onreadystatechange = detectFunc; + result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; req.onreadystatechange = null; }(); } @@ -3609,15 +3624,15 @@ issue_2497: { expect: { function sample() { if (true) - for (i = 0; i < 1; ++i) - for (k = 0; k < 1; ++k) { + for (var i = 0; i < 1; ++i) + for (var k = 0; k < 1; ++k) { value = 1; value = value ? value + 1 : 0; } else - for (var i = 0; i < 1; ++i) - for (var k = 0; k < 1; ++k) - var value=1; + for (i = 0; i < 1; ++i) + for (k = 0; k < 1; ++k) + var value = 1; } } } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index eb6f9df4..189a0008 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1294,8 +1294,8 @@ issue_2288: { expect: { function foo(o) { o.a; - for (i = 0; i < 0; i++); for (var i = 0; i < 0; i++); + for (i = 0; i < 0; i++); } } } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 02ff5e43..82b5ad6d 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -972,8 +972,8 @@ inner_var_for_2: { } expect: { !function() { - a = 1; - for (var b = 1; --b;) var a = 2; + var a = 1; + for (var b = 1; --b;) a = 2; console.log(a); }(); } From 206a54a7461b76683d690fd7016943fcb461a4fa Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 28 Nov 2017 14:39:00 +0800 Subject: [PATCH 03/17] fix nested `hoist_props` substitution (#2523) fixes #2519 --- lib/compress.js | 3 ++- test/compress/hoist_props.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index f49f0b24..5b26de8b 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2797,7 +2797,7 @@ merge(Compressor.prototype, { if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self; var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false; var defs_by_id = Object.create(null); - return self.transform(new TreeTransformer(function(node) { + return self.transform(new TreeTransformer(function(node, descend) { if (node instanceof AST_VarDef) { var sym = node.name, def, value; if (sym.scope === self @@ -2807,6 +2807,7 @@ merge(Compressor.prototype, { && !top_retain(def) && (value = sym.fixed_value()) === node.value && value instanceof AST_Object) { + descend(node, this); var defs = new Dictionary(); var assignments = []; value.properties.forEach(function(prop) { diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index b2dd0270..a46033d5 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -633,3 +633,34 @@ issue_2508_5: { } expect_stdout: true } + +issue_2519: { + options = { + collapse_vars: true, + evaluate: true, + hoist_props: true, + reduce_vars: true, + unused: true, + } + input: { + function testFunc() { + var dimensions = { + minX: 5, + maxX: 6, + }; + var scale = 1; + var d = { + x: (dimensions.maxX + dimensions.minX) / 2, + }; + return d.x * scale; + } + console.log(testFunc()); + } + expect: { + function testFunc() { + return 1 * ((6 + 5) / 2); + } + console.log(testFunc()); + } + expect_stdout: "5.5" +} From bc5047c1e70594ea2fa8e747945a577298715926 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 29 Nov 2017 13:31:41 +0800 Subject: [PATCH 04/17] fix `inline` on nested substitutions (#2533) fixes #2531 --- lib/compress.js | 4 ++ test/compress/functions.js | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/lib/compress.js b/lib/compress.js index 5b26de8b..dc761948 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2268,6 +2268,10 @@ merge(Compressor.prototype, { self.walk(new TreeWalker(function(node) { if (!result) return true; if (node instanceof AST_SymbolRef) { + if (self.inlined) { + result = false; + return true; + } var def = node.definition(); if (member(def, self.enclosed) && !self.variables.has(def.name)) { diff --git a/test/compress/functions.js b/test/compress/functions.js index 3e5562a2..3ecb4bc3 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -554,3 +554,102 @@ issue_2428: { "PASS", ] } + +issue_2531_1: { + options = { + evaluate: true, + inline: true, + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + function outer() { + function inner(value) { + function closure() { + return value; + } + return function() { + return closure(); + }; + } + return inner("Hello"); + } + console.log("Greeting:", outer()()); + } + expect: { + function outer() { + return function(value) { + return function() { + return value; + }; + }("Hello"); + } + console.log("Greeting:", outer()()); + } + expect_stdout: "Greeting: Hello" +} + +issue_2531_2: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + function outer() { + function inner(value) { + function closure() { + return value; + } + return function() { + return closure(); + }; + } + return inner("Hello"); + } + console.log("Greeting:", outer()()); + } + expect: { + function outer() { + return function() { + return "Hello"; + }; + } + console.log("Greeting:", outer()()); + } + expect_stdout: "Greeting: Hello" +} + +issue_2531_3: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function outer() { + function inner(value) { + function closure() { + return value; + } + return function() { + return closure(); + }; + } + return inner("Hello"); + } + console.log("Greeting:", outer()()); + } + expect: { + console.log("Greeting:", "Hello"); + } + expect_stdout: "Greeting: Hello" +} From 18302bf8e9507bbb661986e37cf66a95e6017271 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 29 Nov 2017 13:32:00 +0800 Subject: [PATCH 05/17] backport test from #2526 (#2534) --- test/compress/reduce_vars.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 82b5ad6d..a8c151f1 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -4525,3 +4525,21 @@ issue_2485: { } expect_stdout: "6" } + +issue_2455: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function foo() { + var that = this; + for (;;) that.bar(); + } + } + expect: { + function foo() { + for (;;) this.bar(); + } + } +} From c58d3936a3c145b883e3e4537d503c690514a456 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 03:18:20 +0800 Subject: [PATCH 06/17] fix corner case in call binding (#2541) --- lib/compress.js | 37 ++++++++++++++++--------------------- test/compress/issue-973.js | 3 ++- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index dc761948..6bdd1599 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -701,7 +701,7 @@ merge(Compressor.prototype, { function make_sequence(orig, expressions) { if (expressions.length == 1) return expressions[0]; return make_node(AST_Sequence, orig, { - expressions: expressions + expressions: expressions.reduce(merge_sequence, []) }); } @@ -758,6 +758,7 @@ merge(Compressor.prototype, { } else { array.push(node); } + return array; } function as_statement_array(thing) { @@ -1397,13 +1398,7 @@ merge(Compressor.prototype, { function cons_seq(right) { n--; var left = prev.body; - if (!(left instanceof AST_Sequence)) { - left = make_node(AST_Sequence, left, { - expressions: [ left ] - }); - } - merge_sequence(left.expressions, right); - return left.transform(compressor); + return make_sequence(left, [ left, right ]).transform(compressor); }; var n = 0, prev; for (var i = 0, len = statements.length; i < len; i++) { @@ -2501,7 +2496,7 @@ merge(Compressor.prototype, { if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) { compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name)); if (def.value) { - merge_sequence(side_effects, make_node(AST_Assign, def, { + side_effects.push(make_node(AST_Assign, def, { operator: "=", left: make_node(AST_SymbolRef, def.name, def.name), right: def.value @@ -2515,7 +2510,7 @@ merge(Compressor.prototype, { if (def.value) { if (side_effects.length > 0) { if (tail.length > 0) { - merge_sequence(side_effects, def.value); + side_effects.push(def.value); def.value = make_sequence(def.value, side_effects); } else { body.push(make_node(AST_SimpleStatement, node, { @@ -2530,14 +2525,14 @@ merge(Compressor.prototype, { } } else if (sym.orig[0] instanceof AST_SymbolCatch) { var value = def.value && def.value.drop_side_effect_free(compressor); - if (value) merge_sequence(side_effects, value); + if (value) side_effects.push(value); def.value = null; head.push(def); } else { var value = def.value && def.value.drop_side_effect_free(compressor); if (value) { compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name)); - merge_sequence(side_effects, value); + side_effects.push(value); } else { compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name)); } @@ -2868,7 +2863,7 @@ merge(Compressor.prototype, { var node = nodes[i].drop_side_effect_free(compressor, first_in_statement); changed |= node !== nodes[i]; if (node) { - merge_sequence(ret, node); + ret.push(node); first_in_statement = false; } } @@ -2983,7 +2978,7 @@ merge(Compressor.prototype, { var expr = last.drop_side_effect_free(compressor); if (expr === last) return this; var expressions = this.expressions.slice(0, -1); - if (expr) merge_sequence(expressions, expr); + if (expr) expressions.push(expr); return make_sequence(this, expressions); }); })(function(node, func){ @@ -3692,7 +3687,7 @@ merge(Compressor.prototype, { trim_right_for_undefined(); if (end > 0 && compressor.option("cascade")) trim_left_for_assignment(); if (end == 0) { - self = maintain_this_binding(compressor.parent(), self, expressions[0]); + self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]); if (!(self instanceof AST_Sequence)) self = self.optimize(compressor); return self; } @@ -4044,10 +4039,10 @@ merge(Compressor.prototype, { var ll = self.left.evaluate(compressor); if (!ll) { compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.left).optimize(compressor); + return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); } else if (ll !== self.left) { compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.right).optimize(compressor); + return maintain_this_binding(compressor.parent(), compressor.self(), self.right).optimize(compressor); } if (compressor.option("booleans") && compressor.in_boolean_context()) { var rr = self.right.evaluate(compressor); @@ -4067,10 +4062,10 @@ merge(Compressor.prototype, { var ll = self.left.evaluate(compressor); if (!ll) { compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.right).optimize(compressor); + return maintain_this_binding(compressor.parent(), compressor.self(), self.right).optimize(compressor); } else if (ll !== self.left) { compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.left).optimize(compressor); + return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); } if (compressor.option("booleans") && compressor.in_boolean_context()) { var rr = self.right.evaluate(compressor); @@ -4509,10 +4504,10 @@ merge(Compressor.prototype, { if (cond !== self.condition) { if (cond) { compressor.warn("Condition always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.consequent); + return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent); } else { compressor.warn("Condition always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.alternative); + return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative); } } var negated = cond.negate(compressor, first_in_statement(compressor)); diff --git a/test/compress/issue-973.js b/test/compress/issue-973.js index fee05dfc..a9fcd84f 100644 --- a/test/compress/issue-973.js +++ b/test/compress/issue-973.js @@ -1,7 +1,8 @@ this_binding_conditionals: { options = { conditionals: true, - evaluate : true + evaluate: true, + side_effects: true, }; input: { (1 && a)(); From 172079a47f2f7bf09d2a5b4e4cf05691a1206358 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 03:40:46 +0800 Subject: [PATCH 07/17] improve code reuse (#2542) --- lib/ast.js | 18 ------------------ lib/compress.js | 40 ++++++++++++++++++++++++++++++---------- lib/propmangle.js | 2 +- lib/scope.js | 7 ++++++- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index a2aa2b40..997486c2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -898,24 +898,6 @@ TreeWalker.prototype = { } } }, - in_boolean_context: function() { - var stack = this.stack; - var i = stack.length, self = stack[--i]; - while (i > 0) { - var p = stack[--i]; - if ((p instanceof AST_If && p.condition === self) || - (p instanceof AST_Conditional && p.condition === self) || - (p instanceof AST_DWLoop && p.condition === self) || - (p instanceof AST_For && p.condition === self) || - (p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self)) - { - return true; - } - if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||"))) - return false; - self = p; - } - }, loopcontrol_target: function(node) { var stack = this.stack; if (node.label) for (var i = stack.length; --i >= 0;) { diff --git a/lib/compress.js b/lib/compress.js index 6bdd1599..206c77f3 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -145,6 +145,26 @@ merge(Compressor.prototype, { return true; return false; }, + in_boolean_context: function() { + if (!this.option("booleans")) return false; + var self = this.self(); + for (var i = 0, p; p = this.parent(i); i++) { + if (p instanceof AST_SimpleStatement + || p instanceof AST_Conditional && p.condition === self + || p instanceof AST_DWLoop && p.condition === self + || p instanceof AST_For && p.condition === self + || p instanceof AST_If && p.condition === self + || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) { + return true; + } + if (p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||") + || p.tail_node() === self) { + self = p; + } else { + return false; + } + } + }, compress: function(node) { if (this.option("expression")) { node.process_expression(true); @@ -1547,7 +1567,7 @@ merge(Compressor.prototype, { || this.alternative._dot_throw(compressor); }) def(AST_Sequence, function(compressor) { - return this.expressions[this.expressions.length - 1]._dot_throw(compressor); + return this.tail_node()._dot_throw(compressor); }); def(AST_SymbolRef, function(compressor) { if (this.is_undefined) return true; @@ -1584,7 +1604,7 @@ merge(Compressor.prototype, { return this.operator == "=" && this.right.is_boolean(); }); def(AST_Sequence, function(){ - return this.expressions[this.expressions.length - 1].is_boolean(); + return this.tail_node().is_boolean(); }); def(AST_True, return_true); def(AST_False, return_true); @@ -1611,7 +1631,7 @@ merge(Compressor.prototype, { || this.operator == "=" && this.right.is_number(compressor); }); def(AST_Sequence, function(compressor){ - return this.expressions[this.expressions.length - 1].is_number(compressor); + return this.tail_node().is_number(compressor); }); def(AST_Conditional, function(compressor){ return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); @@ -1635,7 +1655,7 @@ merge(Compressor.prototype, { return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor); }); def(AST_Sequence, function(compressor){ - return this.expressions[this.expressions.length - 1].is_string(compressor); + return this.tail_node().is_string(compressor); }); def(AST_Conditional, function(compressor){ return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); @@ -2974,7 +2994,7 @@ merge(Compressor.prototype, { return make_sequence(this, [ expression, property ]); }); def(AST_Sequence, function(compressor){ - var last = this.expressions[this.expressions.length - 1]; + var last = this.tail_node(); var expr = last.drop_side_effect_free(compressor); if (expr === last) return this; var expressions = this.expressions.slice(0, -1); @@ -3826,7 +3846,7 @@ merge(Compressor.prototype, { return make_node(AST_Undefined, self).optimize(compressor); } } - if (compressor.option("booleans") && compressor.in_boolean_context()) { + if (compressor.in_boolean_context()) { switch (self.operator) { case "!": if (e instanceof AST_UnaryPrefix && e.operator == "!") { @@ -3979,7 +3999,7 @@ merge(Compressor.prototype, { } break; } - if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) { + if (self.operator == "+" && compressor.in_boolean_context()) { var ll = self.left.evaluate(compressor); var rr = self.right.evaluate(compressor); if (ll && typeof ll == "string") { @@ -4044,7 +4064,7 @@ merge(Compressor.prototype, { compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.right).optimize(compressor); } - if (compressor.option("booleans") && compressor.in_boolean_context()) { + if (compressor.in_boolean_context()) { var rr = self.right.evaluate(compressor); if (!rr) { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); @@ -4067,7 +4087,7 @@ merge(Compressor.prototype, { compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); } - if (compressor.option("booleans") && compressor.in_boolean_context()) { + if (compressor.in_boolean_context()) { var rr = self.right.evaluate(compressor); if (!rr) { compressor.warn("Dropping side-effect-free || in boolean context [{file}:{line},{col}]", self.start); @@ -4841,7 +4861,7 @@ merge(Compressor.prototype, { }); function literals_in_boolean_context(self, compressor) { - if (compressor.option("booleans") && compressor.in_boolean_context()) { + if (compressor.in_boolean_context()) { return best_of(compressor, self, make_sequence(self, [ self, make_node(AST_True, self) diff --git a/lib/propmangle.js b/lib/propmangle.js index 36a67e80..c2f27c42 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -84,7 +84,7 @@ function reserve_quoted_keys(ast, reserved) { function addStrings(node, add) { node.walk(new TreeWalker(function(node) { if (node instanceof AST_Sequence) { - addStrings(node.expressions[node.expressions.length - 1], add); + addStrings(node.tail_node(), add); } else if (node instanceof AST_String) { add(node.value); } else if (node instanceof AST_Conditional) { diff --git a/lib/scope.js b/lib/scope.js index f3010e7c..bbfa037c 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -510,6 +510,11 @@ AST_Toplevel.DEFMETHOD("expand_names", function(options) { } }); +AST_Node.DEFMETHOD("tail_node", return_this); +AST_Sequence.DEFMETHOD("tail_node", function() { + return this.expressions[this.expressions.length - 1]; +}); + AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ options = this._default_mangler_options(options); try { @@ -538,7 +543,7 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ skip_string(node.consequent); skip_string(node.alternative); } else if (node instanceof AST_Sequence) { - skip_string(node.expressions[node.expressions.length - 1]); + skip_string(node.tail_node()); } } }); From b762f2d6f4e81dcbd49ffb4db4b1933953942999 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 05:52:33 +0800 Subject: [PATCH 08/17] improve compression of loop conditions (#2543) --- lib/compress.js | 97 +++++++++++++++++++++--------------- test/compress/dead-code.js | 4 +- test/compress/issue-1656.js | 2 +- test/compress/issue-1833.js | 2 +- test/compress/loops.js | 4 +- test/compress/reduce_vars.js | 1 + 6 files changed, 64 insertions(+), 46 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 206c77f3..e6215f66 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3020,34 +3020,40 @@ merge(Compressor.prototype, { return self; }); - OPT(AST_DWLoop, function(self, compressor){ + OPT(AST_While, function(self, compressor){ + return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self; + }); + + OPT(AST_Do, function(self, compressor){ if (!compressor.option("loops")) return self; - var cond = self.condition.evaluate(compressor); - if (cond !== self.condition) { - if (cond) { - return make_node(AST_For, self, { - body: self.body - }); - } - if (compressor.option("dead_code") && self instanceof AST_While) { - var a = []; - extract_declarations_from_unreachable_code(compressor, self.body, a); - return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor); - } - if (self instanceof AST_Do) { - var has_loop_control = false; - var tw = new TreeWalker(function(node) { - if (node instanceof AST_Scope || has_loop_control) return true; - if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self) - return has_loop_control = true; - }); - var parent = compressor.parent(); - (parent instanceof AST_LabeledStatement ? parent : self).walk(tw); - if (!has_loop_control) return self.body; - } - } - if (self instanceof AST_While) { - return make_node(AST_For, self, self).optimize(compressor); + var cond = self.condition.tail_node().evaluate(compressor); + if (!(cond instanceof AST_Node)) { + if (cond) return make_node(AST_For, self, { + body: make_node(AST_BlockStatement, self.body, { + body: [ + self.body, + make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }) + ] + }) + }).optimize(compressor); + var has_loop_control = false; + var tw = new TreeWalker(function(node) { + if (node instanceof AST_Scope || has_loop_control) return true; + if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self) + return has_loop_control = true; + }); + var parent = compressor.parent(); + (parent instanceof AST_LabeledStatement ? parent : self).walk(tw); + if (!has_loop_control) return make_node(AST_BlockStatement, self.body, { + body: [ + self.body, + make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }) + ] + }).optimize(compressor); } return self; }); @@ -3101,22 +3107,31 @@ merge(Compressor.prototype, { if (!compressor.option("loops")) return self; if (self.condition) { var cond = self.condition.evaluate(compressor); - if (compressor.option("dead_code") && !cond) { - var a = []; - if (self.init instanceof AST_Statement) { - a.push(self.init); + if (!(cond instanceof AST_Node)) { + if (cond) self.condition = null; + else if (!compressor.option("dead_code")) { + var orig = self.condition; + self.condition = make_node_from_constant(cond, self.condition); + self.condition = best_of_expression(self.condition.transform(compressor), orig); } - else if (self.init) { - a.push(make_node(AST_SimpleStatement, self.init, { - body: self.init - })); - } - extract_declarations_from_unreachable_code(compressor, self.body, a); - return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor); } - if (cond !== self.condition) { - cond = make_node_from_constant(cond, self.condition).transform(compressor); - self.condition = best_of_expression(cond, self.condition); + if (compressor.option("dead_code")) { + if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor); + if (!cond) { + var body = []; + extract_declarations_from_unreachable_code(compressor, self.body, body); + body.push(make_node(AST_SimpleStatement, self.condition, { + body: self.condition + })); + if (self.init instanceof AST_Statement) { + body.push(self.init); + } else if (self.init) { + body.push(make_node(AST_SimpleStatement, self.init, { + body: self.init + })); + } + return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); + } } } if_break_in_loop(self, compressor); diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 9e7f489d..e0c30397 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -129,8 +129,8 @@ dead_code_constant_boolean_should_warn_more: { function bar() {} // nothing for the while // as for the for, it should keep: - var x = 10, y; var moo; + var x = 10, y; bar(); } expect_stdout: true @@ -165,8 +165,8 @@ dead_code_constant_boolean_should_warn_more_strict: { var foo; // nothing for the while // as for the for, it should keep: - var x = 10, y; var moo; + var x = 10, y; bar(); } expect_stdout: true diff --git a/test/compress/issue-1656.js b/test/compress/issue-1656.js index 3971ceaa..27d87652 100644 --- a/test/compress/issue-1656.js +++ b/test/compress/issue-1656.js @@ -39,7 +39,7 @@ f7: { "var b = 10;", "", "!function() {", - " for (;b = 100, !1; ) ;", + " b = 100;", "}(), console.log(100, b);", ] expect_stdout: true diff --git a/test/compress/issue-1833.js b/test/compress/issue-1833.js index 4ffa9d5c..e3c385e8 100644 --- a/test/compress/issue-1833.js +++ b/test/compress/issue-1833.js @@ -134,5 +134,5 @@ label_while: { L: while (0) continue L; } } - expect_exact: "function f(){L:;}" + expect_exact: "function f(){L:0}" } diff --git a/test/compress/loops.js b/test/compress/loops.js index bac40494..864276a3 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -148,9 +148,11 @@ parse_do_while_without_semicolon: { evaluate: { options = { - loops: true, dead_code: true, evaluate: true, + loops: true, + passes: 2, + side_effects: true, }; input: { while (true) { diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index a8c151f1..bcfa7b35 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1209,6 +1209,7 @@ toplevel_on_loops_2: { loops: true, reduce_funcs: true, reduce_vars: true, + side_effects: true, toplevel:true, unused: true, } From 5a1e99d713fd0ca4ca4a012422a767c138a75606 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 06:18:31 +0800 Subject: [PATCH 09/17] improve compression of `if` conditions (#2544) --- lib/compress.js | 44 ++++++++++++++++++++--------------- test/compress/conditionals.js | 29 +++++++++++++++++++++++ test/compress/dead-code.js | 2 ++ test/compress/functions.js | 5 ++-- test/compress/global_defs.js | 1 + test/compress/transform.js | 1 + 6 files changed, 60 insertions(+), 22 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index e6215f66..395c4ed0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3147,28 +3147,34 @@ merge(Compressor.prototype, { // “has no side effects”; also it doesn't work for cases like // `x && true`, though it probably should. var cond = self.condition.evaluate(compressor); - if (cond !== self.condition) { - if (cond) { - compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start); - if (compressor.option("dead_code")) { - var a = []; - if (self.alternative) { - extract_declarations_from_unreachable_code(compressor, self.alternative, a); - } - a.push(self.body); - return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor); - } - } else { + if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) { + var orig = self.condition; + self.condition = make_node_from_constant(cond, orig); + self.condition = best_of_expression(self.condition.transform(compressor), orig); + } + if (compressor.option("dead_code")) { + if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor); + if (!cond) { compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); - if (compressor.option("dead_code")) { - var a = []; - extract_declarations_from_unreachable_code(compressor, self.body, a); - if (self.alternative) a.push(self.alternative); - return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor); + var body = []; + extract_declarations_from_unreachable_code(compressor, self.body, body); + body.push(make_node(AST_SimpleStatement, self.condition, { + body: self.condition + })); + if (self.alternative) body.push(self.alternative); + return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); + } else if (!(cond instanceof AST_Node)) { + compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start); + var body = []; + if (self.alternative) { + extract_declarations_from_unreachable_code(compressor, self.alternative, body); } + body.push(make_node(AST_SimpleStatement, self.condition, { + body: self.condition + })); + body.push(self.body); + return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } - cond = make_node_from_constant(cond, self.condition).transform(compressor); - self.condition = best_of_expression(cond, self.condition); } var negated = self.condition.negate(compressor); var self_condition_length = self.condition.print_to_string().length; diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 7a6688ba..9cb27fa5 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -1015,3 +1015,32 @@ delete_conditional_2: { } expect_stdout: true } + +issue_2535: { + options = { + booleans: true, + conditionals: true, + evaluate: true, + passes: 2, + side_effects: true, + } + input: { + if (true || x()) y(); + if (true && x()) y(); + if (x() || true) y(); + if (x() && true) y(); + if (false || x()) y(); + if (false && x()) y(); + if (x() || false) y(); + if (x() && false) y(); + } + expect: { + y(); + x() && y(); + (x(), 0) || y(); + x() && y(); + x() && y(); + x() && y(); + (x(), 1) || y(); + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index e0c30397..9baf9984 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -178,6 +178,8 @@ try_catch_finally: { conditionals: true, dead_code: true, evaluate: true, + passes: 2, + side_effects: true, } input: { var a = 1; diff --git a/test/compress/functions.js b/test/compress/functions.js index 3ecb4bc3..7a336bb8 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -87,6 +87,7 @@ issue_485_crashing_1530: { dead_code: true, evaluate: true, inline: true, + side_effects: true, } input: { (function(a) { @@ -94,9 +95,7 @@ issue_485_crashing_1530: { var b = 42; })(this); } - expect: { - this, void 0; - } + expect: {} } issue_1841_1: { diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js index bd791e2d..7b72819f 100644 --- a/test/compress/global_defs.js +++ b/test/compress/global_defs.js @@ -184,6 +184,7 @@ issue_2167: { global_defs: { "@isDevMode": "function(){}", }, + passes: 2, side_effects: true, } input: { diff --git a/test/compress/transform.js b/test/compress/transform.js index 48aa605e..1e21da5b 100644 --- a/test/compress/transform.js +++ b/test/compress/transform.js @@ -68,6 +68,7 @@ label_if_break: { conditionals: true, dead_code: true, evaluate: true, + side_effects: true, } input: { L: if (true) { From 09b320e8a5ec85410bf323573b76f2da7e08e2ee Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 12:52:36 +0800 Subject: [PATCH 10/17] convert to number under boolean context (#2545) --- lib/compress.js | 3 +++ test/compress/conditionals.js | 4 ++-- test/compress/issue-1639.js | 4 ++-- test/compress/issue-637.js | 2 +- test/compress/issue-640.js | 2 +- test/compress/reduce_vars.js | 2 +- test/compress/transform.js | 6 +++--- test/compress/typeof.js | 4 ++-- 8 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 395c4ed0..8b2951d8 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -4699,6 +4699,9 @@ merge(Compressor.prototype, { }); OPT(AST_Boolean, function(self, compressor){ + if (compressor.in_boolean_context()) return make_node(AST_Number, self, { + value: +self.value + }); if (compressor.option("booleans")) { var p = compressor.parent(); if (p instanceof AST_Binary && (p.operator == "==" diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 9cb27fa5..22947d86 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -1037,10 +1037,10 @@ issue_2535: { expect: { y(); x() && y(); - (x(), 0) || y(); + (x(), 1) && y(); x() && y(); x() && y(); x() && y(); - (x(), 1) || y(); + (x(), 0) && y(); } } diff --git a/test/compress/issue-1639.js b/test/compress/issue-1639.js index b6a9647f..fc3db983 100644 --- a/test/compress/issue-1639.js +++ b/test/compress/issue-1639.js @@ -26,7 +26,7 @@ issue_1639_1: { } expect: { for (var a = 100, b = 10, L1 = 5; --L1 > 0;) - if (--b, !1) var ignore = 0; + if (--b, 0) var ignore = 0; console.log(a, b); } expect_stdout: true @@ -57,7 +57,7 @@ issue_1639_2: { expect: { var a = 100, b = 10; function f19() { - ++a, 1; + ++a, 0; } f19(), console.log(a, b); diff --git a/test/compress/issue-637.js b/test/compress/issue-637.js index 45fd2481..ed42cb8e 100644 --- a/test/compress/issue-637.js +++ b/test/compress/issue-637.js @@ -17,6 +17,6 @@ wrongly_optimized: { foo(); } // TODO: optimize to `func(), bar()` - (func(), 0) || bar(); + (func(), 1) && bar(); } } diff --git a/test/compress/issue-640.js b/test/compress/issue-640.js index c9a68dc9..784092c8 100644 --- a/test/compress/issue-640.js +++ b/test/compress/issue-640.js @@ -100,7 +100,7 @@ wrongly_optimized: { foo(); } // TODO: optimize to `func(), bar()` - if (func(), !0) bar(); + if (func(), 1) bar(); } } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index bcfa7b35..4a098f78 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2347,7 +2347,7 @@ booleans: { } expect: { console.log(function(a) { - if (!1); + if (0); switch (!1) { case 0: return "FAIL"; diff --git a/test/compress/transform.js b/test/compress/transform.js index 1e21da5b..68d33844 100644 --- a/test/compress/transform.js +++ b/test/compress/transform.js @@ -45,9 +45,9 @@ condition_evaluate: { if (void 0 == null); } expect: { - while (!1); - for (; !0;); - if (!0); + while (0); + for (; 1;); + if (1); } } diff --git a/test/compress/typeof.js b/test/compress/typeof.js index 60f3d1d0..34949fbe 100644 --- a/test/compress/typeof.js +++ b/test/compress/typeof.js @@ -44,7 +44,7 @@ typeof_in_boolean_context: { function f2() { return g(), "Yes"; } foo(); console.log(1); - var a = !(console.log(2), !0); + var a = !(console.log(2), 1); foo(); } } @@ -57,6 +57,6 @@ issue_1668: { if (typeof bar); } expect: { - if (!0); + if (1); } } From f6610baaa8c5c6acf8f4a52babf68d0439aead1f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 12:53:59 +0800 Subject: [PATCH 11/17] improve `AST_For.init` & `AST_Switch.expression` compression (#2546) --- lib/compress.js | 17 ++++++++++++----- test/compress/loops.js | 16 ++++++++++++++++ test/compress/sequences.js | 5 ++--- test/compress/switch.js | 20 ++++++++++++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 8b2951d8..dfee94fc 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1436,7 +1436,7 @@ merge(Compressor.prototype, { if (!abort) { if (stat.init) stat.init = cons_seq(stat.init); else { - stat.init = prev.body.drop_side_effect_free(compressor); + stat.init = prev.body; n--; } } @@ -3105,6 +3105,9 @@ merge(Compressor.prototype, { OPT(AST_For, function(self, compressor){ if (!compressor.option("loops")) return self; + if (compressor.option("side_effects") && self.init) { + self.init = self.init.drop_side_effect_free(compressor); + } if (self.condition) { var cond = self.condition.evaluate(compressor); if (!(cond instanceof AST_Node)) { @@ -3286,11 +3289,15 @@ merge(Compressor.prototype, { if (!compressor.option("switches")) return self; var branch; var value = self.expression.evaluate(compressor); - if (value !== self.expression) { - var expression = make_node_from_constant(value, self.expression).transform(compressor); - self.expression = best_of_expression(expression, self.expression); + if (!(value instanceof AST_Node)) { + var orig = self.expression; + self.expression = make_node_from_constant(value, orig); + self.expression = best_of_expression(self.expression.transform(compressor), orig); } if (!compressor.option("dead_code")) return self; + if (value instanceof AST_Node) { + value = self.expression.tail_node().evaluate(compressor); + } var decl = []; var body = []; var default_branch; @@ -3303,7 +3310,7 @@ merge(Compressor.prototype, { } else { eliminate_branch(branch, body[body.length - 1]); } - } else if (value !== self.expression) { + } else if (!(value instanceof AST_Node)) { var exp = branch.expression.evaluate(compressor); if (exp === value) { exact_match = branch; diff --git a/test/compress/loops.js b/test/compress/loops.js index 864276a3..44e92c58 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -452,3 +452,19 @@ in_parenthesis_2: { } expect_exact: 'for(function(){"foo"in{}};0;);' } + +init_side_effects: { + options = { + loops: true, + side_effects: true, + }; + input: { + for (function() {}(), i = 0; i < 5; i++) console.log(i); + for (function() {}(); i < 10; i++) console.log(i); + } + expect: { + for (i = 0; i < 5; i++) console.log(i); + for (; i < 10; i++) console.log(i); + } + expect_stdout: true +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index def62781..26f38c25 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -252,13 +252,12 @@ negate_iife_for: { input: { (function() {})(); for (i = 0; i < 5; i++) console.log(i); - (function() {})(); - for (; i < 5; i++) console.log(i); + for (; i < 10; i++) console.log(i); } expect: { for (!function() {}(), i = 0; i < 5; i++) console.log(i); - for (function() {}(); i < 5; i++) console.log(i); + for (!function() {}(); i < 10; i++) console.log(i); } expect_stdout: true } diff --git a/test/compress/switch.js b/test/compress/switch.js index 7f57a877..b763d741 100644 --- a/test/compress/switch.js +++ b/test/compress/switch.js @@ -817,3 +817,23 @@ issue_1758: { } expect_stdout: "0 3" } + +issue_2535: { + options = { + evaluate: true, + dead_code: true, + switches: true, + } + input: { + switch(w(), 42) { + case 13: x(); + case 42: y(); + default: z(); + } + } + expect: { + w(), 42; + y(); + z(); + } +} From 7ac6fdcc9923e173522c4b94b919ac09049024f9 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 14:32:00 +0800 Subject: [PATCH 12/17] improve switch case compression (#2547) --- lib/compress.js | 20 +++++++++++++------- test/compress/issue-1750.js | 4 ++-- test/compress/switch.js | 27 +++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index dfee94fc..adfbb793 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3312,6 +3312,11 @@ merge(Compressor.prototype, { } } else if (!(value instanceof AST_Node)) { var exp = branch.expression.evaluate(compressor); + if (!(exp instanceof AST_Node) && exp !== value) { + eliminate_branch(branch, body[body.length - 1]); + continue; + } + if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor); if (exp === value) { exact_match = branch; if (default_branch) { @@ -3320,9 +3325,6 @@ merge(Compressor.prototype, { eliminate_branch(default_branch, body[default_index - 1]); default_branch = null; } - } else if (exp !== branch.expression) { - eliminate_branch(branch, body[body.length - 1]); - continue; } } if (aborts(branch)) { @@ -3365,12 +3367,16 @@ merge(Compressor.prototype, { }); self.walk(tw); if (!has_break) { - body = body[0].body.slice(); - body.unshift(make_node(AST_SimpleStatement, self.expression, { - body: self.expression + var statements = body[0].body.slice(); + var exp = body[0].expression; + if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, { + body: exp + })); + statements.unshift(make_node(AST_SimpleStatement, self.expression, { + body:self.expression })); return make_node(AST_BlockStatement, self, { - body: body + body: statements }).optimize(compressor); } } diff --git a/test/compress/issue-1750.js b/test/compress/issue-1750.js index d18bc49f..970cea12 100644 --- a/test/compress/issue-1750.js +++ b/test/compress/issue-1750.js @@ -7,7 +7,7 @@ case_1: { input: { var a = 0, b = 1; switch (true) { - case a, true: + case a || true: default: b = 2; case true: @@ -17,7 +17,7 @@ case_1: { expect: { var a = 0, b = 1; switch (true) { - case a, true: + case a || true: b = 2; } console.log(a, b); diff --git a/test/compress/switch.js b/test/compress/switch.js index b763d741..fbb86eda 100644 --- a/test/compress/switch.js +++ b/test/compress/switch.js @@ -833,7 +833,34 @@ issue_2535: { } expect: { w(), 42; + 42; y(); z(); } } + +issue_1750: { + options = { + dead_code: true, + evaluate: true, + switches: true, + } + input: { + var a = 0, b = 1; + switch (true) { + case a, true: + default: + b = 2; + case true: + } + console.log(a, b); + } + expect: { + var a = 0, b = 1; + true; + a, true; + b = 2; + console.log(a, b); + } + expect_stdout: "0 2" +} From 9a6b11f8e628c66731c4037ff408bb969003e6f7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 1 Dec 2017 22:41:35 +0800 Subject: [PATCH 13/17] improve boolean compression (#2548) fixes #2535 --- lib/compress.js | 70 +++++++++++++++++++++++---------- test/compress/conditionals.js | 73 ++++++++++++++++++++++++++++++++++- test/compress/evaluate.js | 67 ++++++++++++++++++++++++++++++-- 3 files changed, 186 insertions(+), 24 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index adfbb793..5301d48f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -158,6 +158,7 @@ merge(Compressor.prototype, { return true; } if (p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||") + || p instanceof AST_Conditional || p.tail_node() === self) { self = p; } else { @@ -4090,49 +4091,72 @@ merge(Compressor.prototype, { if (compressor.option("evaluate")) { switch (self.operator) { case "&&": - var ll = self.left.evaluate(compressor); + var ll = self.left.truthy ? true : self.left.falsy ? false : self.left.evaluate(compressor); if (!ll) { compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); - } else if (ll !== self.left) { + } else if (!(ll instanceof AST_Node)) { compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), compressor.self(), self.right).optimize(compressor); + return make_sequence(self, [ self.left, self.right ]).optimize(compressor); } - if (compressor.in_boolean_context()) { - var rr = self.right.evaluate(compressor); - if (!rr) { + var rr = self.right.evaluate(compressor); + if (!rr) { + if (compressor.in_boolean_context()) { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, make_node(AST_False, self) ]).optimize(compressor); - } else if (rr !== self.right) { - compressor.warn("Dropping side-effect-free && in boolean context [{file}:{line},{col}]", self.start); + } else self.falsy = true; + } else if (!(rr instanceof AST_Node)) { + var parent = compressor.parent(); + if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) { + compressor.warn("Dropping side-effect-free && [{file}:{line},{col}]", self.start); return self.left.optimize(compressor); } } + // x || false && y ---> x ? y : false + if (self.left.operator == "||") { + var lr = self.left.right.evaluate(compressor); + if (!lr) return make_node(AST_Conditional, self, { + condition: self.left.left, + consequent: self.right, + alternative: self.left.right + }).optimize(compressor); + } break; case "||": - var ll = self.left.evaluate(compressor); + var ll = self.left.truthy ? true : self.left.falsy ? false : self.left.evaluate(compressor); if (!ll) { compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), compressor.self(), self.right).optimize(compressor); - } else if (ll !== self.left) { + return make_sequence(self, [ self.left, self.right ]).optimize(compressor); + } else if (!(ll instanceof AST_Node)) { compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor); } - if (compressor.in_boolean_context()) { - var rr = self.right.evaluate(compressor); - if (!rr) { - compressor.warn("Dropping side-effect-free || in boolean context [{file}:{line},{col}]", self.start); + var rr = self.right.evaluate(compressor); + if (!rr) { + var parent = compressor.parent(); + if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) { + compressor.warn("Dropping side-effect-free || [{file}:{line},{col}]", self.start); return self.left.optimize(compressor); - } else if (rr !== self.right) { + } + } else if (!(rr instanceof AST_Node)) { + if (compressor.in_boolean_context()) { compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); return make_sequence(self, [ self.left, make_node(AST_True, self) ]).optimize(compressor); - } + } else self.truthy = true; + } + if (self.left.operator == "&&") { + var lr = self.left.right.evaluate(compressor); + if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, { + condition: self.left.left, + consequent: self.left.right, + alternative: self.right + }).optimize(compressor); } break; } @@ -4640,7 +4664,7 @@ merge(Compressor.prototype, { consequent ]).optimize(compressor); } - + var in_bool = compressor.in_boolean_context(); if (is_true(self.consequent)) { if (is_false(self.alternative)) { // c ? true : false ---> !!c @@ -4696,18 +4720,24 @@ merge(Compressor.prototype, { // AST_True or !0 function is_true(node) { return node instanceof AST_True + || in_bool + && node instanceof AST_Constant + && node.getValue() || (node instanceof AST_UnaryPrefix && node.operator == "!" && node.expression instanceof AST_Constant - && !node.expression.value); + && !node.expression.getValue()); } // AST_False or !1 function is_false(node) { return node instanceof AST_False + || in_bool + && node instanceof AST_Constant + && !node.getValue() || (node instanceof AST_UnaryPrefix && node.operator == "!" && node.expression instanceof AST_Constant - && !!node.expression.value); + && node.expression.getValue()); } }); diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 22947d86..89c05263 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -1016,7 +1016,7 @@ delete_conditional_2: { expect_stdout: true } -issue_2535: { +issue_2535_1: { options = { booleans: true, conditionals: true, @@ -1044,3 +1044,74 @@ issue_2535: { (x(), 0) && y(); } } + +issue_2535_2: { + options = { + booleans: true, + conditionals: true, + evaluate: true, + side_effects: true, + } + input: { + function x() {} + function y() { + return "foo"; + } + console.log((x() || true) || y()); + console.log((y() || true) || x()); + console.log((x() || true) && y()); + console.log((y() || true) && x()); + console.log((x() && true) || y()); + console.log((y() && true) || x()); + console.log((x() && true) && y()); + console.log((y() && true) && x()); + console.log((x() || false) || y()); + console.log((y() || false) || x()); + console.log((x() || false) && y()); + console.log((y() || false) && x()); + console.log((x() && false) || y()); + console.log((y() && false) || x()); + console.log((x() && false) && y()); + console.log((y() && false) && x()); + } + expect: { + function x() {} + function y() { + return "foo"; + } + console.log(x() || !0); + console.log(y() || !0); + console.log((x(), y())); + console.log((y(), x())); + console.log(!!x() || y()); + console.log(!!y() || x()); + console.log(x() && y()); + console.log(y() && x()); + console.log(x() || y()); + console.log(y() || x()); + console.log(!!x() && y()); + console.log(!!y() && x()); + console.log((x(), y())); + console.log((y(), x())); + console.log(x() && !1); + console.log(y() && !1); + } + expect_stdout: [ + "true", + "foo", + "foo", + "undefined", + "foo", + "true", + "undefined", + "undefined", + "foo", + "foo", + "false", + "undefined", + "foo", + "undefined", + "undefined", + "false", + ] +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 04b15a8c..8e030d22 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1,6 +1,7 @@ and: { options = { - evaluate: true + evaluate: true, + side_effects: true, } input: { var a; @@ -76,7 +77,8 @@ and: { or: { options = { - evaluate: true + evaluate: true, + side_effects: true, } input: { var a; @@ -158,7 +160,8 @@ or: { unary_prefix: { options = { - evaluate: true + evaluate: true, + side_effects: true, } input: { a = !0 && b; @@ -1245,3 +1248,61 @@ self_comparison_2: { } expect_stdout: "false false true true 'number'" } + +issue_2535_1: { + options = { + booleans: true, + evaluate: true, + sequences: true, + side_effects: true, + } + input: { + if ((x() || true) || y()) z(); + if ((x() || true) && y()) z(); + if ((x() && true) || y()) z(); + if ((x() && true) && y()) z(); + if ((x() || false) || y()) z(); + if ((x() || false) && y()) z(); + if ((x() && false) || y()) z(); + if ((x() && false) && y()) z(); + } + expect: { + if (x(), 1) z(); + if (x(), y()) z(); + if (x() || y()) z(); + if (x() && y()) z(); + if (x() || y()) z(); + if (x() && y()) z(); + if (x(), y()) z(); + if (x(), 0) z(); + } +} + +issue_2535_2: { + options = { + booleans: true, + evaluate: true, + sequences: true, + side_effects: true, + } + input: { + (x() || true) || y(); + (x() || true) && y(); + (x() && true) || y(); + (x() && true) && y(); + (x() || false) || y(); + (x() || false) && y(); + (x() && false) || y(); + (x() && false) && y(); + } + expect: { + x(), + x(), y(), + x() || y(), + x() && y(), + x() || y(), + x() && y(), + x(), y(), + x(); + } +} From 8da3754e51140c0eee80f02fcf3f5d99a74ca86e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 2 Dec 2017 02:18:33 +0800 Subject: [PATCH 14/17] improve `evaluate` on `typeof` (#2550) - gated through `typeofs` --- lib/compress.js | 10 ++++-- test/compress/typeof.js | 80 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 5301d48f..7d87c4a6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1828,12 +1828,17 @@ merge(Compressor.prototype, { return this; }); def(AST_UnaryPrefix, function(compressor){ + var e = this.expression; // Function would be evaluated to an array and so typeof would // incorrectly return 'object'. Hence making is a special case. - if (this.operator == "typeof" && this.expression instanceof AST_Function) { + if (compressor.option("typeofs") + && this.operator == "typeof" + && (e instanceof AST_Lambda + || e instanceof AST_SymbolRef + && e.fixed_value() instanceof AST_Lambda)) { return typeof function(){}; } - var e = ev(this.expression, compressor); + e = ev(e, compressor); if (e === this.expression) return this; switch (this.operator) { case "!": return !e; @@ -1894,7 +1899,6 @@ merge(Compressor.prototype, { return value === node ? this : value; }); def(AST_SymbolRef, function(compressor){ - if (!compressor.option("reduce_vars")) return this; var fixed = this.fixed_value(); if (!fixed) return this; this._eval = return_this; diff --git a/test/compress/typeof.js b/test/compress/typeof.js index 34949fbe..180e5451 100644 --- a/test/compress/typeof.js +++ b/test/compress/typeof.js @@ -1,6 +1,7 @@ typeof_evaluation: { options = { - evaluate: true + evaluate: true, + typeofs: true, }; input: { a = typeof 1; @@ -60,3 +61,80 @@ issue_1668: { if (1); } } + +typeof_defun_1: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + typeofs: true, + unused: true, + } + input: { + function f() { + console.log("YES"); + } + function g() { + h = 42; + console.log("NOPE"); + } + function h() { + console.log("YUP"); + } + g = 42; + "function" == typeof f && f(); + "function" == typeof g && g(); + "function" == typeof h && h(); + } + expect: { + function g() { + h = 42; + console.log("NOPE"); + } + function h() { + console.log("YUP"); + } + g = 42; + console.log("YES"); + "function" == typeof g && g(); + h(); + } + expect_stdout: [ + "YES", + "YUP", + ] +} + +typeof_defun_2: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + typeofs: true, + } + input: { + var f = function() { + console.log(x); + }; + var x = 0; + x++ < 2 && typeof f == "function" && f(); + x++ < 2 && typeof f == "function" && f(); + x++ < 2 && typeof f == "function" && f(); + } + expect: { + var f = function() { + console.log(x); + }; + var x = 0; + x++ < 2 && f(); + x++ < 2 && f(); + x++ < 2 && f(); + } + expect_stdout: [ + "1", + "2", + ] +} From 85c56adbd19bff5e7bc2a59dd937799b7bc5a8d4 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 2 Dec 2017 02:26:56 +0800 Subject: [PATCH 15/17] more tests for #2535 (#2551) --- test/compress/evaluate.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 8e030d22..6106fce3 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1306,3 +1306,37 @@ issue_2535_2: { x(); } } + +issue_2535_3: { + options = { + booleans: true, + evaluate: true, + } + input: { + console.log(Object(1) && 1 && 2); + console.log(Object(1) && true && 1 && 2 && Object(2)); + console.log(Object(1) && true && 1 && null && 2 && Object(2)); + console.log(2 == Object(1) || 0 || void 0 || null); + console.log(2 == Object(1) || 0 || void 0 || null || Object(2)); + console.log(2 == Object(1) || 0 || void 0 || "ok" || null || Object(2)); + } + expect: { + console.log(Object(1) && 2); + console.log(Object(1) && Object(2)); + console.log(Object(1) && null); + console.log(2 == Object(1) || null); + console.log(2 == Object(1) || Object(2)); + console.log(2 == Object(1) || "ok"); + } + expect_stdout: true + expect_warnings: [ + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1316,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1317,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1318,20]", + "WARN: Condition left of && always false [test/compress/evaluate.js:1318,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1319,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1320,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1321,20]", + "WARN: Condition left of || always true [test/compress/evaluate.js:1321,20]", + ] +} From 77332a03153285f5e521c99c3b5d796bf5698ef7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 2 Dec 2017 15:46:05 +0800 Subject: [PATCH 16/17] fix `dead_code` on `for` (#2552) --- lib/compress.js | 6 +++--- test/compress/loops.js | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 7d87c4a6..914fdd06 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3128,9 +3128,6 @@ merge(Compressor.prototype, { if (!cond) { var body = []; extract_declarations_from_unreachable_code(compressor, self.body, body); - body.push(make_node(AST_SimpleStatement, self.condition, { - body: self.condition - })); if (self.init instanceof AST_Statement) { body.push(self.init); } else if (self.init) { @@ -3138,6 +3135,9 @@ merge(Compressor.prototype, { body: self.init })); } + body.push(make_node(AST_SimpleStatement, self.condition, { + body: self.condition + })); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } diff --git a/test/compress/loops.js b/test/compress/loops.js index 44e92c58..3538c221 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -468,3 +468,27 @@ init_side_effects: { } expect_stdout: true } + +dead_code_condition: { + options = { + dead_code: true, + evaluate: true, + loops: true, + sequences: true, + } + input: { + for (var a = 0, b = 5; (a += 1, 3) - 3 && b > 0; b--) { + var c = function() { + b--; + }(a++); + } + console.log(a); + } + expect: { + var c; + var a = 0, b = 5; + a += 1, 0, + console.log(a); + } + expect_stdout: "1" +} From b9f3ddfb30897f634a7967e773d1555ceecb21cc Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 3 Dec 2017 11:39:51 +0800 Subject: [PATCH 17/17] v3.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cbde945a..adfc10a4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.2.0", + "version": "3.2.1", "engines": { "node": ">=0.8.0" },