From 99b47ed6f07afb97889ef80fde6f93e24667a562 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:31:25 +0000 Subject: [PATCH] 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`. --- .../collapse_variable_declarations.rs | 12 +-- .../src/peephole/minimize_statements.rs | 81 +++++++++++++------ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs b/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs index 0b01955ca0aab..ae276f7bd63d8 100644 --- a/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs @@ -203,16 +203,16 @@ mod test { #[test] fn test_for_in() { - // test("var a; for(a in b) foo()", "for (var a in b) foo()"); + test("var a; for(a in b) foo()", "for (var a in b) foo()"); test("a = 0; for(a in b) foo()", "for (a in a = 0, b) foo();"); - // test_same("var a = 0; for(a in b) foo()"); + test_same("var a = 0; for(a in b) foo()"); // We don't handle labels yet. - // test_same("var a; a:for(a in b) foo()"); - // test_same("var a; a:b:for(a in b) foo()"); + test_same("var a; a:for(a in b) foo()"); + test_same("var a; a:b:for(a in b) foo()"); // Verify FOR inside IFs. - // test("if(x){var a; for(a in b) foo()}", "if(x) for(var a in b) foo()"); + test("if(x){var a; for(a in b) foo()}", "if(x) for(var a in b) foo()"); // Any other expression. test("init(); for(a in b) foo()", "for (a in init(), b) foo();"); @@ -221,7 +221,7 @@ mod test { test_same("function f(){ for(a in b) foo() }"); // We don't handle destructuring patterns yet. - // test("var a; var b; for ([a, b] in c) foo();", "var a, b; for ([a, b] in c) foo();"); + test("var a; var b; for ([a, b] in c) foo();", "var a, b; for ([a, b] in c) foo();"); } #[test] diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index dca3ad4ced95c..7e853769f47cf 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -342,33 +342,68 @@ impl<'a> PeepholeOptimizations { result.push(Statement::ForStatement(for_stmt)); } Statement::ForInStatement(mut for_in_stmt) => { - // "a; for (var b in c) d" => "for (var b in a, c) d" - if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { - // Annex B.3.5 allows initializers in non-strict mode - // - // If there's a side-effectful initializer, we should not move the previous statement inside. - let has_side_effectful_initializer = { - if let ForStatementLeft::VariableDeclaration(var_decl) = &for_in_stmt.left { - if var_decl.declarations.len() == 1 { - // only var can have a initializer - var_decl.kind.is_var() - && var_decl.declarations[0].init.as_ref().is_some_and(|init| { - ctx.expression_may_have_side_effects(init) - }) + match result.last_mut() { + // "a; for (var b in c) d" => "for (var b in a, c) d" + Some(Statement::ExpressionStatement(prev_expr_stmt)) => { + // Annex B.3.5 allows initializers in non-strict mode + // + // If there's a side-effectful initializer, we should not move the previous statement inside. + let has_side_effectful_initializer = { + if let ForStatementLeft::VariableDeclaration(var_decl) = + &for_in_stmt.left + { + if var_decl.declarations.len() == 1 { + // only var can have a initializer + var_decl.kind.is_var() + && var_decl.declarations[0].init.as_ref().is_some_and( + |init| ctx.expression_may_have_side_effects(init), + ) + } else { + // the spec does not allow multiple declarations though + true + } } else { - // the spec does not allow multiple declarations though - true + false } - } else { - false + }; + if !has_side_effectful_initializer { + let a = &mut prev_expr_stmt.expression; + for_in_stmt.right = Self::join_sequence(a, &mut for_in_stmt.right, ctx); + result.pop(); + self.mark_current_function_as_changed(); } - }; - if !has_side_effectful_initializer { - let a = &mut prev_expr_stmt.expression; - for_in_stmt.right = Self::join_sequence(a, &mut for_in_stmt.right, ctx); - result.pop(); - self.mark_current_function_as_changed(); } + // "var a; for (a in b) c" => "for (var a in b) c" + Some(Statement::VariableDeclaration(prev_var_decl)) => { + if let ForStatementLeft::AssignmentTargetIdentifier(id) = &for_in_stmt.left + { + let prev_var_decl_no_init_item = { + if prev_var_decl.kind.is_var() + && prev_var_decl.declarations.len() == 1 + && prev_var_decl.declarations[0].init.is_none() + { + Some(&prev_var_decl.declarations[0]) + } else { + None + } + }; + if let Some(prev_var_decl_item) = prev_var_decl_no_init_item { + if let BindingPatternKind::BindingIdentifier(decl_id) = + &prev_var_decl_item.id.kind + { + if id.name == decl_id.name { + for_in_stmt.left = + ForStatementLeft::VariableDeclaration(ctx.ast.alloc( + ctx.ast.move_variable_declaration(prev_var_decl), + )); + result.pop(); + self.mark_current_function_as_changed(); + } + } + } + } + } + _ => {} } result.push(Statement::ForInStatement(for_in_stmt)); }