diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 4d02d16c93155..739819eafaade 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> { let mut allocator = Allocator::default(); let printed = minify(&allocator, &source_text, source_type, mangle, nospace); - println!("{printed}"); + // println!("{printed}"); if twice { allocator.reset(); diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index a6c26ec364bb5..f9dec491704ca 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -62,6 +62,7 @@ impl<'a> Ctx<'a, '_> { } } + #[inline] pub fn is_identifier_undefined(self, ident: &IdentifierReference) -> bool { if ident.name == "undefined" && ident.is_global_reference(self.symbols()) { return true; @@ -69,6 +70,7 @@ 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; @@ -76,6 +78,7 @@ impl<'a> Ctx<'a, '_> { false } + #[inline] pub fn is_identifier_nan(self, ident: &IdentifierReference) -> bool { if ident.name == "NaN" && ident.is_global_reference(self.symbols()) { return true; diff --git a/crates/oxc_minifier/src/peephole/fold_constants.rs b/crates/oxc_minifier/src/peephole/fold_constants.rs index badfd76f9aac7..9145db9a97fff 100644 --- a/crates/oxc_minifier/src/peephole/fold_constants.rs +++ b/crates/oxc_minifier/src/peephole/fold_constants.rs @@ -573,34 +573,34 @@ impl<'a, 'b> PeepholeOptimizations { if e.arguments.len() != 1 { return None; } - Some(ctx.value_to_expr( - e.span, - ConstantValue::Number(match &e.arguments[0] { - // `Number(undefined)` -> `NaN` - Argument::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN, - // `Number(null)` -> `0` - Argument::NullLiteral(_) => 0.0, - // `Number(true)` -> `1` `Number(false)` -> `0` - Argument::BooleanLiteral(b) => f64::from(b.value), - // `Number(100)` -> `100` - Argument::NumericLiteral(n) => n.value, - // `Number("a")` -> `+"a"` -> `NaN` - // `Number("1")` -> `+"1"` -> `1` - Argument::StringLiteral(n) => { - let argument = ctx.ast.expression_string_literal(n.span, n.value, n.raw); - if let Some(n) = ctx.eval_to_number(&argument) { - n - } else { - return Some(ctx.ast.expression_unary( - e.span, - UnaryOperator::UnaryPlus, - argument, - )); - } + let arg = e.arguments[0].as_expression()?; + let value = ConstantValue::Number(match arg { + // `Number(undefined)` -> `NaN` + Expression::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN, + // `Number(null)` -> `0` + Expression::NullLiteral(_) => 0.0, + // `Number(true)` -> `1` `Number(false)` -> `0` + Expression::BooleanLiteral(b) => f64::from(b.value), + // `Number(100)` -> `100` + Expression::NumericLiteral(n) => n.value, + // `Number("a")` -> `+"a"` -> `NaN` + // `Number("1")` -> `+"1"` -> `1` + Expression::StringLiteral(n) => { + let argument = ctx.ast.expression_string_literal(n.span, n.value, n.raw); + if let Some(n) = ctx.eval_to_number(&argument) { + n + } else { + return Some(ctx.ast.expression_unary( + e.span, + UnaryOperator::UnaryPlus, + argument, + )); } - _ => return None, - }), - )) + } + e if e.is_void_0() => f64::NAN, + _ => return None, + }); + Some(ctx.value_to_expr(e.span, value)) } fn try_fold_binary_typeof_comparison( @@ -1744,6 +1744,7 @@ mod test { #[test] fn test_number_constructor() { test("Number(undefined)", "NaN"); + test("Number(void 0)", "NaN"); test("Number(null)", "0"); test("Number(true)", "1"); test("Number(false)", "0"); diff --git a/crates/oxc_minifier/src/peephole/minimize_conditions.rs b/crates/oxc_minifier/src/peephole/minimize_conditions.rs index 69d8055c7459f..35063eaddb44e 100644 --- a/crates/oxc_minifier/src/peephole/minimize_conditions.rs +++ b/crates/oxc_minifier/src/peephole/minimize_conditions.rs @@ -4,10 +4,9 @@ use oxc_ecmascript::{ constant_evaluation::{ConstantEvaluation, ValueType}, ToInt32, }; -use oxc_semantic::ReferenceFlags; use oxc_span::{cmp::ContentEq, GetSpan}; use oxc_syntax::es_target::ESTarget; -use oxc_traverse::{Ancestor, MaybeBoundIdentifier, TraverseCtx}; +use oxc_traverse::{Ancestor, TraverseCtx}; use crate::ctx::Ctx; @@ -366,13 +365,9 @@ impl<'a> PeepholeOptimizations { unreachable!() }; let return_stmt = return_stmt.unbox(); - if let Some(e) = return_stmt.argument { - e - } else { - let name = "undefined"; - let symbol_id = ctx.scopes().find_binding(ctx.current_scope_id(), name); - let ident = MaybeBoundIdentifier::new(Atom::from(name), symbol_id); - ident.create_expression(ReferenceFlags::read(), ctx) + match return_stmt.argument { + Some(e) => e, + None => ctx.ast.void_0(return_stmt.span), } } diff --git a/crates/oxc_minifier/src/peephole/normalize.rs b/crates/oxc_minifier/src/peephole/normalize.rs index 408e9adc07bc2..2825dd7adb750 100644 --- a/crates/oxc_minifier/src/peephole/normalize.rs +++ b/crates/oxc_minifier/src/peephole/normalize.rs @@ -3,7 +3,7 @@ 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 oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx}; use crate::{ctx::Ctx, CompressOptions}; @@ -22,6 +22,7 @@ pub struct NormalizeOptions { /// * convert `Infinity` to `f64::INFINITY` /// * convert `NaN` to `f64::NaN` /// * convert `var x; void x` to `void 0` +/// * convert `undefined` to `void 0` /// /// Also /// @@ -62,7 +63,10 @@ impl<'a> Traverse<'a> for Normalize { *expr = ctx.ast.move_expression(&mut paren_expr.expression); } match expr { - Expression::Identifier(_) => { + Expression::Identifier(ident) => { + if let Some(e) = Self::try_compress_undefined(ident, ctx) { + *expr = e; + } Self::convert_infinity_or_nan_into_number(expr, ctx); } Expression::UnaryExpression(e) if e.operator.is_void() => { @@ -154,6 +158,24 @@ 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)] diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 7ad18b1a26c61..26ee54e2128aa 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -871,7 +871,6 @@ impl<'a> LatePeepholeOptimizations { } if let Some(folded_expr) = match expr { - Expression::Identifier(ident) => Self::try_compress_undefined(ident, ctx), Expression::BooleanLiteral(_) => Self::try_compress_boolean(expr, ctx), _ => None, } { @@ -893,23 +892,6 @@ impl<'a> LatePeepholeOptimizations { } } - /// Transforms `undefined` => `void 0` - 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)) - } - /// Transforms boolean expression `true` => `!0` `false` => `!1`. fn try_compress_boolean( expr: &mut Expression<'a>, diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 315dcfb758597..c49db0d51b92c 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -9,7 +9,7 @@ Original | minified | minified | gzip | gzip | Fixture 342.15 kB | 118.19 kB | 118.14 kB | 44.45 kB | 44.37 kB | vue.js -544.10 kB | 71.73 kB | 72.48 kB | 26.14 kB | 26.20 kB | lodash.js +544.10 kB | 71.75 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js 555.77 kB | 272.89 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js