diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 1032abf5efed4..0f1fc15894184 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -59,7 +59,9 @@ impl<'a> PeepholeOptimizations { break; } } - if let Some(stmt) = keep_var.get_variable_declaration_statement() { + if let Some(stmt) = keep_var.get_variable_declaration_statement() + && let Some(stmt) = Self::remove_unused_variable_declaration(stmt, ctx) + { stmts.push(stmt); } diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index ec44ddff16a76..eccbf06afc925 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -90,7 +90,9 @@ impl<'a> PeepholeOptimizations { } else { keep_var.visit_statement(&if_stmt.consequent); } - let var_stmt = keep_var.get_variable_declaration_statement(); + let var_stmt = keep_var + .get_variable_declaration_statement() + .and_then(|stmt| Self::remove_unused_variable_declaration(stmt, ctx)); let has_var_stmt = var_stmt.is_some(); if let Some(var_stmt) = var_stmt { if boolean { @@ -508,10 +510,17 @@ impl<'a> PeepholeOptimizations { #[cfg(test)] mod test { use crate::{ - CompressOptions, - tester::{test, test_options, test_same, test_same_options}, + CompressOptions, CompressOptionsUnused, + tester::{default_options, test, test_options, test_same, test_same_options}, }; + #[track_caller] + fn test_unused(source_text: &str, expected: &str) { + let options = + CompressOptions { unused: CompressOptionsUnused::Remove, ..default_options() }; + test_options(source_text, expected, &options); + } + #[test] fn test_fold_block() { test("{{foo()}}", "foo()"); @@ -629,6 +638,8 @@ mod test { test("if (foo) {} else {}", "foo"); test("if (false) {}", ""); test("if (true) {}", ""); + test("if (false) { var a; console.log(a) }", "if (0) var a"); + test_unused("if (false) { var a; console.log(a) }", ""); } #[test] @@ -668,6 +679,9 @@ mod test { test("while(true) { continue a; unreachable;}", "for(;;) continue a"); test("while(true) { throw a; unreachable;}", "for(;;) throw a"); test("while(true) { return a; unreachable;}", "for(;;) return a"); + + test("(function () { return; var a })()", "(function () { return; var a })()"); + test_unused("(function () { return; var a })()", ""); } #[test] diff --git a/crates/oxc_minifier/src/peephole/remove_unused_declaration.rs b/crates/oxc_minifier/src/peephole/remove_unused_declaration.rs index 67953160ca80e..f5a38088da72d 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_declaration.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_declaration.rs @@ -5,11 +5,17 @@ use crate::{CompressOptionsUnused, ctx::Ctx}; use super::PeepholeOptimizations; impl<'a> PeepholeOptimizations { + fn can_remove_unused_declarators(ctx: &Ctx<'a, '_>) -> bool { + ctx.state.options.unused != CompressOptionsUnused::Keep + && !Self::keep_top_level_var_in_script_mode(ctx) + && !ctx.scoping().root_scope_flags().contains_direct_eval() + } + pub fn should_remove_unused_declarator( decl: &VariableDeclarator<'a>, ctx: &Ctx<'a, '_>, ) -> bool { - if ctx.state.options.unused == CompressOptionsUnused::Keep { + if !Self::can_remove_unused_declarators(ctx) { return false; } if let BindingPatternKind::BindingIdentifier(ident) = &decl.id.kind { @@ -17,13 +23,6 @@ impl<'a> PeepholeOptimizations { if decl.kind.is_using() { return false; } - if Self::keep_top_level_var_in_script_mode(ctx) { - return false; - } - // It is unsafe to remove if direct eval is involved. - if ctx.scoping().root_scope_flags().contains_direct_eval() { - return false; - } if let Some(symbol_id) = ident.symbol_id.get() { return ctx.scoping().symbol_is_unused(symbol_id); } @@ -31,6 +30,21 @@ impl<'a> PeepholeOptimizations { false } + pub fn remove_unused_variable_declaration( + mut stmt: Statement<'a>, + ctx: &Ctx<'a, '_>, + ) -> Option> { + let Statement::VariableDeclaration(var_decl) = &mut stmt else { return Some(stmt) }; + if !Self::can_remove_unused_declarators(ctx) { + return Some(stmt); + } + var_decl.declarations.retain(|decl| !Self::should_remove_unused_declarator(decl, ctx)); + if var_decl.declarations.is_empty() { + return None; + } + Some(stmt) + } + pub fn remove_unused_function_declaration(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) { let Statement::FunctionDeclaration(f) = stmt else { return }; if ctx.state.options.unused == CompressOptionsUnused::Keep {