Skip to content

Commit 4d4e805

Browse files
committed
feat(minifier): collapse if stmt with empty consequent (#8577)
1 parent 41f2070 commit 4d4e805

File tree

3 files changed

+105
-62
lines changed

3 files changed

+105
-62
lines changed

crates/oxc_minifier/src/ast_passes/peephole_minimize_conditions.rs

+100-57
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
6868

6969
if let Some(folded_stmt) = match stmt {
7070
// If the condition is a literal, we'll let other optimizations try to remove useless code.
71-
Statement::IfStatement(s) if !s.test.is_literal() => Self::try_minimize_if(stmt, ctx),
71+
Statement::IfStatement(_) => Self::try_minimize_if(stmt, ctx),
7272
_ => None,
7373
} {
7474
*stmt = folded_stmt;
@@ -139,75 +139,108 @@ impl<'a> PeepholeMinimizeConditions {
139139
ctx: &mut TraverseCtx<'a>,
140140
) -> Option<Statement<'a>> {
141141
let Statement::IfStatement(if_stmt) = stmt else { unreachable!() };
142-
let then_branch = &if_stmt.consequent;
143-
let else_branch = &if_stmt.alternate;
144-
match else_branch {
145-
None => {
146-
if Self::is_foldable_express_block(&if_stmt.consequent) {
147-
let right = Self::get_block_expression(&mut if_stmt.consequent, ctx);
148-
let test = ctx.ast.move_expression(&mut if_stmt.test);
149-
// `if(!x) foo()` -> `x || foo()`
150-
if let Expression::UnaryExpression(unary_expr) = test {
151-
if unary_expr.operator.is_not() {
152-
let left = unary_expr.unbox().argument;
142+
143+
// `if (x) foo()` -> `x && foo()`
144+
if !if_stmt.test.is_literal() {
145+
let then_branch = &if_stmt.consequent;
146+
let else_branch = &if_stmt.alternate;
147+
match else_branch {
148+
None => {
149+
if Self::is_foldable_express_block(&if_stmt.consequent) {
150+
let right = Self::get_block_expression(&mut if_stmt.consequent, ctx);
151+
let test = ctx.ast.move_expression(&mut if_stmt.test);
152+
// `if(!x) foo()` -> `x || foo()`
153+
if let Expression::UnaryExpression(unary_expr) = test {
154+
if unary_expr.operator.is_not() {
155+
let left = unary_expr.unbox().argument;
156+
let logical_expr = ctx.ast.expression_logical(
157+
if_stmt.span,
158+
left,
159+
LogicalOperator::Or,
160+
right,
161+
);
162+
return Some(
163+
ctx.ast.statement_expression(if_stmt.span, logical_expr),
164+
);
165+
}
166+
} else {
167+
// `if(x) foo()` -> `x && foo()`
153168
let logical_expr = ctx.ast.expression_logical(
154169
if_stmt.span,
155-
left,
156-
LogicalOperator::Or,
170+
test,
171+
LogicalOperator::And,
157172
right,
158173
);
159174
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr));
160175
}
161176
} else {
162-
// `if(x) foo()` -> `x && foo()`
163-
let logical_expr = ctx.ast.expression_logical(
177+
// `if (x) if (y) z` -> `if (x && y) z`
178+
if let Some(Statement::IfStatement(then_if_stmt)) =
179+
then_branch.get_one_child()
180+
{
181+
if then_if_stmt.alternate.is_none() {
182+
let and_left = ctx.ast.move_expression(&mut if_stmt.test);
183+
let Some(then_if_stmt) = if_stmt.consequent.get_one_child_mut()
184+
else {
185+
unreachable!()
186+
};
187+
let Statement::IfStatement(mut then_if_stmt) =
188+
ctx.ast.move_statement(then_if_stmt)
189+
else {
190+
unreachable!()
191+
};
192+
let and_right = ctx.ast.move_expression(&mut then_if_stmt.test);
193+
then_if_stmt.test = ctx.ast.expression_logical(
194+
and_left.span(),
195+
and_left,
196+
LogicalOperator::And,
197+
and_right,
198+
);
199+
return Some(Statement::IfStatement(then_if_stmt));
200+
}
201+
}
202+
}
203+
}
204+
Some(else_branch) => {
205+
let then_branch_is_expression_block =
206+
Self::is_foldable_express_block(then_branch);
207+
let else_branch_is_expression_block =
208+
Self::is_foldable_express_block(else_branch);
209+
// `if(foo) bar else baz` -> `foo ? bar : baz`
210+
if then_branch_is_expression_block && else_branch_is_expression_block {
211+
let test = ctx.ast.move_expression(&mut if_stmt.test);
212+
let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx);
213+
let else_branch = if_stmt.alternate.as_mut().unwrap();
214+
let alternate = Self::get_block_expression(else_branch, ctx);
215+
let expr = ctx.ast.expression_conditional(
164216
if_stmt.span,
165217
test,
166-
LogicalOperator::And,
167-
right,
218+
consequent,
219+
alternate,
168220
);
169-
return Some(ctx.ast.statement_expression(if_stmt.span, logical_expr));
170-
}
171-
} else {
172-
// `if (x) if (y) z` -> `if (x && y) z`
173-
if let Some(Statement::IfStatement(then_if_stmt)) = then_branch.get_one_child()
174-
{
175-
if then_if_stmt.alternate.is_none() {
176-
let and_left = ctx.ast.move_expression(&mut if_stmt.test);
177-
let Some(then_if_stmt) = if_stmt.consequent.get_one_child_mut() else {
178-
unreachable!()
179-
};
180-
let Statement::IfStatement(mut then_if_stmt) =
181-
ctx.ast.move_statement(then_if_stmt)
182-
else {
183-
unreachable!()
184-
};
185-
let and_right = ctx.ast.move_expression(&mut then_if_stmt.test);
186-
then_if_stmt.test = ctx.ast.expression_logical(
187-
and_left.span(),
188-
and_left,
189-
LogicalOperator::And,
190-
and_right,
191-
);
192-
return Some(Statement::IfStatement(then_if_stmt));
193-
}
221+
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
194222
}
195223
}
196224
}
197-
Some(else_branch) => {
198-
let then_branch_is_expression_block = Self::is_foldable_express_block(then_branch);
199-
let else_branch_is_expression_block = Self::is_foldable_express_block(else_branch);
200-
// `if(foo) bar else baz` -> `foo ? bar : baz`
201-
if then_branch_is_expression_block && else_branch_is_expression_block {
202-
let test = ctx.ast.move_expression(&mut if_stmt.test);
203-
let consequent = Self::get_block_expression(&mut if_stmt.consequent, ctx);
204-
let else_branch = if_stmt.alternate.as_mut().unwrap();
205-
let alternate = Self::get_block_expression(else_branch, ctx);
206-
let expr =
207-
ctx.ast.expression_conditional(if_stmt.span, test, consequent, alternate);
208-
return Some(ctx.ast.statement_expression(if_stmt.span, expr));
209-
}
210-
}
225+
}
226+
227+
// `if (x) {} else foo` -> `if (!x) foo`
228+
if match &if_stmt.consequent {
229+
Statement::EmptyStatement(_) => true,
230+
Statement::BlockStatement(block_stmt) => block_stmt.body.is_empty(),
231+
_ => false,
232+
} && if_stmt.alternate.is_some()
233+
{
234+
return Some(ctx.ast.statement_if(
235+
if_stmt.span,
236+
ctx.ast.expression_unary(
237+
if_stmt.test.span(),
238+
UnaryOperator::LogicalNot,
239+
ctx.ast.move_expression(&mut if_stmt.test),
240+
),
241+
ctx.ast.move_statement(if_stmt.alternate.as_mut().unwrap()),
242+
None,
243+
));
211244
}
212245

213246
None
@@ -2047,4 +2080,14 @@ mod test {
20472080
test("typeof foo !== `number`", "typeof foo != `number`");
20482081
test("`number` !== typeof foo", "`number` != typeof foo");
20492082
}
2083+
2084+
#[test]
2085+
fn test_negate_empty_if_stmt_consequent() {
2086+
test("if (x) {} else { foo }", "if (!x) { foo }");
2087+
test("if (x) ;else { foo }", "if (!x) { foo }");
2088+
test("if (x) {;} else { foo }", "if (!x) { foo }");
2089+
2090+
test_same("if (x) { var foo } else { bar }");
2091+
test_same("if (x) foo; else { var bar }");
2092+
}
20502093
}

crates/oxc_minifier/tests/ast_passes/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ fn integration() {
7171
}
7272
console.log(c, d);
7373
",
74-
"if ((() => console.log('effect'))(), !0) {} else for (var c = 1, c; unknownGlobal; unknownGlobal && !0) var d;
74+
"if (!((() => console.log('effect'))(), !0)) for (var c = 1, c; unknownGlobal; unknownGlobal && !0) var d;
7575
console.log(c, d);
7676
",
7777
);

tasks/minsize/minsize.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ Original | minified | minified | gzip | gzip | Fixture
1111

1212
544.10 kB | 71.76 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js
1313

14-
555.77 kB | 272.91 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js
14+
555.77 kB | 272.90 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js
1515

16-
1.01 MB | 460.17 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js
16+
1.01 MB | 460.15 kB | 458.89 kB | 126.77 kB | 126.71 kB | bundle.min.js
1717

1818
1.25 MB | 652.88 kB | 646.76 kB | 163.54 kB | 163.73 kB | three.js
1919

20-
2.14 MB | 724.06 kB | 724.14 kB | 179.94 kB | 181.07 kB | victory.js
20+
2.14 MB | 724.05 kB | 724.14 kB | 179.94 kB | 181.07 kB | victory.js
2121

2222
3.20 MB | 1.01 MB | 1.01 MB | 332.01 kB | 331.56 kB | echarts.js
2323

2424
6.69 MB | 2.32 MB | 2.31 MB | 492.44 kB | 488.28 kB | antd.js
2525

26-
10.95 MB | 3.49 MB | 3.49 MB | 907.09 kB | 915.50 kB | typescript.js
26+
10.95 MB | 3.49 MB | 3.49 MB | 907.07 kB | 915.50 kB | typescript.js
2727

0 commit comments

Comments
 (0)