feat(minifier): collapse if stmt with empty consequent (#8577)

This commit is contained in:
camc314 2025-01-18 03:51:40 +00:00
parent 41f2070895
commit 4d4e805691
3 changed files with 106 additions and 63 deletions

View file

@ -68,7 +68,7 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
if let Some(folded_stmt) = match stmt { if let Some(folded_stmt) = match stmt {
// If the condition is a literal, we'll let other optimizations try to remove useless code. // 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, _ => None,
} { } {
*stmt = folded_stmt; *stmt = folded_stmt;
@ -139,77 +139,110 @@ impl<'a> PeepholeMinimizeConditions {
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> { ) -> Option<Statement<'a>> {
let Statement::IfStatement(if_stmt) = stmt else { unreachable!() }; let Statement::IfStatement(if_stmt) = stmt else { unreachable!() };
let then_branch = &if_stmt.consequent;
let else_branch = &if_stmt.alternate; // `if (x) foo()` -> `x && foo()`
match else_branch { if !if_stmt.test.is_literal() {
None => { let then_branch = &if_stmt.consequent;
if Self::is_foldable_express_block(&if_stmt.consequent) { let else_branch = &if_stmt.alternate;
let right = Self::get_block_expression(&mut if_stmt.consequent, ctx); match else_branch {
let test = ctx.ast.move_expression(&mut if_stmt.test); None => {
// `if(!x) foo()` -> `x || foo()` if Self::is_foldable_express_block(&if_stmt.consequent) {
if let Expression::UnaryExpression(unary_expr) = test { let right = Self::get_block_expression(&mut if_stmt.consequent, ctx);
if unary_expr.operator.is_not() { let test = ctx.ast.move_expression(&mut if_stmt.test);
let left = unary_expr.unbox().argument; // `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( let logical_expr = ctx.ast.expression_logical(
if_stmt.span, if_stmt.span,
left, test,
LogicalOperator::Or, LogicalOperator::And,
right, right,
); );
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr));
} }
} else { } else {
// `if(x) foo()` -> `x && foo()` // `if (x) if (y) z` -> `if (x && y) z`
let logical_expr = ctx.ast.expression_logical( if let Some(Statement::IfStatement(then_if_stmt)) =
if_stmt.span, then_branch.get_one_child()
test, {
LogicalOperator::And, if then_if_stmt.alternate.is_none() {
right, let and_left = ctx.ast.move_expression(&mut if_stmt.test);
); let Some(then_if_stmt) = if_stmt.consequent.get_one_child_mut()
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr)); else {
} unreachable!()
} else { };
// `if (x) if (y) z` -> `if (x && y) z` let Statement::IfStatement(mut then_if_stmt) =
if let Some(Statement::IfStatement(then_if_stmt)) = then_branch.get_one_child() ctx.ast.move_statement(then_if_stmt)
{ else {
if then_if_stmt.alternate.is_none() { unreachable!()
let and_left = ctx.ast.move_expression(&mut if_stmt.test); };
let Some(then_if_stmt) = if_stmt.consequent.get_one_child_mut() else { let and_right = ctx.ast.move_expression(&mut then_if_stmt.test);
unreachable!() then_if_stmt.test = ctx.ast.expression_logical(
}; and_left.span(),
let Statement::IfStatement(mut then_if_stmt) = and_left,
ctx.ast.move_statement(then_if_stmt) LogicalOperator::And,
else { and_right,
unreachable!() );
}; return Some(Statement::IfStatement(then_if_stmt));
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) => {
Some(else_branch) => { let then_branch_is_expression_block =
let then_branch_is_expression_block = Self::is_foldable_express_block(then_branch); Self::is_foldable_express_block(then_branch);
let else_branch_is_expression_block = Self::is_foldable_express_block(else_branch); let else_branch_is_expression_block =
// `if(foo) bar else baz` -> `foo ? bar : baz` Self::is_foldable_express_block(else_branch);
if then_branch_is_expression_block && else_branch_is_expression_block { // `if(foo) bar else baz` -> `foo ? bar : baz`
let test = ctx.ast.move_expression(&mut if_stmt.test); if then_branch_is_expression_block && else_branch_is_expression_block {
let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx); let test = ctx.ast.move_expression(&mut if_stmt.test);
let else_branch = if_stmt.alternate.as_mut().unwrap(); let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx);
let alternate = Self::get_block_expression(else_branch, ctx); let else_branch = if_stmt.alternate.as_mut().unwrap();
let expr = let alternate = Self::get_block_expression(else_branch, ctx);
ctx.ast.expression_conditional(if_stmt.span, test, consequent, alternate); let expr = ctx.ast.expression_conditional(
return Some(ctx.ast.statement_expression(if_stmt.span, expr)); 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 None
} }
@ -2047,4 +2080,14 @@ mod test {
test("typeof foo !== `number`", "typeof foo != `number`"); test("typeof foo !== `number`", "typeof foo != `number`");
test("`number` !== typeof foo", "`number` != typeof foo"); 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 }");
}
} }

View file

@ -71,7 +71,7 @@ fn integration() {
} }
console.log(c, d); 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); console.log(c, d);
", ",
); );

View file

@ -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 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 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 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 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