From 48d3b5c08ef663579342d7bba2484505d8c504ca Mon Sep 17 00:00:00 2001 From: Caleb White Date: Wed, 6 Aug 2025 23:04:58 -0500 Subject: [PATCH] feat: add RemoveReadonlyPropertyVisibilityOnReadonlyClassRector This Rector removes the `readonly` visibility modifier from properties in classes that are already marked as `readonly`. This is useful for cleaning up code where the `readonly` modifier is redundant due to the class's readonly status. --- .../Fixture/class_with_attribute.php.inc | 23 ++++ .../class_with_attribute_inline.php.inc | 21 ++++ .../Fixture/class_without_attribute.php.inc | 19 +++ ...ombine_promo_and_property_readonly.php.inc | 35 ++++++ .../implicit_public_readonly_property.php.inc | 31 +++++ ...public_readonly_property_promotion.php.inc | 25 ++++ .../Fixture/only_readonly_property.php.inc | 21 ++++ .../Fixture/only_readonly_property2.php.inc | 25 ++++ .../with_attribute_on_property.php.inc | 23 ++++ ...th_attribute_on_property_promotion.php.inc | 27 ++++ ...rtyVisibilityOnReadonlyClassRectorTest.php | 28 +++++ .../config/configured_rule.php | 10 ++ ...ropertyVisibilityOnReadonlyClassRector.php | 116 ++++++++++++++++++ src/Config/Level/CodeQualityLevel.php | 2 + 14 files changed, 406 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute_inline.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_without_attribute.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/combine_promo_and_property_readonly.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property_promotion.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property2.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property_promotion.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/RemoveReadonlyPropertyVisibilityOnReadonlyClassRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/config/configured_rule.php create mode 100644 rules/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector.php diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute.php.inc new file mode 100644 index 00000000000..63bbdd463a9 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute.php.inc @@ -0,0 +1,23 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute_inline.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute_inline.php.inc new file mode 100644 index 00000000000..c290ca5bca1 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_with_attribute_inline.php.inc @@ -0,0 +1,21 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_without_attribute.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_without_attribute.php.inc new file mode 100644 index 00000000000..cb63b1c0412 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/class_without_attribute.php.inc @@ -0,0 +1,19 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/combine_promo_and_property_readonly.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/combine_promo_and_property_readonly.php.inc new file mode 100644 index 00000000000..1d78a687567 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/combine_promo_and_property_readonly.php.inc @@ -0,0 +1,35 @@ +b = $b ?? 'foo'; + } +} + +?> +----- +b = $b ?? 'foo'; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property.php.inc new file mode 100644 index 00000000000..950575aab59 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property.php.inc @@ -0,0 +1,31 @@ +foo = 'bar'; + } +} + +?> +----- +foo = 'bar'; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property_promotion.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property_promotion.php.inc new file mode 100644 index 00000000000..28bbd50cbd7 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/implicit_public_readonly_property_promotion.php.inc @@ -0,0 +1,25 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property.php.inc new file mode 100644 index 00000000000..d104a6a6290 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property.php.inc @@ -0,0 +1,21 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property2.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property2.php.inc new file mode 100644 index 00000000000..d44061393cb --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/only_readonly_property2.php.inc @@ -0,0 +1,25 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property.php.inc new file mode 100644 index 00000000000..264004169fa --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property.php.inc @@ -0,0 +1,23 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property_promotion.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property_promotion.php.inc new file mode 100644 index 00000000000..d323c4e1373 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/Fixture/with_attribute_on_property_promotion.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/RemoveReadonlyPropertyVisibilityOnReadonlyClassRectorTest.php b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/RemoveReadonlyPropertyVisibilityOnReadonlyClassRectorTest.php new file mode 100644 index 00000000000..983b49bba82 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/RemoveReadonlyPropertyVisibilityOnReadonlyClassRectorTest.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/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/config/configured_rule.php new file mode 100644 index 00000000000..2a2f36a23bc --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RemoveReadonlyPropertyVisibilityOnReadonlyClassRector::class); +}; diff --git a/rules/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector.php b/rules/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector.php new file mode 100644 index 00000000000..c55659eb984 --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/RemoveReadonlyPropertyVisibilityOnReadonlyClassRector.php @@ -0,0 +1,116 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $node->isReadonly()) { + return null; + } + + $hasChanged = false; + $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); + + if ($constructClassMethod instanceof ClassMethod) { + foreach ($constructClassMethod->getParams() as $param) { + if (! $param->isReadonly()) { + continue; + } + + $this->visibilityManipulator->removeReadonly($param); + $hasChanged = true; + + if ($param->attrGroups !== []) { + $this->attributeGroupNewLiner->newLine($this->file, $param); + } + } + } + + foreach ($node->getProperties() as $property) { + if (! $property->isReadonly()) { + continue; + } + + $this->visibilityManipulator->removeReadonly($property); + $hasChanged = true; + + if ($property->attrGroups !== []) { + $this->attributeGroupNewLiner->newLine($this->file, $property); + } + } + + if (! $hasChanged) { + return null; + } + + return $node; + } +} diff --git a/src/Config/Level/CodeQualityLevel.php b/src/Config/Level/CodeQualityLevel.php index 8842465e78e..4db24e8239a 100644 --- a/src/Config/Level/CodeQualityLevel.php +++ b/src/Config/Level/CodeQualityLevel.php @@ -12,6 +12,7 @@ use Rector\CodeQuality\Rector\Catch_\ThrowWithPreviousExceptionRector; use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector; use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; +use Rector\CodeQuality\Rector\Class_\RemoveReadonlyPropertyVisibilityOnReadonlyClassRector; use Rector\CodeQuality\Rector\Class_\StaticToSelfStaticMethodCallOnFinalClassRector; use Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector; use Rector\CodeQuality\Rector\ClassMethod\ExplicitReturnNullRector; @@ -173,6 +174,7 @@ final class CodeQualityLevel RemoveUselessIsObjectCheckRector::class, StaticToSelfStaticMethodCallOnFinalClassRector::class, SortNamedParamRector::class, + RemoveReadonlyPropertyVisibilityOnReadonlyClassRector::class, ]; /**