diff --git a/crates/oxc_ecmascript/src/side_effects/expressions.rs b/crates/oxc_ecmascript/src/side_effects/expressions.rs index c3bf04ca2b29e..eec5bccddcfe0 100644 --- a/crates/oxc_ecmascript/src/side_effects/expressions.rs +++ b/crates/oxc_ecmascript/src/side_effects/expressions.rs @@ -650,6 +650,10 @@ impl<'a> MayHaveSideEffects<'a> for ObjectPropertyKind<'a> { } else { match &e.argument { Expression::ArrayExpression(arr) => arr.may_have_side_effects(ctx), + Expression::ObjectExpression(obj) => obj + .properties + .iter() + .any(|property| property.may_have_side_effects(ctx)), Expression::StringLiteral(_) => false, Expression::TemplateLiteral(t) => t.may_have_side_effects(ctx), _ => true, diff --git a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs index 4c5d38f8b268e..d052682e56ad6 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs @@ -336,7 +336,12 @@ impl<'a> PeepholeOptimizations { return true; } if object_expr.properties.iter().all(ObjectPropertyKind::is_spread) { - return false; + // All-spread objects like `({...x})` can only be removed if + // the spread arguments themselves have no side effects. + return !object_expr + .properties + .iter() + .any(|property| property.may_have_side_effects(ctx)); } let mut transformed_elements = ctx.ast.vec(); 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 7f0c75e41a41c..fb27154e04c59 100644 --- a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs +++ b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs @@ -310,10 +310,11 @@ fn closure_compiler_tests() { test("new Object(...true)", true); // OBJECT_SPREAD - // These could all invoke getters. + // Spreading unknown identifiers could invoke getters. test("({...x})", true); - test("({...{}})", true); - test("({...{a:1}})", true); + // Spreading object literals is side-effect-free if the properties are. + test("({...{}})", false); + test("({...{a:1}})", false); test("({...{a:i++}})", true); test("({...{a:f()}})", true); test("({...f()})", true); @@ -711,6 +712,11 @@ fn test_object_expression() { test("({...`foo${foo}`})", true); test("({...`foo${foo()}`})", true); test("({...foo()})", true); + // Spreading object literals + test("({...{}})", false); + test("({...{a: 1}})", false); + test("({...{a: foo()}})", true); + test("({...{[foo()]: 1}})", true); } #[test] diff --git a/crates/oxc_minifier/tests/peephole/remove_unused_expression.rs b/crates/oxc_minifier/tests/peephole/remove_unused_expression.rs index 4b404f6f40119..c717ea253901c 100644 --- a/crates/oxc_minifier/tests/peephole/remove_unused_expression.rs +++ b/crates/oxc_minifier/tests/peephole/remove_unused_expression.rs @@ -176,6 +176,10 @@ fn test_object_literal() { // Object-spread may trigger getters. test_same("({...a})"); test_same("({...foo()})"); + // Spreading object literals is safe if contents are safe. + test("({...{}})", ""); + test("({...{a: 1}})", ""); + test("({...{a: foo()}})", "foo()"); test("({ [{ foo: foo() }]: 0 })", "foo()"); test("({ foo: { foo: foo() } })", "foo()");