mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 20:28:58 +00:00
feat(minifier): minimize if (x) return; return 1 -> return x ? void 0 : 1 (#8130)
This commit is contained in:
parent
04c7d38287
commit
f615bfa773
3 changed files with 135 additions and 25 deletions
|
|
@ -151,6 +151,7 @@ impl<'a> Traverse<'a> for LatePeepholeOptimizations {
|
|||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x1_collapse_variable_declarations.exit_statements(stmts, ctx);
|
||||
self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx);
|
||||
self.x3_peephole_minimize_conditions.exit_statements(stmts, ctx);
|
||||
}
|
||||
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
|
|
@ -223,19 +224,20 @@ impl<'a> CompressorPass<'a> for PeepholeOptimizations {
|
|||
}
|
||||
|
||||
impl<'a> Traverse<'a> for PeepholeOptimizations {
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx);
|
||||
self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx);
|
||||
}
|
||||
|
||||
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x5_peephole_remove_dead_code.exit_program(program, ctx);
|
||||
}
|
||||
|
||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x2_peephole_minimize_conditions.exit_statements(stmts, ctx);
|
||||
self.x5_peephole_remove_dead_code.exit_statements(stmts, ctx);
|
||||
}
|
||||
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x2_peephole_minimize_conditions.exit_statement(stmt, ctx);
|
||||
self.x5_peephole_remove_dead_code.exit_statement(stmt, ctx);
|
||||
}
|
||||
|
||||
fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x3_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||
|
||||
|
|
@ -26,6 +27,21 @@ impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions {
|
|||
}
|
||||
|
||||
impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
|
||||
fn exit_statements(
|
||||
&mut self,
|
||||
stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
self.try_replace_if(stmts, ctx);
|
||||
while self.changed {
|
||||
self.changed = false;
|
||||
self.try_replace_if(stmts, ctx);
|
||||
if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) {
|
||||
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Some(folded_stmt) = match stmt {
|
||||
// If the condition is a literal, we'll let other optimizations try to remove useless code.
|
||||
|
|
@ -115,6 +131,103 @@ impl<'a> PeepholeMinimizeConditions {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_replace_if(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
for i in 0..stmts.len() {
|
||||
let Statement::IfStatement(if_stmt) = &stmts[i] else {
|
||||
continue;
|
||||
};
|
||||
let then_branch = &if_stmt.consequent;
|
||||
let else_branch = &if_stmt.alternate;
|
||||
let next_node = stmts.get(i + 1);
|
||||
|
||||
if next_node.is_some_and(|s| matches!(s, Statement::IfStatement(_)))
|
||||
&& else_branch.is_none()
|
||||
&& Self::is_return_block(then_branch)
|
||||
{
|
||||
/* TODO */
|
||||
} else if next_node.is_some_and(Self::is_return_expression)
|
||||
&& else_branch.is_none()
|
||||
&& Self::is_return_block(then_branch)
|
||||
{
|
||||
// `if (x) return; return 1` -> `return x ? void 0 : 1`
|
||||
let Statement::IfStatement(if_stmt) = ctx.ast.move_statement(&mut stmts[i]) else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut if_stmt = if_stmt.unbox();
|
||||
let consequent = Self::get_block_return_expression(&mut if_stmt.consequent, ctx);
|
||||
let alternate = Self::take_return_argument(&mut stmts[i + 1], ctx);
|
||||
let argument = ctx.ast.expression_conditional(
|
||||
if_stmt.span,
|
||||
if_stmt.test,
|
||||
consequent,
|
||||
alternate,
|
||||
);
|
||||
stmts[i] = ctx.ast.statement_return(if_stmt.span, Some(argument));
|
||||
self.changed = true;
|
||||
break;
|
||||
} else if else_branch.is_some() && Self::statement_must_exit_parent(then_branch) {
|
||||
let Statement::IfStatement(if_stmt) = &mut stmts[i] else {
|
||||
unreachable!();
|
||||
};
|
||||
let else_branch = if_stmt.alternate.take().unwrap();
|
||||
stmts.insert(i + 1, else_branch);
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_return_block(stmt: &Statement<'a>) -> bool {
|
||||
match stmt {
|
||||
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
|
||||
matches!(block_stmt.body[0], Statement::ReturnStatement(_))
|
||||
}
|
||||
Statement::ReturnStatement(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_return_expression(stmt: &Statement<'a>) -> bool {
|
||||
matches!(stmt, Statement::ReturnStatement(return_stmt) if return_stmt.argument.is_some())
|
||||
}
|
||||
|
||||
fn statement_must_exit_parent(stmt: &Statement<'a>) -> bool {
|
||||
match stmt {
|
||||
Statement::ThrowStatement(_) | Statement::ReturnStatement(_) => true,
|
||||
Statement::BlockStatement(block_stmt) => {
|
||||
block_stmt.body.last().is_some_and(Self::statement_must_exit_parent)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_block_return_expression(
|
||||
stmt: &mut Statement<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
match stmt {
|
||||
Statement::BlockStatement(block_stmt) if block_stmt.body.len() == 1 => {
|
||||
if let Statement::ReturnStatement(_) = &mut block_stmt.body[0] {
|
||||
Self::take_return_argument(stmt, ctx)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
Statement::ReturnStatement(_) => Self::take_return_argument(stmt, ctx),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_return_argument(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||
let Statement::ReturnStatement(return_stmt) = ctx.ast.move_statement(stmt) else {
|
||||
unreachable!()
|
||||
};
|
||||
let return_stmt = return_stmt.unbox();
|
||||
match return_stmt.argument {
|
||||
Some(e) => e,
|
||||
None => ctx.ast.void_0(return_stmt.span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
|
||||
|
|
@ -226,7 +339,6 @@ mod test {
|
|||
|
||||
/** Try to minimize returns */
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_fold_returns() {
|
||||
fold("function f(){if(x)return 1;else return 2}", "function f(){return x?1:2}");
|
||||
fold("function f(){if(x)return 1;return 2}", "function f(){return x?1:2}");
|
||||
|
|
@ -238,10 +350,10 @@ mod test {
|
|||
"function f(){return x?(y+=1):(y+=2)}",
|
||||
);
|
||||
|
||||
fold("function f(){if(x)return;else return 2-x}", "function f(){if(x);else return 2-x}");
|
||||
fold("function f(){if(x)return;else return 2-x}", "function f(){return x?void 0:2-x}");
|
||||
fold("function f(){if(x)return;return 2-x}", "function f(){return x?void 0:2-x}");
|
||||
fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;{}}");
|
||||
fold("function f(){if(x)return x;return}", "function f(){if(x)return x}");
|
||||
fold("function f(){if(x)return x;else return}", "function f(){if(x)return x;return;}");
|
||||
fold("function f(){if(x)return x;return}", "function f(){if(x)return x;return}");
|
||||
|
||||
fold_same("function f(){for(var x in y) { return x.y; } return k}");
|
||||
}
|
||||
|
|
@ -347,7 +459,6 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_fold_returns_integration2() {
|
||||
// late = true;
|
||||
// disableNormalize();
|
||||
|
|
@ -359,7 +470,6 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_dont_remove_duplicate_statements_without_normalization() {
|
||||
// In the following test case, we can't remove the duplicate "alert(x);" lines since each "x"
|
||||
// refers to a different variable.
|
||||
|
|
@ -516,13 +626,11 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_preserve_if() {
|
||||
fold_same("if(!a&&!b)for(;f(););");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_no_swap_with_dangling_else() {
|
||||
fold_same("if(!x) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()");
|
||||
fold_same("if(!a&&!b) {for(;;)foo(); for(;;)bar()} else if(y) for(;;) f()");
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
| Oxc | ESBuild | Oxc | ESBuild |
|
||||
Original | minified | minified | gzip | gzip | Fixture
|
||||
-------------------------------------------------------------------------------------
|
||||
72.14 kB | 23.89 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js
|
||||
72.14 kB | 23.85 kB | 23.70 kB | 8.64 kB | 8.54 kB | react.development.js
|
||||
|
||||
173.90 kB | 61.39 kB | 59.82 kB | 19.60 kB | 19.33 kB | moment.js
|
||||
173.90 kB | 60.60 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js
|
||||
|
||||
287.63 kB | 92.05 kB | 90.07 kB | 32.44 kB | 31.95 kB | jquery.js
|
||||
287.63 kB | 91.40 kB | 90.07 kB | 32.36 kB | 31.95 kB | jquery.js
|
||||
|
||||
342.15 kB | 120.72 kB | 118.14 kB | 44.86 kB | 44.37 kB | vue.js
|
||||
342.15 kB | 120.07 kB | 118.14 kB | 44.79 kB | 44.37 kB | vue.js
|
||||
|
||||
544.10 kB | 73.15 kB | 72.48 kB | 26.28 kB | 26.20 kB | lodash.js
|
||||
544.10 kB | 72.91 kB | 72.48 kB | 26.27 kB | 26.20 kB | lodash.js
|
||||
|
||||
555.77 kB | 275.48 kB | 270.13 kB | 91.48 kB | 90.80 kB | d3.js
|
||||
555.77 kB | 275.31 kB | 270.13 kB | 91.45 kB | 90.80 kB | d3.js
|
||||
|
||||
1.01 MB | 465.45 kB | 458.89 kB | 127.12 kB | 126.71 kB | bundle.min.js
|
||||
1.01 MB | 462.98 kB | 458.89 kB | 127.06 kB | 126.71 kB | bundle.min.js
|
||||
|
||||
1.25 MB | 659.74 kB | 646.76 kB | 164.45 kB | 163.73 kB | three.js
|
||||
1.25 MB | 658.98 kB | 646.76 kB | 164.40 kB | 163.73 kB | three.js
|
||||
|
||||
2.14 MB | 739.59 kB | 724.14 kB | 181.75 kB | 181.07 kB | victory.js
|
||||
2.14 MB | 736.98 kB | 724.14 kB | 181.33 kB | 181.07 kB | victory.js
|
||||
|
||||
3.20 MB | 1.02 MB | 1.01 MB | 332.98 kB | 331.56 kB | echarts.js
|
||||
3.20 MB | 1.02 MB | 1.01 MB | 332.77 kB | 331.56 kB | echarts.js
|
||||
|
||||
6.69 MB | 2.39 MB | 2.31 MB | 496.49 kB | 488.28 kB | antd.js
|
||||
6.69 MB | 2.38 MB | 2.31 MB | 495.91 kB | 488.28 kB | antd.js
|
||||
|
||||
10.95 MB | 3.54 MB | 3.49 MB | 912.49 kB | 915.50 kB | typescript.js
|
||||
10.95 MB | 3.52 MB | 3.49 MB | 911.59 kB | 915.50 kB | typescript.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue