From 4d2b0d503814dbb5e6b7ea369cb290c148f72d62 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:57:40 +0000 Subject: [PATCH] feat(minifier): port esbuild `SimplifyUnusedExpr` (#9036) --- .../peephole/convert_to_dotted_properties.rs | 8 +- .../src/peephole/fold_constants.rs | 11 - crates/oxc_minifier/src/peephole/mod.rs | 1 + .../src/peephole/remove_dead_code.rs | 315 +----------------- .../src/peephole/remove_unused_expression.rs | 303 +++++++++++++++++ .../peephole/substitute_alternate_syntax.rs | 4 +- .../tests/peephole/dead_code_elimination.rs | 3 +- crates/oxc_minifier/tests/peephole/esbuild.rs | 9 + 8 files changed, 329 insertions(+), 325 deletions(-) create mode 100644 crates/oxc_minifier/src/peephole/remove_unused_expression.rs diff --git a/crates/oxc_minifier/src/peephole/convert_to_dotted_properties.rs b/crates/oxc_minifier/src/peephole/convert_to_dotted_properties.rs index 96735dee529b2..e39abc9ee6277 100644 --- a/crates/oxc_minifier/src/peephole/convert_to_dotted_properties.rs +++ b/crates/oxc_minifier/src/peephole/convert_to_dotted_properties.rs @@ -91,10 +91,10 @@ mod test { #[test] fn test_convert_to_dotted_properties_quoted_props() { - test("({'':0})", ""); - test("({'1.0':0})", ""); - test("({'\\u1d17A':0})", ""); - test("({'a\\u0004b':0})", ""); + test_same("({'':0})"); + test_same("({'1.0':0})"); + test("({'\\u1d17A':0})", "({ ᴗA: 0 })"); + // test("({'a\\u0004b':0})"); } #[test] diff --git a/crates/oxc_minifier/src/peephole/fold_constants.rs b/crates/oxc_minifier/src/peephole/fold_constants.rs index c91a651eeeac1..bde78eeab6fc5 100644 --- a/crates/oxc_minifier/src/peephole/fold_constants.rs +++ b/crates/oxc_minifier/src/peephole/fold_constants.rs @@ -102,17 +102,6 @@ impl<'a> PeepholeOptimizations { let left_val = ctx.get_boolean_value(left); if let Some(lval) = left_val { - // Bail `0 && (module.exports = {})` for `cjs-module-lexer`. - if !lval { - if let Expression::AssignmentExpression(assign_expr) = &logical_expr.right { - if let Some(member_expr) = assign_expr.left.as_member_expression() { - if member_expr.is_specific_member_access("module", "exports") { - return None; - } - } - } - } - // (TRUE || x) => TRUE (also, (3 || x) => 3) // (FALSE && x) => FALSE if if lval { op == LogicalOperator::Or } else { op == LogicalOperator::And } { diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 99f72080439a6..b71c46063c9ce 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -11,6 +11,7 @@ mod minimize_not_expression; mod minimize_statements; mod normalize; mod remove_dead_code; +mod remove_unused_expression; mod replace_known_methods; mod statement_fusion; mod substitute_alternate_syntax; diff --git a/crates/oxc_minifier/src/peephole/remove_dead_code.rs b/crates/oxc_minifier/src/peephole/remove_dead_code.rs index 412e3349f9df0..44cb479a9d533 100644 --- a/crates/oxc_minifier/src/peephole/remove_dead_code.rs +++ b/crates/oxc_minifier/src/peephole/remove_dead_code.rs @@ -1,10 +1,6 @@ use oxc_allocator::Vec; use oxc_ast::{ast::*, Visit}; -use oxc_ecmascript::{ - constant_evaluation::{ConstantEvaluation, IsLiteralValue, ValueType}, - side_effects::MayHaveSideEffects, -}; -use oxc_span::GetSpan; +use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, side_effects::MayHaveSideEffects}; use oxc_traverse::Ancestor; use crate::{ctx::Ctx, keep_var::KeepVar}; @@ -32,12 +28,7 @@ impl<'a, 'b> PeepholeOptimizations { self.mark_current_function_as_changed(); } - if let Statement::ExpressionStatement(s) = stmt { - if let Some(new_stmt) = Self::try_fold_expression_stmt(s, ctx) { - *stmt = new_stmt; - self.mark_current_function_as_changed(); - } - } + self.try_fold_expression_stmt(stmt, ctx); } pub fn remove_dead_code_exit_expression( @@ -295,114 +286,19 @@ impl<'a, 'b> PeepholeOptimizations { var_decl.unwrap_or_else(|| ctx.ast.statement_empty(s.span)).into() } - fn try_fold_expression_stmt( - stmt: &mut ExpressionStatement<'a>, - ctx: Ctx<'a, 'b>, - ) -> Option> { + fn try_fold_expression_stmt(&mut self, stmt: &mut Statement<'a>, ctx: Ctx<'a, 'b>) { + let Statement::ExpressionStatement(expr_stmt) = stmt else { return }; // We need to check if it is in arrow function with `expression: true`. // This is the only scenario where we can't remove it even if `ExpressionStatement`. if let Ancestor::ArrowFunctionExpressionBody(body) = ctx.ancestry.ancestor(1) { if *body.expression() { - return None; + return; } } - if stmt.expression.is_literal_value(false) { - return Some(ctx.ast.statement_empty(stmt.span)); - } - - match &mut stmt.expression { - Expression::MetaProperty(e) => Some(ctx.ast.statement_empty(e.span)), - Expression::ArrayExpression(expr) => Self::try_fold_array_expression(expr, ctx), - Expression::ObjectExpression(object_expr) => { - Self::try_fold_object_expression(object_expr, ctx) - } - Expression::TemplateLiteral(template_lit) => { - if !template_lit.expressions.is_empty() { - return None; - } - let mut expressions = ctx.ast.move_vec(&mut template_lit.expressions); - if expressions.len() == 0 { - return Some(ctx.ast.statement_empty(stmt.span)); - } else if expressions.len() == 1 { - return Some( - ctx.ast.statement_expression(template_lit.span, expressions.pop().unwrap()), - ); - } - Some(ctx.ast.statement_expression( - template_lit.span, - ctx.ast.expression_sequence(template_lit.span, expressions), - )) - } - Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => { - Some(ctx.ast.statement_empty(stmt.span)) - } - // `typeof x` -> `` - Expression::UnaryExpression(unary_expr) - if unary_expr.operator.is_typeof() - && unary_expr.argument.is_identifier_reference() => - { - Some(ctx.ast.statement_empty(stmt.span)) - } - // `typeof x.y` -> `x.y`, `void x` -> `x`, `!x` -> `x` - // `+0n` -> `Uncaught TypeError: Cannot convert a BigInt value to a number` - Expression::UnaryExpression(unary_expr) - if matches!( - unary_expr.operator, - UnaryOperator::Typeof | UnaryOperator::Void | UnaryOperator::LogicalNot - ) => - { - Some(ctx.ast.statement_expression( - unary_expr.span, - ctx.ast.move_expression(&mut unary_expr.argument), - )) - } - Expression::NewExpression(e) => { - let Expression::Identifier(ident) = &e.callee else { return None }; - let len = e.arguments.len(); - if match ident.name.as_str() { - "WeakSet" | "WeakMap" if ctx.is_global_reference(ident) => match len { - 0 => true, - 1 => match e.arguments[0].as_expression()? { - Expression::NullLiteral(_) => true, - Expression::ArrayExpression(e) => e.elements.is_empty(), - e if ctx.is_expression_undefined(e) => true, - _ => false, - }, - _ => false, - }, - "Date" if ctx.is_global_reference(ident) => match len { - 0 => true, - 1 => { - let arg = e.arguments[0].as_expression()?; - let ty = ValueType::from(arg); - matches!( - ty, - ValueType::Null - | ValueType::Undefined - | ValueType::Boolean - | ValueType::Number - | ValueType::String - ) && !ctx.expression_may_have_side_effects(arg) - } - _ => false, - }, - "Set" | "Map" if ctx.is_global_reference(ident) => match len { - 0 => true, - 1 => match e.arguments[0].as_expression()? { - Expression::NullLiteral(_) => true, - e if ctx.is_expression_undefined(e) => true, - _ => false, - }, - _ => false, - }, - _ => false, - } { - return Some(ctx.ast.statement_empty(e.span)); - } - None - } - _ => None, + if Self::remove_unused_expression(&mut expr_stmt.expression, ctx) { + *stmt = ctx.ast.statement_empty(expr_stmt.span); + self.mark_current_function_as_changed(); } } @@ -429,82 +325,6 @@ impl<'a, 'b> PeepholeOptimizations { } } - // `([1,2,3, foo()])` -> `foo()` - fn try_fold_array_expression( - array_expr: &mut ArrayExpression<'a>, - ctx: Ctx<'a, 'b>, - ) -> Option> { - let mut transformed_elements = ctx.ast.vec(); - let mut pending_spread_elements = ctx.ast.vec(); - - if array_expr.elements.len() == 0 - || array_expr.elements.iter().all(|el| match el { - ArrayExpressionElement::SpreadElement(_) => true, - ArrayExpressionElement::Identifier(ident) => ctx.is_global_reference(ident), - _ => false, - }) - { - return None; - } - - for el in &mut array_expr.elements { - match el { - ArrayExpressionElement::SpreadElement(_) => { - let spread_element = ctx.ast.move_array_expression_element(el); - pending_spread_elements.push(spread_element); - } - ArrayExpressionElement::Elision(_) => {} - match_expression!(ArrayExpressionElement) => { - let el = el.to_expression_mut(); - let el_expr = ctx.ast.move_expression(el); - if !el_expr.is_literal_value(false) - && !matches!(&el_expr, Expression::Identifier(ident) if !ctx.is_global_reference(ident)) - { - if pending_spread_elements.len() > 0 { - // flush pending spread elements - transformed_elements.push(ctx.ast.expression_array( - el_expr.span(), - pending_spread_elements, - None, - )); - pending_spread_elements = ctx.ast.vec(); - } - transformed_elements.push(el_expr); - } - } - } - } - - if pending_spread_elements.len() > 0 { - transformed_elements.push(ctx.ast.expression_array( - array_expr.span, - pending_spread_elements, - None, - )); - } - - if transformed_elements.is_empty() { - return Some(ctx.ast.statement_empty(array_expr.span)); - } else if transformed_elements.len() == 1 { - return Some( - ctx.ast.statement_expression(array_expr.span, transformed_elements.pop().unwrap()), - ); - } - - Some(ctx.ast.statement_expression( - array_expr.span, - ctx.ast.expression_sequence(array_expr.span, transformed_elements), - )) - } - - // `{a: 1, b: 2, c: foo()}` -> `foo()` - fn try_fold_object_expression( - _object_expr: &mut ObjectExpression<'a>, - _ctx: Ctx<'a, 'b>, - ) -> Option> { - None - } - /// Try folding conditional expression (?:) if the condition results of the condition is known. fn try_fold_conditional_expression( expr: &mut ConditionalExpression<'a>, @@ -726,79 +546,6 @@ mod test { test("for(;'';) foo()", ""); } - #[test] - #[ignore] - fn test_object_literal() { - test("({})", ""); - test("({a:1})", ""); - test("({a:foo()})", "foo()"); - test("({'a':foo()})", "foo()"); - // Object-spread may trigger getters. - test_same("({...a})"); - test_same("({...foo()})"); - - test("({ [bar()]: foo() })", "bar(), foo()"); - test_same("({ ...baz, [bar()]: foo() })"); - } - - #[test] - fn test_array_literal() { - test("([])", ""); - test("([1])", ""); - test("([a])", "[a]"); - test("var a; ([a])", "var a;"); - test("([foo()])", "foo()"); - test_same("baz.map((v) => [v])"); - } - - #[test] - fn test_array_literal_containing_spread() { - test_same("([...c])"); - test("([4, ...c, a])", "([...c], a)"); - test("var a; ([4, ...c, a])", "var a; ([...c])"); - test("([foo(), ...c, bar()])", "(foo(), [...c], bar())"); - test("([...a, b, ...c])", "([...a, b, ...c])"); - test("var b; ([...a, b, ...c])", "var b; ([...a, ...c])"); - test_same("([...b, ...c])"); // It would also be fine if the spreads were split apart. - } - - #[test] - fn test_fold_unary_expression_statement() { - test("typeof x", ""); - test("typeof x?.y", "x?.y"); - test("typeof x.y", "x.y"); - test("typeof x.y.z()", "x.y.z()"); - test("void x", "x"); - test("void x?.y", "x?.y"); - test("void x.y", "x.y"); - test("void x.y.z()", "x.y.z()"); - - test("!x", "x"); - test("!x?.y", "x?.y"); - test("!x.y", "x.y"); - test("!x.y.z()", "x.y.z()"); - test_same("-x.y.z()"); - - test_same("delete x"); - test_same("delete x.y"); - test_same("delete x.y.z()"); - test_same("+0n"); // Uncaught TypeError: Cannot convert a BigInt value to a number - } - - #[test] - fn test_fold_sequence_expr() { - test("('foo', 'bar', 'baz')", ""); - test("('foo', 'bar', baz())", "baz()"); - test("('foo', bar(), baz())", "bar(), baz()"); - test("(() => {}, bar(), baz())", "bar(), baz()"); - test("(function k() {}, k(), baz())", "k(), baz()"); - test_same("(0, o.f)();"); - test("var obj = Object((null, 2, 3), 1, 2);", "var obj = Object(3, 1, 2);"); - test_same("(0 instanceof 0, foo)"); - test_same("(0 in 0, foo)"); - test_same("React.useEffect(() => (isMountRef.current = !1, () => { isMountRef.current = !0; }), [])"); - } - #[test] fn test_fold_try_statement() { test("try { throw 0 } catch (e) { foo() }", "try { throw 0 } catch { foo() }"); @@ -862,52 +609,6 @@ mod test { test_same("class Foo { static { foo() } }"); } - #[test] - fn remove_expression_statement() { - test("void 0", ""); - test("-1", ""); - test("!1", ""); - test("1", ""); - test("import.meta", ""); - } - - #[test] - fn test_new_constructor_side_effect() { - test("new WeakSet()", ""); - test("new WeakSet(null)", ""); - test("new WeakSet(void 0)", ""); - test("new WeakSet([])", ""); - test_same("new WeakSet([x])"); - test_same("new WeakSet(x)"); - test("new WeakMap()", ""); - test("new WeakMap(null)", ""); - test("new WeakMap(void 0)", ""); - test("new WeakMap([])", ""); - test_same("new WeakMap([x])"); - test_same("new WeakMap(x)"); - test("new Date()", ""); - test("new Date('')", ""); - test("new Date(0)", ""); - test("new Date(null)", ""); - test("new Date(true)", ""); - test("new Date(false)", ""); - test("new Date(undefined)", ""); - test_same("new Date(x)"); - test("new Set()", ""); - // test("new Set([a, b, c])", ""); - test("new Set(null)", ""); - test("new Set(undefined)", ""); - test("new Set(void 0)", ""); - test_same("new Set(x)"); - test("new Map()", ""); - test("new Map(null)", ""); - test("new Map(undefined)", ""); - test("new Map(void 0)", ""); - // test_same("new Map([x])"); - test_same("new Map(x)"); - // test("new Map([[a, b], [c, d]])", ""); - } - #[test] fn keep_module_syntax() { test_same("throw foo; export let bar"); diff --git a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs new file mode 100644 index 0000000000000..00d68e7ff61b6 --- /dev/null +++ b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs @@ -0,0 +1,303 @@ +use oxc_ast::ast::*; +use oxc_ecmascript::{ + constant_evaluation::{IsLiteralValue, ValueType}, + side_effects::MayHaveSideEffects, +}; +use oxc_span::GetSpan; + +use crate::ctx::Ctx; + +use super::PeepholeOptimizations; + +impl<'a> PeepholeOptimizations { + /// `SimplifyUnusedExpr`: + pub fn remove_unused_expression(e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + match e { + Expression::NullLiteral(_) + | Expression::BooleanLiteral(_) + | Expression::NumericLiteral(_) + | Expression::BigIntLiteral(_) + | Expression::StringLiteral(_) + | Expression::ThisExpression(_) + | Expression::RegExpLiteral(_) + | Expression::FunctionExpression(_) + | Expression::ArrowFunctionExpression(_) + | Expression::MetaProperty(_) => true, + Expression::Identifier(ident) => ctx.symbols().has_binding(ident.reference_id()), + Expression::ArrayExpression(_) => Self::try_fold_array_expression(e, ctx), + Expression::UnaryExpression(unary_expr) => match unary_expr.operator { + UnaryOperator::Void | UnaryOperator::LogicalNot => { + *e = ctx.ast.move_expression(&mut unary_expr.argument); + Self::remove_unused_expression(e, ctx) + } + UnaryOperator::Typeof => { + if unary_expr.argument.is_identifier_reference() { + true + } else { + *e = ctx.ast.move_expression(&mut unary_expr.argument); + Self::remove_unused_expression(e, ctx) + } + } + _ => false, + }, + Expression::NewExpression(e) => Self::fold_new_constructor(e, ctx), + // TODO + // Expression::TemplateLiteral(_) + // | Expression::ObjectExpression(_) + // | Expression::ConditionalExpression(_) + // | Expression::BinaryExpression(_) => { + // false + // } + _ => false, + } + } + + // `([1,2,3, foo()])` -> `foo()` + fn try_fold_array_expression(e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool { + let Expression::ArrayExpression(array_expr) = e else { + return false; + }; + + let mut transformed_elements = ctx.ast.vec(); + let mut pending_spread_elements = ctx.ast.vec(); + + if array_expr.elements.len() == 0 { + return true; + } + + if array_expr + .elements + .iter() + .any(|el| matches!(el, ArrayExpressionElement::SpreadElement(_))) + { + return false; + } + + for el in &mut array_expr.elements { + match el { + ArrayExpressionElement::SpreadElement(_) => { + let spread_element = ctx.ast.move_array_expression_element(el); + pending_spread_elements.push(spread_element); + } + ArrayExpressionElement::Elision(_) => {} + match_expression!(ArrayExpressionElement) => { + let el = el.to_expression_mut(); + let el_expr = ctx.ast.move_expression(el); + if !el_expr.is_literal_value(false) + && !matches!(&el_expr, Expression::Identifier(ident) if !ctx.is_global_reference(ident)) + { + if pending_spread_elements.len() > 0 { + // flush pending spread elements + transformed_elements.push(ctx.ast.expression_array( + el_expr.span(), + pending_spread_elements, + None, + )); + pending_spread_elements = ctx.ast.vec(); + } + transformed_elements.push(el_expr); + } + } + } + } + + if pending_spread_elements.len() > 0 { + transformed_elements.push(ctx.ast.expression_array( + array_expr.span, + pending_spread_elements, + None, + )); + } + + if transformed_elements.is_empty() { + return true; + } else if transformed_elements.len() == 1 { + *e = transformed_elements.pop().unwrap(); + return false; + } + + *e = ctx.ast.expression_sequence(array_expr.span, transformed_elements); + false + } + + fn fold_new_constructor(e: &mut NewExpression<'a>, ctx: Ctx<'a, '_>) -> bool { + let Expression::Identifier(ident) = &e.callee else { return false }; + let len = e.arguments.len(); + if match ident.name.as_str() { + "WeakSet" | "WeakMap" if ctx.is_global_reference(ident) => match len { + 0 => true, + 1 => match e.arguments[0].as_expression() { + Some(Expression::NullLiteral(_)) => true, + Some(Expression::ArrayExpression(e)) => e.elements.is_empty(), + Some(e) if ctx.is_expression_undefined(e) => true, + _ => false, + }, + _ => false, + }, + "Date" if ctx.is_global_reference(ident) => match len { + 0 => true, + 1 => { + let Some(arg) = e.arguments[0].as_expression() else { return false }; + let ty = ValueType::from(arg); + matches!( + ty, + ValueType::Null + | ValueType::Undefined + | ValueType::Boolean + | ValueType::Number + | ValueType::String + ) && !ctx.expression_may_have_side_effects(arg) + } + _ => false, + }, + "Set" | "Map" if ctx.is_global_reference(ident) => match len { + 0 => true, + 1 => match e.arguments[0].as_expression() { + Some(Expression::NullLiteral(_)) => true, + Some(e) if ctx.is_expression_undefined(e) => true, + _ => false, + }, + _ => false, + }, + _ => false, + } { + return true; + } + false + } +} + +#[cfg(test)] +mod test { + use crate::tester::{test, test_same}; + + #[test] + fn test_remove_unused_expression() { + test("null", ""); + test("true", ""); + test("false", ""); + test("1", ""); + test("1n", ""); + test(";'s'", ""); + test("this", ""); + test("/asdf/", ""); + test("(function () {})", ""); + test("(() => {})", ""); + test("import.meta", ""); + test("var x; x", "var x"); + test("x", "x"); + test("void 0", ""); + test("void x", "x"); + } + + #[test] + fn test_new_constructor_side_effect() { + test("new WeakSet()", ""); + test("new WeakSet(null)", ""); + test("new WeakSet(void 0)", ""); + test("new WeakSet([])", ""); + test_same("new WeakSet([x])"); + test_same("new WeakSet(x)"); + test("new WeakMap()", ""); + test("new WeakMap(null)", ""); + test("new WeakMap(void 0)", ""); + test("new WeakMap([])", ""); + test_same("new WeakMap([x])"); + test_same("new WeakMap(x)"); + test("new Date()", ""); + test("new Date('')", ""); + test("new Date(0)", ""); + test("new Date(null)", ""); + test("new Date(true)", ""); + test("new Date(false)", ""); + test("new Date(undefined)", ""); + test_same("new Date(x)"); + test("new Set()", ""); + // test("new Set([a, b, c])", ""); + test("new Set(null)", ""); + test("new Set(undefined)", ""); + test("new Set(void 0)", ""); + test_same("new Set(x)"); + test("new Map()", ""); + test("new Map(null)", ""); + test("new Map(undefined)", ""); + test("new Map(void 0)", ""); + // test_same("new Map([x])"); + test_same("new Map(x)"); + // test("new Map([[a, b], [c, d]])", ""); + } + + #[test] + fn test_array_literal() { + test("([])", ""); + test("([1])", ""); + test("([a])", "a"); + test("var a; ([a])", "var a;"); + test("([foo()])", "foo()"); + test_same("baz.map((v) => [v])"); + } + + #[test] + fn test_array_literal_containing_spread() { + test_same("([...c])"); + // FIXME + test_same("([4, ...c, a])"); + test_same("var a; ([4, ...c, a])"); + test_same("([foo(), ...c, bar()])"); + test_same("([...a, b, ...c])"); + test_same("var b; ([...a, b, ...c])"); + test_same("([...b, ...c])"); // It would also be fine if the spreads were split apart. + } + + #[test] + fn test_fold_unary_expression_statement() { + test("typeof x", ""); + test("typeof x?.y", "x?.y"); + test("typeof x.y", "x.y"); + test("typeof x.y.z()", "x.y.z()"); + test("void x", "x"); + test("void x?.y", "x?.y"); + test("void x.y", "x.y"); + test("void x.y.z()", "x.y.z()"); + + test("!x", "x"); + test("!x?.y", "x?.y"); + test("!x.y", "x.y"); + test("!x.y.z()", "x.y.z()"); + test_same("-x.y.z()"); + + test_same("delete x"); + test_same("delete x.y"); + test_same("delete x.y.z()"); + test_same("+0n"); // Uncaught TypeError: Cannot convert a BigInt value to a number + } + + #[test] + fn test_fold_sequence_expr() { + test("('foo', 'bar', 'baz')", ""); + test("('foo', 'bar', baz())", "baz()"); + test("('foo', bar(), baz())", "bar(), baz()"); + test("(() => {}, bar(), baz())", "bar(), baz()"); + test("(function k() {}, k(), baz())", "k(), baz()"); + test_same("(0, o.f)();"); + test("var obj = Object((null, 2, 3), 1, 2);", "var obj = Object(3, 1, 2);"); + test_same("(0 instanceof 0, foo)"); + test_same("(0 in 0, foo)"); + test_same("React.useEffect(() => (isMountRef.current = !1, () => { isMountRef.current = !0; }), [])"); + } + + #[test] + #[ignore] + fn test_object_literal() { + test("({})", ""); + test("({a:1})", ""); + test("({a:foo()})", "foo()"); + test("({'a':foo()})", "foo()"); + // Object-spread may trigger getters. + test_same("({...a})"); + test_same("({...foo()})"); + + test("({ [bar()]: foo() })", "bar(), foo()"); + test_same("({ ...baz, [bar()]: foo() })"); + } +} diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 0637a3bbbd8f3..be0b3aa3a95e4 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -1703,7 +1703,7 @@ mod test { #[test] fn test_fold_big_int_constructor() { - test("BigInt(1n)", "1n"); + test("var x = BigInt(1n)", "var x = 1n"); test_same("BigInt()"); test_same("BigInt(1)"); } @@ -1712,7 +1712,7 @@ mod test { fn optional_catch_binding() { test("try { foo } catch(e) {}", "try { foo } catch {}"); test("try { foo } catch(e) {foo}", "try { foo } catch {foo}"); - test_same("try { foo } catch(e) {e}"); + test_same("try { foo } catch(e) { bar(e) }"); test_same("try { foo } catch([e]) {}"); test_same("try { foo } catch({e}) {}"); diff --git a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs index 1eda9149e1896..107493d2f4149 100644 --- a/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs +++ b/crates/oxc_minifier/tests/peephole/dead_code_elimination.rs @@ -80,7 +80,8 @@ fn dce_if_statement() { // Shadowed `undefined` as a variable should not be erased. // This is a rollup test. - test_same("function foo(undefined) { if (!undefined) foo }"); + // + test_same("function foo(undefined) { if (!undefined) throw Error('') }"); test("function foo() { if (undefined) { bar } }", "function foo() { }"); test("function foo() { { bar } }", "function foo() { bar }"); diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index c2c8dbba48aa0..6a39f802d99d7 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -1595,6 +1595,10 @@ fn test_ignored4() { test("x = class y {}", "x = class {};"); test("x = class y { foo() { return y } }", "x = class y { foo() { return y; }};"); test("x = class y { foo() { if (0) return y } }", "x = class { foo() { }};"); +} + +#[test] +fn test_remove_dead_expr() { test("null", ""); test("void 0", ""); test("void 0", ""); @@ -1623,6 +1627,11 @@ fn test_ignored4() { test("delete x", "delete x;"); test("typeof x", ""); test("typeof x()", "x();"); +} + +#[test] +#[ignore] +fn test_remove_dead_expr_ignore() { test("typeof (0, x)", "x;"); test("typeof (0 || x)", "x;"); test("typeof (1 && x)", "x;");