Skip to content

Commit 99b47ed

Browse files
committed
feat(minifier): merge single var declarations without init into for-in (#8812)
Compress `var a; for (a in b) c` into `for (var a in b) c`. This is possible because `var a` can be put in any place within the same scope. This is only possible with `var`.
1 parent d9f1d0d commit 99b47ed

File tree

2 files changed

+64
-29
lines changed

2 files changed

+64
-29
lines changed

crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,16 @@ mod test {
203203

204204
#[test]
205205
fn test_for_in() {
206-
// test("var a; for(a in b) foo()", "for (var a in b) foo()");
206+
test("var a; for(a in b) foo()", "for (var a in b) foo()");
207207
test("a = 0; for(a in b) foo()", "for (a in a = 0, b) foo();");
208-
// test_same("var a = 0; for(a in b) foo()");
208+
test_same("var a = 0; for(a in b) foo()");
209209

210210
// We don't handle labels yet.
211-
// test_same("var a; a:for(a in b) foo()");
212-
// test_same("var a; a:b:for(a in b) foo()");
211+
test_same("var a; a:for(a in b) foo()");
212+
test_same("var a; a:b:for(a in b) foo()");
213213

214214
// Verify FOR inside IFs.
215-
// test("if(x){var a; for(a in b) foo()}", "if(x) for(var a in b) foo()");
215+
test("if(x){var a; for(a in b) foo()}", "if(x) for(var a in b) foo()");
216216

217217
// Any other expression.
218218
test("init(); for(a in b) foo()", "for (a in init(), b) foo();");
@@ -221,7 +221,7 @@ mod test {
221221
test_same("function f(){ for(a in b) foo() }");
222222

223223
// We don't handle destructuring patterns yet.
224-
// test("var a; var b; for ([a, b] in c) foo();", "var a, b; for ([a, b] in c) foo();");
224+
test("var a; var b; for ([a, b] in c) foo();", "var a, b; for ([a, b] in c) foo();");
225225
}
226226

227227
#[test]

crates/oxc_minifier/src/peephole/minimize_statements.rs

+58-23
Original file line numberDiff line numberDiff line change
@@ -342,33 +342,68 @@ impl<'a> PeepholeOptimizations {
342342
result.push(Statement::ForStatement(for_stmt));
343343
}
344344
Statement::ForInStatement(mut for_in_stmt) => {
345-
// "a; for (var b in c) d" => "for (var b in a, c) d"
346-
if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() {
347-
// Annex B.3.5 allows initializers in non-strict mode
348-
// <https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-initializers-in-forin-statement-heads>
349-
// If there's a side-effectful initializer, we should not move the previous statement inside.
350-
let has_side_effectful_initializer = {
351-
if let ForStatementLeft::VariableDeclaration(var_decl) = &for_in_stmt.left {
352-
if var_decl.declarations.len() == 1 {
353-
// only var can have a initializer
354-
var_decl.kind.is_var()
355-
&& var_decl.declarations[0].init.as_ref().is_some_and(|init| {
356-
ctx.expression_may_have_side_effects(init)
357-
})
345+
match result.last_mut() {
346+
// "a; for (var b in c) d" => "for (var b in a, c) d"
347+
Some(Statement::ExpressionStatement(prev_expr_stmt)) => {
348+
// Annex B.3.5 allows initializers in non-strict mode
349+
// <https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-initializers-in-forin-statement-heads>
350+
// If there's a side-effectful initializer, we should not move the previous statement inside.
351+
let has_side_effectful_initializer = {
352+
if let ForStatementLeft::VariableDeclaration(var_decl) =
353+
&for_in_stmt.left
354+
{
355+
if var_decl.declarations.len() == 1 {
356+
// only var can have a initializer
357+
var_decl.kind.is_var()
358+
&& var_decl.declarations[0].init.as_ref().is_some_and(
359+
|init| ctx.expression_may_have_side_effects(init),
360+
)
361+
} else {
362+
// the spec does not allow multiple declarations though
363+
true
364+
}
358365
} else {
359-
// the spec does not allow multiple declarations though
360-
true
366+
false
361367
}
362-
} else {
363-
false
368+
};
369+
if !has_side_effectful_initializer {
370+
let a = &mut prev_expr_stmt.expression;
371+
for_in_stmt.right = Self::join_sequence(a, &mut for_in_stmt.right, ctx);
372+
result.pop();
373+
self.mark_current_function_as_changed();
364374
}
365-
};
366-
if !has_side_effectful_initializer {
367-
let a = &mut prev_expr_stmt.expression;
368-
for_in_stmt.right = Self::join_sequence(a, &mut for_in_stmt.right, ctx);
369-
result.pop();
370-
self.mark_current_function_as_changed();
371375
}
376+
// "var a; for (a in b) c" => "for (var a in b) c"
377+
Some(Statement::VariableDeclaration(prev_var_decl)) => {
378+
if let ForStatementLeft::AssignmentTargetIdentifier(id) = &for_in_stmt.left
379+
{
380+
let prev_var_decl_no_init_item = {
381+
if prev_var_decl.kind.is_var()
382+
&& prev_var_decl.declarations.len() == 1
383+
&& prev_var_decl.declarations[0].init.is_none()
384+
{
385+
Some(&prev_var_decl.declarations[0])
386+
} else {
387+
None
388+
}
389+
};
390+
if let Some(prev_var_decl_item) = prev_var_decl_no_init_item {
391+
if let BindingPatternKind::BindingIdentifier(decl_id) =
392+
&prev_var_decl_item.id.kind
393+
{
394+
if id.name == decl_id.name {
395+
for_in_stmt.left =
396+
ForStatementLeft::VariableDeclaration(ctx.ast.alloc(
397+
ctx.ast.move_variable_declaration(prev_var_decl),
398+
));
399+
result.pop();
400+
self.mark_current_function_as_changed();
401+
}
402+
}
403+
}
404+
}
405+
}
406+
_ => {}
372407
}
373408
result.push(Statement::ForInStatement(for_in_stmt));
374409
}

0 commit comments

Comments
 (0)