diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index f9dec491704ca..18dcf0f17f3ce 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -70,21 +70,6 @@ impl<'a> Ctx<'a, '_> { false } - #[inline] - pub fn is_identifier_infinity(self, ident: &IdentifierReference) -> bool { - if ident.name == "Infinity" && ident.is_global_reference(self.symbols()) { - return true; - } - false - } - - #[inline] - pub fn is_identifier_nan(self, ident: &IdentifierReference) -> bool { - if ident.name == "NaN" && ident.is_global_reference(self.symbols()) { - return true; - } - false - } /// If two expressions are equal. /// Special case `undefined` == `void 0` pub fn expr_eq(self, a: &Expression<'a>, b: &Expression<'a>) -> bool { diff --git a/crates/oxc_minifier/src/peephole/normalize.rs b/crates/oxc_minifier/src/peephole/normalize.rs index 2825dd7adb750..375a0320f47f7 100644 --- a/crates/oxc_minifier/src/peephole/normalize.rs +++ b/crates/oxc_minifier/src/peephole/normalize.rs @@ -1,6 +1,7 @@ use oxc_allocator::Vec; use oxc_ast::ast::*; use oxc_ecmascript::constant_evaluation::ConstantEvaluation; +use oxc_semantic::IsGlobalReference; use oxc_span::GetSpan; use oxc_syntax::scope::ScopeFlags; use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx}; @@ -62,23 +63,22 @@ impl<'a> Traverse<'a> for Normalize { if let Expression::ParenthesizedExpression(paren_expr) = expr { *expr = ctx.ast.move_expression(&mut paren_expr.expression); } - match expr { - Expression::Identifier(ident) => { - if let Some(e) = Self::try_compress_undefined(ident, ctx) { - *expr = e; - } - Self::convert_infinity_or_nan_into_number(expr, ctx); - } + if let Some(e) = match expr { + Expression::Identifier(ident) => Self::try_compress_identifier(ident, ctx), Expression::UnaryExpression(e) if e.operator.is_void() => { Self::convert_void_ident(e, ctx); + None } Expression::ArrowFunctionExpression(e) => { self.recover_arrow_expression_after_drop_console(e); + None } Expression::CallExpression(_) if self.compress_options.drop_console => { - self.compress_console(expr, ctx); + self.compress_console(expr, ctx) } - _ => {} + _ => None, + } { + *expr = e; } } } @@ -95,11 +95,13 @@ impl<'a> Normalize { matches!(stmt, Statement::DebuggerStatement(_)) && self.compress_options.drop_debugger } - fn compress_console(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + fn compress_console( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { debug_assert!(self.compress_options.drop_console); - if Self::is_console(expr) { - *expr = ctx.ast.void_0(expr.span()); - } + Self::is_console(expr).then(|| ctx.ast.void_0(expr.span())) } fn drop_console(&mut self, stmt: &Statement<'a>) -> bool { @@ -135,18 +137,34 @@ impl<'a> Normalize { *stmt = Statement::ForStatement(for_stmt); } - fn convert_infinity_or_nan_into_number(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if let Expression::Identifier(ident) = expr { - let ctx = Ctx(ctx); - let value = if ctx.is_identifier_infinity(ident) { - f64::INFINITY - } else if ctx.is_identifier_nan(ident) { - f64::NAN - } else { - return; - }; - *expr = - ctx.ast.expression_numeric_literal(ident.span, value, None, NumberBase::Decimal); + /// Transforms `undefined` => `void 0`, `Infinity` => `f64::Infinity`, `NaN` -> `f64::NaN`. + /// So subsequent passes don't need to look up whether these variables are shadowed or not. + fn try_compress_identifier( + ident: &IdentifierReference<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + match ident.name.as_str() { + "undefined" if ident.is_global_reference(ctx.symbols()) => { + // `delete undefined` returns `false` + // `delete void 0` returns `true` + if matches!(ctx.parent(), Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete()) + { + return None; + } + Some(ctx.ast.void_0(ident.span)) + } + "Infinity" if ident.is_global_reference(ctx.symbols()) => { + Some(ctx.ast.expression_numeric_literal( + ident.span, + f64::INFINITY, + None, + NumberBase::Decimal, + )) + } + "NaN" if ident.is_global_reference(ctx.symbols()) => Some( + ctx.ast.expression_numeric_literal(ident.span, f64::NAN, None, NumberBase::Decimal), + ), + _ => None, } } @@ -158,24 +176,6 @@ impl<'a> Normalize { } e.argument = ctx.ast.expression_numeric_literal(ident.span, 0.0, None, NumberBase::Decimal); } - - /// Transforms `undefined` => `void 0` - /// So subsequent passes don't need to look up whether `undefined` is shadowed or not. - fn try_compress_undefined( - ident: &IdentifierReference<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> Option> { - if !Ctx(ctx).is_identifier_undefined(ident) { - return None; - } - // `delete undefined` returns `false` - // `delete void 0` returns `true` - if matches!(ctx.parent(), Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete()) - { - return None; - } - Some(ctx.ast.void_0(ident.span)) - } } #[cfg(test)]