diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index 05c1a3647..9852ed603 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -30,6 +30,7 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode { fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { if let Some(new_stmt) = match stmt { Statement::IfStatement(if_stmt) => self.try_fold_if(if_stmt, ctx), + Statement::ForStatement(for_stmt) => self.try_fold_for(for_stmt, ctx), _ => None, } { *stmt = new_stmt; @@ -162,6 +163,35 @@ impl<'a> PeepholeRemoveDeadCode { } } + fn try_fold_for( + &mut self, + for_stmt: &mut ForStatement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + let test_boolean = + for_stmt.test.as_ref().map_or(Tri::Unknown, |test| ctx.get_boolean_value(test)); + match test_boolean { + Tri::False => { + // Remove the entire `for` statement. + // Check vars in statement + let mut keep_var = KeepVar::new(ctx.ast); + keep_var.visit_statement(&for_stmt.body); + Some( + keep_var + .get_variable_declaration_statement() + .unwrap_or_else(|| ctx.ast.statement_empty(SPAN)), + ) + } + Tri::True => { + // Remove the test expression. + for_stmt.test = None; + self.changed = true; + None + } + Tri::Unknown => None, + } + } + /// Try folding conditional expression (?:) if the condition results of the condition is known. fn try_fold_conditional_expression( expr: &mut ConditionalExpression<'a>, @@ -229,4 +259,35 @@ mod test { fold_same("b: { var x = 1; } x = 2;"); fold_same("a: b: { var x = 1; } x = 2;"); } + + #[test] + fn test_fold_useless_for() { + fold("for(;false;) { foo() }", ""); + fold("for(;void 0;) { foo() }", ""); + fold("for(;undefined;) { foo() }", ""); + fold("for(;true;) foo() ", "for(;;) foo() "); + fold_same("for(;;) foo()"); + fold("for(;false;) { var a = 0; }", "var a"); + fold("for(;false;) { const a = 0; }", ""); + fold("for(;false;) { let a = 0; }", ""); + + // Make sure it plays nice with minimizing + fold("for(;false;) { foo(); continue }", ""); + + // fold("l1:for(;false;) { }", ""); + + // TODO handle single block statement + fold_same("for(;a;) { foo(); }"); + } + + #[test] + fn test_minimize_loop_with_constant_condition_vanilla_for() { + fold("for(;true;) foo()", "for(;;) foo()"); + fold("for(;0;) foo()", ""); + fold("for(;0.0;) foo()", ""); + fold("for(;NaN;) foo()", ""); + fold("for(;null;) foo()", ""); + fold("for(;undefined;) foo()", ""); + fold("for(;'';) foo()", ""); + } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index ed47e975f..1229296e2 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -22,5 +22,5 @@ Original | Minified | esbuild | Gzip | esbuild 6.69 MB | 2.44 MB | 2.31 MB | 498.88 kB | 488.28 kB | antd.js -10.95 MB | 3.59 MB | 3.49 MB | 913.92 kB | 915.50 kB | typescript.js +10.95 MB | 3.59 MB | 3.49 MB | 913.91 kB | 915.50 kB | typescript.js