diff --git a/crates/oxc_minifier/src/peephole/normalize.rs b/crates/oxc_minifier/src/peephole/normalize.rs index 8126a5a49132a..69988eb5c4c27 100644 --- a/crates/oxc_minifier/src/peephole/normalize.rs +++ b/crates/oxc_minifier/src/peephole/normalize.rs @@ -185,13 +185,17 @@ impl<'a> Normalize { "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()) - { + if Self::is_unary_delete_ancestor(ctx.ancestors()) { return None; } Some(ctx.ast.void_0(ident.span)) } "Infinity" if ident.is_global_reference(ctx.symbols()) => { + // `delete Infinity` returns `false` + // `delete 1/0` returns `true` + if Self::is_unary_delete_ancestor(ctx.ancestors()) { + return None; + } Some(ctx.ast.expression_numeric_literal( ident.span, f64::INFINITY, @@ -199,13 +203,37 @@ impl<'a> Normalize { NumberBase::Decimal, )) } - "NaN" if ident.is_global_reference(ctx.symbols()) => Some( - ctx.ast.expression_numeric_literal(ident.span, f64::NAN, None, NumberBase::Decimal), - ), + "NaN" if ident.is_global_reference(ctx.symbols()) => { + // `delete NaN` returns `false` + // `delete 0/0` returns `true` + if Self::is_unary_delete_ancestor(ctx.ancestors()) { + return None; + } + Some(ctx.ast.expression_numeric_literal( + ident.span, + f64::NAN, + None, + NumberBase::Decimal, + )) + } _ => None, } } + fn is_unary_delete_ancestor<'t>(ancestors: impl Iterator>) -> bool { + for ancestor in ancestors { + match ancestor { + Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete() => { + return true; + } + Ancestor::ParenthesizedExpressionExpression(_) + | Ancestor::SequenceExpressionExpressions(_) => {} + _ => return false, + } + } + false + } + fn convert_void_ident(e: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>) { debug_assert!(e.operator.is_void()); let Expression::Identifier(ident) = &e.argument else { return }; diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index fb17d256d0ce9..7377b51a2bcdc 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -478,12 +478,41 @@ impl<'a, 'b> PeepholeOptimizations { /// /// * `access_value` - The expression that may need to be kept as indirect reference (`foo.bar` in the example above) pub fn should_keep_indirect_access(access_value: &Expression<'a>, ctx: Ctx<'a, 'b>) -> bool { - matches!( - ctx.parent(), - Ancestor::CallExpressionCallee(_) | Ancestor::TaggedTemplateExpressionTag(_) - ) && match access_value { - Expression::Identifier(id) => id.name == "eval" && ctx.is_global_reference(id), - match_member_expression!(Expression) => true, + match ctx.parent() { + Ancestor::CallExpressionCallee(_) | Ancestor::TaggedTemplateExpressionTag(_) => { + match access_value { + Expression::Identifier(id) => id.name == "eval" && ctx.is_global_reference(id), + match_member_expression!(Expression) => true, + _ => false, + } + } + Ancestor::UnaryExpressionArgument(unary) => match unary.operator() { + UnaryOperator::Typeof => { + // Example case: `typeof (0, foo)` (error) -> `typeof foo` (no error) + if let Expression::Identifier(id) = access_value { + ctx.is_global_reference(id) + } else { + false + } + } + UnaryOperator::Delete => { + match access_value { + // Example case: `delete (0, foo)` (no error) -> `delete foo` (error) + Expression::Identifier(_) + // Example case: `delete (0, foo.#a)` (no error) -> `delete foo.#a` (error) + | Expression::PrivateFieldExpression(_) + // Example case: `typeof (0, foo.bar)` (noop) -> `typeof foo.bar` (deletes bar) + | Expression::ComputedMemberExpression(_) + | Expression::StaticMemberExpression(_) => true, + // Example case: `typeof (0, foo?.bar)` (noop) -> `typeof foo?.bar` (deletes bar) + Expression::ChainExpression(chain) => { + matches!(&chain.expression, match_member_expression!(ChainElement)) + } + _ => false, + } + } + _ => false, + }, _ => false, } } @@ -686,6 +715,18 @@ mod test { test("(true, true, foo.bar)();", "(0, foo.bar)();"); test("var foo; (true, foo.bar)();", "var foo; (0, foo.bar)();"); test("var foo; (true, true, foo.bar)();", "var foo; (0, foo.bar)();"); + + test("typeof (0, foo);", "foo"); + test_same("v = typeof (0, foo);"); + test("var foo; typeof (0, foo);", "var foo;"); + test("var foo; v = typeof (0, foo);", "var foo; v = typeof foo"); + test("typeof 0", ""); + + test_same("delete (0, foo);"); + test_same("delete (0, foo.#bar);"); + test_same("delete (0, foo.bar);"); + test_same("delete (0, foo[bar]);"); + test_same("delete (0, foo?.bar);"); } #[test] diff --git a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs index 078f6684eb2b7..1ece0d6ef1c4a 100644 --- a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs +++ b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs @@ -369,6 +369,7 @@ fn test_unary_expressions() { test("typeof 'foo'", false); test_with_global_variables("typeof a", vec!["a".to_string()], false); + test_with_global_variables("typeof (0, a)", vec!["a".to_string()], true); test("typeof foo()", true); test("+0", false); diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index 1b58da32a697c..e74775c210677 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -1298,13 +1298,13 @@ fn test_flatten_values() { // "function f(a) { let c = a.b; return c`${x}` }", // "function f(a) { return (0, a.b)`${x}`;}", // ); - // test("return typeof (123, x)", "return typeof (0, x);"); + test("return typeof (123, x)", "return typeof (0, x);"); test("return typeof (123, x.y)", "return typeof x.y;"); test("return typeof (123, x); var x", "return typeof x;var x;"); - // test("return typeof (true && x)", "return typeof (0, x);"); + test("return typeof (true && x)", "return typeof (0, x);"); test("return typeof (true && x.y)", "return typeof x.y;"); test("return typeof (true && x); var x", "return typeof x;var x;"); - // test("return typeof (false || x)", "return typeof (0, x);"); + test("return typeof (false || x)", "return typeof (0, x);"); test("return typeof (false || x.y)", "return typeof x.y;"); test("return typeof (false || x); var x", "return typeof x;var x;"); test("return typeof x !== 'undefined'", "return typeof x < 'u';"); @@ -1463,11 +1463,11 @@ fn test_remove_dead_expr() { test("delete x", "delete x;"); test("typeof x", ""); test("typeof x()", "x();"); - // test("typeof (0, x)", "x;"); - // test("typeof (0 || x)", "x;"); - // test("typeof (1 && x)", "x;"); - // test("typeof (1 ? x : 0)", "x;"); - // test("typeof (0 ? 1 : x)", "x;"); + test("typeof (0, x)", "x;"); + test("typeof (0 || x)", "x;"); + test("typeof (1 && x)", "x;"); + test("typeof (1 ? x : 0)", "x;"); + test("typeof (0 ? 1 : x)", "x;"); test("a + b", "a + b;"); test("a - b", "a - b;"); test("a * b", "a * b;"); @@ -1551,22 +1551,22 @@ fn test_remove_dead_expr() { test("delete (x[y])", "delete x[y];"); test("delete (x?.y)", "delete x?.y;"); test("delete (x?.[y])", "delete x?.[y];"); - // test("delete (2, x)", "delete (0, x);"); - // test("delete (2, x); var x", "delete (0, x);var x;"); - // test("delete (2, x.y)", "delete (0, x.y);"); - // test("delete (2, x[y])", "delete (0, x[y]);"); - // test("delete (2, x?.y)", "delete (0, x?.y);"); - // test("delete (2, x?.[y])", "delete (0, x?.[y]);"); - // test("delete (true && x)", "delete (0, x);"); - // test("delete (false || x)", "delete (0, x);"); - // test("delete (null ?? x)", "delete (0, x);"); - // test("delete (1 ? x : 2)", "delete (0, x);"); - // test("delete (0 ? 1 : x)", "delete (0, x);"); + test("delete (2, x)", "delete (0, x);"); + test("delete (2, x); var x", "delete (0, x);var x;"); + test("delete (2, x.y)", "delete (0, x.y);"); + test("delete (2, x[y])", "delete (0, x[y]);"); + test("delete (2, x?.y)", "delete (0, x?.y);"); + test("delete (2, x?.[y])", "delete (0, x?.[y]);"); + test("delete (true && x)", "delete (0, x);"); + test("delete (false || x)", "delete (0, x);"); + test("delete (null ?? x)", "delete (0, x);"); + test("delete (1 ? x : 2)", "delete (0, x);"); + test("delete (0 ? 1 : x)", "delete (0, x);"); test("delete (NaN)", "delete NaN;"); - // test("delete (Infinity)", "delete Infinity;"); + test("delete (Infinity)", "delete Infinity;"); test("delete (-Infinity)", "delete -Infinity;"); - // test("delete (1, NaN)", "delete (0, NaN);"); - // test("delete (1, Infinity)", "delete (0, Infinity);"); + test("delete (1, NaN)", "delete (0, NaN);"); + test("delete (1, Infinity)", "delete (0, Infinity);"); test("delete (1, -Infinity)", "delete -Infinity;"); test("foo ? 1 : 2", "foo;"); test("foo ? 1 : bar", "foo || bar;");