Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions crates/oxc_minifier/src/ast_passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down
93 changes: 87 additions & 6 deletions crates/oxc_minifier/src/ast_passes/normalize.rs
Original file line number Diff line number Diff line change
@@ -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)
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/Normalize.java>
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>) {
Expand All @@ -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(_) => {
Expand All @@ -36,21 +53,63 @@ 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);
}
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>) {
Expand Down Expand Up @@ -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);
}

Expand All @@ -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", "");
}
}
121 changes: 0 additions & 121 deletions crates/oxc_minifier/src/ast_passes/remove_syntax.rs

This file was deleted.

7 changes: 3 additions & 4 deletions crates/oxc_minifier/src/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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)
Expand All @@ -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);
}
}
5 changes: 2 additions & 3 deletions crates/oxc_minifier/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use oxc_span::SourceType;
use oxc_traverse::ReusableTraverseCtx;

use crate::{
ast_passes::{CompressorPass, Normalize, RemoveSyntax},
ast_passes::{CompressorPass, Normalize},
CompressOptions,
};

Expand Down Expand Up @@ -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);
}

Expand Down
Loading