diff --git a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs index ced900bb3bd84..585b1f9e32a27 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -68,7 +68,7 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions { if let Some(folded_stmt) = match stmt { // If the condition is a literal, we'll let other optimizations try to remove useless code. - Statement::IfStatement(s) if !s.test.is_literal() => Self::try_minimize_if(stmt, ctx), + Statement::IfStatement(_) => Self::try_minimize_if(stmt, ctx), _ => None, } { *stmt = folded_stmt; @@ -139,75 +139,108 @@ impl<'a> PeepholeMinimizeConditions { ctx: &mut TraverseCtx<'a>, ) -> Option> { let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; - let then_branch = &if_stmt.consequent; - let else_branch = &if_stmt.alternate; - match else_branch { - None => { - if Self::is_foldable_express_block(&if_stmt.consequent) { - let right = Self::get_block_expression(&mut if_stmt.consequent, ctx); - let test = ctx.ast.move_expression(&mut if_stmt.test); - // `if(!x) foo()` -> `x || foo()` - if let Expression::UnaryExpression(unary_expr) = test { - if unary_expr.operator.is_not() { - let left = unary_expr.unbox().argument; + + // `if (x) foo()` -> `x && foo()` + if !if_stmt.test.is_literal() { + let then_branch = &if_stmt.consequent; + let else_branch = &if_stmt.alternate; + match else_branch { + None => { + if Self::is_foldable_express_block(&if_stmt.consequent) { + let right = Self::get_block_expression(&mut if_stmt.consequent, ctx); + let test = ctx.ast.move_expression(&mut if_stmt.test); + // `if(!x) foo()` -> `x || foo()` + if let Expression::UnaryExpression(unary_expr) = test { + if unary_expr.operator.is_not() { + let left = unary_expr.unbox().argument; + let logical_expr = ctx.ast.expression_logical( + if_stmt.span, + left, + LogicalOperator::Or, + right, + ); + return Some( + ctx.ast.statement_expression(if_stmt.span, logical_expr), + ); + } + } else { + // `if(x) foo()` -> `x && foo()` let logical_expr = ctx.ast.expression_logical( if_stmt.span, - left, - LogicalOperator::Or, + test, + LogicalOperator::And, right, ); return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); } } else { - // `if(x) foo()` -> `x && foo()` - let logical_expr = ctx.ast.expression_logical( + // `if (x) if (y) z` -> `if (x && y) z` + if let Some(Statement::IfStatement(then_if_stmt)) = + then_branch.get_one_child() + { + if then_if_stmt.alternate.is_none() { + let and_left = ctx.ast.move_expression(&mut if_stmt.test); + let Some(then_if_stmt) = if_stmt.consequent.get_one_child_mut() + else { + unreachable!() + }; + let Statement::IfStatement(mut then_if_stmt) = + ctx.ast.move_statement(then_if_stmt) + else { + unreachable!() + }; + let and_right = ctx.ast.move_expression(&mut then_if_stmt.test); + then_if_stmt.test = ctx.ast.expression_logical( + and_left.span(), + and_left, + LogicalOperator::And, + and_right, + ); + return Some(Statement::IfStatement(then_if_stmt)); + } + } + } + } + Some(else_branch) => { + let then_branch_is_expression_block = + Self::is_foldable_express_block(then_branch); + let else_branch_is_expression_block = + Self::is_foldable_express_block(else_branch); + // `if(foo) bar else baz` -> `foo ? bar : baz` + if then_branch_is_expression_block && else_branch_is_expression_block { + let test = ctx.ast.move_expression(&mut if_stmt.test); + let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx); + let else_branch = if_stmt.alternate.as_mut().unwrap(); + let alternate = Self::get_block_expression(else_branch, ctx); + let expr = ctx.ast.expression_conditional( if_stmt.span, test, - LogicalOperator::And, - right, + consequent, + alternate, ); - return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); - } - } else { - // `if (x) if (y) z` -> `if (x && y) z` - if let Some(Statement::IfStatement(then_if_stmt)) = then_branch.get_one_child() - { - if then_if_stmt.alternate.is_none() { - let and_left = ctx.ast.move_expression(&mut if_stmt.test); - let Some(then_if_stmt) = if_stmt.consequent.get_one_child_mut() else { - unreachable!() - }; - let Statement::IfStatement(mut then_if_stmt) = - ctx.ast.move_statement(then_if_stmt) - else { - unreachable!() - }; - let and_right = ctx.ast.move_expression(&mut then_if_stmt.test); - then_if_stmt.test = ctx.ast.expression_logical( - and_left.span(), - and_left, - LogicalOperator::And, - and_right, - ); - return Some(Statement::IfStatement(then_if_stmt)); - } + return Some(ctx.ast.statement_expression(if_stmt.span, expr)); } } } - Some(else_branch) => { - let then_branch_is_expression_block = Self::is_foldable_express_block(then_branch); - let else_branch_is_expression_block = Self::is_foldable_express_block(else_branch); - // `if(foo) bar else baz` -> `foo ? bar : baz` - if then_branch_is_expression_block && else_branch_is_expression_block { - let test = ctx.ast.move_expression(&mut if_stmt.test); - let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx); - let else_branch = if_stmt.alternate.as_mut().unwrap(); - let alternate = Self::get_block_expression(else_branch, ctx); - let expr = - ctx.ast.expression_conditional(if_stmt.span, test, consequent, alternate); - return Some(ctx.ast.statement_expression(if_stmt.span, expr)); - } - } + } + + // `if (x) {} else foo` -> `if (!x) foo` + if match &if_stmt.consequent { + Statement::EmptyStatement(_) => true, + Statement::BlockStatement(block_stmt) => block_stmt.body.is_empty(), + _ => false, + } && if_stmt.alternate.is_some() + { + return Some(ctx.ast.statement_if( + if_stmt.span, + ctx.ast.expression_unary( + if_stmt.test.span(), + UnaryOperator::LogicalNot, + ctx.ast.move_expression(&mut if_stmt.test), + ), + ctx.ast.move_statement(if_stmt.alternate.as_mut().unwrap()), + None, + )); } None @@ -2047,4 +2080,14 @@ mod test { test("typeof foo !== `number`", "typeof foo != `number`"); test("`number` !== typeof foo", "`number` != typeof foo"); } + + #[test] + fn test_negate_empty_if_stmt_consequent() { + test("if (x) {} else { foo }", "if (!x) { foo }"); + test("if (x) ;else { foo }", "if (!x) { foo }"); + test("if (x) {;} else { foo }", "if (!x) { foo }"); + + test_same("if (x) { var foo } else { bar }"); + test_same("if (x) foo; else { var bar }"); + } } diff --git a/crates/oxc_minifier/tests/ast_passes/mod.rs b/crates/oxc_minifier/tests/ast_passes/mod.rs index c5927e905a0d1..126b88881c92a 100644 --- a/crates/oxc_minifier/tests/ast_passes/mod.rs +++ b/crates/oxc_minifier/tests/ast_passes/mod.rs @@ -71,7 +71,7 @@ fn integration() { } console.log(c, d); ", - "if ((() => console.log('effect'))(), !0) {} else for (var c = 1, c; unknownGlobal; unknownGlobal && !0) var d; + "if (!((() => console.log('effect'))(), !0)) for (var c = 1, c; unknownGlobal; unknownGlobal && !0) var d; console.log(c, d); ", ); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 90075c4e65cdb..302461b5413ba 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -11,17 +11,17 @@ Original | minified | minified | gzip | gzip | Fixture 544.10 kB | 71.76 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js -555.77 kB | 272.91 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js +555.77 kB | 272.90 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js -1.01 MB | 460.17 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js +1.01 MB | 460.15 kB | 458.89 kB | 126.77 kB | 126.71 kB | bundle.min.js 1.25 MB | 652.88 kB | 646.76 kB | 163.54 kB | 163.73 kB | three.js -2.14 MB | 724.06 kB | 724.14 kB | 179.94 kB | 181.07 kB | victory.js +2.14 MB | 724.05 kB | 724.14 kB | 179.94 kB | 181.07 kB | victory.js 3.20 MB | 1.01 MB | 1.01 MB | 332.01 kB | 331.56 kB | echarts.js 6.69 MB | 2.32 MB | 2.31 MB | 492.44 kB | 488.28 kB | antd.js -10.95 MB | 3.49 MB | 3.49 MB | 907.09 kB | 915.50 kB | typescript.js +10.95 MB | 3.49 MB | 3.49 MB | 907.07 kB | 915.50 kB | typescript.js