diff --git a/lib/compress/index.js b/lib/compress/index.js index 4c393151b..9ac2311b0 100644 --- a/lib/compress/index.js +++ b/lib/compress/index.js @@ -1598,6 +1598,49 @@ def_optimize(AST_Switch, function(self, compressor) { while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]); self.body = body; + let default_or_exact = default_branch || exact_match; + default_branch = null; + exact_match = null; + + // group equivalent branches so they will be located next to each other, + // that way the next micro-optimization will merge them. + // ** bail micro-optimization if not a simple switch case with breaks + if (body.every((branch, i) => + (branch === default_or_exact || !branch.expression.has_side_effects(compressor)) + && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i)) + ) { + for (let i = 0; i < body.length; i++) { + const branch = body[i]; + for (let j = i + 1; j < body.length; j++) { + const next = body[j]; + if (next.body.length === 0) continue; + const last_branch = j === (body.length - 1); + const equivalentBranch = branches_equivalent(next, branch, false); + if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) { + if (!equivalentBranch && last_branch) { + next.body.push(make_node(AST_Break)); + } + + // let's find previous siblings with inert fallthrough... + let x = j - 1; + let fallthroughDepth = 0; + while (x > i) { + if (is_inert_body(body[x--])) { + fallthroughDepth++; + } else { + break; + } + } + + const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth); + body.splice(i + 1, 0, ...plucked); + i += plucked.length; + } + } + } + } + + // merge equivalent branches in a row for (let i = 0; i < body.length; i++) { let branch = body[i]; if (branch.body.length === 0) continue; @@ -1618,10 +1661,6 @@ def_optimize(AST_Switch, function(self, compressor) { } } - 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; diff --git a/test/compress/switch.js b/test/compress/switch.js index d09b8f9b8..acfd5fb69 100644 --- a/test/compress/switch.js +++ b/test/compress/switch.js @@ -2447,6 +2447,294 @@ collapse_same_branches_2: { expect_stdout: ["PASS", "PASS"] } +collapse_same_branches_not_in_a_row: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS") + break; + case 2: + console.log("FAIL"); + break; + case 3: + console.log("PASS"); + break; + } + } + expect: { + switch (id(1)) { + case 1: + case 3: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + } + } + expect_stdout: ["PASS"] +} + +collapse_same_branches_not_in_a_row2: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + break; + case 3: + console.log("PASS"); + break; + case 4: + console.log("PASS"); + break; + } + } + expect: { + switch (id(1)) { + case 1: + case 3: + case 4: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + } + } + expect_stdout: ["PASS"] +} + +collapse_same_branches_not_in_a_row_including_fallthrough_with_same_body: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + break; + case 3: + case 4: + console.log("PASS"); + break; + case 9: + console.log("FAIL"); + break; + case 5: + case 6: + case 7: + case 8: + console.log("PASS"); + break; + } + } + expect: { + switch (id(1)) { + case 1: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + console.log("PASS"); + break; + case 2: + case 9: + console.log("FAIL"); + } + } + expect_stdout: ["PASS"] +} + +collapse_same_branches_not_in_a_row_ensure_no_side_effects: { + options = { + switches: true, + dead_code: true + } + input: { + let i = 1; + switch (id(2)) { + case 1: + console.log(1); + break; + case 2: + console.log(2); + break; + case ++i: + console.log(1); + break; + } + } + expect: { + let i = 1; + switch (id(2)) { + case 1: + console.log(1); + break; + case 2: + console.log(2); + break; + case ++i: + console.log(1); + } + } + expect_stdout: ["2"] +} +collapse_same_branches_not_in_a_row_even_if_last_case_without_abort: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(3)) { + case 1: + console.log(1); + break; + case 2: + console.log(2); + break; + case 3: + console.log(1); + } + } + expect: { + switch (id(3)) { + case 1: + case 3: + console.log(1); + break; + case 2: + console.log(2); + } + } + expect_stdout: ["1"] +} + + +collapse_same_branches_as_default_not_in_a_row: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + break; + case 3: + console.log("PREVENT_IFS"); + break; + case 4: + console.log("PASS"); + break; + default: + console.log("PASS"); + break; + } + } + expect: { + switch (id(1)) { + default: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + break; + case 3: + console.log("PREVENT_IFS"); + } + } + expect_stdout: ["PASS"] +} + +collapse_same_branches_in_a_row2: { + options = { + switches: true, + dead_code: true + } + input: { + switch (id(1)) { + case 1: + console.log("PASS"); + break; + case 2: + console.log("FAIL"); + break; + case 3: + console.log("PASS"); + break; + case 4: + console.log("FAIL"); + break; + } + } + expect: { + switch (id(1)) { + case 1: + case 3: + console.log("PASS"); + break; + case 2: + case 4: + console.log("FAIL"); + } + } + expect_stdout: ["PASS"] +} + +collapse_same_branches_in_a_row_with_return: { + options = { + switches: true, + dead_code: true + } + input: { + function fn() { + switch (id(1)) { + case 1: + return "PASS"; + case 2: + return "FAIL"; + case 3: + return "PASS"; + case 4: + return "FAIL"; + } + } + console.log(fn()) + } + expect: { + function fn() { + switch (id(1)) { + case 1: + case 3: + return "PASS"; + case 2: + case 4: + return "FAIL"; + } + } + console.log(fn()) + } + expect_stdout: ["PASS"] +} + // Empty branches at the end of the switch get trimmed trim_empty_last_branches: { options = {