fix(minifier): avoid merging if to for when the body contains a function declaration (#25637)

fixes https://github.com/oxc-project/oxc/issues/22222

I used AI to generate the code and then reviewed the code and changed the code and added tests.
This commit is contained in:
sapphi-red committed 2026-08-20 18:32:25 +00:00
1 parent c0f71564c3
commit 58ba651e5c
2 files changed
+54

No files matched your search

@@ -22,6 +22,23 @@ impl<'a> PeepholeOptimizations {
let Statement::IfStatement(if_stmt) = first else {
return;
};
// Moving the condition into the `for` test changes its scope from the
// loop body's block scope to the surrounding scope. Block function
// declarations are initialized on entry, so the condition can observe
// them before their declaration statement. Other lexical declarations
// only differ through TDZ behavior, which the minifier intentionally
// ignores.
let body_has_function_declaration = match &for_stmt.body {
Statement::BlockStatement(block_stmt) => {
block_stmt.body.iter().skip(1).any(Self::statement_has_function_declaration)
}
_ => false,
};
if body_has_function_declaration {
return;
}
// "for (;;) if (x) break;" => "for (; !x;) ;"
// "for (; a;) if (x) break;" => "for (; a && !x;) ;"
// "for (;;) if (x) break; else y();" => "for (; !x;) y();"
@@ -100,6 +117,16 @@ impl<'a> PeepholeOptimizations {
}
}
fn statement_has_function_declaration(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::FunctionDeclaration(_) => true,
Statement::LabeledStatement(label) => {
Self::statement_has_function_declaration(&label.body)
}
_ => false,
}
}
fn drop_first_statement(
span: Span,
body: Option<Statement<'a>>,
@@ -24,6 +24,33 @@ fn test_for_variable_declaration() {
);
}
// https://github.com/oxc-project/oxc/issues/22222
#[test]
fn test_for_break_preserves_block_function_scope_in_module() {
test(
"let n = 0;
for (; n < 2; n++) {
if (typeof f === 'function') break;
function f() {}
}
console.log(n);",
"let n = 0;
for (; n < 2; n++) {
if (typeof f == 'function') break;
function f() {}
}
console.log(n);",
);
test(
"for (;;) { if (x) break; var y = foo(); bar(y); bar(y); }",
"for (; !x;) { var y = foo(); bar(y), bar(y); }",
);
test(
"for (;;) { if (x) break; let y = foo(); bar(y); bar(y); }",
"for (; !x;) { let y = foo(); bar(y), bar(y); }",
);
}
#[test]
fn test_for_continue_in_for() {
test("for( a of b ){ if(c) { continue; } d() }", "for ( a of b ) c || d();");