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 6f1e569abb6b9..99643f997af22 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs @@ -1,7 +1,7 @@ use oxc_allocator::Vec; use oxc_ast::ast::*; -use oxc_span::GetSpan; -use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; +use oxc_span::{GetSpan, SPAN}; +use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx}; use crate::CompressorPass; @@ -383,6 +383,76 @@ impl<'a> PeepholeMinimizeConditions { } } + let in_boolean_context = matches!( + ctx.parent(), + Ancestor::IfStatementTest(_) + | Ancestor::WhileStatementTest(_) + | Ancestor::DoWhileStatementTest(_) + | Ancestor::ExpressionStatementExpression(_) + ); + + // `x ? true : y` -> `x || y` + // `x ? false : y` -> `!x && y` + if let (Expression::Identifier(_), Expression::BooleanLiteral(consequent_lit), _) = + (&expr.test, &expr.consequent, &expr.alternate) + { + if consequent_lit.value { + let ident = ctx.ast.move_expression(&mut expr.test); + return Some(ctx.ast.expression_logical( + expr.span, + if in_boolean_context { + ident + } else { + ctx.ast.expression_unary( + SPAN, + UnaryOperator::LogicalNot, + ctx.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, ident), + ) + }, + LogicalOperator::Or, + ctx.ast.move_expression(&mut expr.alternate), + )); + } + let ident = ctx.ast.move_expression(&mut expr.test); + return Some(ctx.ast.expression_logical( + expr.span, + ctx.ast.expression_unary(expr.span, UnaryOperator::LogicalNot, ident), + LogicalOperator::And, + ctx.ast.move_expression(&mut expr.alternate), + )); + } + + // `x ? y : true` -> `!x || y` + // `x ? y : false` -> `x && y` + if let (Expression::Identifier(_), _, Expression::BooleanLiteral(alternate_lit)) = + (&expr.test, &expr.consequent, &expr.alternate) + { + if alternate_lit.value { + let ident = ctx.ast.move_expression(&mut expr.test); + return Some(ctx.ast.expression_logical( + expr.span, + ctx.ast.expression_unary(expr.span, UnaryOperator::LogicalNot, ident), + LogicalOperator::Or, + ctx.ast.move_expression(&mut expr.consequent), + )); + } + let ident = ctx.ast.move_expression(&mut expr.test); + return Some(ctx.ast.expression_logical( + expr.span, + if in_boolean_context { + ident + } else { + ctx.ast.expression_unary( + SPAN, + UnaryOperator::LogicalNot, + ctx.ast.expression_unary(SPAN, UnaryOperator::LogicalNot, ident), + ) + }, + LogicalOperator::And, + ctx.ast.move_expression(&mut expr.consequent), + )); + } + None } } @@ -686,8 +756,9 @@ mod test { fn test_minimize_expr_condition() { fold("(x ? true : false) && y()", "!!x && y()"); fold("(x ? false : true) && y()", "!x && y()"); - // fold("(x ? true : y) && y()", "(x || y) && y()"); - // fold("(x ? y : false) && y()", "(x && y) && y()"); + fold("(x ? true : y) && y()", "(!!x || y) && y()"); + // TODO: drop the `!!` + fold("(x ? y : false) && y()", "(!!x && y) && y()"); fold("var x; (x && true) && y()", "var x; x && y()"); fold("var x; (x && false) && y()", "var x; false && y()"); fold("(x && true) && y()", "x && y()"); @@ -697,6 +768,10 @@ mod test { fold_same("(x || true) && y()"); fold("(x || false) && y()", "x && y()"); + + fold("let x = foo ? true : false", "let x = !!foo"); + fold("let x = foo ? true : bar", "let x = !!foo || bar"); + fold("let x = foo ? bar : false", "let x = !!foo && bar"); } #[test] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 5ef32c59a39dc..96426ab4a2b76 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -19,9 +19,9 @@ Original | minified | minified | gzip | gzip | Fixture 2.14 MB | 726.19 kB | 724.14 kB | 180.18 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 331.91 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 331.90 kB | 331.56 kB | echarts.js -6.69 MB | 2.32 MB | 2.31 MB | 492.81 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 492.82 kB | 488.28 kB | antd.js -10.95 MB | 3.50 MB | 3.49 MB | 909.31 kB | 915.50 kB | typescript.js +10.95 MB | 3.50 MB | 3.49 MB | 909.30 kB | 915.50 kB | typescript.js