diff --git a/crates/oxc_minifier/src/ast_passes/statement_fusion.rs b/crates/oxc_minifier/src/ast_passes/statement_fusion.rs index abc5bea616a61..1a5de8ed0e6d1 100644 --- a/crates/oxc_minifier/src/ast_passes/statement_fusion.rs +++ b/crates/oxc_minifier/src/ast_passes/statement_fusion.rs @@ -3,7 +3,7 @@ use oxc_ast::ast::*; use oxc_span::SPAN; use oxc_traverse::{Traverse, TraverseCtx}; -use crate::CompressorPass; +use crate::{node_util::MayHaveSideEffects, CompressorPass}; /// Statement Fusion /// @@ -73,8 +73,7 @@ impl<'a> StatementFusion { for_stmt.init.is_none() || for_stmt.init.as_ref().is_some_and(ForStatementInit::is_expression) } - // TODO: support for-in, we need to check the init for side effects - Statement::ForInStatement(_for_in_stmt) => false, + Statement::ForInStatement(for_in_stmt) => !for_in_stmt.left.may_have_side_effects(), Statement::LabeledStatement(labeled_stmt) => { Self::is_fusable_control_statement(&labeled_stmt.body) } @@ -144,6 +143,7 @@ impl<'a> StatementFusion { return; } } + Statement::ForInStatement(for_stmt) => &mut for_stmt.right, Statement::LabeledStatement(labeled_stmt) => { Self::fuse_expression_into_control_flow_statement( &mut labeled_stmt.body, @@ -261,7 +261,6 @@ mod test { } #[test] - #[ignore] fn fuse_into_for_in1() { fuse("a;b;c;for(x in y){}", "for(x in a,b,c,y){}"); } diff --git a/crates/oxc_minifier/src/node_util/check_for_state_change.rs b/crates/oxc_minifier/src/node_util/check_for_state_change.rs index d0c4f66eb766c..16005303152a5 100644 --- a/crates/oxc_minifier/src/node_util/check_for_state_change.rs +++ b/crates/oxc_minifier/src/node_util/check_for_state_change.rs @@ -145,3 +145,177 @@ impl<'a, 'b> CheckForStateChange<'a, 'b> for PropertyKey<'a> { } } } + +impl<'a, 'b> CheckForStateChange<'a, 'b> for ForStatementLeft<'a> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + match self { + match_assignment_target!(Self) => { + self.to_assignment_target().check_for_state_change(check_for_new_objects) + } + ForStatementLeft::VariableDeclaration(variable_declaration) => { + variable_declaration.check_for_state_change(check_for_new_objects) + } + } + } +} + +impl<'a, 'b> CheckForStateChange<'a, 'b> for VariableDeclaration<'a> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + self.declarations.iter().any(|decl| decl.check_for_state_change(check_for_new_objects)) + } +} + +impl<'a, 'b> CheckForStateChange<'a, 'b> for VariableDeclarator<'a> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + self.id.check_for_state_change(check_for_new_objects) + || self + .init + .as_ref() + .map_or(false, |init| init.check_for_state_change(check_for_new_objects)) + } +} + +impl CheckForStateChange<'_, '_> for BindingPattern<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + match &self.kind { + BindingPatternKind::BindingIdentifier(_) => false, + BindingPatternKind::ObjectPattern(object_pattern) => { + object_pattern + .properties + .iter() + .any(|element| element.check_for_state_change(check_for_new_objects)) + || object_pattern + .rest + .as_ref() + .is_some_and(|rest| rest.check_for_state_change(check_for_new_objects)) + } + BindingPatternKind::ArrayPattern(array_pattern) => { + array_pattern.elements.iter().any(|element| { + element.as_ref().is_some_and(|element| { + element.check_for_state_change(check_for_new_objects) + }) + }) || array_pattern + .rest + .as_ref() + .is_some_and(|rest| rest.check_for_state_change(check_for_new_objects)) + } + BindingPatternKind::AssignmentPattern(assignment_pattern) => { + assignment_pattern.left.check_for_state_change(check_for_new_objects) + && assignment_pattern.right.check_for_state_change(check_for_new_objects) + } + } + } +} + +impl CheckForStateChange<'_, '_> for BindingRestElement<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + self.argument.check_for_state_change(check_for_new_objects) + } +} + +impl CheckForStateChange<'_, '_> for BindingProperty<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + self.key.check_for_state_change(check_for_new_objects) + || self.value.check_for_state_change(check_for_new_objects) + } +} + +impl CheckForStateChange<'_, '_> for AssignmentTarget<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + match self { + AssignmentTarget::AssignmentTargetIdentifier(_) => false, + AssignmentTarget::TSAsExpression(ts_as_expression) => { + ts_as_expression.expression.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::TSSatisfiesExpression(ts_satisfies_expression) => { + ts_satisfies_expression.expression.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::TSNonNullExpression(ts_non_null_expression) => { + ts_non_null_expression.expression.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::TSTypeAssertion(ts_type_assertion) => { + ts_type_assertion.expression.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::TSInstantiationExpression(ts_instantiation_expression) => { + ts_instantiation_expression.expression.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::ComputedMemberExpression(computed_member_expression) => { + computed_member_expression.object.check_for_state_change(check_for_new_objects) + || computed_member_expression + .expression + .check_for_state_change(check_for_new_objects) + } + AssignmentTarget::StaticMemberExpression(static_member_expression) => { + static_member_expression.object.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::PrivateFieldExpression(private_field_expression) => { + private_field_expression.object.check_for_state_change(check_for_new_objects) + } + AssignmentTarget::ArrayAssignmentTarget(array_assignment_target) => { + array_assignment_target.elements.iter().any(|element| { + element.as_ref().is_some_and(|element| { + element.check_for_state_change(check_for_new_objects) + }) + }) || array_assignment_target + .rest + .as_ref() + .is_some_and(|rest| rest.check_for_state_change(check_for_new_objects)) + } + AssignmentTarget::ObjectAssignmentTarget(object_assignment_target) => { + object_assignment_target + .properties + .iter() + .any(|property| property.check_for_state_change(check_for_new_objects)) + || object_assignment_target + .rest + .as_ref() + .is_some_and(|rest| rest.check_for_state_change(check_for_new_objects)) + } + } + } +} + +impl CheckForStateChange<'_, '_> for AssignmentTargetProperty<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + match self { + AssignmentTargetProperty::AssignmentTargetPropertyIdentifier( + assignment_target_property_identifier, + ) => assignment_target_property_identifier + .init + .as_ref() + .map_or(false, |init| init.check_for_state_change(check_for_new_objects)), + AssignmentTargetProperty::AssignmentTargetPropertyProperty( + assignment_target_property_property, + ) => { + assignment_target_property_property + .name + .check_for_state_change(check_for_new_objects) + || assignment_target_property_property + .binding + .check_for_state_change(check_for_new_objects) + } + } + } +} + +impl CheckForStateChange<'_, '_> for AssignmentTargetRest<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + self.target.check_for_state_change(check_for_new_objects) + } +} + +impl CheckForStateChange<'_, '_> for AssignmentTargetMaybeDefault<'_> { + fn check_for_state_change(&self, check_for_new_objects: bool) -> bool { + match self { + match_assignment_target!(Self) => { + self.to_assignment_target().check_for_state_change(check_for_new_objects) + } + Self::AssignmentTargetWithDefault(assignment_target_with_default) => { + assignment_target_with_default.binding.check_for_state_change(check_for_new_objects) + && assignment_target_with_default + .init + .check_for_state_change(check_for_new_objects) + } + } + } +} diff --git a/crates/oxc_minifier/src/node_util/may_have_side_effects.rs b/crates/oxc_minifier/src/node_util/may_have_side_effects.rs index 32ff26a4b2043..194f8e16e423c 100644 --- a/crates/oxc_minifier/src/node_util/may_have_side_effects.rs +++ b/crates/oxc_minifier/src/node_util/may_have_side_effects.rs @@ -18,3 +18,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> {}