From 2a8665475aaa20ee9561f0a5c31e1efaeb502768 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Mon, 11 Aug 2025 21:10:42 +0200 Subject: [PATCH 1/3] [Removing] Add `RemoveAttributeRector` --- .../remove_attribute_everywhere.php.inc | 64 +++++++++ ...attribute_from_class_constant_only.php.inc | 70 +++++++++ ...e_attribute_from_class_method_only.php.inc | 76 ++++++++++ .../remove_attribute_from_class_only.php.inc | 70 +++++++++ ...move_attribute_from_enum_case_only.php.inc | 28 ++++ .../remove_attribute_from_enum_only.php.inc | 28 ++++ ...emove_attribute_from_function_only.php.inc | 70 +++++++++ ...move_attribute_from_interface_only.php.inc | 50 +++++++ ...move_attribute_from_parameter_only.php.inc | 68 +++++++++ ...emove_attribute_from_property_only.php.inc | 69 +++++++++ .../remove_attribute_from_trait_only.php.inc | 70 +++++++++ .../RemoveAttributeRectorTest.php | 28 ++++ .../Attribute/RemoveEverywhereAttribute.php | 12 ++ .../Attribute/RemoveFromClassAttribute.php | 12 ++ .../RemoveFromClassConstantAttribute.php | 12 ++ .../RemoveFromClassMethodAttribute.php | 12 ++ .../Attribute/RemoveFromEnumAttribute.php | 12 ++ .../Attribute/RemoveFromEnumCaseAttribute.php | 12 ++ .../Attribute/RemoveFromFunctionAttribute.php | 12 ++ .../RemoveFromInterfaceAttribute.php | 12 ++ .../RemoveFromParameterAttribute.php | 12 ++ .../Attribute/RemoveFromPropertyAttribute.php | 12 ++ .../Attribute/RemoveFromTraitAttribute.php | 12 ++ .../config/configured_rule.php | 95 +++++++++++++ .../Attribute/RemoveAttributeRector.php | 134 ++++++++++++++++++ .../Removing/ValueObject/RemoveAttribute.php | 37 +++++ 26 files changed, 1089 insertions(+) create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_everywhere.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_constant_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_method_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_case_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_function_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_interface_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_parameter_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_property_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_trait_only.php.inc create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/RemoveAttributeRectorTest.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveEverywhereAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromClassAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromClassConstantAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromClassMethodAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromEnumAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromEnumCaseAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromFunctionAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromInterfaceAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromParameterAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromPropertyAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveFromTraitAttribute.php create mode 100644 rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/config/configured_rule.php create mode 100644 rules/Removing/Rector/Attribute/RemoveAttributeRector.php create mode 100644 rules/Removing/ValueObject/RemoveAttribute.php diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_everywhere.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_everywhere.php.inc new file mode 100644 index 00000000000..119633d5c1b --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_everywhere.php.inc @@ -0,0 +1,64 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_constant_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_constant_only.php.inc new file mode 100644 index 00000000000..a63bf943894 --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_constant_only.php.inc @@ -0,0 +1,70 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_method_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_method_only.php.inc new file mode 100644 index 00000000000..1b01880c409 --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_method_only.php.inc @@ -0,0 +1,76 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_only.php.inc new file mode 100644 index 00000000000..d431d07d7ec --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_class_only.php.inc @@ -0,0 +1,70 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_case_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_case_only.php.inc new file mode 100644 index 00000000000..d604531e6b2 --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_case_only.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_only.php.inc new file mode 100644 index 00000000000..092d3f46a3b --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_enum_only.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_function_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_function_only.php.inc new file mode 100644 index 00000000000..2235de7d1be --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_function_only.php.inc @@ -0,0 +1,70 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_interface_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_interface_only.php.inc new file mode 100644 index 00000000000..99858f1c688 --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_interface_only.php.inc @@ -0,0 +1,50 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_parameter_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_parameter_only.php.inc new file mode 100644 index 00000000000..63a53d52eec --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_parameter_only.php.inc @@ -0,0 +1,68 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_property_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_property_only.php.inc new file mode 100644 index 00000000000..7913e4c3e16 --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_property_only.php.inc @@ -0,0 +1,69 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_trait_only.php.inc b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_trait_only.php.inc new file mode 100644 index 00000000000..c2301e6d6cf --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Fixture/remove_attribute_from_trait_only.php.inc @@ -0,0 +1,70 @@ + +----- + diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/RemoveAttributeRectorTest.php b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/RemoveAttributeRectorTest.php new file mode 100644 index 00000000000..eae958666fb --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/RemoveAttributeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveEverywhereAttribute.php b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveEverywhereAttribute.php new file mode 100644 index 00000000000..1ac01cd7c0e --- /dev/null +++ b/rules-tests/Removing/Rector/Attribute/RemoveAttributeRector/Source/Attribute/RemoveEverywhereAttribute.php @@ -0,0 +1,12 @@ +ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromClassAttribute', + [Class_::class] + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromTraitAttribute', + [Trait_::class] + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromInterfaceAttribute', + [Interface_::class] + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromEnumAttribute', + [Enum_::class] + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromEnumCaseAttribute', + [EnumCase::class] + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromPropertyAttribute', + [Property::class] + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromClassConstantAttribute', + [ClassConst::class], + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromClassMethodAttribute', + [ClassMethod::class], + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromFunctionAttribute', + [Function_::class], + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveFromParameterAttribute', + [Param::class], + ), + ]); + + $rectorConfig->ruleWithConfiguration(RemoveAttributeRector::class, [ + new RemoveAttribute( + 'Rector\Tests\Removing\Rector\Attribute\RemoveAttributeRector\Source\Attribute\RemoveEverywhereAttribute', + ), + ]); +}; diff --git a/rules/Removing/Rector/Attribute/RemoveAttributeRector.php b/rules/Removing/Rector/Attribute/RemoveAttributeRector.php new file mode 100644 index 00000000000..3bde0aa52d5 --- /dev/null +++ b/rules/Removing/Rector/Attribute/RemoveAttributeRector.php @@ -0,0 +1,134 @@ + + */ + private array $removeAttributes = []; + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Removes attributes (from specific node types)', [ + new ConfiguredCodeSample( + <<<'CODE_SAMPLE' +#[Foo] +class SomeClass +{ +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +#[Foo] +class SomeClass +{ +} +CODE_SAMPLE + , + [new RemoveAttribute('Foo')] + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Node::class]; + } + + public function refactor(Node $node): ?Node + { + if (! in_array('attrGroups', $node->getSubNodeNames(), true)) { + return null; + } + + if (! isset($node->attrGroups) || $node->attrGroups === null || $node->attrGroups === []) { + return null; + } + + foreach ($node->attrGroups as $attrGroup) { + if (! $attrGroup instanceof AttributeGroup) { + return null; + } + } + + $nodeTypes = [$node::class]; + if ($node instanceof Param && $node->isPromoted()) { + // An attribute removed from a parameter or property must be removed from a promoted property because an + // attribute on a promoted property applies to the constructor parameter and the object property. + $nodeTypes[] = Property::class; + } + + $relevantRemoveAttributes = []; + foreach ($this->removeAttributes as $removeAttribute) { + if ($removeAttribute->getNodeTypes() === [] || array_intersect( + $nodeTypes, + $removeAttribute->getNodeTypes() + ) !== []) { + $relevantRemoveAttributes[] = $removeAttribute; + } + } + + if ($relevantRemoveAttributes === []) { + return null; + } + + $hasChanged = false; + + /** @var array $attrGroups */ + $attrGroups = $node->attrGroups; + + foreach ($attrGroups as $attrGroupKey => $attrGroup) { + foreach ($attrGroup->attrs as $key => $attribute) { + foreach ($relevantRemoveAttributes as $removeAttribute) { + if (! $this->isName($attribute, $removeAttribute->getClass())) { + continue; + } + + unset($attrGroup->attrs[$key]); + + $hasChanged = true; + } + } + + if ($attrGroup->attrs === []) { + unset($node->attrGroups[$attrGroupKey]); + + $hasChanged = true; + } + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + public function configure(array $configuration): void + { + Assert::allIsInstanceOf($configuration, RemoveAttribute::class); + + $this->removeAttributes = $configuration; + } +} diff --git a/rules/Removing/ValueObject/RemoveAttribute.php b/rules/Removing/ValueObject/RemoveAttribute.php new file mode 100644 index 00000000000..8c727908fd5 --- /dev/null +++ b/rules/Removing/ValueObject/RemoveAttribute.php @@ -0,0 +1,37 @@ +> $nodeTypes + */ + public function __construct( + private string $class, + private array $nodeTypes = [], + ) { + RectorAssert::className($class); + foreach ($nodeTypes as $nodeType) { + RectorAssert::className($nodeType); + } + } + + public function getClass(): string + { + return $this->class; + } + + /** + * @return list> + */ + public function getNodeTypes(): array + { + return $this->nodeTypes; + } +} From efa36107655ea2118aeb2ce0ebfdd22ca042770f Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Tue, 12 Aug 2025 05:53:55 +0200 Subject: [PATCH 2/3] [Removing] Use explicit node types in `RemoveAttributeRector` --- .../Attribute/RemoveAttributeRector.php | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/rules/Removing/Rector/Attribute/RemoveAttributeRector.php b/rules/Removing/Rector/Attribute/RemoveAttributeRector.php index 3bde0aa52d5..9b0a56cba08 100644 --- a/rules/Removing/Rector/Attribute/RemoveAttributeRector.php +++ b/rules/Removing/Rector/Attribute/RemoveAttributeRector.php @@ -6,7 +6,16 @@ use PhpParser\Node; use PhpParser\Node\AttributeGroup; +use PhpParser\Node\Expr\ArrowFunction; +use PhpParser\Node\Expr\Closure; use PhpParser\Node\Param; +use PhpParser\Node\PropertyHook; +use PhpParser\Node\Stmt\ClassConst; +use PhpParser\Node\Stmt\ClassLike; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Const_; +use PhpParser\Node\Stmt\EnumCase; +use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Rector\AbstractRector; @@ -53,25 +62,26 @@ class SomeClass */ public function getNodeTypes(): array { - return [Node::class]; + return [ + ArrowFunction::class, + ClassConst::class, + ClassLike::class, + ClassMethod::class, + Closure::class, + Const_::class, + EnumCase::class, + Function_::class, + Param::class, + Property::class, + PropertyHook::class, + ]; } + /** + * @param ArrowFunction|ClassConst|ClassLike|ClassMethod|Closure|Const_|EnumCase|Function_|Param|Property|PropertyHook $node + */ public function refactor(Node $node): ?Node { - if (! in_array('attrGroups', $node->getSubNodeNames(), true)) { - return null; - } - - if (! isset($node->attrGroups) || $node->attrGroups === null || $node->attrGroups === []) { - return null; - } - - foreach ($node->attrGroups as $attrGroup) { - if (! $attrGroup instanceof AttributeGroup) { - return null; - } - } - $nodeTypes = [$node::class]; if ($node instanceof Param && $node->isPromoted()) { // An attribute removed from a parameter or property must be removed from a promoted property because an From d4c9c1790027a4df1549f0e124f88cfd645da873 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Tue, 12 Aug 2025 16:46:06 +0200 Subject: [PATCH 3/3] Remove special consideration for promoted properties --- .../Rector/Attribute/RemoveAttributeRector.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/rules/Removing/Rector/Attribute/RemoveAttributeRector.php b/rules/Removing/Rector/Attribute/RemoveAttributeRector.php index 9b0a56cba08..6f2859393b0 100644 --- a/rules/Removing/Rector/Attribute/RemoveAttributeRector.php +++ b/rules/Removing/Rector/Attribute/RemoveAttributeRector.php @@ -82,19 +82,13 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $nodeTypes = [$node::class]; - if ($node instanceof Param && $node->isPromoted()) { - // An attribute removed from a parameter or property must be removed from a promoted property because an - // attribute on a promoted property applies to the constructor parameter and the object property. - $nodeTypes[] = Property::class; - } - $relevantRemoveAttributes = []; foreach ($this->removeAttributes as $removeAttribute) { - if ($removeAttribute->getNodeTypes() === [] || array_intersect( - $nodeTypes, - $removeAttribute->getNodeTypes() - ) !== []) { + if ($removeAttribute->getNodeTypes() === [] || in_array( + $node::class, + $removeAttribute->getNodeTypes(), + true + )) { $relevantRemoveAttributes[] = $removeAttribute; } }