diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index f3e4ff96902d4..44f6f0764f3c5 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -588,7 +588,7 @@ mod test { fn test_fold_returns() { test("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}"); test("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}"); - test("function f(){if(x)return;return 2}", "function f(){return x?void 0:2}"); + test("function f(){if(x)return;return 2}", "function f(){if (!x) return 2;}"); test("function f(){if(x)return 1+x;else return 2-x}", "function f(){return x?1+x:2-x}"); test("function f(){if(x)return 1+x;return 2-x}", "function f(){return x?1+x:2-x}"); test( @@ -596,8 +596,8 @@ mod test { "function f(){return x?(y+=1):(y+=2)}", ); - test("function f(){if(x)return;else return 2-x}", "function f(){return x?void 0:2-x}"); - test("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}"); + test("function f(){if(x)return;else return 2-x}", "function f(){if (!x) return 2 - x;}"); + test("function f(){if(x)return;return 2-x}", "function f(){if (!x) return 2 - x;}"); test("function f(){if(x)return x;else return}", "function f(){if(x)return x;}"); test("function f(){if(x)return x;return}", "function f(){if(x)return x}"); diff --git a/crates/oxc_minifier/src/peephole/minimize_if_statement.rs b/crates/oxc_minifier/src/peephole/minimize_if_statement.rs index edbd8e66a4be0..9d7b2920c87ec 100644 --- a/crates/oxc_minifier/src/peephole/minimize_if_statement.rs +++ b/crates/oxc_minifier/src/peephole/minimize_if_statement.rs @@ -1,8 +1,7 @@ use oxc_ast::ast::*; +use oxc_semantic::ScopeId; use oxc_span::GetSpan; -use oxc_syntax::scope::ScopeFlags; -use oxc_traverse::TraverseCtx; use crate::ctx::Ctx; @@ -13,10 +12,9 @@ impl<'a> PeepholeOptimizations { pub fn try_minimize_if( &mut self, if_stmt: &mut IfStatement<'a>, - traverse_ctx: &mut TraverseCtx<'a>, + ctx: Ctx<'a, '_>, ) -> Option> { - self.wrap_to_avoid_ambiguous_else(if_stmt, traverse_ctx); - let ctx = Ctx(traverse_ctx); + self.wrap_to_avoid_ambiguous_else(if_stmt, ctx); if let Statement::ExpressionStatement(expr_stmt) = &mut if_stmt.consequent { if if_stmt.alternate.is_none() { let (op, e) = match &mut if_stmt.test { @@ -94,7 +92,7 @@ impl<'a> PeepholeOptimizations { // "if (!a) return b; else return c;" => "if (a) return c; else return b;" if_stmt.test = ctx.ast.move_expression(&mut unary_expr.argument); std::mem::swap(&mut if_stmt.consequent, alternate); - self.wrap_to_avoid_ambiguous_else(if_stmt, traverse_ctx); + self.wrap_to_avoid_ambiguous_else(if_stmt, ctx); self.mark_current_function_as_changed(); } } @@ -124,14 +122,11 @@ impl<'a> PeepholeOptimizations { /// Wrap to avoid ambiguous else. /// `if (foo) if (bar) baz else quaz` -> `if (foo) { if (bar) baz else quaz }` - fn wrap_to_avoid_ambiguous_else( - &mut self, - if_stmt: &mut IfStatement<'a>, - ctx: &mut TraverseCtx<'a>, - ) { + #[expect(clippy::cast_possible_truncation)] + fn wrap_to_avoid_ambiguous_else(&mut self, if_stmt: &mut IfStatement<'a>, ctx: Ctx<'a, '_>) { if let Statement::IfStatement(if2) = &mut if_stmt.consequent { if if2.consequent.is_jump_statement() && if2.alternate.is_some() { - let scope_id = ctx.create_child_scope_of_current(ScopeFlags::empty()); + let scope_id = ScopeId::new(ctx.scoping.scopes().len() as u32); if_stmt.consequent = Statement::BlockStatement(ctx.ast.alloc( ctx.ast.block_statement_with_scope_id( if_stmt.consequent.span(), diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 0a81ce96d8ef9..2287f1876c894 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -1,6 +1,9 @@ +use std::ops::ControlFlow; + use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, Visit}; use oxc_ecmascript::side_effects::MayHaveSideEffects; +use oxc_semantic::ScopeId; use oxc_span::{ContentEq, GetSpan}; use oxc_traverse::Ancestor; @@ -30,7 +33,9 @@ impl<'a> PeepholeOptimizations { let mut result: Vec<'a, Statement<'a>> = ctx.ast.vec_with_capacity(stmts.len()); let mut is_control_flow_dead = false; let mut keep_var = KeepVar::new(ctx.ast); - for stmt in ctx.ast.vec_from_iter(stmts.drain(..)) { + let mut new_stmts = ctx.ast.vec_from_iter(stmts.drain(..)); + for i in 0..new_stmts.len() { + let stmt = ctx.ast.move_statement(&mut new_stmts[i]); if is_control_flow_dead && !stmt.is_module_declaration() && !matches!(stmt.as_declaration(), Some(Declaration::FunctionDeclaration(_))) @@ -38,7 +43,19 @@ impl<'a> PeepholeOptimizations { keep_var.visit_statement(&stmt); continue; } - self.minimize_statement(stmt, &mut result, &mut is_control_flow_dead, ctx); + if self + .minimize_statement( + stmt, + i, + &mut new_stmts, + &mut result, + &mut is_control_flow_dead, + ctx, + ) + .is_break() + { + break; + }; } if let Some(stmt) = keep_var.get_variable_declaration_statement() { result.push(stmt); @@ -176,10 +193,12 @@ impl<'a> PeepholeOptimizations { fn minimize_statement( &mut self, stmt: Statement<'a>, + i: usize, + stmts: &mut Vec<'a, Statement<'a>>, result: &mut Vec<'a, Statement<'a>>, is_control_flow_dead: &mut bool, ctx: Ctx<'a, '_>, - ) { + ) -> ControlFlow<()> { match stmt { Statement::EmptyStatement(_) => (), Statement::BreakStatement(s) => { @@ -220,60 +239,10 @@ impl<'a> PeepholeOptimizations { } result.push(Statement::SwitchStatement(switch_stmt)); } - Statement::IfStatement(mut if_stmt) => { - if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { - let a = &mut prev_expr_stmt.expression; - let b = &mut if_stmt.test; - if_stmt.test = Self::join_sequence(a, b, ctx); - result.pop(); - self.mark_current_function_as_changed(); + Statement::IfStatement(if_stmt) => { + if self.handle_if_statement(i, stmts, result, if_stmt, ctx).is_break() { + return ControlFlow::Break(()); } - - if if_stmt.consequent.is_jump_statement() { - // Absorb a previous if statement - if let Some(Statement::IfStatement(prev_if_stmt)) = result.last_mut() { - if prev_if_stmt.alternate.is_none() - && Self::jump_stmts_look_the_same( - &prev_if_stmt.consequent, - &if_stmt.consequent, - ) - { - // "if (a) break c; if (b) break c;" => "if (a || b) break c;" - // "if (a) continue c; if (b) continue c;" => "if (a || b) continue c;" - // "if (a) return c; if (b) return c;" => "if (a || b) return c;" - // "if (a) throw c; if (b) throw c;" => "if (a || b) throw c;" - if_stmt.test = Self::join_with_left_associative_op( - if_stmt.test.span(), - LogicalOperator::Or, - ctx.ast.move_expression(&mut prev_if_stmt.test), - ctx.ast.move_expression(&mut if_stmt.test), - ctx, - ); - result.pop(); - self.mark_current_function_as_changed(); - } - } - - if if_stmt.alternate.is_some() { - // "if (a) return b; else if (c) return d; else return e;" => "if (a) return b; if (c) return d; return e;" - result.push(Statement::IfStatement(if_stmt)); - loop { - if let Some(Statement::IfStatement(if_stmt)) = result.last_mut() { - if if_stmt.consequent.is_jump_statement() { - if let Some(stmt) = if_stmt.alternate.take() { - result.push(stmt); - self.mark_current_function_as_changed(); - continue; - } - } - } - break; - } - return; - } - } - - result.push(Statement::IfStatement(if_stmt)); } Statement::ReturnStatement(mut ret_stmt) => { if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { @@ -443,6 +412,7 @@ impl<'a> PeepholeOptimizations { Statement::BlockStatement(block_stmt) => self.handle_block(result, block_stmt), stmt => result.push(stmt), } + ControlFlow::Continue(()) } fn join_sequence( @@ -475,6 +445,148 @@ impl<'a> PeepholeOptimizations { false } + #[expect(clippy::cast_possible_truncation)] + fn handle_if_statement( + &mut self, + i: usize, + stmts: &mut Vec<'a, Statement<'a>>, + result: &mut Vec<'a, Statement<'a>>, + mut if_stmt: Box<'a, IfStatement<'a>>, + ctx: Ctx<'a, '_>, + ) -> ControlFlow<()> { + // Absorb a previous expression statement + if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { + let a = &mut prev_expr_stmt.expression; + let b = &mut if_stmt.test; + if_stmt.test = Self::join_sequence(a, b, ctx); + result.pop(); + self.mark_current_function_as_changed(); + } + + if if_stmt.consequent.is_jump_statement() { + // Absorb a previous if statement + if let Some(Statement::IfStatement(prev_if_stmt)) = result.last_mut() { + if prev_if_stmt.alternate.is_none() + && Self::jump_stmts_look_the_same(&prev_if_stmt.consequent, &if_stmt.consequent) + { + // "if (a) break c; if (b) break c;" => "if (a || b) break c;" + // "if (a) continue c; if (b) continue c;" => "if (a || b) continue c;" + // "if (a) return c; if (b) return c;" => "if (a || b) return c;" + // "if (a) throw c; if (b) throw c;" => "if (a || b) throw c;" + if_stmt.test = Self::join_with_left_associative_op( + if_stmt.test.span(), + LogicalOperator::Or, + ctx.ast.move_expression(&mut prev_if_stmt.test), + ctx.ast.move_expression(&mut if_stmt.test), + ctx, + ); + result.pop(); + self.mark_current_function_as_changed(); + } + } + + let mut optimize_implicit_jump = false; + // "while (x) { if (y) continue; z(); }" => "while (x) { if (!y) z(); }" + // "while (x) { if (y) continue; else z(); w(); }" => "while (x) { if (!y) { z(); w(); } }" => "for (; x;) !y && (z(), w());" + if ctx.ancestors().nth(1).is_some_and(Ancestor::is_for_statement) { + if let Statement::ContinueStatement(continue_stmt) = &if_stmt.consequent { + if continue_stmt.label.is_none() { + optimize_implicit_jump = true; + } + } + } + + // "let x = () => { if (y) return; z(); };" => "let x = () => { if (!y) z(); };" + // "let x = () => { if (y) return; else z(); w(); };" => "let x = () => { if (!y) { z(); w(); } };" => "let x = () => { !y && (z(), w()); };" + if ctx.parent().is_function_body() { + if let Statement::ReturnStatement(return_stmt) = &if_stmt.consequent { + if return_stmt.argument.is_none() { + optimize_implicit_jump = true; + } + } + } + if optimize_implicit_jump { + // Don't do this transformation if the branch condition could + // potentially access symbols declared later on on this scope below. + // If so, inverting the branch condition and nesting statements after + // this in a block would break that access which is a behavior change. + // + // // This transformation is incorrect + // if (a()) return; function a() {} + // if (!a()) { function a() {} } + // + // // This transformation is incorrect + // if (a(() => b)) return; let b; + // if (a(() => b)) { let b; } + // + let mut can_move_branch_condition_outside_scope = true; + if let Some(alternate) = &if_stmt.alternate { + if Self::statement_cares_about_scope(alternate) { + can_move_branch_condition_outside_scope = false; + } + } + if let Some(stmts) = stmts.get(i + 1..) { + for stmt in stmts { + if Self::statement_cares_about_scope(stmt) { + can_move_branch_condition_outside_scope = false; + break; + } + } + } + + if can_move_branch_condition_outside_scope { + let mut body = ctx.ast.vec(); + if let Some(alternate) = if_stmt.alternate.take() { + body.push(alternate); + } + body.extend(stmts.drain(i + 1..)); + + self.minimize_statements(&mut body, ctx); + let span = + if body.is_empty() { if_stmt.consequent.span() } else { body[0].span() }; + let test = ctx.ast.move_expression(&mut if_stmt.test); + let mut test = Self::minimize_not(test.span(), test, ctx); + Self::try_fold_expr_in_boolean_context(&mut test, ctx); + let consequent = if body.len() == 1 { + body.remove(0) + } else { + let scope_id = ScopeId::new(ctx.scopes().len() as u32); + let block_stmt = + ctx.ast.block_statement_with_scope_id(span, body, scope_id); + Statement::BlockStatement(ctx.ast.alloc(block_stmt)) + }; + let mut if_stmt = ctx.ast.if_statement(test.span(), test, consequent, None); + let if_stmt = self + .try_minimize_if(&mut if_stmt, ctx) + .unwrap_or_else(|| Statement::IfStatement(ctx.ast.alloc(if_stmt))); + result.push(if_stmt); + return ControlFlow::Break(()); + } + } + + if if_stmt.alternate.is_some() { + // "if (a) return b; else if (c) return d; else return e;" => "if (a) return b; if (c) return d; return e;" + result.push(Statement::IfStatement(if_stmt)); + loop { + if let Some(Statement::IfStatement(if_stmt)) = result.last_mut() { + if if_stmt.consequent.is_jump_statement() { + if let Some(stmt) = if_stmt.alternate.take() { + result.push(stmt); + self.mark_current_function_as_changed(); + continue; + } + } + } + break; + } + return ControlFlow::Continue(()); + } + } + + result.push(Statement::IfStatement(if_stmt)); + ControlFlow::Continue(()) + } + /// `appendIfOrLabelBodyPreservingScope`: fn handle_block( &mut self, diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 2a91d64528db2..47d4d60e9aeac 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -146,19 +146,20 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { self.minimize_statements(stmts, ctx); } - fn exit_statement(&mut self, stmt: &mut Statement<'a>, traverse_ctx: &mut TraverseCtx<'a>) { + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { if !self.is_prev_function_changed() { return; } - Self::try_fold_stmt_in_boolean_context(stmt, Ctx(traverse_ctx)); - self.remove_dead_code_exit_statement(stmt, Ctx(traverse_ctx)); + let ctx = Ctx(ctx); + Self::try_fold_stmt_in_boolean_context(stmt, ctx); + self.remove_dead_code_exit_statement(stmt, ctx); if let Statement::IfStatement(if_stmt) = stmt { - if let Some(folded_stmt) = self.try_minimize_if(if_stmt, traverse_ctx) { + if let Some(folded_stmt) = self.try_minimize_if(if_stmt, ctx) { *stmt = folded_stmt; self.mark_current_function_as_changed(); } } - self.substitute_exit_statement(stmt, Ctx(traverse_ctx)); + self.substitute_exit_statement(stmt, ctx); } fn exit_for_statement(&mut self, stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) { diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index 96a4ad529ae5c..4fa358123b41e 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -650,7 +650,7 @@ mod test { test("{ (function foo(){x++; foo()}) }", ""); test("function f(){return;}", "function f(){}"); test("function f(){return 3;}", "function f(){return 3}"); - test("function f(){if(x)return; x=3; return; }", "function f(){if(x)return; x=3; }"); + test("function f(){if(x)return; x=3; return; }", "function f(){ x ||= 3; }"); test("{x=3;;;y=2;;;}", "x=3, y=2"); // Cases to test for empty block. diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index c6564b784f79a..a380009f95ab2 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -1148,7 +1148,7 @@ mod test { test("function f(){return void 0;}", "function f(){}"); test("function f(){return void foo();}", "function f(){return void foo()}"); test("function f(){return undefined;}", "function f(){}"); - test("function f(){if(a()){return undefined;}}", "function f(){if(a())return}"); + test("function f(){if(a()){return undefined;}}", "function f(){!a()}"); test_same("function a(undefined) { return undefined; }"); test_same("function f(){return foo()}"); diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index a39eff447fb07..86bc7b8bde35c 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -197,7 +197,11 @@ fn js_parser_test() { "while (x) { debugger; if (y) { if (1) break; z() } }", "for (; x; ) { debugger; if (y) break; }", ); - // test("while (x) { debugger; if (y) { if (1) continue; z() } }", "for (; x; ) { debugger; y; }"); + // FIXME: remove `!` from `!y` + test( + "while (x) { debugger; if (y) { if (1) continue; z() } }", + "for (; x; ) { debugger; !y; }", + ); test( "while (x) { debugger; if (1) { if (1) break; z() } }", "for (; x; ) { debugger; break; }", @@ -210,31 +214,31 @@ fn js_parser_test() { "label: while (x) while (y) { z(); continue label }", "label: for (; x; ) for (; y; ) { z(); continue label;}", ); - // test("while (x) { if (y) continue; z(); }", "for (; x; ) y || z();"); - // test("while (x) { if (y) continue; else z(); w(); }", "for (; x; ) y || (z(), w());"); - // test("while (x) { t(); if (y) continue; z(); }", "for (; x; ) t(), !y && z();"); - // test( - // "while (x) { t(); if (y) continue; else z(); w(); }", - // "for (; x; ) t(), !y && (z(), w());", - // ); - // test("while (x) { debugger; if (y) continue; z(); }", "for (; x; ) { debugger; y || z(); }"); - // test( - // "while (x) { debugger; if (y) continue; else z(); w(); }", - // "for (; x; ) { debugger; y || (z(), w());}", - // ); + test("while (x) { if (y) continue; z(); }", "for (; x; ) y || z();"); + test("while (x) { if (y) continue; else z(); w(); }", "for (; x; ) y || (z(), w());"); + test("while (x) { t(); if (y) continue; z(); }", "for (; x; ) t(), !y && z();"); + test( + "while (x) { t(); if (y) continue; else z(); w(); }", + "for (; x; ) t(), !y && (z(), w());", + ); + test("while (x) { debugger; if (y) continue; z(); }", "for (; x; ) { debugger; y || z(); }"); + test( + "while (x) { debugger; if (y) continue; else z(); w(); }", + "for (; x; ) { debugger; y || (z(), w());}", + ); // test( // "while (x) { if (y) continue; function y() {} }", // "for (; x; ) { let y = function() { }; var y = y; }", // ); test("while (x) { if (y) continue; let y }", "for (; x; ) { if (y) continue; let y; }"); - // test("while (x) { if (y) continue; var y }", "for (; x; ) if (!y) var y; "); + test("while (x) { if (y) continue; var y }", "for (; x; ) if (!y) var y; "); test("console.log(undefined)", "console.log(void 0);"); test("console.log(+undefined)", "console.log(NaN);"); test("console.log(undefined + undefined)", "console.log(void 0 + void 0);"); test("const x = undefined", "const x = void 0;"); test("let x = undefined", "let x;"); test("var x = undefined", "var x = void 0;"); - // test("function foo(a) { if (!a) return undefined; a() }", "function foo(a) { a && a(); }"); + test("function foo(a) { if (!a) return undefined; a() }", "function foo(a) { a && a(); }"); test("delete undefined", "delete undefined;"); test("undefined--", "undefined--;"); test("undefined++", "undefined++;"); @@ -561,7 +565,7 @@ fn js_parser_test() { test("a = b === c ? true : false", "a = b === c;"); test("a = b !== c ? true : false", "a = b !== c;"); test("a ? b(c) : b(d)", "a ? b(c) : b(d);"); - // test("let a; a ? b(c) : b(d)", "let a; a ? b(c) : b(d);"); + test("let a; a ? b(c) : b(d)", "let a; a ? b(c) : b(d);"); test("let a, b; a ? b(c) : b(d)", "let a, b;b(a ? c : d);"); test("let a, b; a ? b(c, 0) : b(d)", "let a, b; a ? b(c, 0) : b(d);"); test("let a, b; a ? b(c) : b(d, 0)", "let a, b; a ? b(c) : b(d, 0);"); @@ -576,7 +580,7 @@ fn js_parser_test() { test("let a, b; a ? b(...c) : b(...a)", "let a, b;b(...a && c);"); test("let a; a.x ? b(c) : b(d)", "let a;a.x ? b(c) : b(d);"); test("let a, b; a.x ? b(c) : b(d)", "let a, b; a.x ? b(c) : b(d);"); - // test("let a, b; a ? b.y(c) : b.y(d)", "let a, b; a ? b.y(c) : b.y(d);"); + test("let a, b; a ? b.y(c) : b.y(d)", "let a, b; a ? b.y(c) : b.y(d);"); test("let a, b; a.x ? b.y(c) : b.y(d)", "let a, b; a.x ? b.y(c) : b.y(d);"); test("a ? b : c ? b : d", "a || c ? b : d;"); test("a ? b ? c : d : d", "a && b ? c : d;"); @@ -610,12 +614,12 @@ fn js_parser_test() { test("let b; a = null !== b ? b : c", "let b; a = b === null ? c : b;"); test("let b; a = null === b || b === undefined ? c : b", "let b; a = b ?? c;"); test("let b; a = b !== undefined && b !== null ? b : c", "let b; a = b ?? c;"); - // test("a(b ? 0 : 0)", "a((b, 0));"); + test("a(b ? 0 : 0)", "a((b, 0));"); // test("a(b ? +0 : -0)", "a(b ? 0 : -0);"); // test("a(b ? +0 : 0)", "a((b, 0));"); // test("a(b ? -0 : 0)", "a(b ? -0 : 0);"); test("a ? b : b", "a, b;"); - // test("let a; a ? b : b", "let a; b;"); + test("let a; a ? b : b", "let a; b;"); test("a ? -b : -b", "a, -b;"); test("a ? b.c : b.c", "a, b.c;"); test("a ? b?.c : b?.c", "a, b?.c;"); @@ -662,29 +666,31 @@ fn js_parser_test() { ); test( "function _() { if (a) return c; if (b) return; }", - "function _() { if (a) return c;if (b) return; }", + // FIXME: remove `!` from `!b` + "function _() { if (a) return c; !b; }", ); test( "function _() { if (a) return; if (b) return c; }", - "function _() { if (a) return;if (b) return c; }", + "function _() { if (!a && b) return c; }", ); - test("function _() { if (a) return; if (b) return; }", "function _() { if (a || b) return; }"); + // FIXME: remove `!` from `!b` + test("function _() { if (a) return; if (b) return; }", "function _() { a || !b }"); test("if (a) throw c; if (b) throw d;", "if (a) throw c;if (b) throw d;"); test("if (a) throw c; if (b) throw c;", "if (a || b) throw c;"); - // test("while (x) { if (a) break; if (b) break; }", "for (; x && !(a || b); ) ;"); + test("while (x) { if (a) break; if (b) break; }", "for (; x && !(a || b); ) ;"); // test("while (x) { if (a) continue; if (b) continue; }", "for (; x; ) a || b;"); - // test( - // "while (x) { debugger; if (a) break; if (b) break; }", - // "for (; x; ) { debugger; if (a || b) break;}", - // ); + test( + "while (x) { debugger; if (a) break; if (b) break; }", + "for (; x; ) { debugger; if (a || b) break;}", + ); // test( // "while (x) { debugger; if (a) continue; if (b) continue; }", // "for (; x; ) { debugger; a || b;}", // ); - // test( - // "x: while (x) y: while (y) { if (a) break x; if (b) break y; }", - // "x: for (; x; ) y: for (; y; ) { if (a) break x; if (b) break y;}", - // ); + test( + "x: while (x) y: while (y) { if (a) break x; if (b) break y; }", + "x: for (; x; ) y: for (; y; ) { if (a) break x; if (b) break y;}", + ); // test( // "x: while (x) y: while (y) { if (a) continue x; if (b) continue y; }", // "x: for (; x; ) y: for (; y; ) { if (a) continue x; if (b) continue y;}", diff --git a/crates/oxc_minifier/tests/peephole/mod.rs b/crates/oxc_minifier/tests/peephole/mod.rs index a83fcab3f8d01..ea4f9d5c9a00a 100644 --- a/crates/oxc_minifier/tests/peephole/mod.rs +++ b/crates/oxc_minifier/tests/peephole/mod.rs @@ -41,8 +41,8 @@ fn integration() { return undefined; return isTimeDisabled === null || isTimeDisabled === void 0 ? void 0 : isTimeDisabled(value); }", - "function foo() { - return value === null || Array.isArray(value) || isTimeDisabled == null ? void 0 : isTimeDisabled(value); + "function foo() { + if (!(value === null || Array.isArray(value))) return isTimeDisabled == null ? void 0 : isTimeDisabled(value); }"); test_same("a && (b && (c && (d && (e && (f && (g && (h && i && j && k && l && m && n && o && p && q && r && s && t && u && v && w && x && y && z)))))))"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index faa61e2e62b1c..f7a50e9b26d9b 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.51 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js +72.14 kB | 23.49 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js -173.90 kB | 59.54 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js +173.90 kB | 59.53 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js -287.63 kB | 89.44 kB | 90.07 kB | 30.96 kB | 31.95 kB | jquery.js +287.63 kB | 89.34 kB | 90.07 kB | 30.95 kB | 31.95 kB | jquery.js -342.15 kB | 117.46 kB | 118.14 kB | 43.33 kB | 44.37 kB | vue.js +342.15 kB | 117.25 kB | 118.14 kB | 43.31 kB | 44.37 kB | vue.js -544.10 kB | 71.40 kB | 72.48 kB | 25.86 kB | 26.20 kB | lodash.js +544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | lodash.js -555.77 kB | 271.11 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js +555.77 kB | 270.83 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js -1.01 MB | 440.48 kB | 458.89 kB | 122.41 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.17 kB | 458.89 kB | 122.38 kB | 126.71 kB | bundle.min.js -1.25 MB | 647.42 kB | 646.76 kB | 160.30 kB | 163.73 kB | three.js +1.25 MB | 647.05 kB | 646.76 kB | 160.27 kB | 163.73 kB | three.js -2.14 MB | 716.51 kB | 724.14 kB | 161.81 kB | 181.07 kB | victory.js +2.14 MB | 716.14 kB | 724.14 kB | 161.79 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.27 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.15 kB | 331.56 kB | echarts.js -6.69 MB | 2.28 MB | 2.31 MB | 466.21 kB | 488.28 kB | antd.js +6.69 MB | 2.28 MB | 2.31 MB | 466.12 kB | 488.28 kB | antd.js -10.95 MB | 3.35 MB | 3.49 MB | 861.48 kB | 915.50 kB | typescript.js +10.95 MB | 3.35 MB | 3.49 MB | 861.02 kB | 915.50 kB | typescript.js