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 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,77 +139,110 @@ impl<'a> PeepholeMinimizeConditions {
ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> {
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_stmt.span,
test,
LogicalOperator::And,
right,
);
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));
// `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, consequent, alternate);
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 }");
}
}

View file

@ -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);
",
);

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