From 2e3446122feffa359b2d5de5e651150af5f47132 Mon Sep 17 00:00:00 2001 From: copilot-swe-agent <198982749+copilot-swe-agent@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:17:38 +0000 Subject: [PATCH] fix(minifier): prevent expression inlining into block-scoped for-in declarations (#18651) --- .../src/peephole/minimize_statements.rs | 9 ++++++- .../tests/peephole/minimize_statements.rs | 24 +++++++++++++++++++ .../allocs_minifier.snap | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 7b595096e7ff7..9690498db0b62 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -1019,7 +1019,14 @@ impl<'a> PeepholeOptimizations { false } }; - if !has_side_effectful_initializer { + // Only allow inlining when the for-in variable is declared with `var`. + // Block-scoped declarations (let/const) can cause variable shadowing issues + // where the inlined expression might reference a variable with the same name + // as the for-in variable, but after inlining, it would incorrectly refer to + // the shadowed for-in variable instead. + // See: https://github.com/oxc-project/oxc/issues/18650 + let is_block_scoped = matches!(&for_in_stmt.left, ForStatementLeft::VariableDeclaration(var_decl) if !var_decl.kind.is_var()); + if !has_side_effectful_initializer && !is_block_scoped { let a = &mut prev_expr_stmt.expression; for_in_stmt.right = Self::join_sequence(a, &mut for_in_stmt.right, ctx); result.pop(); diff --git a/crates/oxc_minifier/tests/peephole/minimize_statements.rs b/crates/oxc_minifier/tests/peephole/minimize_statements.rs index c2e1564ec6d45..ad201fc057a24 100644 --- a/crates/oxc_minifier/tests/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/tests/peephole/minimize_statements.rs @@ -32,3 +32,27 @@ fn test_for_continue_in_for() { test("for( a in b ){ c(); continue; }", "for ( a in b ) c();"); test("for( ; ; ){ c(); continue; }", "for ( ; ; ) c();"); } + +#[test] +fn test_for_in_block_scoped_no_inline() { + // Should NOT inline when for-in uses `let` or `const` because it can cause variable shadowing + // https://github.com/oxc-project/oxc/issues/18650 + // The inlined expression might reference a variable with the same name as the for-in variable, + // causing it to incorrectly reference the shadowed for-in variable instead of the outer variable. + test( + "{ var name = 'name1'; const foo = { foo: 1 }; name = 'name2'; for (let name in foo) { console.log(name); } console.log(name); }", + "{ var name = 'name1'; let foo = { foo: 1 }; name = 'name2'; for (let name in foo) console.log(name); console.log(name); }", + ); + test( + "{ var name = 'name1'; const foo = { foo: 1 }; name = 'name2'; for (const name in foo) { console.log(name); } console.log(name); }", + "{ var name = 'name1'; let foo = { foo: 1 }; name = 'name2'; for (let name in foo) console.log(name); console.log(name); }", + ); + test( + "{ var name = 'name1'; const foo = { foo: 1 }; name = 'name2'; for (var name in foo) { console.log(name); } console.log(name); }", + "var name = 'name1'; for (var name in name = 'name2', { foo: 1 }) console.log(name); console.log(name);", + ); + test( + "{ var name = 'name1'; const foo = { foo: 1 }; name = 'name2'; for (name in foo) { console.log(name); } console.log(name); }", + "var name = 'name1'; for (name in name = 'name2', { foo: 1 }) console.log(name); console.log(name);", + ); +} diff --git a/tasks/track_memory_allocations/allocs_minifier.snap b/tasks/track_memory_allocations/allocs_minifier.snap index 602434b835376..5ee1ecda3e63e 100644 --- a/tasks/track_memory_allocations/allocs_minifier.snap +++ b/tasks/track_memory_allocations/allocs_minifier.snap @@ -6,7 +6,7 @@ cal.com.tsx | 1.06 MB || 21098 | 474 | RadixUIAdoptionSection.jsx | 2.52 kB || 58 | 3 || 30 | 6 -pdf.mjs | 567.30 kB || 4691 | 569 || 47464 | 7730 +pdf.mjs | 567.30 kB || 4691 | 569 || 47462 | 7734 antd.js | 6.69 MB || 10728 | 2514 || 331644 | 69358