diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index baab56947e29a..dce0c01981be9 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -195,8 +195,11 @@ pub trait CompilerInterface { .build(&program) .semantic .into_scoping(); - Compressor::new(&allocator, CompressOptions::default()) - .dead_code_elimination_with_scoping(scoping, &mut program); + Compressor::new(&allocator).dead_code_elimination_with_scoping( + &mut program, + scoping, + CompressOptions::smallest(), + ); } } @@ -276,7 +279,7 @@ pub trait CompilerInterface { program: &mut Program<'a>, options: CompressOptions, ) { - Compressor::new(allocator, options).build(program); + Compressor::new(allocator).build(program, options); } fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> Scoping { diff --git a/crates/oxc_minifier/examples/dce.rs b/crates/oxc_minifier/examples/dce.rs index 0ca38d05cd02d..cc433d8ee669b 100644 --- a/crates/oxc_minifier/examples/dce.rs +++ b/crates/oxc_minifier/examples/dce.rs @@ -40,7 +40,7 @@ fn main() -> std::io::Result<()> { fn dce(allocator: &Allocator, source_text: &str, source_type: SourceType, nospace: bool) -> String { let ret = Parser::new(allocator, source_text, source_type).parse(); let mut program = ret.program; - Compressor::new(allocator, CompressOptions::default()).dead_code_elimination(&mut program); + Compressor::new(allocator).dead_code_elimination(&mut program, CompressOptions::smallest()); Codegen::new() .with_options(CodegenOptions { minify: nospace, ..CodegenOptions::default() }) .build(&program) diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 8e905f6304726..479d55eaa5ca0 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use oxc_allocator::Allocator; use oxc_ast::ast::*; use oxc_semantic::{Scoping, SemanticBuilder}; @@ -16,37 +14,45 @@ use crate::{ pub struct Compressor<'a> { allocator: &'a Allocator, - options: Rc, } impl<'a> Compressor<'a> { - pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self { - Self { allocator, options: Rc::new(options) } + pub fn new(allocator: &'a Allocator) -> Self { + Self { allocator } } - pub fn build(self, program: &mut Program<'a>) { + pub fn build(self, program: &mut Program<'a>, options: CompressOptions) { let scoping = SemanticBuilder::new().build(program).semantic.into_scoping(); - self.build_with_scoping(scoping, program); + self.build_with_scoping(program, scoping, options); } - pub fn build_with_scoping(self, scoping: Scoping, program: &mut Program<'a>) { - let state = MinifierState::new(Rc::clone(&self.options)); + pub fn build_with_scoping( + self, + program: &mut Program<'a>, + scoping: Scoping, + options: CompressOptions, + ) { + let state = MinifierState::new(options); let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator); let normalize_options = NormalizeOptions { convert_while_to_fors: true, convert_const_to_let: true }; Normalize::new(normalize_options).build(program, &mut ctx); - PeepholeOptimizations::new(self.options.target, self.options.keep_names) - .run_in_loop(program, &mut ctx); - LatePeepholeOptimizations::new(self.options.target).build(program, &mut ctx); + PeepholeOptimizations::new().run_in_loop(program, &mut ctx); + LatePeepholeOptimizations::new().build(program, &mut ctx); } - pub fn dead_code_elimination(self, program: &mut Program<'a>) { + pub fn dead_code_elimination(self, program: &mut Program<'a>, options: CompressOptions) { let scoping = SemanticBuilder::new().build(program).semantic.into_scoping(); - self.dead_code_elimination_with_scoping(scoping, program); + self.dead_code_elimination_with_scoping(program, scoping, options); } - pub fn dead_code_elimination_with_scoping(self, scoping: Scoping, program: &mut Program<'a>) { - let state = MinifierState::new(Rc::clone(&self.options)); + pub fn dead_code_elimination_with_scoping( + self, + program: &mut Program<'a>, + scoping: Scoping, + options: CompressOptions, + ) { + let state = MinifierState::new(options); let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator); let normalize_options = NormalizeOptions { convert_while_to_fors: false, convert_const_to_let: false }; diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index 9aec50488bf69..b7830616ffd75 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -11,7 +11,7 @@ use oxc_semantic::{IsGlobalReference, Scoping}; use oxc_span::format_atom; use oxc_syntax::reference::ReferenceId; -use crate::state::MinifierState; +use crate::{options::CompressOptions, state::MinifierState}; pub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, MinifierState<'a>>; @@ -93,10 +93,14 @@ pub fn is_exact_int64(num: f64) -> bool { } impl<'a> Ctx<'a, '_> { - fn scoping(&self) -> &Scoping { + pub fn scoping(&self) -> &Scoping { self.0.scoping() } + pub fn options(&self) -> &CompressOptions { + &self.0.state.options + } + pub fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool { ident.is_global_reference(self.0.scoping()) } diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index 6e8eb60b4d0cb..842c4b7a86993 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -47,11 +47,11 @@ impl Minifier { } pub fn build<'a>(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn { - let stats = if let Some(compress) = self.options.compress { + let stats = if let Some(options) = self.options.compress { let semantic = SemanticBuilder::new().build(program).semantic; let stats = semantic.stats(); let scoping = semantic.into_scoping(); - Compressor::new(allocator, compress).build_with_scoping(scoping, program); + Compressor::new(allocator).build_with_scoping(program, scoping, options); stats } else { Stats::default() diff --git a/crates/oxc_minifier/src/options.rs b/crates/oxc_minifier/src/options.rs index efcb0b1e7a8d7..f5005c9108a53 100644 --- a/crates/oxc_minifier/src/options.rs +++ b/crates/oxc_minifier/src/options.rs @@ -35,10 +35,9 @@ pub struct CompressOptions { pub treeshake: TreeShakeOptions, } -#[expect(clippy::derivable_impls)] impl Default for CompressOptions { fn default() -> Self { - Self { drop_console: false, ..Self::smallest() } + Self::smallest() } } diff --git a/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs b/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs index 6bab9f53ac604..932f1661ea2ac 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs @@ -319,7 +319,7 @@ impl<'a> PeepholeOptimizations { } // Try using the "??" or "?." operators - if self.target >= ESTarget::ES2020 { + if ctx.options().target >= ESTarget::ES2020 { if let Expression::BinaryExpression(test_binary) = &mut expr.test { if let Some(is_negate) = match test_binary.operator { BinaryOperator::Inequality => Some(true), diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index d706affdbc54e..c6f0516915bbe 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -219,7 +219,7 @@ impl<'a> PeepholeOptimizations { expr: &mut AssignmentExpression<'a>, ctx: &mut Ctx<'a, '_>, ) -> bool { - if self.target < ESTarget::ES2020 { + if ctx.options().target < ESTarget::ES2020 { return false; } if !matches!(expr.operator, AssignmentOperator::Assign) { diff --git a/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs b/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs index cd7af0ebf2588..132c4eb1234a8 100644 --- a/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs +++ b/crates/oxc_minifier/src/peephole/minimize_logical_expression.rs @@ -207,7 +207,7 @@ impl<'a> PeepholeOptimizations { expr: &mut LogicalExpression<'a>, ctx: &mut Ctx<'a, '_>, ) -> Option> { - if self.target < ESTarget::ES2020 { + if ctx.options().target < ESTarget::ES2020 { return None; } let Expression::AssignmentExpression(assignment_expr) = &mut expr.right else { diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 92d9c6618b4ab..6b92fdcde27aa 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -23,12 +23,11 @@ use rustc_hash::FxHashSet; use oxc_allocator::Vec; use oxc_ast::ast::*; use oxc_data_structures::stack::NonEmptyStack; -use oxc_syntax::{es_target::ESTarget, scope::ScopeId}; +use oxc_syntax::scope::ScopeId; use oxc_traverse::{ReusableTraverseCtx, Traverse, traverse_mut_with_ctx}; use crate::{ ctx::{Ctx, TraverseCtx}, - options::CompressOptionsKeepNames, state::MinifierState, }; @@ -40,9 +39,6 @@ pub struct State { } pub struct PeepholeOptimizations { - target: ESTarget, - keep_names: CompressOptionsKeepNames, - /// Walk the ast in a fixed point loop until no changes are made. /// `prev_function_changed`, `functions_changed` and `current_function` track changes /// in top level and each function. No minification code are run if the function is not changed @@ -56,10 +52,8 @@ pub struct PeepholeOptimizations { } impl<'a> PeepholeOptimizations { - pub fn new(target: ESTarget, keep_names: CompressOptionsKeepNames) -> Self { + pub fn new() -> Self { Self { - target, - keep_names, iteration: 0, prev_functions_changed: FxHashSet::default(), functions_changed: FxHashSet::default(), @@ -405,13 +399,11 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations { /// Changes that do not interfere with optimizations that are run inside the fixed-point loop, /// which can be done as a last AST pass. -pub struct LatePeepholeOptimizations { - target: ESTarget, -} +pub struct LatePeepholeOptimizations; impl<'a> LatePeepholeOptimizations { - pub fn new(target: ESTarget) -> Self { - Self { target } + pub fn new() -> Self { + Self } pub fn build( @@ -463,12 +455,7 @@ pub struct DeadCodeElimination { impl<'a> DeadCodeElimination { pub fn new() -> Self { - Self { - inner: PeepholeOptimizations::new( - ESTarget::ESNext, - CompressOptionsKeepNames::all_true(), - ), - } + Self { inner: PeepholeOptimizations::new() } } pub fn build( diff --git a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs index 53ee3326502b3..7df7ac8f72029 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs @@ -99,7 +99,7 @@ impl<'a> PeepholeOptimizations { } // try optional chaining and nullish coalescing - if self.target >= ESTarget::ES2020 { + if ctx.options().target >= ESTarget::ES2020 { let LogicalExpression { span: logical_span, left: logical_left, diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index 6fa050afd0c28..9b73255fdc792 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -412,7 +412,7 @@ impl<'a> PeepholeOptimizations { object: &Expression<'a>, ctx: &mut Ctx<'a, '_>, ) -> Option> { - if self.target < ESTarget::ES2016 { + if ctx.options().target < ESTarget::ES2016 { return None; } if !Self::validate_global_reference(object, "Math", ctx) @@ -726,7 +726,7 @@ impl<'a> PeepholeOptimizations { } } Expression::StringLiteral(base_str) => { - if self.target < ESTarget::ES2015 + if ctx.state.options.target < ESTarget::ES2015 || args.is_empty() || !args.iter().all(Argument::is_expression) { @@ -917,7 +917,7 @@ impl<'a> PeepholeOptimizations { "NEGATIVE_INFINITY" => num(span, f64::NEG_INFINITY), "NaN" => num(span, f64::NAN), "MAX_SAFE_INTEGER" => { - if self.target < ESTarget::ES2016 { + if ctx.options().target < ESTarget::ES2016 { num(span, 2.0f64.powi(53) - 1.0) } else { // 2**53 - 1 @@ -925,7 +925,7 @@ impl<'a> PeepholeOptimizations { } } "MIN_SAFE_INTEGER" => { - if self.target < ESTarget::ES2016 { + if ctx.options().target < ESTarget::ES2016 { num(span, -(2.0f64.powi(53) - 1.0)) } else { // -(2**53 - 1) @@ -937,7 +937,7 @@ impl<'a> PeepholeOptimizations { } } "EPSILON" => { - if self.target < ESTarget::ES2016 { + if ctx.options().target < ESTarget::ES2016 { return None; } // 2**-52 diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 14e5b0caf1377..a9a402a16ba6e 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -1023,7 +1023,7 @@ impl<'a> PeepholeOptimizations { state: &mut State, ctx: &mut Ctx<'a, '_>, ) { - if self.keep_names.function { + if ctx.options().keep_names.function { return; } @@ -1044,7 +1044,7 @@ impl<'a> PeepholeOptimizations { state: &mut State, ctx: &mut Ctx<'a, '_>, ) { - if self.keep_names.class { + if ctx.options().keep_names.class { return; } @@ -1171,7 +1171,7 @@ impl<'a> LatePeepholeOptimizations { } pub fn substitute_catch_clause(&self, catch: &mut CatchClause<'a>, ctx: &mut Ctx<'a, '_>) { - if self.target >= ESTarget::ES2019 { + if ctx.options().target >= ESTarget::ES2019 { if let Some(param) = &catch.param { if let BindingPatternKind::BindingIdentifier(ident) = ¶m.pattern.kind { if catch.body.body.is_empty() diff --git a/crates/oxc_minifier/src/state.rs b/crates/oxc_minifier/src/state.rs index a3e1b3c3391d6..13611cfd0e240 100644 --- a/crates/oxc_minifier/src/state.rs +++ b/crates/oxc_minifier/src/state.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use rustc_hash::FxHashMap; use oxc_ecmascript::constant_evaluation::ConstantValue; @@ -8,7 +6,7 @@ use oxc_semantic::SymbolId; use crate::CompressOptions; pub struct MinifierState<'a> { - pub options: Rc, + pub options: CompressOptions, /// Constant values evaluated from expressions. /// @@ -18,7 +16,7 @@ pub struct MinifierState<'a> { } impl MinifierState<'_> { - pub fn new(options: Rc) -> Self { + pub fn new(options: CompressOptions) -> Self { Self { options, constant_values: FxHashMap::default() } } } diff --git a/crates/oxc_minifier/src/tester.rs b/crates/oxc_minifier/src/tester.rs index b8aac3c092cb9..69a774347197a 100644 --- a/crates/oxc_minifier/src/tester.rs +++ b/crates/oxc_minifier/src/tester.rs @@ -45,7 +45,7 @@ pub fn run(source_text: &str, options: Option) -> String { assert!(ret.errors.is_empty(), "{source_text}"); let mut program = ret.program; if let Some(options) = options { - Compressor::new(&allocator, options).build(&mut program); + Compressor::new(&allocator).build(&mut program, options); } Codegen::new() .with_options(CodegenOptions { diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index f0d3cb4418940..af42a37424054 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -41,7 +41,7 @@ pub(crate) fn run( assert!(ret.errors.is_empty(), "{source_text}"); let mut program = ret.program; if let Some(options) = options { - Compressor::new(&allocator, options).build(&mut program); + Compressor::new(&allocator).build(&mut program, options); } Codegen::new() .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) diff --git a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs index 7412f28db89e0..a1d286597f0ec 100644 --- a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs +++ b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs @@ -15,7 +15,7 @@ fn run(source_text: &str, source_type: SourceType, options: Option