diff --git a/crates/oxc_minifier/src/peephole/fold_constants.rs b/crates/oxc_minifier/src/peephole/fold_constants.rs index 29004d038858c..21c3f066978b6 100644 --- a/crates/oxc_minifier/src/peephole/fold_constants.rs +++ b/crates/oxc_minifier/src/peephole/fold_constants.rs @@ -9,15 +9,24 @@ use oxc_syntax::operator::{BinaryOperator, LogicalOperator}; use crate::ctx::Ctx; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; impl<'a> PeepholeOptimizations { /// Constant Folding /// /// - pub fn fold_constants_exit_expression(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) { - if let Expression::TemplateLiteral(t) = expr { - self.try_inline_values_in_template_literal(t, ctx); + pub fn fold_constants_exit_expression( + &self, + expr: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { + match expr { + Expression::TemplateLiteral(t) => { + self.try_inline_values_in_template_literal(t, state, ctx); + } + Expression::ObjectExpression(e) => self.fold_object_spread(e, state, ctx), + _ => {} } if let Some(folded_expr) = match expr { @@ -29,11 +38,10 @@ impl<'a> PeepholeOptimizations { Expression::LogicalExpression(e) => Self::try_fold_logical_expr(e, ctx), Expression::ChainExpression(e) => Self::try_fold_optional_chain(e, ctx), Expression::CallExpression(e) => Self::try_fold_number_constructor(e, ctx), - Expression::ObjectExpression(e) => self.fold_object_spread(e, ctx), _ => None, } { *expr = folded_expr; - self.mark_current_function_as_changed(); + state.changed = true; }; } @@ -633,10 +641,11 @@ impl<'a> PeepholeOptimizations { } fn fold_object_spread( - &mut self, + &self, e: &mut ObjectExpression<'a>, + state: &mut State, ctx: Ctx<'a, '_>, - ) -> Option> { + ) { let len = e.properties.len(); e.properties.retain(|p| { if let ObjectPropertyKind::SpreadProperty(spread_element) = p { @@ -659,17 +668,17 @@ impl<'a> PeepholeOptimizations { true }); if e.properties.len() != len { - self.mark_current_function_as_changed(); + state.changed = true; } - None } /// Inline constant values in template literals /// /// - `foo${1}bar${i}` => `foo1bar${i}` fn try_inline_values_in_template_literal( - &mut self, + &self, t: &mut TemplateLiteral<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { let has_expr_to_inline = t @@ -719,7 +728,7 @@ impl<'a> PeepholeOptimizations { } } - self.mark_current_function_as_changed(); + state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index b67b344d100ff..8d2a2dbabec2f 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -5,15 +5,16 @@ use oxc_syntax::es_target::ESTarget; use crate::ctx::Ctx; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; /// Minimize Conditions /// /// impl<'a> PeepholeOptimizations { pub fn minimize_conditions_exit_expression( - &mut self, + &self, expr: &mut Expression<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { let mut changed = false; @@ -50,7 +51,7 @@ impl<'a> PeepholeOptimizations { } } if changed { - self.mark_current_function_as_changed(); + state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_for_statement.rs b/crates/oxc_minifier/src/peephole/minimize_for_statement.rs index b5488adfcf3ee..ff85df67620f3 100644 --- a/crates/oxc_minifier/src/peephole/minimize_for_statement.rs +++ b/crates/oxc_minifier/src/peephole/minimize_for_statement.rs @@ -3,11 +3,16 @@ use oxc_span::GetSpan; use crate::ctx::Ctx; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; impl<'a> PeepholeOptimizations { /// `mangleFor`: - pub fn minimize_for_statement(&mut self, for_stmt: &mut ForStatement<'a>, ctx: Ctx<'a, '_>) { + pub fn minimize_for_statement( + &self, + for_stmt: &mut ForStatement<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { // Get the first statement in the loop let mut first = &for_stmt.body; if let Statement::BlockStatement(block_stmt) = first { @@ -60,7 +65,7 @@ impl<'a> PeepholeOptimizations { let alternate = if_stmt.alternate.take(); for_stmt.body = Self::drop_first_statement(span, body, alternate, ctx); - self.mark_current_function_as_changed(); + state.changed = true; return; } // "for (;;) if (x) y(); else break;" => "for (; x;) y();" @@ -97,7 +102,7 @@ impl<'a> PeepholeOptimizations { let consequent = ctx.ast.move_statement(&mut if_stmt.consequent); for_stmt.body = Self::drop_first_statement(span, body, Some(consequent), ctx); - self.mark_current_function_as_changed(); + state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/minimize_if_statement.rs b/crates/oxc_minifier/src/peephole/minimize_if_statement.rs index 2d575fea4ae92..d70909ebc6c01 100644 --- a/crates/oxc_minifier/src/peephole/minimize_if_statement.rs +++ b/crates/oxc_minifier/src/peephole/minimize_if_statement.rs @@ -5,16 +5,17 @@ use oxc_span::GetSpan; use crate::ctx::Ctx; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; impl<'a> PeepholeOptimizations { /// `MangleIf`: pub fn try_minimize_if( - &mut self, + &self, if_stmt: &mut IfStatement<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) -> Option> { - self.wrap_to_avoid_ambiguous_else(if_stmt, ctx); + self.wrap_to_avoid_ambiguous_else(if_stmt, state, ctx); if let Statement::ExpressionStatement(expr_stmt) = &mut if_stmt.consequent { if if_stmt.alternate.is_none() { let (op, e) = match &mut if_stmt.test { @@ -46,7 +47,7 @@ impl<'a> PeepholeOptimizations { { // "if (a) {}" => "a;" let mut expr = ctx.ast.move_expression(&mut if_stmt.test); - self.remove_unused_expression(&mut expr, ctx); + self.remove_unused_expression(&mut expr, state, ctx); return Some(ctx.ast.statement_expression(if_stmt.span, expr)); } else if let Some(Statement::ExpressionStatement(expr_stmt)) = &mut if_stmt.alternate { let (op, e) = match &mut if_stmt.test { @@ -69,7 +70,7 @@ impl<'a> PeepholeOptimizations { if_stmt.test = ctx.ast.move_expression(&mut unary_expr.argument); if_stmt.consequent = ctx.ast.move_statement(stmt); if_stmt.alternate = None; - self.mark_current_function_as_changed(); + state.changed = true; } // "if (a) {} else return b;" => "if (!a) return b;" _ => { @@ -80,8 +81,8 @@ impl<'a> PeepholeOptimizations { ); if_stmt.consequent = ctx.ast.move_statement(stmt); if_stmt.alternate = None; - self.try_minimize_if(if_stmt, ctx); - self.mark_current_function_as_changed(); + self.try_minimize_if(if_stmt, state, ctx); + state.changed = true; } } } @@ -94,8 +95,8 @@ impl<'a> PeepholeOptimizations { // "if (!a) return b; else return c;" => "if (a) return c; else return b;" if_stmt.test = ctx.ast.move_expression(&mut unary_expr.argument); std::mem::swap(&mut if_stmt.consequent, alternate); - self.wrap_to_avoid_ambiguous_else(if_stmt, ctx); - self.mark_current_function_as_changed(); + self.wrap_to_avoid_ambiguous_else(if_stmt, state, ctx); + state.changed = true; } } // "if (a) return b; else {}" => "if (a) return b;" is handled by remove_dead_code @@ -114,7 +115,7 @@ impl<'a> PeepholeOptimizations { ctx, ); if_stmt.consequent = ctx.ast.move_statement(&mut if2_stmt.consequent); - self.mark_current_function_as_changed(); + state.changed = true; } } } @@ -125,7 +126,12 @@ impl<'a> PeepholeOptimizations { /// Wrap to avoid ambiguous else. /// `if (foo) if (bar) baz else quaz` -> `if (foo) { if (bar) baz else quaz }` #[expect(clippy::cast_possible_truncation)] - fn wrap_to_avoid_ambiguous_else(&mut self, if_stmt: &mut IfStatement<'a>, ctx: Ctx<'a, '_>) { + fn wrap_to_avoid_ambiguous_else( + &self, + if_stmt: &mut IfStatement<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { if let Statement::IfStatement(if2) = &mut if_stmt.consequent { if if2.consequent.is_jump_statement() && if2.alternate.is_some() { let scope_id = ScopeId::new(ctx.scoping.scoping().scopes_len() as u32); @@ -136,7 +142,7 @@ impl<'a> PeepholeOptimizations { scope_id, ), )); - self.mark_current_function_as_changed(); + state.changed = true; } } } diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 75f4cfad1efae..9cf4af713a454 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -10,7 +10,7 @@ use oxc_traverse::Ancestor; use crate::{ctx::Ctx, keep_var::KeepVar}; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; impl<'a> PeepholeOptimizations { /// `mangleStmts`: @@ -30,7 +30,12 @@ impl<'a> PeepholeOptimizations { /// /// ## MinimizeExitPoints: /// - pub fn minimize_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: Ctx<'a, '_>) { + pub fn minimize_statements( + &self, + stmts: &mut Vec<'a, Statement<'a>>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { let mut result: Vec<'a, Statement<'a>> = ctx.ast.vec_with_capacity(stmts.len()); let mut is_control_flow_dead = false; let mut keep_var = KeepVar::new(ctx.ast); @@ -51,6 +56,7 @@ impl<'a> PeepholeOptimizations { &mut new_stmts, &mut result, &mut is_control_flow_dead, + state, ctx, ) .is_break() @@ -69,14 +75,14 @@ impl<'a> PeepholeOptimizations { Statement::ContinueStatement(s) if s.label.is_none() => { if let Some(Ancestor::ForStatementBody(_)) = ctx.ancestors().nth(1) { result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } // "function f() { x(); return; }" => "function f() { x(); }" Statement::ReturnStatement(s) if s.argument.is_none() => { if let Ancestor::FunctionBodyStatements(_) = ctx.parent() { result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } _ => {} @@ -96,7 +102,7 @@ impl<'a> PeepholeOptimizations { break 'return_loop; } } - self.mark_current_function_as_changed(); + state.changed = true; // "a(); return b;" => "return a(), b;" let last_stmt = result.pop().unwrap(); let Statement::ReturnStatement(mut last_return) = last_stmt else { @@ -124,7 +130,7 @@ impl<'a> PeepholeOptimizations { break 'return_loop; }; - self.mark_current_function_as_changed(); + state.changed = true; let last_stmt = result.pop().unwrap(); let Statement::ReturnStatement(last_return) = last_stmt else { unreachable!() @@ -175,7 +181,7 @@ impl<'a> PeepholeOptimizations { right, ctx, ); - self.minimize_conditions_exit_expression(&mut expr, ctx); + self.minimize_conditions_exit_expression(&mut expr, state, ctx); expr }; let last_return_stmt = @@ -191,7 +197,7 @@ impl<'a> PeepholeOptimizations { let prev_stmt = &result[prev_index]; match prev_stmt { Statement::ExpressionStatement(_) => { - self.mark_current_function_as_changed(); + state.changed = true; // "a(); throw b;" => "throw a(), b;" let last_stmt = result.pop().unwrap(); let Statement::ThrowStatement(mut last_throw) = last_stmt else { @@ -221,7 +227,7 @@ impl<'a> PeepholeOptimizations { break 'throw_loop; }; - self.mark_current_function_as_changed(); + state.changed = true; let last_stmt = result.pop().unwrap(); let Statement::ThrowStatement(last_throw) = last_stmt else { unreachable!() @@ -263,7 +269,7 @@ impl<'a> PeepholeOptimizations { right, ctx, ); - self.minimize_conditions_exit_expression(&mut expr, ctx); + self.minimize_conditions_exit_expression(&mut expr, state, ctx); expr }; let last_throw_stmt = ctx.ast.statement_throw(right_span, argument); @@ -279,12 +285,13 @@ impl<'a> PeepholeOptimizations { } fn minimize_statement( - &mut self, + &self, stmt: Statement<'a>, i: usize, stmts: &mut Vec<'a, Statement<'a>>, result: &mut Vec<'a, Statement<'a>>, is_control_flow_dead: &mut bool, + state: &mut State, ctx: Ctx<'a, '_>, ) -> ControlFlow<()> { match stmt { @@ -298,35 +305,35 @@ impl<'a> PeepholeOptimizations { result.push(Statement::ContinueStatement(s)); } Statement::VariableDeclaration(var_decl) => { - self.handle_variable_declaration(var_decl, result); + self.handle_variable_declaration(var_decl, result, state); } Statement::ExpressionStatement(expr_stmt) => { - self.handle_expression_statement(expr_stmt, result, ctx); + self.handle_expression_statement(expr_stmt, result, state, ctx); } Statement::SwitchStatement(switch_stmt) => { - self.handle_switch_statement(switch_stmt, result, ctx); + self.handle_switch_statement(switch_stmt, result, state, ctx); } Statement::IfStatement(if_stmt) => { - if self.handle_if_statement(i, stmts, if_stmt, result, ctx).is_break() { + if self.handle_if_statement(i, stmts, if_stmt, result, state, ctx).is_break() { return ControlFlow::Break(()); } } Statement::ReturnStatement(ret_stmt) => { - self.handle_return_statement(ret_stmt, result, ctx, is_control_flow_dead); + self.handle_return_statement(ret_stmt, result, is_control_flow_dead, state, ctx); } Statement::ThrowStatement(throw_stmt) => { - self.handle_throw_statement(throw_stmt, result, ctx, is_control_flow_dead); + self.handle_throw_statement(throw_stmt, result, is_control_flow_dead, state, ctx); } Statement::ForStatement(for_stmt) => { - self.handle_for_statement(for_stmt, result, ctx); + self.handle_for_statement(for_stmt, result, state, ctx); } Statement::ForInStatement(for_in_stmt) => { - self.handle_for_in_statement(for_in_stmt, result, ctx); + self.handle_for_in_statement(for_in_stmt, result, state, ctx); } Statement::ForOfStatement(for_of_stmt) => { - self.handle_for_of_statement(for_of_stmt, result, ctx); + self.handle_for_of_statement(for_of_stmt, result, state, ctx); } - Statement::BlockStatement(block_stmt) => self.handle_block(result, block_stmt), + Statement::BlockStatement(block_stmt) => self.handle_block(result, block_stmt, state), stmt => result.push(stmt), } ControlFlow::Continue(()) @@ -363,24 +370,26 @@ impl<'a> PeepholeOptimizations { } fn handle_variable_declaration( - &mut self, + &self, mut var_decl: Box<'a, VariableDeclaration<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ) { if let Some(Statement::VariableDeclaration(prev_var_decl)) = result.last_mut() { if var_decl.kind == prev_var_decl.kind { var_decl.declarations.splice(0..0, prev_var_decl.declarations.drain(..)); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } result.push(Statement::VariableDeclaration(var_decl)); } fn handle_expression_statement( - &mut self, + &self, mut expr_stmt: Box<'a, ExpressionStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) { if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { @@ -388,15 +397,16 @@ impl<'a> PeepholeOptimizations { let b = &mut expr_stmt.expression; expr_stmt.expression = Self::join_sequence(a, b, ctx); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } result.push(Statement::ExpressionStatement(expr_stmt)); } fn handle_switch_statement( - &mut self, + &self, mut switch_stmt: Box<'a, SwitchStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) { if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { @@ -404,18 +414,19 @@ impl<'a> PeepholeOptimizations { let b = &mut switch_stmt.discriminant; switch_stmt.discriminant = Self::join_sequence(a, b, ctx); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } result.push(Statement::SwitchStatement(switch_stmt)); } #[expect(clippy::cast_possible_truncation)] fn handle_if_statement( - &mut self, + &self, i: usize, stmts: &mut Vec<'a, Statement<'a>>, mut if_stmt: Box<'a, IfStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) -> ControlFlow<()> { // Absorb a previous expression statement @@ -424,7 +435,7 @@ impl<'a> PeepholeOptimizations { let b = &mut if_stmt.test; if_stmt.test = Self::join_sequence(a, b, ctx); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } if if_stmt.consequent.is_jump_statement() { @@ -445,7 +456,7 @@ impl<'a> PeepholeOptimizations { ctx, ); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } @@ -505,7 +516,7 @@ impl<'a> PeepholeOptimizations { } body.extend(stmts.drain(i + 1..)); - self.minimize_statements(&mut body, ctx); + self.minimize_statements(&mut body, state, ctx); let span = if body.is_empty() { if_stmt.consequent.span() } else { body[0].span() }; let test = ctx.ast.move_expression(&mut if_stmt.test); @@ -521,10 +532,10 @@ impl<'a> PeepholeOptimizations { }; let mut if_stmt = ctx.ast.if_statement(test.span(), test, consequent, None); let if_stmt = self - .try_minimize_if(&mut if_stmt, ctx) + .try_minimize_if(&mut if_stmt, state, ctx) .unwrap_or_else(|| Statement::IfStatement(ctx.ast.alloc(if_stmt))); result.push(if_stmt); - self.mark_current_function_as_changed(); + state.changed = true; return ControlFlow::Break(()); } } @@ -537,10 +548,10 @@ impl<'a> PeepholeOptimizations { if if_stmt.consequent.is_jump_statement() { if let Some(stmt) = if_stmt.alternate.take() { if let Statement::BlockStatement(block_stmt) = stmt { - self.handle_block(result, block_stmt); + self.handle_block(result, block_stmt, state); } else { result.push(stmt); - self.mark_current_function_as_changed(); + state.changed = true; } continue; } @@ -557,18 +568,19 @@ impl<'a> PeepholeOptimizations { } fn handle_return_statement( - &mut self, + &self, mut ret_stmt: Box<'a, ReturnStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, - ctx: Ctx<'a, '_>, is_control_flow_dead: &mut bool, + state: &mut State, + ctx: Ctx<'a, '_>, ) { if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { if let Some(argument) = &mut ret_stmt.argument { let a = &mut prev_expr_stmt.expression; *argument = Self::join_sequence(a, argument, ctx); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } result.push(Statement::ReturnStatement(ret_stmt)); @@ -576,27 +588,29 @@ impl<'a> PeepholeOptimizations { } fn handle_throw_statement( - &mut self, + &self, mut throw_stmt: Box<'a, ThrowStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, - ctx: Ctx<'a, '_>, is_control_flow_dead: &mut bool, + state: &mut State, + ctx: Ctx<'a, '_>, ) { if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { let a = &mut prev_expr_stmt.expression; let b = &mut throw_stmt.argument; throw_stmt.argument = Self::join_sequence(a, b, ctx); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } result.push(Statement::ThrowStatement(throw_stmt)); *is_control_flow_dead = true; } fn handle_for_statement( - &mut self, + &self, mut for_stmt: Box<'a, ForStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) { match result.last_mut() { @@ -606,14 +620,14 @@ impl<'a> PeepholeOptimizations { let a = &mut prev_expr_stmt.expression; *init = Self::join_sequence(a, init, ctx); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } else { for_stmt.init = Some(ForStatementInit::from( ctx.ast.move_expression(&mut prev_expr_stmt.expression), )); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } Some(Statement::VariableDeclaration(prev_var_decl)) => { @@ -625,7 +639,7 @@ impl<'a> PeepholeOptimizations { .declarations .splice(0..0, prev_var_decl.declarations.drain(..)); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } } @@ -634,7 +648,7 @@ impl<'a> PeepholeOptimizations { for_stmt.init = Some(ForStatementInit::VariableDeclaration(ctx.ast.alloc(var_decl))); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } _ => {} @@ -643,9 +657,10 @@ impl<'a> PeepholeOptimizations { } fn handle_for_in_statement( - &mut self, + &self, mut for_in_stmt: Box<'a, ForInStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) { match result.last_mut() { @@ -675,7 +690,7 @@ impl<'a> PeepholeOptimizations { 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(); + state.changed = true; } } // "var a; for (a in b) c" => "for (var a in b) c" @@ -700,7 +715,7 @@ impl<'a> PeepholeOptimizations { ctx.ast.alloc(ctx.ast.move_variable_declaration(prev_var_decl)), ); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } } @@ -712,9 +727,10 @@ impl<'a> PeepholeOptimizations { } fn handle_for_of_statement( - &mut self, + &self, mut for_of_stmt: Box<'a, ForOfStatement<'a>>, result: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) { // "var a; for (a of b) c" => "for (var a of b) c" @@ -739,7 +755,7 @@ impl<'a> PeepholeOptimizations { ctx.ast.alloc(ctx.ast.move_variable_declaration(prev_var_decl)), ); result.pop(); - self.mark_current_function_as_changed(); + state.changed = true; } } } @@ -750,16 +766,17 @@ impl<'a> PeepholeOptimizations { /// `appendIfOrLabelBodyPreservingScope`: fn handle_block( - &mut self, + &self, result: &mut Vec<'a, Statement<'a>>, block_stmt: Box<'a, BlockStatement<'a>>, + state: &mut State, ) { let keep_block = block_stmt.body.iter().any(Self::statement_cares_about_scope); if keep_block { result.push(Statement::BlockStatement(block_stmt)); } else { result.append(&mut block_stmt.unbox().body); - self.mark_current_function_as_changed(); + state.changed = true; } } diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index ddfe59a8fc82b..62e28f1abe39d 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unused_self)] + mod collapse_variable_declarations; mod convert_to_dotted_properties; mod fold_constants; @@ -29,6 +31,11 @@ use crate::{ctx::Ctx, options::CompressOptionsKeepNames}; pub use self::normalize::{Normalize, NormalizeOptions}; +#[derive(Debug, Default, Clone, Copy)] +pub struct State { + pub changed: bool, +} + pub struct PeepholeOptimizations { target: ESTarget, keep_names: CompressOptionsKeepNames, @@ -152,7 +159,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.minimize_statements(stmts, ctx); + let mut state = State::default(); + self.minimize_statements(stmts, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -160,21 +171,29 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); + let mut state = State::default(); self.try_fold_stmt_in_boolean_context(stmt, ctx); - self.remove_dead_code_exit_statement(stmt, ctx); + self.remove_dead_code_exit_statement(stmt, &mut state, ctx); if let Statement::IfStatement(if_stmt) = stmt { - if let Some(folded_stmt) = self.try_minimize_if(if_stmt, ctx) { + if let Some(folded_stmt) = self.try_minimize_if(if_stmt, &mut state, ctx) { *stmt = folded_stmt; self.mark_current_function_as_changed(); } } + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_for_statement(&mut self, stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) { if !self.is_prev_function_changed() { return; } - self.minimize_for_statement(stmt, Ctx(ctx)); + let mut state = State::default(); + self.minimize_for_statement(stmt, &mut state, Ctx(ctx)); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -182,7 +201,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_return_statement(stmt, ctx); + let mut state = State::default(); + self.substitute_return_statement(stmt, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_variable_declaration( @@ -194,7 +217,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_variable_declaration(decl, ctx); + let mut state = State::default(); + self.substitute_variable_declaration(decl, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { @@ -202,11 +229,15 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.fold_constants_exit_expression(expr, ctx); - self.minimize_conditions_exit_expression(expr, ctx); - self.remove_dead_code_exit_expression(expr, ctx); - self.replace_known_methods_exit_expression(expr, ctx); - self.substitute_exit_expression(expr, ctx); + let mut state = State::default(); + self.fold_constants_exit_expression(expr, &mut state, ctx); + self.minimize_conditions_exit_expression(expr, &mut state, ctx); + self.remove_dead_code_exit_expression(expr, &mut state, ctx); + self.replace_known_methods_exit_expression(expr, &mut state, ctx); + self.substitute_exit_expression(expr, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_unary_expression(&mut self, expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>) { @@ -225,7 +256,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_call_expression(expr, ctx); + let mut state = State::default(); + self.substitute_call_expression(expr, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_new_expression(&mut self, expr: &mut NewExpression<'a>, ctx: &mut TraverseCtx<'a>) { @@ -233,7 +268,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_new_expression(expr, ctx); + let mut state = State::default(); + self.substitute_new_expression(expr, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) { @@ -241,7 +280,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_object_property(prop, ctx); + let mut state = State::default(); + self.substitute_object_property(prop, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_assignment_target_property( @@ -253,7 +296,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_assignment_target_property(node, ctx); + let mut state = State::default(); + self.substitute_assignment_target_property(node, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_assignment_target_property_property( @@ -265,7 +312,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_assignment_target_property_property(prop, ctx); + let mut state = State::default(); + self.substitute_assignment_target_property_property(prop, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) { @@ -273,7 +324,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_binding_property(prop, ctx); + let mut state = State::default(); + self.substitute_binding_property(prop, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_method_definition( @@ -285,7 +340,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_method_definition(prop, ctx); + let mut state = State::default(); + self.substitute_method_definition(prop, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_property_definition( @@ -297,7 +356,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_property_definition(prop, ctx); + let mut state = State::default(); + self.substitute_property_definition(prop, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } fn exit_accessor_property( @@ -309,7 +372,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { return; } let ctx = Ctx(ctx); - self.substitute_accessor_property(prop, ctx); + let mut state = State::default(); + self.substitute_accessor_property(prop, &mut state, ctx); + if state.changed { + self.mark_current_function_as_changed(); + } } } @@ -380,16 +447,19 @@ impl<'a> DeadCodeElimination { impl<'a> Traverse<'a> for DeadCodeElimination { fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { - self.inner.remove_dead_code_exit_statement(stmt, Ctx(ctx)); + let mut state = State::default(); + self.inner.remove_dead_code_exit_statement(stmt, &mut state, Ctx(ctx)); } fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - self.inner.remove_dead_code_exit_statements(stmts, Ctx(ctx)); + let mut state = State::default(); + self.inner.remove_dead_code_exit_statements(stmts, &mut state, Ctx(ctx)); stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_))); } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - self.inner.fold_constants_exit_expression(expr, Ctx(ctx)); - self.inner.remove_dead_code_exit_expression(expr, Ctx(ctx)); + let mut state = State::default(); + self.inner.fold_constants_exit_expression(expr, &mut state, Ctx(ctx)); + self.inner.remove_dead_code_exit_expression(expr, &mut state, Ctx(ctx)); } } diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index 1d6ebe49c45a7..bcf6a00f08c22 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -7,7 +7,7 @@ use oxc_traverse::Ancestor; use crate::{ctx::Ctx, keep_var::KeepVar}; -use super::{LatePeepholeOptimizations, PeepholeOptimizations}; +use super::{LatePeepholeOptimizations, PeepholeOptimizations, State}; /// Remove Dead Code from the AST. /// @@ -16,43 +16,52 @@ use super::{LatePeepholeOptimizations, PeepholeOptimizations}; /// See `KeepVar` at the end of this file for `var` hoisting logic. /// impl<'a, 'b> PeepholeOptimizations { - pub fn remove_dead_code_exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: Ctx<'a, '_>) { + pub fn remove_dead_code_exit_statement( + &self, + stmt: &mut Statement<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { if let Some(new_stmt) = match stmt { Statement::BlockStatement(s) => Self::try_optimize_block(s, ctx), - Statement::IfStatement(s) => self.try_fold_if(s, ctx), - Statement::ForStatement(s) => self.try_fold_for(s, ctx), + Statement::IfStatement(s) => Self::try_fold_if(s, state, ctx), + Statement::ForStatement(s) => self.try_fold_for(s, state, ctx), Statement::TryStatement(s) => Self::try_fold_try(s, ctx), Statement::LabeledStatement(s) => Self::try_fold_labeled(s, ctx), _ => None, } { *stmt = new_stmt; - self.mark_current_function_as_changed(); + state.changed = true; } - self.try_fold_expression_stmt(stmt, ctx); + self.try_fold_expression_stmt(stmt, state, ctx); } pub fn remove_dead_code_exit_expression( - &mut self, + &self, expr: &mut Expression<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { if let Some(folded_expr) = match expr { - Expression::ConditionalExpression(e) => self.try_fold_conditional_expression(e, ctx), + Expression::ConditionalExpression(e) => { + self.try_fold_conditional_expression(e, state, ctx) + } Expression::SequenceExpression(sequence_expression) => { - self.try_fold_sequence_expression(sequence_expression, ctx) + self.try_fold_sequence_expression(sequence_expression, state, ctx) } _ => None, } { *expr = folded_expr; - self.mark_current_function_as_changed(); + state.changed = true; } } /// Removes dead code thats comes after `return`, `throw`, `continue` and `break` statements. pub fn remove_dead_code_exit_statements( - &mut self, + &self, stmts: &mut Vec<'a, Statement<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) { // Remove code after `return` and `throw` statements @@ -104,12 +113,12 @@ impl<'a, 'b> PeepholeOptimizations { if let Some(stmt) = keep_var.get_variable_declaration_statement() { stmts.push(stmt); if !all_hoisted { - self.mark_current_function_as_changed(); + state.changed = true; } } if stmts.len() != len { - self.mark_current_function_as_changed(); + state.changed = true; } } @@ -150,29 +159,29 @@ impl<'a, 'b> PeepholeOptimizations { } fn try_fold_if( - &mut self, if_stmt: &mut IfStatement<'a>, + state: &mut State, ctx: Ctx<'a, 'b>, ) -> Option> { // Descend and remove `else` blocks first. match &mut if_stmt.alternate { Some(Statement::IfStatement(alternate)) => { - if let Some(new_stmt) = self.try_fold_if(alternate, ctx) { + if let Some(new_stmt) = Self::try_fold_if(alternate, state, ctx) { if matches!(new_stmt, Statement::EmptyStatement(_)) { if_stmt.alternate = None; } else { if_stmt.alternate = Some(new_stmt); } - self.mark_current_function_as_changed(); + state.changed = true; } } Some(Statement::BlockStatement(s)) if s.body.is_empty() => { if_stmt.alternate = None; - self.mark_current_function_as_changed(); + state.changed = true; } Some(Statement::EmptyStatement(_)) => { if_stmt.alternate = None; - self.mark_current_function_as_changed(); + state.changed = true; } _ => {} } @@ -235,22 +244,23 @@ impl<'a, 'b> PeepholeOptimizations { } fn try_fold_for( - &mut self, + &self, for_stmt: &mut ForStatement<'a>, + state: &mut State, ctx: Ctx<'a, 'b>, ) -> Option> { if let Some(init) = &mut for_stmt.init { if let Some(init) = init.as_expression_mut() { - if self.remove_unused_expression(init, ctx) { + if self.remove_unused_expression(init, state, ctx) { for_stmt.init = None; - self.mark_current_function_as_changed(); + state.changed = true; } } } if let Some(update) = &mut for_stmt.update { - if self.remove_unused_expression(update, ctx) { + if self.remove_unused_expression(update, state, ctx) { for_stmt.update = None; - self.mark_current_function_as_changed(); + state.changed = true; } } @@ -293,7 +303,7 @@ impl<'a, 'b> PeepholeOptimizations { Some(true) => { // Remove the test expression. for_stmt.test = None; - self.mark_current_function_as_changed(); + state.changed = true; None } None => None, @@ -324,7 +334,12 @@ impl<'a, 'b> PeepholeOptimizations { var_decl.unwrap_or_else(|| ctx.ast.statement_empty(s.span)).into() } - fn try_fold_expression_stmt(&mut self, stmt: &mut Statement<'a>, ctx: Ctx<'a, 'b>) { + fn try_fold_expression_stmt( + &self, + stmt: &mut Statement<'a>, + state: &mut State, + ctx: Ctx<'a, 'b>, + ) { let Statement::ExpressionStatement(expr_stmt) = stmt else { return }; // We need to check if it is in arrow function with `expression: true`. // This is the only scenario where we can't remove it even if `ExpressionStatement`. @@ -334,9 +349,9 @@ impl<'a, 'b> PeepholeOptimizations { } } - if self.remove_unused_expression(&mut expr_stmt.expression, ctx) { + if self.remove_unused_expression(&mut expr_stmt.expression, state, ctx) { *stmt = ctx.ast.statement_empty(expr_stmt.span); - self.mark_current_function_as_changed(); + state.changed = true; } } @@ -375,8 +390,9 @@ impl<'a, 'b> PeepholeOptimizations { /// Try folding conditional expression (?:) if the condition results of the condition is known. fn try_fold_conditional_expression( - &mut self, + &self, expr: &mut ConditionalExpression<'a>, + state: &mut State, ctx: Ctx<'a, 'b>, ) -> Option> { expr.test.evaluate_value_to_boolean(&ctx).map(|v| { @@ -385,7 +401,7 @@ impl<'a, 'b> PeepholeOptimizations { let exprs = ctx.ast.vec_from_array([ { let mut test = ctx.ast.move_expression(&mut expr.test); - self.remove_unused_expression(&mut test, ctx); + self.remove_unused_expression(&mut test, state, ctx); test }, ctx.ast.move_expression(if v { @@ -426,8 +442,9 @@ impl<'a, 'b> PeepholeOptimizations { } fn try_fold_sequence_expression( - &mut self, + &self, sequence_expr: &mut SequenceExpression<'a>, + state: &mut State, ctx: Ctx<'a, 'b>, ) -> Option> { let should_keep_as_sequence_expr = sequence_expr @@ -446,28 +463,28 @@ impl<'a, 'b> PeepholeOptimizations { sequence_expr.expressions.retain_mut(|e| { i += 1; if should_keep_as_sequence_expr && i == old_len - 1 { - if self.remove_unused_expression(e, ctx) { + if self.remove_unused_expression(e, state, ctx) { *e = ctx.ast.expression_numeric_literal( e.span(), 0.0, None, NumberBase::Decimal, ); - self.mark_current_function_as_changed(); + state.changed = true; } return true; } if i == old_len { return true; } - !self.remove_unused_expression(e, ctx) + !self.remove_unused_expression(e, state, ctx) }); if sequence_expr.expressions.len() == 1 { return Some(sequence_expr.expressions.pop().unwrap()); } if sequence_expr.expressions.len() != old_len { - self.mark_current_function_as_changed(); + state.changed = true; } None } diff --git a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs index b88dd3cba63c0..f56ea0ddeaaed 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs @@ -12,70 +12,90 @@ use oxc_syntax::es_target::ESTarget; use crate::ctx::Ctx; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; impl<'a> PeepholeOptimizations { /// `SimplifyUnusedExpr`: - pub fn remove_unused_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + pub fn remove_unused_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { match e { - Expression::ArrayExpression(_) => self.fold_array_expression(e, ctx), - Expression::UnaryExpression(_) => self.fold_unary_expression(e, ctx), - Expression::NewExpression(_) => self.fold_new_constructor(e, ctx), - Expression::LogicalExpression(_) => self.fold_logical_expression(e, ctx), - Expression::SequenceExpression(_) => self.fold_sequence_expression(e, ctx), - Expression::TemplateLiteral(_) => self.fold_template_literal(e, ctx), - Expression::ObjectExpression(_) => self.fold_object_expression(e, ctx), - Expression::ConditionalExpression(_) => self.fold_conditional_expression(e, ctx), - Expression::BinaryExpression(_) => self.fold_binary_expression(e, ctx), - Expression::CallExpression(_) => self.fold_call_expression(e, ctx), + Expression::ArrayExpression(_) => self.fold_array_expression(e, state, ctx), + Expression::UnaryExpression(_) => self.fold_unary_expression(e, state, ctx), + Expression::NewExpression(_) => self.fold_new_constructor(e, state, ctx), + Expression::LogicalExpression(_) => self.fold_logical_expression(e, state, ctx), + Expression::SequenceExpression(_) => self.fold_sequence_expression(e, state, ctx), + Expression::TemplateLiteral(_) => self.fold_template_literal(e, state, ctx), + Expression::ObjectExpression(_) => self.fold_object_expression(e, state, ctx), + Expression::ConditionalExpression(_) => self.fold_conditional_expression(e, state, ctx), + Expression::BinaryExpression(_) => self.fold_binary_expression(e, state, ctx), + Expression::CallExpression(_) => self.fold_call_expression(e, state, ctx), _ => !e.may_have_side_effects(&ctx), } } - fn fold_unary_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_unary_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::UnaryExpression(unary_expr) = e else { return false }; match unary_expr.operator { UnaryOperator::Void | UnaryOperator::LogicalNot => { *e = ctx.ast.move_expression(&mut unary_expr.argument); - self.mark_current_function_as_changed(); - self.remove_unused_expression(e, ctx) + state.changed = true; + self.remove_unused_expression(e, state, ctx) } UnaryOperator::Typeof => { if unary_expr.argument.is_identifier_reference() { true } else { *e = ctx.ast.move_expression(&mut unary_expr.argument); - self.mark_current_function_as_changed(); - self.remove_unused_expression(e, ctx) + state.changed = true; + self.remove_unused_expression(e, state, ctx) } } _ => false, } } - fn fold_sequence_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_sequence_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::SequenceExpression(sequence_expr) = e else { return false }; let old_len = sequence_expr.expressions.len(); - sequence_expr.expressions.retain_mut(|e| !self.remove_unused_expression(e, ctx)); + sequence_expr.expressions.retain_mut(|e| !self.remove_unused_expression(e, state, ctx)); if sequence_expr.expressions.len() != old_len { - self.mark_current_function_as_changed(); + state.changed = true; } sequence_expr.expressions.is_empty() } - fn fold_logical_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_logical_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::LogicalExpression(logical_expr) = e else { return false }; if !logical_expr.operator.is_coalesce() && self.try_fold_expr_in_boolean_context(&mut logical_expr.left, ctx) { - self.mark_current_function_as_changed(); + state.changed = true; } - if self.remove_unused_expression(&mut logical_expr.right, ctx) { - self.remove_unused_expression(&mut logical_expr.left, ctx); + if self.remove_unused_expression(&mut logical_expr.right, state, ctx) { + self.remove_unused_expression(&mut logical_expr.left, state, ctx); *e = ctx.ast.move_expression(&mut logical_expr.left); - self.mark_current_function_as_changed(); + state.changed = true; return false; } @@ -110,7 +130,7 @@ impl<'a> PeepholeOptimizations { ctx, ) { *e = ctx.ast.move_expression(logical_right); - self.mark_current_function_as_changed(); + state.changed = true; return false; } } @@ -141,7 +161,7 @@ impl<'a> PeepholeOptimizations { assignment_expr.span = *logical_span; assignment_expr.operator = AssignmentOperator::LogicalNullish; *e = ctx.ast.move_expression(logical_right); - self.mark_current_function_as_changed(); + state.changed = true; return false; } } @@ -152,7 +172,7 @@ impl<'a> PeepholeOptimizations { LogicalOperator::Coalesce, ctx.ast.move_expression(logical_right), ); - self.mark_current_function_as_changed(); + state.changed = true; return false; } } @@ -165,7 +185,12 @@ impl<'a> PeepholeOptimizations { } // `([1,2,3, foo()])` -> `foo()` - fn fold_array_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_array_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::ArrayExpression(array_expr) = e else { return false; }; @@ -179,11 +204,11 @@ impl<'a> PeepholeOptimizations { ArrayExpressionElement::Elision(_) => false, match_expression!(ArrayExpressionElement) => { let el_expr = el.to_expression_mut(); - !self.remove_unused_expression(el_expr, ctx) + !self.remove_unused_expression(el_expr, state, ctx) } }); if array_expr.elements.len() != old_len { - self.mark_current_function_as_changed(); + state.changed = true; } if array_expr.elements.len() == 0 { @@ -215,21 +240,26 @@ impl<'a> PeepholeOptimizations { false } - fn fold_new_constructor(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_new_constructor( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::NewExpression(new_expr) = e else { return false }; if new_expr.pure { let mut exprs = - self.fold_arguments_into_needed_expressions(&mut new_expr.arguments, ctx); + self.fold_arguments_into_needed_expressions(&mut new_expr.arguments, state, ctx); if exprs.is_empty() { return true; } else if exprs.len() == 1 { *e = exprs.pop().unwrap(); - self.mark_current_function_as_changed(); + state.changed = true; return false; } *e = ctx.ast.expression_sequence(new_expr.span, exprs); - self.mark_current_function_as_changed(); + state.changed = true; return false; } @@ -279,7 +309,12 @@ impl<'a> PeepholeOptimizations { } // "`${1}2${foo()}3`" -> "`${foo()}`" - fn fold_template_literal(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_template_literal( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::TemplateLiteral(temp_lit) = e else { return false }; if temp_lit.expressions.is_empty() { return true; @@ -296,7 +331,7 @@ impl<'a> PeepholeOptimizations { for mut e in temp_lit.expressions.drain(..) { if e.to_primitive(&ctx).is_symbol() != Some(false) { pending_to_string_required_exprs.push(e); - } else if !self.remove_unused_expression(&mut e, ctx) { + } else if !self.remove_unused_expression(&mut e, state, ctx) { if pending_to_string_required_exprs.len() > 0 { // flush pending to string required expressions let expressions = @@ -349,17 +384,22 @@ impl<'a> PeepholeOptimizations { return true; } else if transformed_elements.len() == 1 { *e = transformed_elements.pop().unwrap(); - self.mark_current_function_as_changed(); + state.changed = true; return false; } *e = ctx.ast.expression_sequence(temp_lit.span, transformed_elements); - self.mark_current_function_as_changed(); + state.changed = true; false } // `({ 1: 1, [foo()]: bar() })` -> `foo(), bar()` - fn fold_object_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_object_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::ObjectExpression(object_expr) = e else { return false; }; @@ -396,13 +436,13 @@ impl<'a> PeepholeOptimizations { PropertyKey::StaticIdentifier(_) | PropertyKey::PrivateIdentifier(_) => {} match_expression!(PropertyKey) => { let mut prop_key = key.into_expression(); - if !self.remove_unused_expression(&mut prop_key, ctx) { + if !self.remove_unused_expression(&mut prop_key, state, ctx) { transformed_elements.push(prop_key); } } } - if !self.remove_unused_expression(&mut value, ctx) { + if !self.remove_unused_expression(&mut value, state, ctx) { transformed_elements.push(value); } } @@ -421,31 +461,37 @@ impl<'a> PeepholeOptimizations { return true; } else if transformed_elements.len() == 1 { *e = transformed_elements.pop().unwrap(); - self.mark_current_function_as_changed(); + state.changed = true; return false; } *e = ctx.ast.expression_sequence(object_expr.span, transformed_elements); - self.mark_current_function_as_changed(); + state.changed = true; false } - fn fold_conditional_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_conditional_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::ConditionalExpression(conditional_expr) = e else { return false; }; - let consequent = self.remove_unused_expression(&mut conditional_expr.consequent, ctx); - let alternate = self.remove_unused_expression(&mut conditional_expr.alternate, ctx); + let consequent = + self.remove_unused_expression(&mut conditional_expr.consequent, state, ctx); + let alternate = self.remove_unused_expression(&mut conditional_expr.alternate, state, ctx); // "foo() ? 1 : 2" => "foo()" if consequent && alternate { - let test = self.remove_unused_expression(&mut conditional_expr.test, ctx); + let test = self.remove_unused_expression(&mut conditional_expr.test, state, ctx); if test { return true; } *e = ctx.ast.move_expression(&mut conditional_expr.test); - self.mark_current_function_as_changed(); + state.changed = true; return false; } @@ -458,7 +504,7 @@ impl<'a> PeepholeOptimizations { ctx.ast.move_expression(&mut conditional_expr.alternate), ctx, ); - self.mark_current_function_as_changed(); + state.changed = true; return false; } @@ -471,14 +517,19 @@ impl<'a> PeepholeOptimizations { ctx.ast.move_expression(&mut conditional_expr.consequent), ctx, ); - self.mark_current_function_as_changed(); + state.changed = true; return false; } false } - fn fold_binary_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_binary_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::BinaryExpression(binary_expr) = e else { return false; }; @@ -492,18 +543,18 @@ impl<'a> PeepholeOptimizations { | BinaryOperator::LessEqualThan | BinaryOperator::GreaterThan | BinaryOperator::GreaterEqualThan => { - let left = self.remove_unused_expression(&mut binary_expr.left, ctx); - let right = self.remove_unused_expression(&mut binary_expr.right, ctx); + let left = self.remove_unused_expression(&mut binary_expr.left, state, ctx); + let right = self.remove_unused_expression(&mut binary_expr.right, state, ctx); match (left, right) { (true, true) => true, (true, false) => { *e = ctx.ast.move_expression(&mut binary_expr.right); - self.mark_current_function_as_changed(); + state.changed = true; false } (false, true) => { *e = ctx.ast.move_expression(&mut binary_expr.left); - self.mark_current_function_as_changed(); + state.changed = true; false } (false, false) => { @@ -514,13 +565,13 @@ impl<'a> PeepholeOptimizations { ctx.ast.move_expression(&mut binary_expr.right), ]), ); - self.mark_current_function_as_changed(); + state.changed = true; false } } } BinaryOperator::Addition => { - self.fold_string_addition_chain(e, ctx); + Self::fold_string_addition_chain(e, state, ctx); matches!(e, Expression::StringLiteral(_)) } _ => !e.may_have_side_effects(&ctx), @@ -528,7 +579,11 @@ impl<'a> PeepholeOptimizations { } /// returns whether the passed expression is a string - fn fold_string_addition_chain(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_string_addition_chain( + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::BinaryExpression(binary_expr) = e else { return e.to_primitive(&ctx).is_string() == Some(true); }; @@ -536,14 +591,14 @@ impl<'a> PeepholeOptimizations { return e.to_primitive(&ctx).is_string() == Some(true); } - let left_is_string = self.fold_string_addition_chain(&mut binary_expr.left, ctx); + let left_is_string = Self::fold_string_addition_chain(&mut binary_expr.left, state, ctx); if left_is_string { if !binary_expr.left.may_have_side_effects(&ctx) && !binary_expr.left.is_specific_string_literal("") { binary_expr.left = ctx.ast.expression_string_literal(binary_expr.left.span(), "", None); - self.mark_current_function_as_changed(); + state.changed = true; } let right_as_primitive = binary_expr.right.to_primitive(&ctx); @@ -551,7 +606,7 @@ impl<'a> PeepholeOptimizations { && !binary_expr.right.may_have_side_effects(&ctx) { *e = ctx.ast.move_expression(&mut binary_expr.left); - self.mark_current_function_as_changed(); + state.changed = true; return true; } return true; @@ -564,28 +619,33 @@ impl<'a> PeepholeOptimizations { { binary_expr.right = ctx.ast.expression_string_literal(binary_expr.right.span(), "", None); - self.mark_current_function_as_changed(); + state.changed = true; } return true; } false } - fn fold_call_expression(&mut self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + fn fold_call_expression( + &self, + e: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) -> bool { let Expression::CallExpression(call_expr) = e else { return false }; if call_expr.pure { let mut exprs = - self.fold_arguments_into_needed_expressions(&mut call_expr.arguments, ctx); + self.fold_arguments_into_needed_expressions(&mut call_expr.arguments, state, ctx); if exprs.is_empty() { return true; } else if exprs.len() == 1 { *e = exprs.pop().unwrap(); - self.mark_current_function_as_changed(); + state.changed = true; return false; } *e = ctx.ast.expression_sequence(call_expr.span, exprs); - self.mark_current_function_as_changed(); + state.changed = true; return false; } @@ -607,19 +667,19 @@ impl<'a> PeepholeOptimizations { // Replace "(() => foo())()" with "foo()" let expr = f.get_expression_mut().unwrap(); *e = ctx.ast.move_expression(expr); - return self.remove_unused_expression(e, ctx); + return self.remove_unused_expression(e, state, ctx); } match &mut f.body.statements[0] { Statement::ExpressionStatement(expr_stmt) => { // Replace "(() => { foo() })" with "foo()" *e = ctx.ast.move_expression(&mut expr_stmt.expression); - return self.remove_unused_expression(e, ctx); + return self.remove_unused_expression(e, state, ctx); } Statement::ReturnStatement(ret_stmt) => { if let Some(argument) = &mut ret_stmt.argument { // Replace "(() => { return foo() })" with "foo()" *e = ctx.ast.move_expression(argument); - return self.remove_unused_expression(e, ctx); + return self.remove_unused_expression(e, state, ctx); } // Replace "(() => { return })" with "" return true; @@ -634,8 +694,9 @@ impl<'a> PeepholeOptimizations { } fn fold_arguments_into_needed_expressions( - &mut self, + &self, args: &mut Vec<'a, Argument<'a>>, + state: &mut State, ctx: Ctx<'a, '_>, ) -> Vec<'a, Expression<'a>> { ctx.ast.vec_from_iter(args.drain(..).filter_map(|arg| { @@ -647,7 +708,7 @@ impl<'a> PeepholeOptimizations { ), match_expression!(Argument) => arg.into_expression(), }; - (!self.remove_unused_expression(&mut expr, ctx)).then_some(expr) + (!self.remove_unused_expression(&mut expr, state, ctx)).then_some(expr) })) } } diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index b8f0b2097e957..bea857fa511f7 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -15,7 +15,7 @@ use oxc_traverse::Ancestor; use crate::ctx::Ctx; -use super::PeepholeOptimizations; +use super::{PeepholeOptimizations, State}; type Arguments<'a> = oxc_allocator::Vec<'a, Argument<'a>>; @@ -23,16 +23,22 @@ impl<'a> PeepholeOptimizations { /// Minimize With Known Methods /// pub fn replace_known_methods_exit_expression( - &mut self, + &self, node: &mut Expression<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { - self.try_fold_concat_chain(node, ctx); - self.try_fold_known_global_methods(node, ctx); - self.try_fold_known_property_access(node, ctx); + self.try_fold_concat_chain(node, state, ctx); + self.try_fold_known_global_methods(node, state, ctx); + self.try_fold_known_property_access(node, state, ctx); } - fn try_fold_known_global_methods(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) { + fn try_fold_known_global_methods( + &self, + node: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { let Expression::CallExpression(ce) = node else { return }; let CallExpression { span, callee, arguments, .. } = ce.as_mut(); let (name, object) = match &callee { @@ -75,7 +81,7 @@ impl<'a> PeepholeOptimizations { _ => None, }; if let Some(replacement) = replacement { - self.mark_current_function_as_changed(); + state.changed = true; *node = replacement; } } @@ -530,7 +536,12 @@ impl<'a> PeepholeOptimizations { /// `[].concat(a).concat(b)` -> `[].concat(a, b)` /// `"".concat(a).concat(b)` -> `"".concat(a, b)` - fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) { + fn try_fold_concat_chain( + &self, + node: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { let original_span = if let Expression::CallExpression(root_call_expr) = node { root_call_expr.span } else { @@ -606,7 +617,7 @@ impl<'a> PeepholeOptimizations { ), false, ); - self.mark_current_function_as_changed(); + state.changed = true; } /// `[].concat(1, 2)` -> `[1, 2]` @@ -771,7 +782,12 @@ impl<'a> PeepholeOptimizations { } } - fn try_fold_known_property_access(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) { + fn try_fold_known_property_access( + &self, + node: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { let (name, object, span) = match node { Expression::StaticMemberExpression(member) if !member.optional => { (member.property.name.as_str(), &member.object, member.span) @@ -788,7 +804,7 @@ impl<'a> PeepholeOptimizations { span, ctx, ) { - self.mark_current_function_as_changed(); + state.changed = true; *node = replacement; } } @@ -806,7 +822,7 @@ impl<'a> PeepholeOptimizations { span, ctx, ) { - self.mark_current_function_as_changed(); + state.changed = true; *node = replacement; } } @@ -829,7 +845,7 @@ impl<'a> PeepholeOptimizations { _ => None, }; if let Some(replacement) = replacement { - self.mark_current_function_as_changed(); + state.changed = true; *node = replacement; } } diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 87f5f69535bcd..70a9694518966 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -16,14 +16,19 @@ use oxc_traverse::Ancestor; use crate::ctx::Ctx; -use super::{LatePeepholeOptimizations, PeepholeOptimizations}; +use super::{LatePeepholeOptimizations, PeepholeOptimizations, State}; /// A peephole optimization that minimizes code by simplifying conditional /// expressions, replacing IFs with HOOKs, replacing object constructors /// with literals, and simplifying returns. /// impl<'a> PeepholeOptimizations { - pub fn substitute_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: Ctx<'a, '_>) { + pub fn substitute_object_property( + &self, + prop: &mut ObjectProperty<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { // if !prop.method { if let PropertyKey::StringLiteral(str) = &prop.key { @@ -34,28 +39,31 @@ impl<'a> PeepholeOptimizations { } } - self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + self.try_compress_property_key(&mut prop.key, &mut prop.computed, state, ctx); } pub fn substitute_assignment_target_property_property( - &mut self, + &self, prop: &mut AssignmentTargetPropertyProperty<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { - self.try_compress_property_key(&mut prop.name, &mut prop.computed, ctx); + self.try_compress_property_key(&mut prop.name, &mut prop.computed, state, ctx); } pub fn substitute_assignment_target_property( - &mut self, + &self, prop: &mut AssignmentTargetProperty<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { - self.try_compress_assignment_target_property(prop, ctx); + self.try_compress_assignment_target_property(prop, state, ctx); } pub fn try_compress_assignment_target_property( - &mut self, + &self, prop: &mut AssignmentTargetProperty<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { // `a: a` -> `a` @@ -72,22 +80,24 @@ impl<'a> PeepholeOptimizations { ctx.ast.identifier_reference(assign_target_prop_prop.span, prop_name), None, ); - self.mark_current_function_as_changed(); + state.changed = true; } } } pub fn substitute_binding_property( - &mut self, + &self, prop: &mut BindingProperty<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { - self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + self.try_compress_property_key(&mut prop.key, &mut prop.computed, state, ctx); } pub fn substitute_method_definition( - &mut self, + &self, prop: &mut MethodDefinition<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { let property_key_parent: ClassPropertyKeyParent = prop.into(); @@ -96,12 +106,13 @@ impl<'a> PeepholeOptimizations { return; } } - self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + self.try_compress_property_key(&mut prop.key, &mut prop.computed, state, ctx); } pub fn substitute_property_definition( - &mut self, + &self, prop: &mut PropertyDefinition<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { let property_key_parent: ClassPropertyKeyParent = prop.into(); @@ -110,12 +121,13 @@ impl<'a> PeepholeOptimizations { return; } } - self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + self.try_compress_property_key(&mut prop.key, &mut prop.computed, state, ctx); } pub fn substitute_accessor_property( - &mut self, + &self, prop: &mut AccessorProperty<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { let property_key_parent: ClassPropertyKeyParent = prop.into(); @@ -124,43 +136,64 @@ impl<'a> PeepholeOptimizations { return; } } - self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + self.try_compress_property_key(&mut prop.key, &mut prop.computed, state, ctx); } pub fn substitute_return_statement( - &mut self, + &self, stmt: &mut ReturnStatement<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { - self.compress_return_statement(stmt, ctx); + self.compress_return_statement(stmt, state, ctx); } pub fn substitute_variable_declaration( - &mut self, + &self, decl: &mut VariableDeclaration<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { for declarator in &mut decl.declarations { - self.compress_variable_declarator(declarator, ctx); + self.compress_variable_declarator(declarator, state, ctx); } } - pub fn substitute_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: Ctx<'a, '_>) { - self.try_flatten_arguments(&mut expr.arguments, ctx); + pub fn substitute_call_expression( + &self, + expr: &mut CallExpression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { + self.try_flatten_arguments(&mut expr.arguments, state, ctx); } - pub fn substitute_new_expression(&mut self, expr: &mut NewExpression<'a>, ctx: Ctx<'a, '_>) { - self.try_flatten_arguments(&mut expr.arguments, ctx); + pub fn substitute_new_expression( + &self, + expr: &mut NewExpression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { + self.try_flatten_arguments(&mut expr.arguments, state, ctx); } - pub fn substitute_exit_expression(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) { + pub fn substitute_exit_expression( + &self, + expr: &mut Expression<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { // Change syntax match expr { - Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx), - Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx), + Expression::ArrowFunctionExpression(e) => { + self.try_compress_arrow_expression(e, state, ctx); + } + Expression::ChainExpression(e) => { + self.try_compress_chain_call_expression(e, state, ctx); + } Expression::BinaryExpression(e) => Self::swap_binary_expressions(e), - Expression::FunctionExpression(e) => self.try_remove_name_from_functions(e, ctx), - Expression::ClassExpression(e) => self.try_remove_name_from_classes(e, ctx), + Expression::FunctionExpression(e) => self.try_remove_name_from_functions(e, state, ctx), + Expression::ClassExpression(e) => self.try_remove_name_from_classes(e, state, ctx), _ => {} } @@ -185,7 +218,7 @@ impl<'a> PeepholeOptimizations { _ => None, } { *expr = folded_expr; - self.mark_current_function_as_changed(); + state.changed = true; } } @@ -200,8 +233,9 @@ impl<'a> PeepholeOptimizations { /// `() => { return foo })` -> `() => foo` fn try_compress_arrow_expression( - &mut self, + &self, arrow_expr: &mut ArrowFunctionExpression<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { if !arrow_expr.expression @@ -215,7 +249,7 @@ impl<'a> PeepholeOptimizations { if let Some(arg) = return_stmt_arg { *body = ctx.ast.statement_expression(arg.span(), arg); arrow_expr.expression = true; - self.mark_current_function_as_changed(); + state.changed = true; } } } @@ -567,7 +601,12 @@ impl<'a> PeepholeOptimizations { /// /// `return undefined` -> `return` /// `return void 0` -> `return` - fn compress_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: Ctx<'a, '_>) { + fn compress_return_statement( + &self, + stmt: &mut ReturnStatement<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { let Some(argument) = &stmt.argument else { return }; if !match argument { Expression::Identifier(ident) => ctx.is_identifier_undefined(ident), @@ -587,12 +626,13 @@ impl<'a> PeepholeOptimizations { } } stmt.argument = None; - self.mark_current_function_as_changed(); + state.changed = true; } fn compress_variable_declarator( - &mut self, + &self, decl: &mut VariableDeclarator<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { // Destructuring Pattern has error throwing side effect. @@ -609,12 +649,11 @@ impl<'a> PeepholeOptimizations { && decl.init.as_ref().is_some_and(|init| ctx.is_expression_undefined(init)) { decl.init = None; - self.mark_current_function_as_changed(); + state.changed = true; } } - /// Fold `Boolean`, `Number`, `String`, `BigInt` constructors. - /// + /// Fold `Boolean`, /// /// `Boolean(a)` -> `!!a` /// `Number(0)` -> `0` /// `String()` -> `''` @@ -848,8 +887,9 @@ impl<'a> PeepholeOptimizations { } fn try_compress_chain_call_expression( - &mut self, + &self, chain_expr: &mut ChainExpression<'a>, + state: &mut State, ctx: Ctx<'a, '_>, ) { if let ChainElement::CallExpression(call_expr) = &mut chain_expr.expression { @@ -861,7 +901,7 @@ impl<'a> PeepholeOptimizations { .is_some_and(|mem_expr| mem_expr.is_specific_member_access("window", "Object")) { call_expr.callee = ctx.ast.expression_identifier(call_expr.callee.span(), "Object"); - self.mark_current_function_as_changed(); + state.changed = true; } } } @@ -872,9 +912,10 @@ impl<'a> PeepholeOptimizations { // fn try_compress_property_key( - &mut self, + &self, key: &mut PropertyKey<'a>, computed: &mut bool, + state: &mut State, ctx: Ctx<'a, '_>, ) { if let PropertyKey::NumericLiteral(_) = key { @@ -888,7 +929,7 @@ impl<'a> PeepholeOptimizations { if is_identifier_name(value) { *computed = false; *key = PropertyKey::StaticIdentifier(ctx.ast.alloc_identifier_name(s.span, s.value)); - self.mark_current_function_as_changed(); + state.changed = true; return; } if let Some(value) = Ctx::string_to_equivalent_number_value(value) { @@ -900,7 +941,7 @@ impl<'a> PeepholeOptimizations { None, NumberBase::Decimal, )); - self.mark_current_function_as_changed(); + state.changed = true; return; } } @@ -911,7 +952,12 @@ impl<'a> PeepholeOptimizations { // `foo(...[1,2,3])` -> `foo(1,2,3)` // `new Foo(...[1,2,3])` -> `new Foo(1,2,3)` - fn try_flatten_arguments(&mut self, args: &mut Vec<'a, Argument<'a>>, ctx: Ctx<'a, '_>) { + fn try_flatten_arguments( + &self, + args: &mut Vec<'a, Argument<'a>>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { let (new_size, should_fold) = args.iter().fold((0, false), |(mut new_size, mut should_fold), arg| { new_size += if let Argument::SpreadElement(spread_el) = arg { @@ -964,7 +1010,7 @@ impl<'a> PeepholeOptimizations { new_args.push(arg); } } - self.mark_current_function_as_changed(); + state.changed = true; } /// Remove name from function expressions if it is not used. @@ -972,14 +1018,19 @@ impl<'a> PeepholeOptimizations { /// e.g. `var a = function f() {}` -> `var a = function () {}` /// /// This compression is not safe if the code relies on `Function::name`. - fn try_remove_name_from_functions(&mut self, func: &mut Function<'a>, ctx: Ctx<'a, '_>) { + fn try_remove_name_from_functions( + &self, + func: &mut Function<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { if self.keep_names.function { return; } if func.id.as_ref().is_some_and(|id| !ctx.scoping().symbol_is_used(id.symbol_id())) { func.id = None; - self.mark_current_function_as_changed(); + state.changed = true; } } @@ -988,14 +1039,19 @@ impl<'a> PeepholeOptimizations { /// e.g. `var a = class C {}` -> `var a = class {}` /// /// This compression is not safe if the code relies on `Class::name`. - fn try_remove_name_from_classes(&mut self, class: &mut Class<'a>, ctx: Ctx<'a, '_>) { + fn try_remove_name_from_classes( + &self, + class: &mut Class<'a>, + state: &mut State, + ctx: Ctx<'a, '_>, + ) { if self.keep_names.class { return; } if class.id.as_ref().is_some_and(|id| !ctx.scoping().symbol_is_used(id.symbol_id())) { class.id = None; - self.mark_current_function_as_changed(); + state.changed = true; } } } diff --git a/crates/oxc_minifier/src/tester.rs b/crates/oxc_minifier/src/tester.rs index 19ec2a63bd74d..8e2599770cd16 100644 --- a/crates/oxc_minifier/src/tester.rs +++ b/crates/oxc_minifier/src/tester.rs @@ -5,16 +5,19 @@ use oxc_span::SourceType; use crate::{CompressOptions, Compressor}; +#[track_caller] pub fn test_same(source_text: &str) { test(source_text, source_text); } +#[track_caller] pub fn test(source_text: &str, expected: &str) { let result = run(source_text, Some(CompressOptions::smallest())); let expected = run(expected, None); assert_eq!(result, expected, "\nfor source\n{source_text}\nexpect\n{expected}\ngot\n{result}"); } +#[track_caller] pub fn run(source_text: &str, options: Option) -> String { let allocator = Allocator::default(); let source_type = SourceType::mjs(); diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index 43b7635c2adc8..aca65ee4b6bc8 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -10,6 +10,7 @@ use oxc_minifier::{CompressOptions, Compressor}; use oxc_parser::{ParseOptions, Parser}; use oxc_span::SourceType; +#[track_caller] pub(crate) fn test(source_text: &str, expected: &str, options: CompressOptions) { let source_type = SourceType::default(); let first = run(source_text, source_type, Some(options)); diff --git a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs index f2f594f226669..51ff9b523f924 100644 --- a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs +++ b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs @@ -7,6 +7,7 @@ use oxc_minifier::Compressor; use oxc_parser::Parser; use oxc_span::SourceType; +#[track_caller] fn run(source_text: &str, source_type: SourceType, options: Option) -> String { let allocator = Allocator::default(); let mut ret = Parser::new(&allocator, source_text, source_type).parse(); @@ -17,6 +18,7 @@ fn run(source_text: &str, source_type: SourceType, options: Option