feat(minifier): enable termination statement optimization for do while (#24921)

- allow removing trailing unlabeled `continue;` in `do...while` block
bodies (same as other loop block bodies).
- allow removing trailing unlabeled `break;` in `do...while` block
bodies **only** when the loop test is side-effect-free and statically
`false`.
- enable and update `do...while` exit-point tests.

#### changes in stack
1. https://github.com/oxc-project/oxc/pull/24920
2. https://github.com/oxc-project/oxc/pull/24921 < This PR
This commit is contained in:
Armano authored and GitHub committed 2026-07-28 09:39:05 +08:00
1 parent e6e584b9be
commit 73fc747699
2 files changed
+38 -15

No files matched your search

@@ -5,7 +5,7 @@ use oxc_allocator::{ArenaBox, ArenaVec, TakeIn};
use oxc_ast::ast::*;
use oxc_ast_visit::{VisitJs, walk_js};
use oxc_ecmascript::{
constant_evaluation::{DetermineValueType, IsLiteralValue, ValueType},
constant_evaluation::{ConstantEvaluation, DetermineValueType, IsLiteralValue, ValueType},
side_effects::MayHaveSideEffects,
};
use oxc_semantic::ScopeFlags;
@@ -2066,7 +2066,7 @@ impl<'a> PeepholeOptimizations {
/// - Bare `return` statements that terminate a function body
fn can_remove_termination_statement(stmt: &Statement<'a>, ctx: &TraverseCtx<'a>) -> bool {
match stmt {
// unlabeled `continue;` that terminates a `for`, `for...in`, `for...of`, or `while` body.
// unlabeled `continue;` that terminates a `for`, `for...in`, `for...of`, `while`, `do...while` body.
Statement::ContinueStatement(stmt) if stmt.label.is_none() => {
matches!(
ctx.ancestors().nth(1),
@@ -2075,9 +2075,19 @@ impl<'a> PeepholeOptimizations {
| Ancestor::ForInStatementBody(_)
| Ancestor::ForOfStatementBody(_)
| Ancestor::WhileStatementBody(_)
| Ancestor::DoWhileStatementBody(_)
)
)
}
// unlabeled `break;` that terminates a `do...while` body if test is false.
Statement::BreakStatement(stmt) if stmt.label.is_none() => {
match ctx.ancestors().nth(1) {
Some(Ancestor::DoWhileStatementBody(do_while)) => {
do_while.test().get_side_free_boolean_value(ctx) == Some(false)
}
_ => false,
}
}
// bare `return;` in function-body scope.
Statement::ReturnStatement(stmt) if stmt.argument.is_none() => {
ctx.parent().is_function_body()
@@ -144,40 +144,53 @@ fn test_while_continue_optimization() {
}
#[test]
#[ignore = "TODO: Do-while continue optimization not yet implemented"]
fn test_do_continue_optimization() {
test("do{if(x)continue; x=3; continue; }while(true)", "do x||(x=3);while(!0)");
test("do{if(x)continue; x=3; continue; }while(true)", "do x||=3;while(!0);");
test("do{a();continue;b()}while(true)", "do a();while(!0)");
test("do{if(true){a();continue;}else;b();}while(true)", "do a();while(!0)");
test("do{if(false){a();continue;}else;b();continue;}while(true)", "do b();while(!0)");
test("do{if(a()){b();continue;}else;c();}while(true)", "do a()?b():c();while(!0)");
test("do{if(a()){b();}else{c();continue;}}while(true)", "do a()?b():c();while(!0)");
test("do{if(a()){b();continue;}else;}while(true)", "do a()&&b();while(!0)");
test(
"do{if(a()){b();continue;}else;c();}while(true)",
"do{if(a()){b();continue}c()}while(!0);",
); // do a()?b():c();while(!0)
test(
"do{if(a()){b();}else{c();continue;}}while(true)",
"do if(a())b();else{c();continue}while(!0);",
); // do a()?b():c();while(!0)
test("do{if(a()){b();continue;}else;}while(true)", "do if(a()){b();continue}while(!0);"); // do a()&&b();while(!0)
test("do{if(a()){continue;}else{continue;} continue;}while(true)", "do a();while(!0)");
test("do{if(a()){continue;}else{continue;} b();}while(true)", "do a();while(!0)");
test("do{while(a())continue;}while(true)", "do for(;a(););while(!0)");
test("do{for(x in a())continue}while(true)", "do for(x in a());while(!0)");
test("do{while(a())continue;}while(true)", "do for(;a();)continue;while(!0);"); // do for(;a(););while(!0)
test("do{for(x in a())continue}while(true)", "do for(x in a())continue;while(!0);"); // do for(x in a());while(!0)
test("do{while(a())break;}while(true)", "do for(;a();)break;while(!0)");
test("do for(x in a())break;while(true)", "do for(x in a())break;while(!0)");
test("do{try{continue;}catch(e){continue;}}while(true)", "do;while(!0)");
test(
"do{try{continue;}catch(e){continue;}}while(true)",
"do try{continue}catch{continue}while(!0);",
); // do;while(!0)
test(
"do{try{if(a()){continue;}else{continue;} continue;}catch(e){}}while(true)",
"do try{a()}catch{}while(!0);",
);
"do try{if(a())continue;continue}catch{}while(!0);",
); // do try{a()}catch{}while(!0);
test("do{g:continue}while(true)", "do;while(!0)");
test("do{g:continue}while(true)", "do;while(!0);");
// This case could be improved.
test("do{g:if(a()){continue;}else{continue;} continue;}while(true)", "do g:a();while(!0)");
test("do{g:if(a()){continue;}else{continue;} continue;}while(true)", "do g:a();while(!0);");
test("do { foo(); continue; } while(false)", "do foo();while(!1)");
test("do { foo(); break; } while(false)", "do foo();while(!1)");
test("do{break}while(fn());", "do break; while(fn());");
test("do{break}while(true);", "do break; while(!0);"); // do while(!1);
test("do{break}while(!new Date());", "do;while(!1);");
test_same("do { foo(); switch (x) { case 1: break; default: f()}; } while(false)");
test(
"do { foo(); switch (x) { case 1: break; default: f()} } while(false)",
"do switch (foo(),x) { case 1: break; default: f() } while(!1);",
);
}
#[test]