Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions crates/oxc_minifier/src/peephole/minimize_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,37 @@ impl<'a> PeepholeOptimizations {
result.pop();
ctx.state.changed = true;
}

// Prune empty case clauses before a trailing default
// e.g., `switch (x) { case 0: foo(); break; case 1: default: bar() }`
// => `switch (x) { case 0: foo(); break; default: bar() }`
// https://github.com/evanw/esbuild/commit/add452ed51333953dd38a26f28a775bb220ea2e9
if let Some(last_case) = switch_stmt.cases.last()
&& last_case.test.is_none()
{
let default_idx = switch_stmt.cases.len() - 1;
let mut first_empty_idx = default_idx;
// Iterate backward through preceding cases
while first_empty_idx > 0 {
let case = &switch_stmt.cases[first_empty_idx - 1];
// Only remove empty cases with primitive literal tests
if case.consequent.is_empty()
Copy link
Copy Markdown
Contributor

@armano2 armano2 Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case that contains only break without label, block stmt, block with break could be also be considered empty

&& case.test.as_ref().is_some_and(Expression::is_literal)
{
first_empty_idx -= 1;
} else {
break;
}
}
// If we found cases to remove, keep cases [0..first_empty_idx] + [default]
if first_empty_idx < default_idx {
let default_case = switch_stmt.cases.pop().unwrap();
switch_stmt.cases.truncate(first_empty_idx);
switch_stmt.cases.push(default_case);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default case should be ommited when its empty, either empty block or no stmt

ctx.state.changed = true;
}
}

result.push(Statement::SwitchStatement(switch_stmt));
}

Expand Down
46 changes: 46 additions & 0 deletions crates/oxc_minifier/tests/peephole/esbuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2377,3 +2377,49 @@ fn test_remove_dead_expr_other() {
test("using x = null, y = z", "using x = null, y = z;");
test("using x = z, y = undefined", "using x = z, y = void 0;");
}

// https://github.com/evanw/esbuild/commit/add452ed51333953dd38a26f28a775bb220ea2e9
#[test]
fn prune_empty_case_before_default() {
// Basic case: empty case with literal before default
test(
"switch (x) { case 0: foo(); break; case 1: default: bar() }",
"switch (x) { case 0: foo(); break; default: bar();}",
);

// Multiple empty cases with literals before default
test(
"switch (x) { case 0: foo(); break; case 1: case 2: case 3: default: bar() }",
"switch (x) { case 0: foo(); break; default: bar();}",
);

// Empty case with non-literal (identifier) should NOT be removed
test(
"switch (x) { case 0: foo(); break; case y: default: bar() }",
"switch (x) { case 0: foo(); break; case y: default: bar();}",
);

// Empty default not at end should be preserved
test("switch (x) { default: case 1: bar() }", "switch (x) { default: case 1: bar();}");

// String literals should also be removed
test("switch (x) { case 'a': case 'b': default: bar() }", "switch (x) { default: bar();}");

// null literal (booleans get transformed to !0/!1 before this optimization runs)
test("switch (x) { case null: default: bar() }", "switch (x) { default: bar();}");

// BigInt literals
test("switch (x) { case 1n: case 2n: default: bar() }", "switch (x) { default: bar();}");

// Non-empty case should stop the pruning
test(
"switch (x) { case 0: case 1: foo(); case 2: case 3: default: bar() }",
"switch (x) { case 0: case 1: foo(); default: bar();}",
);

// Only default - nothing to prune
test("switch (x) { default: bar() }", "switch (x) { default: bar();}");

// No default - nothing to prune
test("switch (x) { case 0: foo(); case 1: }", "switch (x) { case 0: foo(); case 1:}");
}
2 changes: 1 addition & 1 deletion tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Iterations | Fi

6.69 MB | 2.22 MB | 2.31 MB | 458.44 kB | 488.28 kB | 4 | antd.js

10.95 MB | 3.33 MB | 3.49 MB | 853.36 kB | 915.50 kB | 4 | typescript.js
10.95 MB | 3.33 MB | 3.49 MB | 853.35 kB | 915.50 kB | 4 | typescript.js

Loading