diff --git a/lib/compress/index.js b/lib/compress/index.js index be2288f4d..10efba64d 100644 --- a/lib/compress/index.js +++ b/lib/compress/index.js @@ -53,7 +53,6 @@ import { AST_Boolean, AST_Break, AST_Call, - AST_Case, AST_Catch, AST_Chain, AST_Class, @@ -109,6 +108,7 @@ import { AST_String, AST_Sub, AST_Switch, + AST_SwitchBranch, AST_Symbol, AST_SymbolBlockDeclaration, AST_SymbolCatch, @@ -1592,58 +1592,251 @@ def_optimize(AST_Switch, function(self, compressor) { } } } - if (aborts(branch)) { - var prev = body[body.length - 1]; - if (aborts(prev) && prev.body.length == branch.body.length - && make_node(AST_BlockStatement, prev, prev).equivalent_to(make_node(AST_BlockStatement, branch, branch))) { - prev.body = []; - } - } body.push(branch); } while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]); + self.body = body; + + for (let i = 0; i < body.length; i++) { + let branch = body[i]; + if (branch.body.length === 0) continue; + if (!aborts(branch)) continue; + + for (let j = i + 1; j < body.length; i++, j++) { + let next = body[j]; + if (next.body.length === 0) continue; + if ( + branches_equivalent(next, branch, false) + || (j === body.length - 1 && branches_equivalent(next, branch, true)) + ) { + branch.body = []; + branch = next; + continue; + } + break; + } + } + + let default_or_exact = default_branch || exact_match; + default_branch = null; + exact_match = null; + + // Prune any empty branches at the end of the switch statement. + { + let i = body.length - 1; + for (; i >= 0; i--) { + let bbody = body[i].body; + if (is_break(bbody[bbody.length - 1], compressor)) bbody.pop(); + if (!is_inert_body(body[i])) break; + } + // i now points to the index of a branch that contains a body. By incrementing, it's + // pointing to the first branch that's empty. + i++; + if (!default_or_exact || body.indexOf(default_or_exact) >= i) { + // The default behavior is to do nothing. We can take advantage of that to + // remove all case expressions that are side-effect free that also do + // nothing, since they'll default to doing nothing. But we can't remove any + // case expressions before one that would side-effect, since they may cause + // the side-effect to be skipped. + for (let j = body.length - 1; j >= i; j--) { + let branch = body[j]; + if (branch === default_or_exact) { + default_or_exact = null; + body.pop(); + } else if (!branch.expression.has_side_effects(compressor)) { + body.pop(); + } else { + break; + } + } + } + } + + + // Prune side-effect free branches that fall into default. + if (default_or_exact) { + let default_index = body.indexOf(default_or_exact); + let default_body_index = default_index; + for (; default_body_index < body.length - 1; default_body_index++) { + if (!is_inert_body(body[default_body_index])) break; + } + let side_effect_index = body.length - 1; + for (; side_effect_index >= 0; side_effect_index--) { + let branch = body[side_effect_index]; + if (branch === default_or_exact) continue; + if (branch.expression.has_side_effects(compressor)) break; + } + // If the default behavior comes after any side-effect case expressions, + // then we can fold all side-effect free cases into the default branch. + // If the side-effect case is after the default, then any side-effect + // free cases could prevent the side-effect from occurring. + if (default_body_index > side_effect_index) { + let prev_body_index = default_index - 1; + for (; prev_body_index >= 0; prev_body_index--) { + if (!is_inert_body(body[prev_body_index])) break; + } + let before = Math.max(side_effect_index, prev_body_index) + 1; + let after = default_index; + if (side_effect_index > default_index) { + // If the default falls into the same body as a side-effect + // case, then we need preserve that case and only prune the + // cases after it. + after = side_effect_index; + body[side_effect_index].body = body[default_body_index].body; + } else { + // The default will be the last branch. + default_or_exact.body = body[default_body_index].body; + } + + // Prune everything after the default (or last side-effect case) + // until the next case with a body. + body.splice(after + 1, default_body_index - after); + // Prune everything before the default that falls into it. + body.splice(before, default_index - before); + } + } + + // See if we can remove the switch entirely if all cases (the default) fall into the same case body. + DEFAULT: if (default_or_exact) { + let i = body.findIndex(branch => !is_inert_body(branch)); + let caseBody; + // `i` is equal to one of the following: + // - `-1`, there is no body in the switch statement. + // - `body.length - 1`, all cases fall into the same body. + // - anything else, there are multiple bodies in the switch. + if (i === body.length - 1) { + // All cases fall into the case body. + let branch = body[i]; + if (has_nested_break(self)) break DEFAULT; + + // This is the last case body, and we've already pruned any breaks, so it's + // safe to hoist. + caseBody = make_node(AST_BlockStatement, branch, { + body: branch.body + }); + branch.body = []; + } else if (i !== -1) { + // If there are multiple bodies, then we cannot optimize anything. + break DEFAULT; + } + + let sideEffect = body.find(branch => { + return ( + branch !== default_or_exact + && branch.expression.has_side_effects(compressor) + ); + }); + // If no cases cause a side-effect, we can eliminate the switch entirely. + if (!sideEffect) { + return make_node(AST_BlockStatement, self, { + body: decl.concat( + statement(self.expression), + default_or_exact.expression ? statement(default_or_exact.expression) : [], + caseBody || [] + ) + }).optimize(compressor); + } + + // If we're this far, either there was no body or all cases fell into the same body. + // If there was no body, then we don't need a default branch (because the default is + // do nothing). If there was a body, we'll extract it to after the switch, so the + // switch's new default is to do nothing and we can still prune it. + const default_index = body.indexOf(default_or_exact); + body.splice(default_index, 1); + default_or_exact = null; + + if (caseBody) { + // Recurse into switch statement one more time so that we can append the case body + // outside of the switch. This recursion will only happen once since we've pruned + // the default case. + return make_node(AST_BlockStatement, self, { + body: decl.concat(self, caseBody) + }).optimize(compressor); + } + // If we fall here, there is a default branch somewhere, there are no case bodies, + // and there's a side-effect somewhere. Just let the below paths take care of it. + } + if (body.length > 0) { body[0].body = decl.concat(body[0].body); } - self.body = body; - while (branch = body[body.length - 1]) { - var stat = branch.body[branch.body.length - 1]; - if (stat instanceof AST_Break && compressor.loopcontrol_target(stat) === self) - branch.body.pop(); - if (branch.body.length || branch instanceof AST_Case - && (default_branch || branch.expression.has_side_effects(compressor))) break; - if (body.pop() === default_branch) default_branch = null; - } + if (body.length == 0) { return make_node(AST_BlockStatement, self, { - body: decl.concat(make_node(AST_SimpleStatement, self.expression, { - body: self.expression - })) + body: decl.concat(statement(self.expression)) }).optimize(compressor); } - if (body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) { - var has_break = false; - var tw = new TreeWalker(function(node) { - if (has_break - || node instanceof AST_Lambda - || node instanceof AST_SimpleStatement) return true; - if (node instanceof AST_Break && tw.loopcontrol_target(node) === self) - has_break = true; - }); - self.walk(tw); - if (!has_break) { - 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: statements + if (body.length == 1 && !has_nested_break(self)) { + // This is the last case body, and we've already pruned any breaks, so it's + // safe to hoist. + let branch = body[0]; + return make_node(AST_If, self, { + condition: make_node(AST_Binary, self, { + operator: "===", + left: self.expression, + right: branch.expression, + }), + body: make_node(AST_BlockStatement, branch, { + body: branch.body + }), + alternative: null + }).optimize(compressor); + } + if (body.length === 2 && default_or_exact && !has_nested_break(self)) { + let branch = body[0] === default_or_exact ? body[1] : body[0]; + let exact_exp = default_or_exact.expression && statement(default_or_exact.expression); + if (aborts(body[0])) { + // Only the first branch body could have a break (at the last statement) + let first = body[0]; + if (is_break(first.body[first.body.length - 1], compressor)) { + first.body.pop(); + } + return make_node(AST_If, self, { + condition: make_node(AST_Binary, self, { + operator: "===", + left: self.expression, + right: branch.expression, + }), + body: make_node(AST_BlockStatement, branch, { + body: branch.body + }), + alternative: make_node(AST_BlockStatement, default_or_exact, { + body: [].concat( + exact_exp || [], + default_or_exact.body + ) + }) }).optimize(compressor); } + let operator = "==="; + let consequent = make_node(AST_BlockStatement, branch, { + body: branch.body, + }); + let always = make_node(AST_BlockStatement, default_or_exact, { + body: [].concat( + exact_exp || [], + default_or_exact.body + ) + }); + if (body[0] === default_or_exact) { + operator = "!=="; + let tmp = always; + always = consequent; + consequent = tmp; + } + return make_node(AST_BlockStatement, self, { + body: [ + make_node(AST_If, self, { + condition: make_node(AST_Binary, self, { + operator: operator, + left: self.expression, + right: branch.expression, + }), + body: consequent, + alternative: null + }) + ].concat(always) + }).optimize(compressor); } return self; @@ -1654,6 +1847,50 @@ def_optimize(AST_Switch, function(self, compressor) { trim_unreachable_code(compressor, branch, decl); } } + function branches_equivalent(branch, prev, insertBreak) { + let bbody = branch.body; + let pbody = prev.body; + if (insertBreak) { + bbody = bbody.concat(make_node(AST_Break)); + } + if (bbody.length !== pbody.length) return false; + let bblock = make_node(AST_BlockStatement, branch, { body: bbody }); + let pblock = make_node(AST_BlockStatement, prev, { body: pbody }); + return bblock.equivalent_to(pblock); + } + function statement(expression) { + return make_node(AST_SimpleStatement, expression, { + body: expression + }); + } + function has_nested_break(root) { + let has_break = false; + let tw = new TreeWalker(node => { + if (has_break) return true; + if (node instanceof AST_Lambda) return true; + if (node instanceof AST_SimpleStatement) return true; + if (!is_break(node, tw)) return; + let parent = tw.parent(); + if ( + parent instanceof AST_SwitchBranch + && parent.body[parent.body.length - 1] === node + ) { + return; + } + has_break = true; + }); + root.walk(tw); + return has_break; + } + function is_break(node, stack) { + return node instanceof AST_Break + && stack.loopcontrol_target(node) === self; + } + function is_inert_body(branch) { + return !aborts(branch) && !make_node(AST_BlockStatement, branch, { + body: branch.body + }).has_side_effects(compressor) + } }); def_optimize(AST_Try, function(self, compressor) { diff --git a/test/compress/functions.js b/test/compress/functions.js index 148a6f326..c0b2288f4 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -1328,9 +1328,7 @@ issue_2620_4: { expect: { var c = "FAIL"; !function() { - switch (NaN) { - case void (c = "PASS"): - } + if (NaN === void (c = "PASS")); }(); console.log(c); } diff --git a/test/compress/issue-1750.js b/test/compress/issue-1750.js index 970cea120..e0b6f341b 100644 --- a/test/compress/issue-1750.js +++ b/test/compress/issue-1750.js @@ -16,10 +16,7 @@ case_1: { } expect: { var a = 0, b = 1; - switch (true) { - case a || true: - b = 2; - } + if (true === (a || true)) b = 2; console.log(a, b); } expect_stdout: "0 2" @@ -44,10 +41,7 @@ case_2: { } expect: { var a = 0, b = 1; - switch (0) { - case a: - a = 3; - } + if (0 === a) a = 3; console.log(a, b); } expect_stdout: "3 1" diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 00d566413..fd0c943d0 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1947,13 +1947,8 @@ issue_1670_6: { } expect: { (function(a) { - switch (1) { - case a = 1: - console.log(a); - break; - default: - console.log(2); - } + if (1 === (a = 1)) console.log(a); + else console.log(2); })(1); } expect_stdout: "1" diff --git a/test/compress/switch.js b/test/compress/switch.js index 4b9d06bb0..d09b8f9b8 100644 --- a/test/compress/switch.js +++ b/test/compress/switch.js @@ -266,124 +266,1577 @@ drop_default_1: { } } expect: { + if ("bar" === foo) baz(); + } +} + +drop_default_2: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 'bar': baz(); break; + default: + break; + } + } + expect: { + if ("bar" === foo) baz(); + } +} + +keep_default: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 'bar': baz(); + default: + something(); + break; + } + } + expect: { + if ('bar' === foo) baz(); + something(); + } +} + +remove_switch_1: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 1: + 1; + case 2: + invariant(); + case 3: + default: + doSomething(); + } + function invariant() { + /* production build */ + } + } + expect: { + foo; + doSomething(); + } +} + +remove_switch_2: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 1: + doSomething(); + break; + default: + doSomething(); + break; + } + } + expect: { + foo; + doSomething(); + } +} + +remove_switch_3: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + default: + doSomething(); + break; + case 1: + doSomething(); + break; + } + } + expect: { + foo; + doSomething(); + } +} + +remove_switch_4: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 1: + doSomething(); + break; + default: + doSomething(); + break; + case 2: + doSomething(); + break; + } + } + expect: { + foo; + doSomething(); + } +} + +remove_switch_5: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case "bar": + doSomething(); + break; + default: + doSomething(); + break; + case "qux": + doSomething(); + break; + } + + } + expect: { + foo; + doSomething(); + } +} + +remove_switch_6: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 1: + doSomething(); + break; + default: + doSomething(); + break; + case 2: + doSomething(); + } + } + expect: { + foo; + doSomething(); + } +} + +remove_switch_7: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + doSomething(); + break; + default: + doSomething(); + break; + case qux: + doSomething(); + break; + } + } + expect: { + switch (foo) { + case bar: + case qux: + } + doSomething(); + } +} + +remove_switch_8: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + function test(foo) { + switch (foo) { + case 1: + return 1; + case 2: + default: + case 3: + } + } + + console.log(test(1)); + } + expect: { + console.log(function (foo) { + if (1 === foo) return 1; + }(1)); + } +} + +remove_switch_9: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 1: + doSomethting(); + break; + case 2: + default: + break; + case 3: + } + } + expect: { + if (1 === foo) doSomethting(); + } +} + +remove_switch_10: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (1) { + case 0: + var x = 1; + case bar(): + default: + case bar(): + case 1: + console.log(x); + } + function bar() {} + } + expect: { + var x; + switch (1) { + case bar(): + case bar(): + } + console.log(x); + function bar() {} + } + expect_stdout: ["undefined"] +} + +remove_switch_11: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (1) { + case 0: + var x = 1; + case 1: + console.log(x); + } + } + expect: { + var x; + console.log(x); + } + expect_stdout: ["undefined"] +} + +remove_switch_12: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (1) { + case 0: + var x = 1; + case foo(): + console.log('foo'); + case 1: + console.log(x); + } + function foo() {} + } + expect: { + if (1 === foo()) { + var x; + console.log('foo'); + } + console.log(x); + function foo() {} + } + expect_stdout: ["undefined"] +} + +remove_switch_13: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (1) { + case 0: + var x = 1; + case foo(): + console.log('foo'); + break; + case 1: + console.log(x); + } + function foo() {} + } + expect: { + if (1 === foo()) { + var x; + console.log('foo'); + } else console.log(x); + function foo() {} + } + expect_stdout: ["undefined"] +} + +remove_switch_14: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + function test(foo) { + var x = 1; + switch (foo) { + case 0: + case 1: + default: + case x--: + console.log(x); + } + } + test(0); + test(1); + test(2); + } + expect: { + function test(foo) { + var x = 1; + switch (foo) { + case 0: + case 1: + case x--: + } + console.log(x); + } + test(0); + test(1); + test(2); + } + expect_stdout: ["1", "1", "0"] +} + +remove_switch_15: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + function test(foo) { + var x = 1; + switch (foo) { + case 0: + default: + case x--: + console.log(x); + } + } + test(0); + test(1); + test(2); + } + expect: { + function test(foo) { + var x = 1; + switch (foo) { + case 0: + case x--: + } + console.log(x); + } + test(0); + test(1); + test(2); + } + expect_stdout: ["1", "0", "0"] +} + +remove_switch_16: { + options = { + dead_code: true, + switches: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + function test(foo) { + var x = 1; + switch (foo) { + case 0: + default: + case x--: + } + console.log(x); + } + test(0); + test(1); + test(2); + } + expect: { + function test(foo) { + var x = 1; + switch (foo) { + case 0: + case x--: + } + console.log(x); + } + test(0); + test(1); + test(2); + } + expect_stdout: ["1", "0", "0"] +} + +collapse_into_default_1: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 'bar': + bar(); + case 'baz': + baz(); + case 'qux': + default: + other(); + } + } + expect: { + switch (foo) { + case 'bar': + bar(); + case 'baz': + baz(); + default: + other(); + } + } +} + +collapse_into_default_2: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case 'bar': + bar(); + case 'baz': + baz(); + default: + case 'qux': + other(); + } + } + expect: { + switch (foo) { + case 'bar': + bar(); + case 'baz': + baz(); + default: + other(); + } + } +} + +collapse_into_default_3: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case qux: + default: + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case qux: + default: + other(); + } + } +} + +collapse_into_default_4: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + default: + case qux: + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + default: + case qux: + other(); + } + } +} + +collapse_into_default_5: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case 'qux': + case qux: + default: + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case 'qux': + case qux: + default: + other(); + } + } +} + +collapse_into_default_6: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case qux: + case 'qux': + default: + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case qux: + default: + other(); + } + } +} + +collapse_into_default_7: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + default: + case 'qux': + case qux: + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + default: + case 'qux': + case qux: + other(); + } + } +} + +collapse_into_default_8: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + default: + case qux: + case 'qux': + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + default: + case qux: + other(); + } + } +} + +collapse_into_default_9: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case 'qux': + default: + case qux: + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case 'qux': + default: + case qux: + other(); + } + } +} + +collapse_into_default_10: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case qux: + default: + case 'qux': + other(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case baz: + baz(); + case qux: + default: + other(); + } + } +} + +collapse_into_default_11: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case 'qux': + case qux: + default: + other(); + case 'baz': + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case 'qux': + case qux: + default: + other(); + case 'baz': + baz(); + } + } +} + +collapse_into_default_12: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case qux: + case 'qux': + default: + other(); + case 'baz': + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case qux: + default: + other(); + case 'baz': + baz(); + } + } +} + +collapse_into_default_13: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + default: + case 'qux': + case qux: + other(); + case 'baz': + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + default: + case 'qux': + case qux: + other(); + case 'baz': + baz(); + } + } +} + +collapse_into_default_14: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + default: + case qux: + case 'qux': + other(); + case 'baz': + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + default: + case qux: + other(); + case 'baz': + baz(); + } + } +} + +collapse_into_default_15: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case 'qux': + default: + case qux: + other(); + case 'baz': + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case 'qux': + default: + case qux: + other(); + case 'baz': + baz(); + } + } +} + +collapse_into_default_16: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case qux: + default: + case 'qux': + other(); + case 'baz': + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case qux: + default: + other(); + case 'baz': + baz(); + } + } +} + +collapse_into_default_17: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case 'qux': + case qux: + default: + other(); + case baz: + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case 'qux': + case qux: + default: + other(); + case baz: + baz(); + } + } +} + +collapse_into_default_18: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { switch (foo) { - case 'bar': baz(); + case bar: + bar(); + case qux: + case 'qux': + default: + other(); + case baz: + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case qux: + case 'qux': + default: + other(); + case baz: + baz(); } } } -drop_default_2: { +collapse_into_default_19: { options = { dead_code: true, switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, } input: { switch (foo) { - case 'bar': baz(); break; + case bar: + bar(); + default: + case 'qux': + case qux: + other(); + case baz: + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + default: + case 'qux': + case qux: + other(); + case baz: + baz(); + } + } +} + +collapse_into_default_20: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + default: + case qux: + case 'qux': + other(); + case baz: + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + default: + case qux: + case 'qux': + other(); + case baz: + baz(); + } + } +} + +collapse_into_default_21: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case 'qux': + default: + case qux: + other(); + case baz: + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case 'qux': + default: + case qux: + other(); + case baz: + baz(); + } + } +} + +collapse_into_default_22: { + options = { + dead_code: true, + switches: true, + reduce_vars: true, + side_effects: true, + unused: true, + module: true, + evaluate: true, + } + input: { + switch (foo) { + case bar: + bar(); + case qux: + default: + case 'qux': + other(); + case baz: + baz(); + } + } + expect: { + switch (foo) { + case bar: + bar(); + case qux: default: + case 'qux': + other(); + case baz: + baz(); + } + } +} + +issue_1663: { + options = { + dead_code: true, + evaluate: true, + side_effects: true, + switches: true, + } + input: { + var a = 100, b = 10; + function f() { + switch (1) { + case 1: + b = a++; + return ++b; + default: + var b; + } + } + f(); + console.log(a, b); + } + expect: { + var a = 100, b = 10; + function f() { + var b; + b = a++; + return ++b; + } + f(); + console.log(a, b); + } + expect_stdout: true +} + +drop_case: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 'bar': baz(); break; + case 'moo': + break; + } + } + expect: { + if ('bar' === foo) baz(); + } +} + +drop_case_2: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 'bar': bar(); break; + case 'moo': + case moo: + case 'baz': break; } } expect: { switch (foo) { - case 'bar': baz(); + case 'bar': bar(); + case 'moo': + case moo: } } } -keep_default: { +keep_case: { options = { dead_code: true, switches: true, } input: { + switch (foo) { + case 'bar': baz(); break; + case moo: + break; + } + } + expect: { switch (foo) { case 'bar': baz(); + case moo: + } + } +} + +if_else: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 'bar': + bar(); + break; default: - something(); + other(); + } + } + expect: { + if ('bar' === foo) bar(); + else other(); + } +} + +if_else2: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 'bar': + bar(); + default: + other(); + } + } + expect: { + if ('bar' === foo) bar(); + other(); + } +} + +if_else3: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + default: + other(); break; + case 'bar': + bar(); } } expect: { + if ('bar' === foo) bar(); + else other(); + } +} + +if_else4: { + options = { + dead_code: true, + switches: true, + } + input: { switch (foo) { - case 'bar': baz(); default: - something(); + other(); + case 'bar': + bar(); + } + } + expect: { + if ('bar' !== foo) other(); + bar(); + } +} + +if_else5: { + options = { + dead_code: true, + switches: true, + evaluate: true, + } + input: { + switch (1) { + case bar: + bar(); + break; + case 1: + other(); } } + expect: { + if (1 === bar) bar(); + else { + 1; + other(); + } + } } -issue_1663: { +if_else6: { options = { dead_code: true, - evaluate: true, - side_effects: true, switches: true, + evaluate: true, } input: { - var a = 100, b = 10; - function f() { - switch (1) { - case 1: - b = a++; - return ++b; - default: - var b; - } + switch (1) { + case bar: + bar(); + case 1: + other(); } - f(); - console.log(a, b); } expect: { - var a = 100, b = 10; - function f() { - var b; - b = a++; - return ++b; - } - f(); - console.log(a, b); + if (1 === bar) bar(); + 1; + other(); } - expect_stdout: true } -drop_case: { +if_else7: { options = { dead_code: true, switches: true, } input: { switch (foo) { - case 'bar': baz(); break; - case 'moo': + case 'bar': break; + bar(); + default: + other(); } } expect: { - switch (foo) { - case 'bar': baz(); - } + if ('bar' === foo); + else other(); } } -keep_case: { +if_else8: { options = { - dead_code: true, - switches: true, + defaults: true, } input: { - switch (foo) { - case 'bar': baz(); break; - case moo: - break; + function test(foo) { + switch (foo) { + case 'bar': + return 'PASS'; + default: + return 'FAIL'; + } } + console.log(test('bar')); } expect: { - switch (foo) { - case 'bar': baz(); break; - case moo: + function test(foo) { + return 'bar' === foo ? 'PASS' : 'FAIL'; + } + console.log(test('bar')); } + expect_stdout: ["PASS"] } issue_376: { @@ -403,10 +1856,7 @@ issue_376: { } } expect: { - switch (true) { - case boolCondition: - console.log(1); - } + if (true === boolCondition) console.log(1); } } @@ -432,9 +1882,8 @@ issue_441_1: { switch (foo) { case bar: case baz: - default: - qux(); } + qux(); } } @@ -446,7 +1895,6 @@ issue_441_2: { input: { switch (foo) { case bar: - // TODO: Fold into the case below qux(); break; case fall: @@ -459,14 +1907,81 @@ issue_441_2: { } } expect: { + switch (foo) { + case bar: + case fall: + case baz: + } + qux(); + } +} + +issue_441_3: { + options = { + dead_code: true, + switches: true, + } + input: { switch (foo) { case bar: qux(); break; case fall: case baz: - default: qux(); + break; + } + } + expect: { + switch (foo) { + case bar: + case fall: + case baz: + qux(); + } + } +} + +issue_441_4: { + options = { + dead_code: true, + switches: true, + } + input: { + switch (foo) { + case 1: + qux(); + break; + case fall1: + case 2: + qux(); + break; + case 3: + other(); + break; + case 4: + qux(); + break; + case fall2: + case 5: + qux(); + break; + } + } + expect: { + switch (foo) { + case 1: + case fall1: + case 2: + qux(); + break; + case 3: + other(); + break; + case 4: + case fall2: + case 5: + qux() } } } @@ -499,6 +2014,8 @@ issue_1679: { dead_code: true, evaluate: true, switches: true, + conditionals: true, + side_effects: true, } input: { var a = 100, b = 10; @@ -528,18 +2045,14 @@ issue_1679: { case !function x() {}: break; case b--: - switch (0) { - default: - case a--: - } - break; + a--; case (a++): } } f(); console.log(a, b); } - expect_stdout: true + expect_stdout: ["99 8"] } issue_1680_1: { @@ -608,16 +2121,14 @@ issue_1680_2: { var a = 100, b = 10; switch (b) { case a--: - break; case b: var c; - break; case a: case a--: } console.log(a, b); } - expect_stdout: true + expect_stdout: ["99 10"] } issue_1690_1: { @@ -701,11 +2212,7 @@ issue_1705_1: { } expect: { var a = 0; - switch (a) { - default: - console.log("FAIL"); - case 0: - } + if (0 !== a) console.log("FAIL"); } expect_stdout: true } @@ -885,3 +2392,256 @@ issue_445: { } expect_stdout: "PASS" } + +collapse_same_branches: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS"); + break + + case 2: + console.log("PASS"); + break + + } + } + expect: { + switch (id(1)) { + case 1: + case 2: + console.log("PASS"); + } + } + expect_stdout: "PASS" +} + +// Not when the branches are break-less +collapse_same_branches_2: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS"); + + case 2: + console.log("PASS"); + } + } + expect: { + switch (id(1)) { + case 1: + console.log("PASS"); + + case 2: + console.log("PASS"); + } + } + expect_stdout: ["PASS", "PASS"] +} + +// Empty branches at the end of the switch get trimmed +trim_empty_last_branches: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS") + case 2: + // break should be removed too + break + case 3: {} + case 4: + } + } + expect: { + if (1 === id(1)) console.log("PASS") + } + expect_stdout: "PASS" +} + +// ... But break should be kept if we're breaking to somewhere else +trim_empty_last_branches_2: { + options = { + switches: true, + dead_code: true + } + input: { + somewhere_else: if (id(true)) { + switch (id(1)) { + case 1: + console.log("PASS") + case 2: + break somewhere_else + case 3: {} + case 4: + } + } + } + expect: { + somewhere_else: if (id(true)) + switch (id(1)) { + case 1: + console.log("PASS") + case 2: + break somewhere_else + } + } + expect_stdout: "PASS" +} + +trim_empty_last_branches_3: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS") + case 2: + "no side effect" + } + } + expect: { + if (1 === id(1)) console.log("PASS") + } + expect_stdout: "PASS" +} + +trim_side_effect_free_branches_falling_into_default: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 0: + "no side effect" + case 1: + // Not here either + default: + console.log("PASS default") + case 2: + console.log("PASS 2") + } + } + expect: { + if (2 !== id(1)) + console.log("PASS default"); + console.log("PASS 2") + } +} + +trim_side_effect_free_branches_falling_into_default_2: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + default: + case 0: + "no side effect" + case 1: + console.log("PASS default") + case 2: + console.log("PASS 2") + } + } + expect: { + if (2 !== id(1)) + console.log("PASS default"); + console.log("PASS 2") + } +} + +gut_entire_switch: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(123)) { + case 1: + case 2: + case 3: + default: + console.log("PASS"); + } + } + expect: { + id(123); console.log("PASS"); + } + expect_stdout: "PASS" +} + +gut_entire_switch_2: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(123)) { + case 1: + "no side effect" + case 1: + // Not here either + default: + console.log("PASS"); + } + } + expect: { + id(123); console.log("PASS"); + } + expect_stdout: "PASS" +} + +turn_into_if: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case id(2): + console.log("FAIL"); + } + console.log("PASS"); + } + expect: { + if (id(1) === id(2)) console.log("FAIL"); + console.log("PASS"); + } + expect_stdout: "PASS" +} + +turn_into_if_2: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case id(2): + console.log("FAIL"); + default: + console.log("PASS"); + } + } + expect: { + if (id(1) === id(2)) console.log("FAIL"); + console.log("PASS"); + } + expect_stdout: "PASS" +}