diff --git a/crates/oxc_minifier/src/compressor/mod.rs b/crates/oxc_minifier/src/compressor/mod.rs index 86c80c34f..8a22a2a59 100644 --- a/crates/oxc_minifier/src/compressor/mod.rs +++ b/crates/oxc_minifier/src/compressor/mod.rs @@ -2,6 +2,7 @@ mod ast_util; mod fold; +mod prepass; mod util; use oxc_allocator::{Allocator, Vec}; @@ -14,6 +15,8 @@ use oxc_syntax::{ NumberBase, }; +use self::prepass::Prepass; + #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Copy)] pub struct CompressOptions { @@ -70,16 +73,19 @@ impl Default for CompressOptions { pub struct Compressor<'a> { ast: AstBuilder<'a>, options: CompressOptions, + + prepass: Prepass<'a>, } const SPAN: Span = Span::new(0, 0); impl<'a> Compressor<'a> { pub fn new(allocator: &'a Allocator, options: CompressOptions) -> Self { - Self { ast: AstBuilder::new(allocator), options } + Self { ast: AstBuilder::new(allocator), options, prepass: Prepass::new(allocator) } } pub fn build<'b>(mut self, program: &'b mut Program<'a>) { + self.prepass.visit_program(program); self.visit_program(program); } @@ -208,16 +214,15 @@ impl<'a> Compressor<'a> { /* Expressions */ /// Transforms `undefined` => `void 0` - fn compress_undefined<'b>(&mut self, _expr: &'b mut Expression<'a>) -> bool { - // let Expression::Identifier(ident) = expr else { return false }; - // if let Some(reference_id) = ident.reference_id.clone().into_inner() { - // if ident.name == "undefined" - // && self.semantic.symbols().is_global_reference(reference_id) - // { - // *expr = self.create_void_0(); - // return true; - // } - // } + fn compress_undefined<'b>(&mut self, expr: &'b mut Expression<'a>) -> bool { + let Expression::Identifier(ident) = expr else { return false }; + if ident.name == "undefined" { + // if let Some(reference_id) = ident.reference_id.clone().into_inner() { + // && self.semantic.symbols().is_global_reference(reference_id) + *expr = self.create_void_0(); + return true; + // } + } false } @@ -225,13 +230,13 @@ impl<'a> Compressor<'a> { #[allow(unused)] fn compress_infinity<'b>(&mut self, expr: &'b mut Expression<'a>) -> bool { let Expression::Identifier(ident) = expr else { return false }; - // if let Some(reference_id) = ident.reference_id.clone().into_inner() { - // if ident.name == "Infinity" && self.semantic.symbols().is_global_reference(reference_id) - // { - // *expr = self.create_one_div_zero(); - // return true; - // } - // } + if ident.name == "Infinity" { + // if let Some(reference_id) = ident.reference_id.clone().into_inner() { + //&& self.semantic.symbols().is_global_reference(reference_id) + *expr = self.create_one_div_zero(); + return true; + // } + } false } @@ -319,7 +324,18 @@ impl<'a> Compressor<'a> { impl<'a, 'b> VisitMut<'a, 'b> for Compressor<'a> { fn visit_statements(&mut self, stmts: &'b mut Vec<'a, Statement<'a>>) { - stmts.retain(|stmt| !(self.drop_debugger(stmt) || self.drop_console(stmt))); + stmts.retain(|stmt| { + if matches!(stmt, Statement::EmptyStatement(_)) { + return false; + } + if self.drop_debugger(stmt) { + return false; + } + if self.drop_console(stmt) { + return false; + } + true + }); self.join_vars(stmts); diff --git a/crates/oxc_minifier/src/compressor/prepass.rs b/crates/oxc_minifier/src/compressor/prepass.rs new file mode 100644 index 000000000..78e895378 --- /dev/null +++ b/crates/oxc_minifier/src/compressor/prepass.rs @@ -0,0 +1,28 @@ +use oxc_allocator::Allocator; + +#[allow(clippy::wildcard_imports)] +use oxc_ast::{ast::*, AstBuilder, VisitMut}; + +pub struct Prepass<'a> { + ast: AstBuilder<'a>, +} + +impl<'a> Prepass<'a> { + pub fn new(allocator: &'a Allocator) -> Self { + Self { ast: AstBuilder::new(allocator) } + } + + fn strip_parenthesized_expression<'b>(&self, expr: &'b mut Expression<'a>) { + if let Expression::ParenthesizedExpression(paren_expr) = expr { + *expr = self.ast.move_expression(&mut paren_expr.expression); + self.strip_parenthesized_expression(expr); + } + } +} + +impl<'a, 'b> VisitMut<'a, 'b> for Prepass<'a> { + fn visit_expression(&mut self, expr: &'b mut Expression<'a>) { + self.strip_parenthesized_expression(expr); + self.visit_expression_match(expr); + } +} diff --git a/crates/oxc_minifier/src/printer/gen.rs b/crates/oxc_minifier/src/printer/gen.rs index 000c0a64d..47d55fa1c 100644 --- a/crates/oxc_minifier/src/printer/gen.rs +++ b/crates/oxc_minifier/src/printer/gen.rs @@ -76,8 +76,9 @@ impl<'a> Gen for Statement<'a> { Self::BreakStatement(stmt) => stmt.gen(p, ctx), Self::ContinueStatement(stmt) => stmt.gen(p, ctx), Self::DebuggerStatement(stmt) => stmt.gen(p, ctx), + Self::Declaration(decl) => decl.gen(p, ctx), Self::DoWhileStatement(stmt) => stmt.gen(p, ctx), - Self::EmptyStatement(decl) => {} + Self::EmptyStatement(stmt) => stmt.gen(p, ctx), Self::ExpressionStatement(stmt) => stmt.gen(p, ctx), Self::ForInStatement(stmt) => stmt.gen(p, ctx), Self::ForOfStatement(stmt) => stmt.gen(p, ctx), @@ -91,7 +92,6 @@ impl<'a> Gen for Statement<'a> { Self::TryStatement(stmt) => stmt.gen(p, ctx), Self::WhileStatement(stmt) => stmt.gen(p, ctx), Self::WithStatement(stmt) => stmt.gen(p, ctx), - Self::Declaration(decl) => decl.gen(p, ctx), } } } @@ -295,6 +295,12 @@ impl<'a> Gen for DoWhileStatement<'a> { } } +impl Gen for EmptyStatement { + fn gen(&self, p: &mut Printer, ctx: Context) { + p.print(b';'); + } +} + impl Gen for ContinueStatement { fn gen(&self, p: &mut Printer, ctx: Context) { p.print_str(b"continue"); @@ -768,6 +774,9 @@ impl<'a> GenExpr for Expression<'a> { Self::ClassExpression(expr) => expr.gen(p, ctx), Self::JSXElement(el) => el.gen(p, ctx), Self::JSXFragment(fragment) => fragment.gen(p, ctx), + Self::ParenthesizedExpression(_) => { + panic!("The compressor mut strip this ParenthesizedExpression.") + } _ => {} } } diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index 9a08a842e..ae52b1249 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -14,10 +14,10 @@ pub(crate) fn test(source_text: &str, expected: &str) { test_with_options(source_text, expected, options); } -pub(crate) fn test_with_options(source_text: &str, _expected: &str, options: MinifierOptions) { +pub(crate) fn test_with_options(source_text: &str, expected: &str, options: MinifierOptions) { let source_type = SourceType::default(); - let _minified = Minifier::new(source_text, source_type, options).build(); - // assert_eq!(expected, minified, "for source {source_text}"); + let minified = Minifier::new(source_text, source_type, options).build(); + assert_eq!(expected, minified, "for source {source_text}"); } pub(crate) fn test_same(source_text: &str) { @@ -28,17 +28,17 @@ pub(crate) fn test_reparse(source_text: &str) { let source_type = SourceType::default(); let options = MinifierOptions { mangle: false, ..MinifierOptions::default() }; let minified = Minifier::new(source_text, source_type, options).build(); - let _minified2 = Minifier::new(&minified, source_type, options).build(); - // assert_eq!(minified, minified2, "for source {source_text}"); + let minified2 = Minifier::new(&minified, source_type, options).build(); + assert_eq!(minified, minified2, "for source {source_text}"); } -pub(crate) fn test_without_compress_booleans(source_text: &str, _expected: &str) { +pub(crate) fn test_without_compress_booleans(source_text: &str, expected: &str) { let source_type = SourceType::default(); let compress_options = CompressOptions { booleans: false, ..CompressOptions::default() }; let options = MinifierOptions { mangle: false, compress: compress_options, print: PrinterOptions }; - let _minified = Minifier::new(source_text, source_type, options).build(); - // assert_eq!(expected, minified, "for source {source_text}"); + let minified = Minifier::new(source_text, source_type, options).build(); + assert_eq!(expected, minified, "for source {source_text}"); } pub(crate) fn test_snapshot(name: &str, sources: S)