diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 6d8e86076c37b..911731b382f98 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -13,7 +13,6 @@ mod peephole_minimize_conditions; mod peephole_remove_dead_code; mod peephole_replace_known_methods; mod peephole_substitute_alternate_syntax; -mod remove_syntax; mod remove_unused_code; mod statement_fusion; @@ -27,7 +26,6 @@ pub use peephole_minimize_conditions::PeepholeMinimizeConditions; pub use peephole_remove_dead_code::PeepholeRemoveDeadCode; pub use peephole_replace_known_methods::PeepholeReplaceKnownMethods; pub use peephole_substitute_alternate_syntax::PeepholeSubstituteAlternateSyntax; -pub use remove_syntax::RemoveSyntax; #[expect(unused)] pub use remove_unused_code::RemoveUnusedCode; pub use statement_fusion::StatementFusion; diff --git a/crates/oxc_minifier/src/ast_passes/normalize.rs b/crates/oxc_minifier/src/ast_passes/normalize.rs index e0a6359a54bb4..f8067f4c97729 100644 --- a/crates/oxc_minifier/src/ast_passes/normalize.rs +++ b/crates/oxc_minifier/src/ast_passes/normalize.rs @@ -1,22 +1,31 @@ +use oxc_allocator::Vec; use oxc_ast::ast::*; use oxc_ecmascript::constant_evaluation::ConstantEvaluation; use oxc_span::GetSpan; use oxc_syntax::scope::ScopeFlags; use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; -use crate::{ctx::Ctx, CompressorPass}; +use crate::{ctx::Ctx, CompressOptions, CompressorPass}; /// Normalize AST /// /// Make subsequent AST passes easier to analyze: /// +/// * remove `Statement::EmptyStatement` +/// * remove `ParenthesizedExpression` /// * convert whiles to fors /// * convert `Infinity` to `f64::INFINITY` /// * convert `NaN` to `f64::NaN` /// * convert `var x; void x` to `void 0` /// +/// Also +/// +/// * remove `debugger` and `console.log` (optional) +/// /// -pub struct Normalize; +pub struct Normalize { + options: CompressOptions, +} impl<'a> CompressorPass<'a> for Normalize { fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { @@ -25,6 +34,14 @@ impl<'a> CompressorPass<'a> for Normalize { } impl<'a> Traverse<'a> for Normalize { + fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) { + stmts.retain(|stmt| { + !(matches!(stmt, Statement::EmptyStatement(_)) + || self.drop_debugger(stmt) + || self.drop_console(stmt)) + }); + } + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { match stmt { Statement::WhileStatement(_) => { @@ -36,6 +53,9 @@ impl<'a> Traverse<'a> for Normalize { } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if let Expression::ParenthesizedExpression(paren_expr) = expr { + *expr = ctx.ast.move_expression(&mut paren_expr.expression); + } match expr { Expression::Identifier(_) => { Self::convert_infinity_or_nan_into_number(expr, ctx); @@ -43,14 +63,53 @@ impl<'a> Traverse<'a> for Normalize { Expression::UnaryExpression(e) if e.operator.is_void() => { Self::convert_void_ident(e, ctx); } + Expression::ArrowFunctionExpression(e) => { + self.recover_arrow_expression_after_drop_console(e); + } + Expression::CallExpression(_) if self.options.drop_console => { + self.compress_console(expr, ctx); + } _ => {} } } } impl<'a> Normalize { - pub fn new() -> Self { - Self + pub fn new(options: CompressOptions) -> Self { + Self { options } + } + + /// Drop `drop_debugger` statement. + /// + /// Enabled by `compress.drop_debugger` + fn drop_debugger(&mut self, stmt: &Statement<'a>) -> bool { + matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger + } + + fn compress_console(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + debug_assert!(self.options.drop_console); + if Self::is_console(expr) { + *expr = ctx.ast.void_0(expr.span()); + } + } + + fn drop_console(&mut self, stmt: &Statement<'a>) -> bool { + self.options.drop_console + && matches!(stmt, Statement::ExpressionStatement(expr) if Self::is_console(&expr.expression)) + } + + fn recover_arrow_expression_after_drop_console(&self, expr: &mut ArrowFunctionExpression<'a>) { + if self.options.drop_console && expr.expression && expr.body.is_empty() { + expr.expression = false; + } + } + + fn is_console(expr: &Expression<'_>) -> bool { + let Expression::CallExpression(call_expr) = &expr else { return false }; + let Some(member_expr) = call_expr.callee.as_member_expression() else { return false }; + let obj = member_expr.object(); + let Some(ident) = obj.get_identifier_reference() else { return false }; + ident.name == "console" } fn convert_while_to_for(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -112,11 +171,16 @@ impl<'a> Normalize { mod test { use oxc_allocator::Allocator; - use crate::tester; + use crate::{tester, CompressOptions}; fn test(source_text: &str, expected: &str) { let allocator = Allocator::default(); - let mut pass = super::Normalize::new(); + let options = CompressOptions { + drop_debugger: true, + drop_console: true, + ..CompressOptions::default() + }; + let mut pass = super::Normalize::new(options); tester::test(&allocator, source_text, expected, &mut pass); } @@ -131,4 +195,21 @@ mod test { test("var x; void x", "var x; void 0"); test("void x", "void x"); // reference error } + + #[test] + fn parens() { + test("(((x)))", "x"); + test("(((a + b))) * c", "(a + b) * c"); + } + + #[test] + fn drop_console() { + test("console.log()", "void 0;\n"); + test("() => console.log()", "() => void 0"); + } + + #[test] + fn drop_debugger() { + test("debugger", ""); + } } diff --git a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs b/crates/oxc_minifier/src/ast_passes/remove_syntax.rs deleted file mode 100644 index c72189134a13d..0000000000000 --- a/crates/oxc_minifier/src/ast_passes/remove_syntax.rs +++ /dev/null @@ -1,121 +0,0 @@ -use oxc_allocator::Vec; -use oxc_ast::ast::*; -use oxc_span::GetSpan; -use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; - -use crate::{CompressOptions, CompressorPass}; - -/// Remove syntax from the AST. -/// -/// * Parenthesized Expression -/// * `debugger` -/// * `console.log` -pub struct RemoveSyntax { - options: CompressOptions, -} - -impl<'a> CompressorPass<'a> for RemoveSyntax { - fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { - traverse_mut_with_ctx(self, program, ctx); - } -} - -impl<'a> Traverse<'a> for RemoveSyntax { - fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) { - stmts.retain(|stmt| { - !(matches!(stmt, Statement::EmptyStatement(_)) - || self.drop_debugger(stmt) - || self.drop_console(stmt)) - }); - } - - fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - self.compress_console(expr, ctx); - Self::strip_parenthesized_expression(expr, ctx); - } - - fn exit_arrow_function_expression( - &mut self, - expr: &mut ArrowFunctionExpression<'a>, - _ctx: &mut TraverseCtx<'a>, - ) { - self.recover_arrow_expression_after_drop_console(expr); - } -} - -impl<'a> RemoveSyntax { - pub fn new(options: CompressOptions) -> Self { - Self { options } - } - - fn strip_parenthesized_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if let Expression::ParenthesizedExpression(paren_expr) = expr { - *expr = ctx.ast.move_expression(&mut paren_expr.expression); - } - } - - /// Drop `drop_debugger` statement. - /// - /// Enabled by `compress.drop_debugger` - fn drop_debugger(&mut self, stmt: &Statement<'a>) -> bool { - matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger - } - - /// Drop `console.*` expressions. - /// - /// Enabled by `compress.drop_console - fn drop_console(&mut self, stmt: &Statement<'a>) -> bool { - self.options.drop_console - && matches!(stmt, Statement::ExpressionStatement(expr) if Self::is_console(&expr.expression)) - } - - fn compress_console(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.drop_console && Self::is_console(expr) { - *expr = ctx.ast.void_0(expr.span()); - } - } - - fn recover_arrow_expression_after_drop_console(&self, expr: &mut ArrowFunctionExpression<'a>) { - if self.options.drop_console && expr.expression && expr.body.is_empty() { - expr.expression = false; - } - } - - fn is_console(expr: &Expression<'_>) -> bool { - // let Statement::ExpressionStatement(expr) = stmt else { return false }; - let Expression::CallExpression(call_expr) = &expr else { return false }; - let Some(member_expr) = call_expr.callee.as_member_expression() else { return false }; - let obj = member_expr.object(); - let Some(ident) = obj.get_identifier_reference() else { return false }; - ident.name == "console" - } -} - -#[cfg(test)] -mod test { - use oxc_allocator::Allocator; - - use crate::{tester, CompressOptions}; - - fn test(source_text: &str, expected: &str) { - let allocator = Allocator::default(); - let mut pass = super::RemoveSyntax::new(CompressOptions::all_true()); - tester::test(&allocator, source_text, expected, &mut pass); - } - - #[test] - fn parens() { - test("(((x)))", "x"); - test("(((a + b))) * c", "(a + b) * c"); - } - - #[test] - fn drop_console() { - test("console.log()", "void 0;\n"); - } - - #[test] - fn drop_debugger() { - test("debugger", ""); - } -} diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 816695e0b62a8..9a64a380ca0bf 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -4,7 +4,7 @@ use oxc_semantic::{ScopeTree, SemanticBuilder, SymbolTable}; use oxc_traverse::ReusableTraverseCtx; use crate::{ - ast_passes::{DeadCodeElimination, Normalize, PeepholeOptimizations, RemoveSyntax}, + ast_passes::{DeadCodeElimination, Normalize, PeepholeOptimizations}, CompressOptions, CompressorPass, }; @@ -31,9 +31,8 @@ impl<'a> Compressor<'a> { program: &mut Program<'a>, ) { let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator); - RemoveSyntax::new(self.options).build(program, &mut ctx); // RemoveUnusedCode::new(self.options).build(program, &mut ctx); - Normalize::new().build(program, &mut ctx); + Normalize::new(self.options).build(program, &mut ctx); PeepholeOptimizations::new(self.options.target, true, self.options) .run_in_loop(program, &mut ctx); PeepholeOptimizations::new(self.options.target, false, self.options) @@ -53,7 +52,7 @@ impl<'a> Compressor<'a> { program: &mut Program<'a>, ) { let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator); - RemoveSyntax::new(self.options).build(program, &mut ctx); + Normalize::new(self.options).build(program, &mut ctx); DeadCodeElimination::new().build(program, &mut ctx); } } diff --git a/crates/oxc_minifier/src/tester.rs b/crates/oxc_minifier/src/tester.rs index 25e438531028a..e7f89a54fd769 100644 --- a/crates/oxc_minifier/src/tester.rs +++ b/crates/oxc_minifier/src/tester.rs @@ -6,7 +6,7 @@ use oxc_span::SourceType; use oxc_traverse::ReusableTraverseCtx; use crate::{ - ast_passes::{CompressorPass, Normalize, RemoveSyntax}, + ast_passes::{CompressorPass, Normalize}, CompressOptions, }; @@ -44,8 +44,7 @@ fn run<'a, P: CompressorPass<'a>>( let (symbols, scopes) = SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); let mut ctx = ReusableTraverseCtx::new(scopes, symbols, allocator); - RemoveSyntax::new(CompressOptions::all_false()).build(&mut program, &mut ctx); - Normalize::new().build(&mut program, &mut ctx); + Normalize::new(CompressOptions::all_false()).build(&mut program, &mut ctx); pass.build(&mut program, &mut ctx); }