diff --git a/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs b/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs index bd47317e2e478a..c08cd96ae6fcd1 100644 --- a/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs +++ b/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs @@ -1,4 +1,4 @@ -use oxc_ast::ast::{Expression, ForStatementLeft, UnaryExpression}; +use oxc_ast::ast::{Expression, ForStatementLeft, PropertyKey, UnaryExpression}; use super::check_for_state_change::CheckForStateChange; @@ -19,3 +19,4 @@ where impl<'a, 'b> MayHaveSideEffects<'a, 'b> for Expression<'a> {} impl<'a, 'b> MayHaveSideEffects<'a, 'b> for UnaryExpression<'a> {} impl<'a, 'b> MayHaveSideEffects<'a, 'b> for ForStatementLeft<'a> {} +impl<'a, 'b> MayHaveSideEffects<'a, 'b> for PropertyKey<'a> {} diff --git a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs index 9b675905e90a50..27024d841d0265 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs @@ -1,6 +1,7 @@ use oxc_allocator::Vec; use oxc_ast::{ast::*, Visit}; use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, IsLiteralValue}; +use oxc_ecmascript::side_effects::MayHaveSideEffects; use oxc_span::SPAN; use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; @@ -253,8 +254,9 @@ impl<'a, 'b> PeepholeRemoveDeadCode { .then(|| Some(ctx.ast.statement_empty(SPAN))) .unwrap_or_else(|| match &mut stmt.expression { Expression::ArrayExpression(expr) => Self::try_fold_array_expression(expr, ctx), - // TODO: handle object expression - Expression::ObjectExpression(_object_expr) => None, + Expression::ObjectExpression(object_expr) => { + Self::try_fold_object_expression(object_expr, ctx) + } _ => None, }) } @@ -328,6 +330,97 @@ impl<'a, 'b> PeepholeRemoveDeadCode { )); } + // `{a: 1, b: 2, c: foo()}` -> `foo()` + fn try_fold_object_expression( + object_expr: &mut ObjectExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + let spread_count = object_expr + .properties + .iter() + .filter(|prop| matches!(prop, ObjectPropertyKind::SpreadProperty(_))) + .count(); + + if spread_count == object_expr.properties.len() { + return None; + } + + // if there is a spread, we can't remove the object expression + if spread_count > 0 { + let original_property_count = object_expr.properties.len(); + + object_expr.properties.retain(|v| match v { + ObjectPropertyKind::ObjectProperty(object_property) => { + object_property.key.may_have_side_effects() + || object_property.value.may_have_side_effects() + || object_property.init.as_ref().is_some_and( + oxc_ecmascript::side_effects::MayHaveSideEffects::may_have_side_effects, + ) + } + ObjectPropertyKind::SpreadProperty(_) => true, + }); + + if original_property_count == object_expr.properties.len() { + return None; + } + return Some(ctx.ast.statement_expression( + object_expr.span, + ctx.ast.expression_from_object(ctx.ast.object_expression( + object_expr.span, + ctx.ast.move_vec(&mut object_expr.properties), + None, + )), + )); + } + + // we can replace the object with a sequence expression + let mut filtered_properties = ctx.ast.vec(); + + for prop in object_expr.properties.iter_mut() { + match prop { + ObjectPropertyKind::ObjectProperty(object_prop) => { + let key = object_prop.key.as_expression_mut(); + if let Some(key) = key { + if key.may_have_side_effects() { + let key_expr = ctx.ast.move_expression(key); + filtered_properties.push(key_expr); + } + } + + if object_prop.value.may_have_side_effects() { + let mut expr = ctx.ast.move_expression(&mut object_prop.value); + filtered_properties.push(ctx.ast.move_expression(&mut expr)); + } + + if object_prop.init.as_ref().is_some_and( + oxc_ecmascript::side_effects::MayHaveSideEffects::may_have_side_effects, + ) { + let mut expr = object_prop.init.take().unwrap(); + filtered_properties.push(ctx.ast.move_expression(&mut expr)); + } + } + ObjectPropertyKind::SpreadProperty(_) => { + unreachable!("spread property should have been filtered out"); + } + } + } + + if filtered_properties.len() == 0 { + return Some(ctx.ast.statement_empty(object_expr.span)); + } else if filtered_properties.len() == 1 { + return Some( + ctx.ast.statement_expression(object_expr.span, filtered_properties.pop().unwrap()), + ); + } + + return Some(ctx.ast.statement_expression( + object_expr.span, + ctx.ast.expression_from_sequence( + ctx.ast.sequence_expression(object_expr.span, filtered_properties), + ), + )); + } + /// Try folding conditional expression (?:) if the condition results of the condition is known. fn try_fold_conditional_expression( expr: &mut ConditionalExpression<'a>, @@ -460,11 +553,14 @@ mod test { fn test_object_literal() { fold("({})", ""); fold("({a:1})", ""); - // fold("({a:foo()})", "foo()"); - // fold("({'a':foo()})", "foo()"); + fold("({a:foo()})", "foo()"); + fold("({'a':foo()})", "foo()"); // Object-spread may trigger getters. fold_same("({...a})"); fold_same("({...foo()})"); + + fold("({ [bar()]: foo() })", "bar(), foo()"); + fold_same("({ ...baz, [bar()]: foo() })"); } #[test]