diff --git a/apps/oxlint/src-js/generated/walk.js b/apps/oxlint/src-js/generated/walk.js index 6bea75c377f89..b094ea8c90010 100644 --- a/apps/oxlint/src-js/generated/walk.js +++ b/apps/oxlint/src-js/generated/walk.js @@ -520,7 +520,6 @@ function walkNode(node, visitors) { break; case "TSUnionType": walkTSUnionType(node, visitors); - break; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_switch_statement.rs b/crates/oxc_minifier/src/peephole/minimize_switch_statement.rs new file mode 100644 index 0000000000000..5489cc238de2c --- /dev/null +++ b/crates/oxc_minifier/src/peephole/minimize_switch_statement.rs @@ -0,0 +1,367 @@ +use super::PeepholeOptimizations; +use crate::ctx::Ctx; +use oxc_allocator::{TakeIn, Vec}; +use oxc_ast::ast::*; +use oxc_ast_visit::{Visit, walk}; +use oxc_ecmascript::side_effects::MayHaveSideEffects; +use oxc_span::{GetSpan, SPAN}; +use oxc_syntax::{operator::BinaryOperator, scope::ScopeFlags}; + +impl<'a> PeepholeOptimizations { + /// Attempts to minimize a `switch` statement by applying a series of transformations + /// - Removes the trailing `break` statement from the last case of the `switch`, if it's unnecessary. + /// - Merges or removes consecutive empty cases within the switch to simplify its structure. + /// - Eliminates the entire `switch` statement if it contains no meaningful cases or logic. + /// - Converts the `switch` if it contains only one or two cases to `if`/`else` statements. + pub fn try_minimize_switch(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { + Self::try_remove_last_break_from_case(stmt, ctx); + Self::collapse_empty_switch_cases(stmt, ctx); + Self::remove_empty_switch(stmt, ctx); + Self::fold_switch_with_one_case(stmt, ctx); + Self::fold_switch_with_two_cases(stmt, ctx); + } + + /// Attempts to remove the last `break` statement from the last case of a switch statement. + fn try_remove_last_break_from_case(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { + let Statement::SwitchStatement(switch_stmt) = stmt else { + return; + }; + + if let Some(last_case) = switch_stmt.cases.last_mut() + && Self::remove_last_break(&mut last_case.consequent, ctx) + { + ctx.state.changed = true; + } + } + + /// Collapses empty cases in a `SwitchStatement` by removing redundant cases with empty + /// consequent's and consolidating them into a more concise representation. + /// + /// - If the switch statement contains one or fewer cases, it is considered already optimal, and no actions are taken. + /// - If the `default` case is the last case, it is treated as a special case where its emptiness directly + /// influences the analysis of the rest of the cases. + /// - The function identifies a `removable suffix` of cases at the end of the statement, starting from the first + /// non-empty case or case with side-effect-producing expressions backward to the last case. + /// - All cases in the identified removable suffix are eliminated, except for the last case, + /// which is preserved and its test is removed (if applicable). + fn collapse_empty_switch_cases(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { + let Statement::SwitchStatement(switch_stmt) = stmt else { + return; + }; + + let case_count = switch_stmt.cases.len(); + if case_count <= 1 { + return; + } + + // if a default case is last we can skip checking if it has body + let (end, allow_break) = if let Some(default_pos) = + switch_stmt.cases.iter().rposition(SwitchCase::is_default_case) + { + if default_pos == case_count - 1 { + ( + case_count - 1, + Self::is_empty_switch_case(&switch_stmt.cases[default_pos].consequent, true), + ) + } else { + (case_count, false) + } + } else { + (case_count, true) + }; + + // Find the last non-removable case (any case whose consequent is non-empty). + let last_non_empty_before_last = switch_stmt.cases[..end].iter().rposition(|case| { + !Self::is_empty_switch_case(&case.consequent, allow_break) + || case.test.as_ref().is_some_and(|test| test.may_have_side_effects(ctx)) + }); + + // start is the first index of the removable suffix + let start = match last_non_empty_before_last { + Some(pos) => pos + 1, + None => 0, + }; + + // nothing removable + if start >= end { + return; + } + + let Some(mut last) = switch_stmt.cases.pop() else { + return; + }; + switch_stmt.cases.truncate(start); + + if !Self::is_empty_switch_case(&last.consequent, true) { + last.test = None; + switch_stmt.cases.push(last); + } + ctx.state.changed = true; + } + + /// Removes an empty switch statement from the given AST statement. + fn remove_empty_switch(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { + let Statement::SwitchStatement(switch_stmt) = stmt else { + return; + }; + if switch_stmt.cases.is_empty() { + if switch_stmt.discriminant.may_have_side_effects(ctx) { + *stmt = ctx.ast.statement_expression( + switch_stmt.span, + switch_stmt.discriminant.take_in(ctx.ast), + ); + } else { + *stmt = ctx.ast.statement_empty(switch_stmt.span); + } + ctx.state.changed = true; + } + } + + /// Simplifies a `switch` statement with exactly two cases into an equivalent `if` statement. + /// + /// This transformation is applicable when the `switch` statement meets the following criteria: + /// - It contains exactly two cases. + /// - One of the cases represents the `default` case, and the other defines a condition (`test`). + /// - Both cases can be safely inlined without reordering or modifying program behavior. + /// - Both cases are terminated properly (e.g., with a `break` statement). + fn fold_switch_with_two_cases(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { + let Statement::SwitchStatement(switch_stmt) = stmt else { + return; + }; + + // check whatever its default + case + if switch_stmt.cases.len() != 2 + || switch_stmt.cases[0].test.is_some() == switch_stmt.cases[1].test.is_some() + || !Self::is_terminated_switch_case(&switch_stmt.cases[0].consequent) + || !Self::can_case_be_inlined(&switch_stmt.cases[0], ctx) + || !Self::can_case_be_inlined(&switch_stmt.cases[1], ctx) + { + return; + } + + let mut first = switch_stmt.cases.pop().unwrap(); + let mut second = switch_stmt.cases.pop().unwrap(); + Self::remove_last_break(&mut first.consequent, ctx); + Self::remove_last_break(&mut second.consequent, ctx); + + let (test, consequent, alternate) = if first.test.is_some() { + (first.test.unwrap(), first.consequent, second.consequent) + } else { + (second.test.unwrap(), second.consequent, first.consequent) + }; + + ctx.state.changed = true; + *stmt = ctx.ast.statement_if( + switch_stmt.span, + ctx.ast.expression_binary( + SPAN, + switch_stmt.discriminant.take_in(ctx.ast), + BinaryOperator::StrictEquality, + test, + ), + Self::create_if_block_from_switch_case(consequent, ctx), + Some(Self::create_if_block_from_switch_case(alternate, ctx)), + ); + } + + fn create_if_block_from_switch_case( + mut vec: Vec<'a, Statement<'a>>, + ctx: &mut Ctx<'a, '_>, + ) -> Statement<'a> { + if vec.len() == 1 && matches!(vec.first(), Some(Statement::BlockStatement(_))) { + vec.pop().unwrap() + } else { + ctx.ast.statement_block_with_scope_id( + SPAN, + vec, + ctx.create_child_scope_of_current(ScopeFlags::empty()), + ) + } + } + + fn fold_switch_with_one_case(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { + let Statement::SwitchStatement(switch_stmt) = stmt else { + return; + }; + if switch_stmt.cases.len() == 1 { + let Some(first_case) = switch_stmt.cases.first() else { return }; + if !Self::can_case_be_inlined(first_case, ctx) { + return; + } + let mut case = switch_stmt.cases.pop().unwrap(); + + ctx.state.changed = true; + let discriminant = switch_stmt.discriminant.take_in(ctx.ast); + Self::remove_last_break(&mut case.consequent, ctx); + + if let Some(test) = case.test { + *stmt = ctx.ast.statement_if( + switch_stmt.span, + ctx.ast.expression_binary( + SPAN, + discriminant, + BinaryOperator::StrictEquality, + test, + ), + Self::create_if_block_from_switch_case(case.consequent, ctx), + None, + ); + } else { + let mut stmts = ctx.ast.vec(); + if discriminant.may_have_side_effects(ctx) { + stmts.push(ctx.ast.statement_expression(discriminant.span(), discriminant)); + } + if !Self::is_empty_switch_case(&case.consequent, true) { + stmts.extend(case.consequent); + } + *stmt = ctx.ast.statement_block_with_scope_id( + switch_stmt.span, + stmts, + ctx.create_child_scope_of_current(ScopeFlags::empty()), + ); + } + } + } + + fn is_empty_switch_case(stmt: &Vec<'a, Statement<'a>>, allow_break: bool) -> bool { + if stmt.len() != 1 { + return stmt.is_empty(); + } + match stmt.last() { + Some(Statement::EmptyStatement(_)) => true, + Some(Statement::BlockStatement(block_stmt)) => { + Self::is_empty_switch_case(&block_stmt.body, allow_break) + } + Some(Statement::BreakStatement(break_stmt)) => { + break_stmt.label.is_none() && allow_break + } + _ => false, + } + } + + fn remove_break_from_statement(stmt: &mut Statement<'a>, ctx: &Ctx<'a, '_>) -> bool { + match stmt { + Statement::BreakStatement(break_stmt) => { + if break_stmt.label.is_none() { + *stmt = ctx.ast.statement_empty(break_stmt.span); + true + } else { + false + } + } + Statement::BlockStatement(block_stmt) => { + Self::remove_last_break(&mut block_stmt.body, ctx) + } + Statement::IfStatement(if_stmt) => { + let mut changed = Self::remove_break_from_statement(&mut if_stmt.consequent, ctx); + if let Some(alternate) = &mut if_stmt.alternate { + changed |= Self::remove_break_from_statement(alternate, ctx); + } + changed + } + _ => false, + } + } + + fn remove_last_break(stmt: &mut Vec<'a, Statement<'a>>, ctx: &Ctx<'a, '_>) -> bool { + if stmt.is_empty() { + return false; + } + + let len = stmt.len(); + match stmt.last_mut() { + Some(Statement::BreakStatement(break_stmt)) => { + if break_stmt.label.is_none() { + stmt.truncate(len - 1); + true + } else { + false + } + } + Some(stmt) => Self::remove_break_from_statement(stmt, ctx), + _ => false, + } + } + + fn is_terminated_switch_case(stmt: &Vec<'a, Statement<'a>>) -> bool { + if stmt.is_empty() { + return false; + } + match stmt.last() { + Some(Statement::BlockStatement(block_stmt)) => { + Self::is_terminated_switch_case(&block_stmt.body) + } + Some(last) => last.is_jump_statement(), + _ => false, + } + } + + fn can_case_be_inlined(case: &SwitchCase<'a>, ctx: &Ctx<'a, '_>) -> bool { + if case.test.as_ref().is_some_and(|test| test.may_have_side_effects(ctx)) { + return false; + } + + let mut break_finder = FindNestedBreak::new(); + break_finder.visit_switch_case(case); + !break_finder.nested_unlabelled_break + } +} + +struct FindNestedBreak { + top_level: bool, + nested_unlabelled_break: bool, +} + +impl FindNestedBreak { + pub fn new() -> Self { + Self { top_level: true, nested_unlabelled_break: false } + } +} + +// TODO: This is to aggressive, we should allow `break` for last elements in statements +impl<'a> Visit<'a> for FindNestedBreak { + fn visit_expression(&mut self, _it: &Expression<'a>) { + // do nothing + } + + fn visit_statement(&mut self, it: &Statement<'a>) { + if it.is_declaration() || it.is_iteration_statement() { + return; + } + match it { + Statement::ThrowStatement(_) + | Statement::SwitchStatement(_) + | Statement::ContinueStatement(_) + | Statement::ReturnStatement(_) + | Statement::ExpressionStatement(_) => {} + _ => walk::walk_statement(self, it), + } + } + + fn visit_if_statement(&mut self, it: &IfStatement<'a>) { + let was_top = self.top_level; + self.top_level = false; + walk::walk_if_statement(self, it); + self.top_level = was_top; + } + + fn visit_break_statement(&mut self, it: &BreakStatement<'a>) { + if !self.top_level && it.label.is_none() { + self.nested_unlabelled_break = true; + } + } + + fn visit_with_statement(&mut self, it: &WithStatement<'a>) { + let was_top = self.top_level; + self.top_level = false; + walk::walk_with_statement(self, it); + self.top_level = was_top; + } + + fn visit_try_statement(&mut self, it: &TryStatement<'a>) { + let was_top = self.top_level; + self.top_level = false; + walk::walk_try_statement(self, it); + self.top_level = was_top; + } +} diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 5882629b89551..d75370432cb87 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -9,6 +9,7 @@ mod minimize_if_statement; mod minimize_logical_expression; mod minimize_not_expression; mod minimize_statements; +mod minimize_switch_statement; mod normalize; mod remove_dead_code; mod remove_unused_declaration; @@ -226,6 +227,9 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations { ctx.state.changed = true; } } + Statement::SwitchStatement(_) => { + Self::try_minimize_switch(stmt, ctx); + } Statement::WhileStatement(s) => { Self::minimize_expression_in_boolean_context(&mut s.test, ctx); } diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index f85edf6562d87..143144c48a30a 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -2021,7 +2021,7 @@ fn test_inline_single_use_variable() { ); test( "function wrapper(arg0, arg1) { let x = arg0; switch (x) { case 0: return 1; }}", - "function wrapper(arg0, arg1) { switch (arg0) { case 0: return 1; }}", + "function wrapper(arg0, arg1) { if (arg0 === 0) return 1; }", ); test( "function wrapper(arg0, arg1) { let x = arg0; let y = x; return y + y;}", @@ -2382,15 +2382,12 @@ fn test_remove_dead_expr_other() { #[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();}", - ); + test("switch (x) { case 0: foo(); break; case 1: default: bar() }", "x === 0 ? foo() : 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();}", + "x === 0 ? foo() : bar()", ); // Empty case with non-literal (identifier) should NOT be removed @@ -2403,13 +2400,13 @@ fn prune_empty_case_before_default() { 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();}"); + test("switch (x) { case 'a': case 'b': default: bar() }", "x, bar()"); // null literal (booleans get transformed to !0/!1 before this optimization runs) - test("switch (x) { case null: default: bar() }", "switch (x) { default: bar();}"); + test("switch (x) { case null: default: bar() }", "x, bar()"); // BigInt literals - test("switch (x) { case 1n: case 2n: default: bar() }", "switch (x) { default: bar();}"); + test("switch (x) { case 1n: case 2n: default: bar() }", "x, bar()"); // Non-empty case should stop the pruning test( @@ -2418,8 +2415,8 @@ fn prune_empty_case_before_default() { ); // Only default - nothing to prune - test("switch (x) { default: bar() }", "switch (x) { default: bar();}"); + test("switch (x) { default: bar() }", "x, bar()"); // No default - nothing to prune - test("switch (x) { case 0: foo(); case 1: }", "switch (x) { case 0: foo(); case 1:}"); + test("switch (x) { case 0: foo(); case 1: }", "x === 0 && foo()"); } diff --git a/crates/oxc_minifier/tests/peephole/minimize_conditions.rs b/crates/oxc_minifier/tests/peephole/minimize_conditions.rs index df41be5050a39..0fcaabc7d6613 100644 --- a/crates/oxc_minifier/tests/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/tests/peephole/minimize_conditions.rs @@ -29,7 +29,7 @@ fn test_fold_one_child_blocks() { test_same("function f(){try{foo()}catch(e){bar(e)}finally{baz()}}"); // Try it out with switch statements - test_same("function f(){switch(x){case 1:break}}"); + test("function f(){switch(x){case 1:break}}", "function f(){x;}"); // Do while loops stay in a block if that's where they started test( diff --git a/crates/oxc_minifier/tests/peephole/minimize_switch_statement.rs b/crates/oxc_minifier/tests/peephole/minimize_switch_statement.rs new file mode 100644 index 0000000000000..e949a47131fcf --- /dev/null +++ b/crates/oxc_minifier/tests/peephole/minimize_switch_statement.rs @@ -0,0 +1,124 @@ +use crate::{test, test_same}; + +#[expect(clippy::literal_string_with_formatting_args)] +#[test] +fn minimize_switch() { + test("switch(a()){}", "a()"); + test("switch(a){default: }", "a;"); + test("switch(a){default: break;}", "a;"); + test("switch(a){default: var b; break;}", "a;var b"); + test("switch(a){default: b()}", "a, b();"); + test("switch(a){default: b(); return;}", "a, b(); return"); + + test("switch(a){case 1: break;}", "a;"); + test("switch(a){case 1: b();}", "a === 1 && b()"); + test("switch(a){case 1: b();break; }", "a === 1 && b()"); + test("switch(a){case 1: b();return; }", "if (a === 1) { b(); return; }"); + + test("switch(a){default: case 1: }", "a"); + test("switch(a){case 1: default: }", "a"); + test_same("switch(a){case 1: default: break; case 2: b()}"); + test_same("switch(a){case 1: b(); default: 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( + "switch(a){case 1: b(); break; case 2: c();break;}", + "switch(a){case 1: b(); break; case 2: c();}", + ); + test_same("switch(a){case 1: b(); case 2: b();}"); + test("switch(a){case 1: var c=2; break;}", "if (a === 1) var c = 2"); + test("switch(a){case 1: case 2: default: b(); break;}", "a, b()"); + + test("switch(a){default: break; case 1: break;}", "a"); + test("switch(a){default: b();break;case 1: c();break;}", "a === 1 ? c() : b()"); + test("switch(a){default: {b();break;} case 1: {c();break;}}", "a === 1 ? c() : b()"); + + test_same("switch(a){case b(): default:}"); + test("switch(a){case 2: case 1: break; default: break;}", "a;"); + test("switch(a){case 3: b(); break; case 2: break;}", "a === 3 && b()"); + test("switch(a){case 3: b(); case 2: break;}", "a === 3 && b()"); + 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;}", "a === 3 && b()"); + test("switch(a){case 3: b(); case 2: case 1: }", "a === 3 && b()"); + test("switch(a){case 3: if (b) break }", "a === 3 && b"); + test("switch(a){case 3: { if (b) break } }", "a === 3 && b"); + test("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;} }}", + "a === 3 && (b ? c() : d())", + ); + test("switch(a){case 3: { for (;;) break } }", "if(a === 3) for (;;) break;"); + test("switch(a){case 3: { for (b of c) break } }", "if (a === 3) for (b of c) break;"); + test_same("switch(a){case 3: with(b) break}"); + test("switch(a){case 3: while(!0) break}", "if (a === 3) for (;;) break;"); + + test( + "switch(a){case 1: c(); case 2: default: b();break;}", + "switch(a){case 1: c(); default: b();}", + ); + test("var x=1;switch(x){case 1: var y;}", "var y;"); + test("function f(){switch(a){case 1: return;}}", "function f() {a;}"); + test("switch(a()) { default: {let y;} }", "a();{let y;}"); + test( + "function f(){switch('x'){case 'x': var x = 1;break; case 'y': break; }}", + "function f(){var x = 1;}", + ); + test("switch(a){default: if(a) {break;}c();}", "switch(a){default: if(a) break;c();}"); + test("switch(a){case 1: if(a) {b();}c();}", "a === 1 && (a && b(), c())"); + test("switch ('\\v') {case '\\u000B': foo();}", "foo()"); + + test("x: switch(a){case 1: break x;}", "x: if (a === 1) break x;"); + test_same("x: switch(a){case 2: break x; case 1: break x;}"); + test("x: switch(2){case 2: f(); break outer; }", "x: {f(); break outer;}"); + test( + "x: switch(x){case 2: f(); for (;;){break outer;}}", + "x: if(x===2) for(f();;) break outer", + ); + test("x: switch(a){case 2: if(b) { break outer; } }", "x: if(a===2 && b) break outer;"); + + test( + "switch('r'){case 'r': a();break; case 'r': var x=0;break;}", + "switch('r'){case 'r': a();break; case 'r': var x=0;}", + ); + test( + "switch('r'){case 'r': a();break; case 'r': bar();break;}", + "switch('r'){case 'r': a();break; case 'r': bar()}", + ); + test_same("switch(2) {default: a; case 1: b()}"); + test("switch(1) {case 1: a();break; default: b();}", "a()"); + test_same("switch('e') {case 'e': case 'f': a();}"); + test( + "switch('a') {case 'a': a();break; case 'b': b();break;}", + "switch('a') {case 'a': a();break; case 'b': b();}", + ); + 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();}", + ); + 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( + "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();}", + ); + test("switch ('d') {case 'foo': foo();break; default: bar();break;}", "bar()"); + test( + "switch(0){case NaN: foobar();break;case -0.0: foo();break; case 2: bar();break;}", + "switch(0){case NaN: foobar();break;case -0.0: foo();break; case 2: bar();}", + ); + test("let x = 1; switch('x') { case 'x': let x = 2; break;}", "let x = 1; { let x = 2 }"); + test("switch(1){case 2: var x=0;}", "if (0) var x;"); + test( + "switch(b){case 2: switch(a){case 2: a();break;case 3: foo();break;}}", + "if (b === 2) switch (a) {case 2: a(); break; case 3: foo();}", + ); + test("switch(b){case 2: switch(a){case 2: foo()}}", "b === 2 && a === 2 && foo();"); +} diff --git a/crates/oxc_minifier/tests/peephole/mod.rs b/crates/oxc_minifier/tests/peephole/mod.rs index c9f59fc868f61..d5fccddf41daf 100644 --- a/crates/oxc_minifier/tests/peephole/mod.rs +++ b/crates/oxc_minifier/tests/peephole/mod.rs @@ -14,6 +14,7 @@ mod minimize_expression_in_boolean_context; mod minimize_if_statement; mod minimize_not_expression; mod minimize_statements; +mod minimize_switch_statement; mod normalize; mod obscure_edge_cases; mod oxc; diff --git a/crates/oxc_minifier/tests/peephole/obscure_edge_cases.rs b/crates/oxc_minifier/tests/peephole/obscure_edge_cases.rs index f08401e826907..4212ee337beb9 100644 --- a/crates/oxc_minifier/tests/peephole/obscure_edge_cases.rs +++ b/crates/oxc_minifier/tests/peephole/obscure_edge_cases.rs @@ -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( + "switch (2) { case 1: a(); break; case 2: b(); break; case 3: c(); break; }", + "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; }", "b()"); // Could be optimized to just: b(); // Test switch with fall-through - more complex, keep as same for safety diff --git a/crates/oxc_minifier/tests/peephole/statement_fusion.rs b/crates/oxc_minifier/tests/peephole/statement_fusion.rs index 93b6973fd945c..df83ee51a5d08 100644 --- a/crates/oxc_minifier/tests/peephole/statement_fusion.rs +++ b/crates/oxc_minifier/tests/peephole/statement_fusion.rs @@ -39,7 +39,7 @@ fn fold_block_throw() { #[test] fn fold_switch() { - test("a;b;c;switch(x){}", "switch(a,b,c,x){}"); + test("a;b;c;switch(x){}", "a,b,c,x"); } #[test] diff --git a/napi/parser/src-js/generated/visit/walk.js b/napi/parser/src-js/generated/visit/walk.js index 739ee1a4e6ece..243a00a17aebb 100644 --- a/napi/parser/src-js/generated/visit/walk.js +++ b/napi/parser/src-js/generated/visit/walk.js @@ -506,7 +506,6 @@ function walkNode(node, visitors) { break; case "TSUnionType": walkTSUnionType(node, visitors); - break; } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 8d79741b5275b..c76120ac2e915 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Iterations | Fi ------------------------------------------------------------------------------------- 72.14 kB | 23.18 kB | 23.70 kB | 8.38 kB | 8.54 kB | 2 | react.development.js -173.90 kB | 59.40 kB | 59.82 kB | 19.16 kB | 19.33 kB | 2 | moment.js +173.90 kB | 59.36 kB | 59.82 kB | 19.15 kB | 19.33 kB | 2 | moment.js 287.63 kB | 89.26 kB | 90.07 kB | 30.91 kB | 31.95 kB | 2 | jquery.js -342.15 kB | 116.98 kB | 118.14 kB | 43.17 kB | 44.37 kB | 2 | vue.js +342.15 kB | 116.97 kB | 118.14 kB | 43.17 kB | 44.37 kB | 2 | vue.js -544.10 kB | 71.04 kB | 72.48 kB | 25.78 kB | 26.20 kB | 2 | lodash.js +544.10 kB | 71.04 kB | 72.48 kB | 25.77 kB | 26.20 kB | 2 | lodash.js -555.77 kB | 267.39 kB | 270.13 kB | 88.00 kB | 90.80 kB | 2 | d3.js +555.77 kB | 267.21 kB | 270.13 kB | 87.99 kB | 90.80 kB | 2 | d3.js -1.01 MB | 439.37 kB | 458.89 kB | 122.06 kB | 126.71 kB | 2 | bundle.min.js +1.01 MB | 439.32 kB | 458.89 kB | 122.03 kB | 126.71 kB | 2 | bundle.min.js -1.25 MB | 642.66 kB | 646.76 kB | 159.40 kB | 163.73 kB | 2 | three.js +1.25 MB | 642.55 kB | 646.76 kB | 159.37 kB | 163.73 kB | 2 | three.js -2.14 MB | 711.11 kB | 724.14 kB | 160.43 kB | 181.07 kB | 2 | victory.js +2.14 MB | 710.98 kB | 724.14 kB | 160.41 kB | 181.07 kB | 2 | victory.js -3.20 MB | 1.00 MB | 1.01 MB | 322.58 kB | 331.56 kB | 3 | echarts.js +3.20 MB | 1.00 MB | 1.01 MB | 322.57 kB | 331.56 kB | 3 | echarts.js -6.69 MB | 2.22 MB | 2.31 MB | 458.44 kB | 488.28 kB | 4 | antd.js +6.69 MB | 2.22 MB | 2.31 MB | 458.41 kB | 488.28 kB | 4 | antd.js -10.95 MB | 3.33 MB | 3.49 MB | 853.30 kB | 915.50 kB | 4 | typescript.js +10.95 MB | 3.33 MB | 3.49 MB | 853.20 kB | 915.50 kB | 4 | typescript.js diff --git a/tasks/track_memory_allocations/allocs_minifier.snap b/tasks/track_memory_allocations/allocs_minifier.snap index 2f33000316ad0..a0bfbb6ff3898 100644 --- a/tasks/track_memory_allocations/allocs_minifier.snap +++ b/tasks/track_memory_allocations/allocs_minifier.snap @@ -1,14 +1,14 @@ File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs | Arena bytes ------------------------------------------------------------------------------------------------------------------------------------------- -checker.ts | 2.92 MB || 4128 | 1669 || 152600 | 28244 +checker.ts | 2.92 MB || 4128 | 1669 || 152534 | 28184 -cal.com.tsx | 1.06 MB || 21098 | 474 || 37138 | 4586 +cal.com.tsx | 1.06 MB || 21098 | 474 || 37141 | 4586 RadixUIAdoptionSection.jsx | 2.52 kB || 65 | 3 || 30 | 6 -pdf.mjs | 567.30 kB || 4691 | 569 || 47464 | 7730 +pdf.mjs | 567.30 kB || 4691 | 569 || 47434 | 7704 -antd.js | 6.69 MB || 10723 | 2505 || 331648 | 69358 +antd.js | 6.69 MB || 10723 | 2505 || 331535 | 69298 -binder.ts | 193.08 kB || 431 | 119 || 7075 | 824 +binder.ts | 193.08 kB || 431 | 119 || 7073 | 822