feat(minfier): drop last break from last switch case (#24673)

After many trial and errors, this seem to be the best way to remove
break stmts in switch cases,

This change targets only last break in last case of switch smts if its
unlabelled

this change differs from #23914 as its no longer targets all branches as
that should not be no longer necessary if all cases are correctly
rewritten

https://github.com/oxc-project/oxc/issues/24672

-----

cases like `switch (a) { case 3: if (b) break; }` are not changed

-----

ref: #17544
This commit is contained in:
Armano authored and GitHub committed 2026-07-20 13:21:03 +08:00
1 parent cb102dedd1
commit 7b045cd417
7 files changed
+103 -57

No files matched your search

-1
View File
@@ -518,7 +518,6 @@ function walkNode(node, visitors) {
break;
case "TSUnionType":
walkTSUnionType(node, visitors);
break;
}
}
@@ -820,6 +820,12 @@ impl<'a> PeepholeOptimizations {
ctx,
));
return;
} else if let Some(last_case) = switch_stmt.cases.last_mut()
&& let Some(Statement::BreakStatement(last_break)) = last_case.consequent.last()
&& last_break.label.is_none()
{
let dropped = last_case.consequent.pop().unwrap();
ctx.drop_statement(&dropped);
}
result.push(Statement::SwitchStatement(switch_stmt));
@@ -129,13 +129,13 @@ fn test_handle_switch_statement() {
test("switch (a()) {}", "a()");
test("switch (a) { default: }", "a;");
test("switch (a) { default: break;}", " a;");
test_same("switch (a) { default: var b; break;}"); // a; var b;
test("switch (a) { default: var b; break;}", "switch (a) { default: var b; }"); // a; var b;
test_same("switch (a) { default: b()}"); // a; b();
test_same("switch (a) { default: b(); return;}"); // a, b(); return;
test("switch (a) { case 1: break;}", "a;");
test_same("switch (a) { case 1: b();}"); // a === 1 && b();
test_same("switch (a) { case 1: b();break; }"); // a === 1 && b();
test("switch (a) { case 1: b();break; }", "switch (a) { case 1: b(); }"); // a === 1 && b();
test_same("switch (a) { case 1: b();return; }"); // if (a === 1) { b(); return; }
test("switch (a) { default: case 1: }", "a;");
@@ -145,35 +145,41 @@ fn test_handle_switch_statement() {
test_same("switch (a) { case 1: default: b(); case 2: c();}");
test_same("switch (a) { case 1: b(); default: break; case 2: c()}");
test_same("switch (a) { case 1: b(); case 2: break; case 3: c()}");
test_same("switch (a) { case 1: b(); break; case 2: c();break;}");
test(
"switch (a) { case 1: b(); break; case 2: c();break;}",
"switch (a) { case 1: b(); break; case 2: c();}",
);
test_same("switch (x) { default: foo(); case 1: }");
test_same("switch (a) { case 1: b(); case 2: b();}");
test_same("switch (a) { case 1: case 2: b(); }");
test_same("switch (a) { case 1: var c=2; break;}"); // if (a === 1) { var c=2; }
test(
"switch (a) { case 1: case 2: default: b(); break;}",
"switch (a) { default: b(); break;}",
); // a, b();
test("switch (a) { case 1: var c=2; break;}", "switch (a) { case 1: var c=2; }"); // if (a === 1) { var c=2; }
test("switch (a) { case 1: case 2: default: b(); break;}", "switch (a) { default: b(); }"); // a, b();
test("switch (a) { default: break; case 1: break;}", "a;");
test_same("switch (a) { default: b();break;case 1: c();break;}"); // a === 1 ? c() : b();
test(
"switch (a) { default: b();break;case 1: c();break;}",
"switch (a) { default: b();break;case 1: c();}",
); // a === 1 ? c() : b();
test(
"switch (a) { default: {b();break;} case 1: {c();break;}}",
"switch (a) { default: b();break;case 1: c();break;}",
"switch (a) { default: b();break;case 1: c(); }",
); // a === 1 ? c() : b();
test("switch (a) { case b(): default:}", "switch (a) { case b(): }"); // a, b();
test("switch (a) { case 2: case 1: break; default: break;}", "a;");
test("switch (a) { case 3: b(); break; case 2: break;}", "switch (a) { case 3: b(); break; }"); // a === 3 && b();
test("switch (a) { case 3: b(); break; case 2: break;}", "switch (a) { case 3: b(); }"); // a === 3 && b();
test("switch (a) { case 3: b(); case 2: break;}", "switch (a) { case 3: b(); }"); // a === 3 && b();
test_same("switch (a) { case 3: b(); case 2: c(); break;}");
test(
"switch (a) { case 3: b(); case 2: c(); break;}",
"switch (a) { case 3: b(); case 2: c(); }",
);
test("switch (a) { case 3: b(); case 2: case 1: break;}", "switch (a) { case 3: b(); }"); // a === 3 && b();
test("switch (a) { case 3: b(); case 2: case 1: }", "switch (a) { case 3: b(); }"); // a === 3 && b();
test_same("switch (x) { default: case 1: foo(); case 2: }"); // x !== 2 && foo();
test_same("switch (a) { case 3: if (b) break }"); // a === 3 && b;
test("switch (a) { case 3: if (b) break }", "switch (a) { case 3: if (b) break; }"); // a === 3 && b;
test(
"switch (a) { case 3: { if(b) {c()} else {break;} }}",
"switch (a) { case 3: if (b) c(); else break;}",
"switch (a) { case 3: if (b) c(); else break; }",
); // a === 3 && b && c();
test(
"switch (a) { case 3: { if(b) {c(); break;} else { d(); break;} }}",
@@ -189,13 +195,13 @@ fn test_handle_switch_statement() {
test(
"switch (a) { case 1: c(); case 2: default: b();break;}",
"switch (a) { case 1: c(); default: b(); break;}",
"switch (a) { case 1: c(); default: b(); }",
); // a === 1 && c(); b();
test_same("function f() { switch (a) { case 1: return;} }"); // function f() { a; }
test("switch (a()) { default: {let y;} }", "switch (a()) { default: { let y; } }"); // a(); { let y; }
test(
"function f(){switch ('x') { case 'x': var x = 1;break; case 'y': break; }}",
"function f(){switch ('x') { case 'x': var x = 1;break; }}",
"function f(){switch ('x') { case 'x': var x = 1; }}",
);
test("switch (a) { default: if(a) {break;}c();}", "switch (a) { default: if(a) break;c();}"); // a, !a && c();
test("switch (a) { case 1: if(a) {b();}c();}", "switch (a) { case 1: a && b(), c(); }"); // if (a === 1) { a && b(), c(); }
@@ -213,26 +219,56 @@ fn test_handle_switch_statement() {
"x: switch (a) { case 2: if (b) break outer; }",
); // x: if (a === 2 && b) break outer;
test_same("switch ('r') { case 'r': a();break; case 'r': var x=0;break;}"); // a();
test(
"switch ('r') { case 'r': a();break; case 'r': var x=0;break;}",
"switch ('r') { case 'r': a();break; case 'r': var x=0;}",
); // a();
test_same("switch (2) { default: a; case 1: b()}"); // a, b();
test_same("switch (1) { case 1: a();break; default: b();}"); // a();
test_same("switch ('e') { case 'e': case 'f': a();}"); // a();
test_same("switch ('a') { case 'a': a();break; case 'b': b();break;}"); // a();
test_same("switch ('c') { case 'a': a();break; case 'b': b();break;}"); // ;
test_same("switch (1) { case 1: a();break; case 2: bar();break;}"); // a();
test(
"switch ('a') { case 'a': a();break; case 'b': b();break;}",
"switch ('a') { case 'a': a();break; case 'b': b();}",
); // a();
test(
"switch ('c') { case 'a': a();break; case 'b': b();break;}",
"switch ('c') { case 'a': a();break; case 'b': b();}",
); // ;
test(
"switch (1) { case 1: a();break; case 2: bar();break;}",
"switch (1) { case 1: a();break; case 2: bar();}",
); // a();
test_same("switch ('f') { case 'f': a(); case 'b': b();}");
test_same("switch ('f') { case 'f': if (a() > 0) {b();break;} c(); case 'd': f();}");
test_same("switch ('f') { case 'b': bar();break; case x: x();break; case 'f': f();break;}");
test(
"switch ('f') { case 'b': bar();break; case x: x();break; case 'f': f();break;}",
"switch ('f') { case 'b': bar();break; case x: x();break; case 'f': f(); }",
);
test(
"switch (1) { case 1: case 2: {break;} case 3: case 4: default: b(); break;}",
"switch (1) { case 1: case 2: break; default: b(); break; }",
"switch (1) { case 1: case 2: break; default: b(); }",
);
test_same("switch ('d') { case 'foo': foo();break; default: bar();break;}"); // bar()
test_same("switch (0) { case NaN: foobar();break;case -0: foo();break; case 2: bar();break;}"); // foo()
test_same("let x = 1; switch ('x') { case 'x': let x = 2; break;}"); // let x = 1; { let x = 2; }
test(
"switch ('d') { case 'foo': foo();break; default: bar();break;}",
"switch ('d') { case 'foo': foo();break; default: bar();}",
); // bar()
test(
"switch (0) { case NaN: foobar();break;case -0: foo();break; case 2: bar();break;}",
"switch (0) { case NaN: foobar();break;case -0: foo();break; case 2: bar();}",
); // foo()
test(
"let x = 1; switch ('x') { case 'x': let x = 2; break;}",
"let x = 1; switch ('x') { case 'x': let x = 2; }",
); // let x = 1; { let x = 2; }
test_same("switch (1) { case 2: var x=0;}"); // if (0) var x;
test_same("switch (b) { case 2: switch (a) { case 2: a();break;case 3: foo();break;}}"); // ;
test(
"switch (b) { case 2: switch (a) { case 2: a();break;case 3: foo();break;}}",
"switch (b) { case 2: switch (a) { case 2: a();break;case 3: foo();}}",
); // ;
test_same("switch (b) { case 2: switch (a) { case 2: foo()}}"); // if (b === 2 && a === 2) foo()
test_same("function f(){ switch (0) { case x: break; } let x = 1; }"); // TDZ
test(
"function f(){ switch (0) { case x: break; } let x = 1; }",
"function f(){ switch (0) { case x: } let x = 1; }",
); // TDZ
}
@@ -303,18 +303,24 @@ fn test_loop_optimization_edge_cases() {
#[test]
fn test_switch_statement_edge_cases() {
// Test switch with constant discriminant - might be optimized in future
test_same("switch (2) { case 1: a(); break; case 2: b(); break; case 3: c(); break; }");
test_same("switch (2) { case 1: a(); break; case 2: b(); break; case 3: c(); }");
// Could be optimized to just: b();
test_same("switch ('test') { case 'foo': a(); break; case 'test': b(); break; default: c(); }");
// Could be optimized to just: b();
// Test switch with no matching case
test_same("switch (5) { case 1: a(); break; case 2: b(); break; }");
test(
"switch (5) { case 1: a(); break; case 2: b(); break; }",
"switch (5) { case 1: a(); break; case 2: b(); }",
);
// Could be optimized to empty
// Test switch with default
test_same("switch (5) { case 1: a(); break; default: b(); break; }");
test(
"switch (5) { case 1: a(); break; default: b(); break; }",
"switch (5) { case 1: a(); break; default: b(); }",
);
// Could be optimized to just: b();
// Test switch with fall-through - more complex, keep as same for safety
@@ -504,7 +504,6 @@ function walkNode(node, visitors) {
break;
case "TSUnionType":
walkTSUnionType(node, visitors);
break;
}
}
+10 -10
View File
@@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
-------------------------------------------------------------------------------------
72.14 kB | 23.12 kB | 23.70 kB | 8.34 kB | 8.54 kB | 2 | react.development.js
173.90 kB | 59.40 kB | 59.82 kB | 19.15 kB | 19.33 kB | 2 | moment.js
173.90 kB | 59.37 kB | 59.82 kB | 19.15 kB | 19.33 kB | 2 | moment.js
287.63 kB | 89.24 kB | 90.07 kB | 30.91 kB | 31.95 kB | 2 | jquery.js
342.15 kB | 116.97 kB | 118.14 kB | 43.17 kB | 44.37 kB | 2 | vue.js
342.15 kB | 116.95 kB | 118.14 kB | 43.17 kB | 44.37 kB | 2 | vue.js
544.10 kB | 70.92 kB | 72.48 kB | 25.73 kB | 26.20 kB | 2 | lodash.js
544.10 kB | 70.91 kB | 72.48 kB | 25.73 kB | 26.20 kB | 2 | lodash.js
555.77 kB | 267.38 kB | 270.13 kB | 88.00 kB | 90.80 kB | 2 | d3.js
555.77 kB | 267.20 kB | 270.13 kB | 87.98 kB | 90.80 kB | 2 | d3.js
1.01 MB | 439.33 kB | 458.89 kB | 122.04 kB | 126.71 kB | 2 | bundle.min.js
1.01 MB | 439.29 kB | 458.89 kB | 122.05 kB | 126.71 kB | 2 | bundle.min.js
1.25 MB | 642.61 kB | 646.76 kB | 159.40 kB | 163.73 kB | 2 | three.js
1.25 MB | 642.52 kB | 646.76 kB | 159.39 kB | 163.73 kB | 2 | three.js
2.14 MB | 711.03 kB | 724.14 kB | 160.38 kB | 181.07 kB | 2 | victory.js
2.14 MB | 710.90 kB | 724.14 kB | 160.38 kB | 181.07 kB | 2 | victory.js
3.20 MB | 1.00 MB | 1.01 MB | 322.59 kB | 331.56 kB | 2 | echarts.js
3.20 MB | 1.00 MB | 1.01 MB | 322.56 kB | 331.56 kB | 2 | echarts.js
6.69 MB | 2.12 MB | 2.31 MB | 453.85 kB | 488.28 kB | 5 | antd.js
6.69 MB | 2.12 MB | 2.31 MB | 453.83 kB | 488.28 kB | 5 | antd.js
10.95 MB | 3.33 MB | 3.49 MB | 853.03 kB | 915.50 kB | 4 | typescript.js
10.95 MB | 3.33 MB | 3.49 MB | 852.92 kB | 915.50 kB | 4 | typescript.js
@@ -5,9 +5,9 @@ checker.ts:
sys deallocs: 152
sys alloc bytes: 15028748 # 15.03 MB
sys peak growth: 10203896 # 10.20 MB
arena allocs: 152763
arena reallocs: 28276
arena size: 19312968 # 19.31 MB
arena allocs: 152706
arena reallocs: 28225
arena size: 19309904 # 19.31 MB
App.tsx:
file size: 415342 # 415.34 kB
@@ -16,8 +16,8 @@ App.tsx:
sys deallocs: 63
sys alloc bytes: 2136669 # 2.14 MB
sys peak growth: 1620605 # 1.62 MB
arena allocs: 17029
arena reallocs: 2702
arena allocs: 17027
arena reallocs: 2700
arena size: 2915736 # 2.92 MB
RadixUIAdoptionSection.jsx:
@@ -38,9 +38,9 @@ pdf.mjs:
sys deallocs: 2467
sys alloc bytes: 5348727 # 5.35 MB
sys peak growth: 3707391 # 3.71 MB
arena allocs: 47452
arena reallocs: 7743
arena size: 6375424 # 6.38 MB
arena allocs: 47424
arena reallocs: 7715
arena size: 6374064 # 6.37 MB
antd.js:
file size: 6686316 # 6.69 MB
@@ -49,9 +49,9 @@ antd.js:
sys deallocs: 1038
sys alloc bytes: 29987563 # 29.99 MB
sys peak growth: 19934725 # 19.93 MB
arena allocs: 371535
arena reallocs: 76376
arena size: 49107608 # 49.11 MB
arena allocs: 371435
arena reallocs: 76276
arena size: 49102648 # 49.10 MB
binder.ts:
file size: 193077 # 193.08 kB
@@ -60,8 +60,8 @@ binder.ts:
sys deallocs: 33
sys alloc bytes: 963614 # 963.61 kB
sys peak growth: 658042 # 658.04 kB
arena allocs: 7084
arena reallocs: 840
arena allocs: 7082
arena reallocs: 838
arena size: 1169808 # 1.17 MB
kitchen-sink.tsx:
@@ -71,7 +71,7 @@ kitchen-sink.tsx:
sys deallocs: 1867
sys alloc bytes: 5346850 # 5.35 MB
sys peak growth: 3738631 # 3.74 MB
arena allocs: 71947
arena reallocs: 12262
arena allocs: 71929
arena reallocs: 12244
arena size: 9560960 # 9.56 MB