mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(minifier): avoid merging if to for in sloppy mode when containing function declaration (#25638)
```js
for (var i = 0; i++ < 1;) {
if (x) break;
else function f() {}
f = 0;
}
console.log(typeof f, f);
```
was compressed to
```js
for (var i = 0; i++ < 1 && !x;) {
function f() {}
f = 0;
}
console.log(typeof f, f);
```
But this is wrong in sloppy mode. The output changes `number 0` / `undefined undefined` to `function f() {}`.
This PR fixes that.
I used AI to generate the code and then reviewed the code and added tests.
This commit is contained in:
2 files changed
+70
-1
No files matched your search
@@ -38,6 +38,7 @@ impl<'a> PeepholeOptimizations {
|
||||
if body_has_function_declaration {
|
||||
return;
|
||||
}
|
||||
let is_sloppy_mode = !ctx.current_scope_flags().is_strict_mode();
|
||||
|
||||
// "for (;;) if (x) break;" => "for (; !x;) ;"
|
||||
// "for (; a;) if (x) break;" => "for (; a && !x;) ;"
|
||||
@@ -47,6 +48,12 @@ impl<'a> PeepholeOptimizations {
|
||||
if break_stmt.label.is_some() {
|
||||
return;
|
||||
}
|
||||
// Annex B gives a direct function declaration in an `if` branch an implicit scope.
|
||||
if is_sloppy_mode
|
||||
&& if_stmt.alternate.as_ref().is_some_and(Self::statement_has_function_declaration)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let span = for_stmt.body.span();
|
||||
let (first, body) = match for_stmt.body.take_in(ctx) {
|
||||
@@ -85,6 +92,10 @@ impl<'a> PeepholeOptimizations {
|
||||
if break_stmt.label.is_some() {
|
||||
return;
|
||||
}
|
||||
// Annex B gives a direct function declaration in an `if` branch an implicit scope.
|
||||
if is_sloppy_mode && Self::statement_has_function_declaration(&if_stmt.consequent) {
|
||||
return;
|
||||
}
|
||||
|
||||
let span = for_stmt.body.span();
|
||||
let (first, body) = match for_stmt.body.take_in(ctx) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use crate::{test, test_same};
|
||||
use oxc_span::SourceType;
|
||||
|
||||
use crate::{default_options, test, test_options_source_type, test_same};
|
||||
|
||||
#[test]
|
||||
fn test_for_variable_declaration() {
|
||||
@@ -51,6 +53,62 @@ fn test_for_break_preserves_block_function_scope_in_module() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_for_break_preserves_annex_b_function_scope() {
|
||||
let options = default_options();
|
||||
test_options_source_type(
|
||||
"for (var i = 0; i++ < 1;) {
|
||||
if (x) break;
|
||||
else function f() {}
|
||||
f = 0;
|
||||
}
|
||||
console.log(typeof f, f);",
|
||||
"for (var i = 0; i++ < 1;) {
|
||||
if (x) break;
|
||||
else function f() {}
|
||||
f = 0;
|
||||
}
|
||||
console.log(typeof f, f);",
|
||||
SourceType::script(),
|
||||
&options,
|
||||
);
|
||||
test_options_source_type(
|
||||
"for (var i = 0; i++ < 1;) {
|
||||
if (x) function f() {}
|
||||
else break;
|
||||
f = 0;
|
||||
}
|
||||
console.log(typeof f, f);",
|
||||
"for (var i = 0; i++ < 1;) {
|
||||
if (x) function f() {}
|
||||
else break;
|
||||
f = 0;
|
||||
}
|
||||
console.log(typeof f, f);",
|
||||
SourceType::script(),
|
||||
&options,
|
||||
);
|
||||
|
||||
test_options_source_type(
|
||||
"for (;;) {
|
||||
if (x) break;
|
||||
else { function f() {} }
|
||||
}",
|
||||
"for (; !x;) { function f() {} }",
|
||||
SourceType::mjs(),
|
||||
&options,
|
||||
);
|
||||
test_options_source_type(
|
||||
"for (;;) {
|
||||
if (x) { function f() {} }
|
||||
else break;
|
||||
}",
|
||||
"for (; x;) { function f() {} }",
|
||||
SourceType::mjs(),
|
||||
&options,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_for_continue_in_for() {
|
||||
test("for( a of b ){ if(c) { continue; } d() }", "for ( a of b ) c || d();");
|
||||
|
||||
Reference in new issue
Block a user