From cecef0883700fa1b8700ae011ccd2de12fd514c1 Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 17:42:48 +0530 Subject: [PATCH 01/24] FinalPropertyPromotionRector --- .../FinalPropertyPromotionRectorTest.php | 28 +++++ .../Fixture/final_promotion.php.inc | 30 +++++ .../config/configured_rule.php | 11 ++ .../FinalPropertyPromotionRector.php | 117 ++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php create mode 100644 rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc create mode 100644 rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/config/configured_rule.php create mode 100644 rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php diff --git a/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php b/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php new file mode 100644 index 00000000000..a1514369d24 --- /dev/null +++ b/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.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/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc b/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc new file mode 100644 index 00000000000..7efdc13789c --- /dev/null +++ b/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/config/configured_rule.php b/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/config/configured_rule.php new file mode 100644 index 00000000000..1dadaa044bb --- /dev/null +++ b/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/config/configured_rule.php @@ -0,0 +1,11 @@ +rule(FinalPropertyPromotionRector::class); + +}; diff --git a/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php b/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php new file mode 100644 index 00000000000..2abf305f40b --- /dev/null +++ b/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php @@ -0,0 +1,117 @@ +isName($node, MethodName::CONSTRUCT)) { + return null; + } + + $hasChanged = false; + foreach ($node->params as $param) { + if (! $param->isPromoted()) { + continue; + } + + if (! $this->visibilityManipulator->hasVisibility($param, Visibility::PUBLIC)) { + continue; + } + + $this->removePhpDocTag($param); + $this->visibilityManipulator->makeFinal($param); + + $hasChanged = true; + + } + + if($hasChanged){ + return $node; + } + + return null; + } + + private function removePhpDocTag(Property|Param $node): bool + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + + if (!$phpDocInfo->hasByName(self::TAGNAME)) { + return false; + } + + $phpDocInfo->removeByName(self::TAGNAME); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + return true; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::DEPRECATED_ATTRIBUTE_ON_CONSTANT; + } +} From 062bbcd19ab828e5f87545f4ecd26806ffc1b5bc Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 17:54:38 +0530 Subject: [PATCH 02/24] FinalPropertyPromotionRector --- phpstan.neon | 2 ++ rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 429e9f6e8d5..1e95e1c3f7d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -220,6 +220,8 @@ parameters: - '#Register "Rector\\Php80\\Rector\\NotIdentical\\MbStrContainsRector" service to "php80\.php" config set#' + - '#Register "Rector\\Php85\\Rector\\ClassMethod\\FinalPropertyPromotionRector" service to "php85\.php" config set#' + # closure detailed - '#Method Rector\\Config\\RectorConfig\:\:singleton\(\) has parameter \$concrete with no signature specified for Closure#' diff --git a/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php b/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php index 2abf305f40b..1e4bbea5264 100644 --- a/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php @@ -22,7 +22,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see \Rector\Tests\Php85\Rector\ClassMethod\FinalPropertyPromotionRector\FinalPropertyPromotionRectorTest + * @see \Rector\Tests\Php85\Rector\MethodCall\FinalPropertyPromotionRector\FinalPropertyPromotionRectorTest */ final class FinalPropertyPromotionRector extends AbstractRector implements MinPhpVersionInterface { From cd40ec7596d1bd4d0eea5becc7c79de507559672 Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 19:37:59 +0530 Subject: [PATCH 03/24] FinalPropertyPromotionRector --- .../FinalPropertyPromotionRectorTest.php | 2 +- .../Fixture/fixture.php.inc} | 8 +- .../Fixture/skip_already_final_param.php.inc | 14 ++ .../skip_child_from_final_parent.php.inc | 14 ++ .../Fixture/skip_final_class.php.inc | 15 ++ .../Source/FinalParent.php | 7 + .../config/configured_rule.php | 2 +- .../FinalPromotionManipulator.php | 216 ++++++++++++++++++ .../FinalPropertyPromotionRector.php | 117 ---------- .../Class_/FinalPropertyPromotionRector.php | 66 ++++++ src/ValueObject/PhpVersionFeature.php | 6 + 11 files changed, 344 insertions(+), 123 deletions(-) rename rules-tests/Php85/Rector/{MethodCall => Class_}/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php (88%) rename rules-tests/Php85/Rector/{MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc => Class_/FinalPropertyPromotionRector/Fixture/fixture.php.inc} (51%) create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_already_final_param.php.inc create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_final_class.php.inc create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php rename rules-tests/Php85/Rector/{MethodCall => Class_}/FinalPropertyPromotionRector/config/configured_rule.php (74%) create mode 100644 rules/Php85/NodeManipulator/FinalPromotionManipulator.php delete mode 100644 rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php create mode 100644 rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php diff --git a/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php similarity index 88% rename from rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php rename to rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php index a1514369d24..bf8c0f4b02a 100644 --- a/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/FinalPropertyPromotionRectorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Tests\Php85\Rector\MethodCall\FinalPropertyPromotionRector; +namespace Rector\Tests\Php85\Rector\Class_\FinalPropertyPromotionRector; use Iterator; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/fixture.php.inc similarity index 51% rename from rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc rename to rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/fixture.php.inc index 7efdc13789c..bbb21a0bb7c 100644 --- a/rules-tests/Php85/Rector/MethodCall/FinalPropertyPromotionRector/Fixture/final_promotion.php.inc +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/fixture.php.inc @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Rector\Tests\Php85\Rector\MethodCall\FinalPropertyPromotionRector\Fixture; +namespace Rector\Tests\Php85\Rector\Class_\FinalPropertyPromotionRector\Fixture; -final class FinalPropertyPromotion +class FinalPropertyPromotion { public function __construct( /** @final */ @@ -18,9 +18,9 @@ final class FinalPropertyPromotion declare(strict_types=1); -namespace Rector\Tests\Php85\Rector\MethodCall\FinalPropertyPromotionRector\Fixture; +namespace Rector\Tests\Php85\Rector\Class_\FinalPropertyPromotionRector\Fixture; -final class FinalPropertyPromotion +class FinalPropertyPromotion { public function __construct( final public string $id diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_already_final_param.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_already_final_param.php.inc new file mode 100644 index 00000000000..4523731292a --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_already_final_param.php.inc @@ -0,0 +1,14 @@ + diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc new file mode 100644 index 00000000000..7414635182f --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc @@ -0,0 +1,14 @@ + diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_final_class.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_final_class.php.inc new file mode 100644 index 00000000000..5103a213dfe --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_final_class.php.inc @@ -0,0 +1,15 @@ + diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php new file mode 100644 index 00000000000..037ba52801e --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php @@ -0,0 +1,7 @@ +rule(FinalPropertyPromotionRector::class); diff --git a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php new file mode 100644 index 00000000000..1c1c6d4b5df --- /dev/null +++ b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php @@ -0,0 +1,216 @@ +shouldSkip($class, $scope)) { + return null; + } + + $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); + + if (! $constructClassMethod instanceof ClassMethod) { + return null; + } + + foreach ($constructClassMethod->getParams() as $param) { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($param); + + if (!$phpDocInfo->hasByName(self::TAGNAME)) { + continue; + } + $this->visibilityManipulator->makeFinal($param); + $this->removePhpDocTag($param); + } + + return $class; + } + + private function removePhpDocTag(Property|Param $node): bool + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + + if (!$phpDocInfo->hasByName(self::TAGNAME)) { + return false; + } + + $phpDocInfo->removeByName(self::TAGNAME); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + return true; + } + + /** + * @return ClassReflection[] + */ + private function resolveParentClassReflections(Scope $scope): array + { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return []; + } + + return $classReflection->getParents(); + } + + private function shouldSkip(Class_ $class, Scope $scope): bool + { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return true; + } + + if ($this->shouldSkipClass($class)) { + return true; + } + + $parents = $this->resolveParentClassReflections($scope); + + if ($class->isAnonymous()) { + return true; + } + + if ($class->isFinal()) { + return true; + } + + foreach ($parents as $parent) { + if (! $parent->isFinal()) { + return true; + } + } + + $properties = $class->getProperties(); + + if ($this->shouldSkipConsumeTraitProperty($class)) { + return true; + } + + $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); + if (! $constructClassMethod instanceof ClassMethod) { + // no __construct means no property promotion, skip if class has no property defined + return $properties === []; + } + + $params = $constructClassMethod->getParams(); + if ($params === []) { + // no params means no property promotion, skip if class has no property defined + return $properties === []; + } + + return $this->shouldSkipParams($params); + } + + private function shouldSkipConsumeTraitProperty(Class_ $class): bool + { + $traitUses = $class->getTraitUses(); + foreach ($traitUses as $traitUse) { + foreach ($traitUse->traits as $trait) { + $traitName = $trait->toString(); + + // trait not autoloaded + if (! $this->reflectionProvider->hasClass($traitName)) { + return true; + } + + $traitClassReflection = $this->reflectionProvider->getClass($traitName); + $nativeReflection = $traitClassReflection->getNativeReflection(); + + if ($this->hasFinalProperty($nativeReflection->getProperties())) { + return true; + } + } + } + + return false; + } + + /** + * @param ReflectionProperty[] $properties + */ + private function hasFinalProperty(array $properties): bool + { + foreach ($properties as $property) { + if (! $property->isFinal()) { + return true; + } + } + + return false; + } + + private function shouldSkipClass(Class_ $class): bool + { + // need to have test fixture once feature added to nikic/PHP-Parser + if ($this->visibilityManipulator->hasVisibility($class, Visibility::FINAL)) { + return true; + } + + if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, AttributeName::ALLOW_DYNAMIC_PROPERTIES)) { + return true; + } + + return $class->extends instanceof FullyQualified && ! $this->reflectionProvider->hasClass( + $class->extends->toString() + ); + } + + /** + * @param Param[] $params + */ + private function shouldSkipParams(array $params): bool + { + foreach ($params as $param) { + // has non-readonly property promotion + if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { + return true; + } + + // type is missing, invalid syntax + if ($param->type === null) { + return true; + } + } + return false; + } +} diff --git a/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php b/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php deleted file mode 100644 index 1e4bbea5264..00000000000 --- a/rules/Php85/Rector/ClassMethod/FinalPropertyPromotionRector.php +++ /dev/null @@ -1,117 +0,0 @@ -isName($node, MethodName::CONSTRUCT)) { - return null; - } - - $hasChanged = false; - foreach ($node->params as $param) { - if (! $param->isPromoted()) { - continue; - } - - if (! $this->visibilityManipulator->hasVisibility($param, Visibility::PUBLIC)) { - continue; - } - - $this->removePhpDocTag($param); - $this->visibilityManipulator->makeFinal($param); - - $hasChanged = true; - - } - - if($hasChanged){ - return $node; - } - - return null; - } - - private function removePhpDocTag(Property|Param $node): bool - { - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); - - if (!$phpDocInfo->hasByName(self::TAGNAME)) { - return false; - } - - $phpDocInfo->removeByName(self::TAGNAME); - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); - return true; - } - - public function provideMinPhpVersion(): int - { - return PhpVersionFeature::DEPRECATED_ATTRIBUTE_ON_CONSTANT; - } -} diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php new file mode 100644 index 00000000000..aa7eaf31cdb --- /dev/null +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -0,0 +1,66 @@ +finalPromotionManipulator->process($node); + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::FINAL_PROPERTY_PROMOTION; + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index 9747fdb8345..29669baedc5 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -787,6 +787,12 @@ final class PhpVersionFeature */ public const DEPRECATED_NULL_DEBUG_INFO_RETURN = PhpVersion::PHP_85; + /** + * @see https://wiki.php.net/rfc/attributes-on-constants + * @var int + */ + public const FINAL_PROPERTY_PROMOTION = PhpVersion::PHP_85; + /** * @see https://wiki.php.net/rfc/attributes-on-constants * @var int From 214ba0210aea649cec581008379405149689df6b Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 19:39:59 +0530 Subject: [PATCH 04/24] FinalPropertyPromotionRector --- rules/Php85/NodeManipulator/FinalPromotionManipulator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php index 1c1c6d4b5df..020f1f3c790 100644 --- a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php +++ b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php @@ -201,7 +201,7 @@ private function shouldSkipClass(Class_ $class): bool private function shouldSkipParams(array $params): bool { foreach ($params as $param) { - // has non-readonly property promotion + // has non-final property promotion if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { return true; } From f521838f2662d3401ca06b4e93610f62fa0e3c4f Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 19:46:01 +0530 Subject: [PATCH 05/24] FinalPropertyPromotionRector --- .../Fixture/readonly.php.inc | 30 +++++++++++++++++++ .../Fixture/skip_anonymous_class.php.inc | 12 ++++++++ 2 files changed, 42 insertions(+) create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/readonly.php.inc create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/readonly.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/readonly.php.inc new file mode 100644 index 00000000000..820b1427098 --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/readonly.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc new file mode 100644 index 00000000000..164e6e7fcd3 --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc @@ -0,0 +1,12 @@ + \ No newline at end of file From 942b6a8c28a5c713594106f5d02ff88719fda463 Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 19:57:21 +0530 Subject: [PATCH 06/24] FinalPropertyPromotionRector --- .../FinalPromotionManipulator.php | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php index 020f1f3c790..99ff9c6c6fd 100644 --- a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php +++ b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php @@ -35,7 +35,6 @@ public function __construct( private VisibilityManipulator $visibilityManipulator, private PhpAttributeAnalyzer $phpAttributeAnalyzer, private ReflectionProvider $reflectionProvider, - private AttributeGroupNewLiner $attributeGroupNewLiner, private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, ) { @@ -61,25 +60,14 @@ public function process(Class_ $class): Class_|null continue; } $this->visibilityManipulator->makeFinal($param); - $this->removePhpDocTag($param); + + $phpDocInfo->removeByName(self::TAGNAME); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($param); } return $class; } - private function removePhpDocTag(Property|Param $node): bool - { - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); - - if (!$phpDocInfo->hasByName(self::TAGNAME)) { - return false; - } - - $phpDocInfo->removeByName(self::TAGNAME); - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); - return true; - } - /** * @return ClassReflection[] */ From 0fd25a315425ea9109d5731de8461f87ad406461 Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 20:01:14 +0530 Subject: [PATCH 07/24] FinalPropertyPromotionRector --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 1e95e1c3f7d..8f7465de0c5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -220,7 +220,7 @@ parameters: - '#Register "Rector\\Php80\\Rector\\NotIdentical\\MbStrContainsRector" service to "php80\.php" config set#' - - '#Register "Rector\\Php85\\Rector\\ClassMethod\\FinalPropertyPromotionRector" service to "php85\.php" config set#' + - '#Register "Rector\\Php85\\Rector\\Class\\FinalPropertyPromotionRector" service to "php85\.php" config set#' # closure detailed - '#Method Rector\\Config\\RectorConfig\:\:singleton\(\) has parameter \$concrete with no signature specified for Closure#' From e03200e33e0fee3550ab178fd820513522e1c7bf Mon Sep 17 00:00:00 2001 From: Arshid Date: Mon, 8 Sep 2025 20:07:18 +0530 Subject: [PATCH 08/24] FinalPropertyPromotionRector --- phpstan.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 8f7465de0c5..bcb230046b6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -219,8 +219,8 @@ parameters: - '#Register "Rector\\Php81\\Rector\\ClassMethod\\NewInInitializerRector" service to "php81\.php" config set#' - '#Register "Rector\\Php80\\Rector\\NotIdentical\\MbStrContainsRector" service to "php80\.php" config set#' - - - '#Register "Rector\\Php85\\Rector\\Class\\FinalPropertyPromotionRector" service to "php85\.php" config set#' + + - '#Register "Rector\\Php85\\Rector\\Class_\\FinalPropertyPromotionRector" service to "php85\.php" config set#' # closure detailed - '#Method Rector\\Config\\RectorConfig\:\:singleton\(\) has parameter \$concrete with no signature specified for Closure#' From d562db68892ffc09eeda169aa3d8e2ee53c71fca Mon Sep 17 00:00:00 2001 From: Arshid Date: Tue, 9 Sep 2025 11:48:51 +0530 Subject: [PATCH 09/24] FinalPropertyPromotionRector --- .../Fixture/child_from_parent_class.php.inc | 28 +++++++++++++++++++ .../Source/ParentClass.php | 7 +++++ .../FinalPromotionManipulator.php | 7 ++--- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/child_from_parent_class.php.inc create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/ParentClass.php diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/child_from_parent_class.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/child_from_parent_class.php.inc new file mode 100644 index 00000000000..a6c1a295986 --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/child_from_parent_class.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/ParentClass.php b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/ParentClass.php new file mode 100644 index 00000000000..d7d9c4c339e --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/ParentClass.php @@ -0,0 +1,7 @@ +visibilityManipulator->makeFinal($param); - - $phpDocInfo->removeByName(self::TAGNAME); + $phpDocInfo->removeByName(self::TAGNAME); echo $param->var->name; $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($param); } @@ -103,7 +102,7 @@ private function shouldSkip(Class_ $class, Scope $scope): bool } foreach ($parents as $parent) { - if (! $parent->isFinal()) { + if ($parent->isFinal()) { return true; } } @@ -177,7 +176,7 @@ private function shouldSkipClass(Class_ $class): bool if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, AttributeName::ALLOW_DYNAMIC_PROPERTIES)) { return true; } - + return $class->extends instanceof FullyQualified && ! $this->reflectionProvider->hasClass( $class->extends->toString() ); From 71931fc944e86612e0ce0d555eb8e57320067523 Mon Sep 17 00:00:00 2001 From: Arshid Date: Tue, 9 Sep 2025 11:52:19 +0530 Subject: [PATCH 10/24] FinalPropertyPromotionRector --- rules/Php85/NodeManipulator/FinalPromotionManipulator.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php index caa3d1ec49f..63448fbf56c 100644 --- a/rules/Php85/NodeManipulator/FinalPromotionManipulator.php +++ b/rules/Php85/NodeManipulator/FinalPromotionManipulator.php @@ -4,7 +4,6 @@ namespace Rector\Php85\NodeManipulator; -use PhpParser\Builder\Property; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; @@ -17,10 +16,8 @@ use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\Php81\Enum\AttributeName; -use Rector\Php81\NodeManipulator\AttributeGroupNewLiner; use Rector\PHPStan\ScopeFetcher; use Rector\Privatization\NodeManipulator\VisibilityManipulator; -use Rector\ValueObject\Application\File; use Rector\ValueObject\MethodName; use Rector\ValueObject\Visibility; @@ -60,7 +57,7 @@ public function process(Class_ $class): Class_|null continue; } $this->visibilityManipulator->makeFinal($param); - $phpDocInfo->removeByName(self::TAGNAME); echo $param->var->name; + $phpDocInfo->removeByName(self::TAGNAME); $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($param); } From 0dff2805699ea94440b9eb537595be1de3701dce Mon Sep 17 00:00:00 2001 From: Arshid Date: Tue, 9 Sep 2025 14:51:18 +0530 Subject: [PATCH 11/24] FinalPropertyPromotionRector --- .../Fixture/skip_anonymous_class.php.inc | 12 ++ .../skip_child_from_final_parent.php.inc | 14 -- .../Source/FinalParent.php | 7 - .../FinalPromotionManipulator.php | 200 ------------------ .../Class_/FinalPropertyPromotionRector.php | 118 ++++++++++- 5 files changed, 127 insertions(+), 224 deletions(-) delete mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc delete mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php delete mode 100644 rules/Php85/NodeManipulator/FinalPromotionManipulator.php diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc index 164e6e7fcd3..90e97ea0abe 100644 --- a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc @@ -9,4 +9,16 @@ $obj = new class { public string $id ){} }; +?> +----- + \ No newline at end of file diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc deleted file mode 100644 index 7414635182f..00000000000 --- a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_child_from_final_parent.php.inc +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php deleted file mode 100644 index 037ba52801e..00000000000 --- a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Source/FinalParent.php +++ /dev/null @@ -1,7 +0,0 @@ -shouldSkip($class, $scope)) { - return null; - } - - $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); - - if (! $constructClassMethod instanceof ClassMethod) { - return null; - } - - foreach ($constructClassMethod->getParams() as $param) { - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($param); - - if (!$phpDocInfo->hasByName(self::TAGNAME)) { - continue; - } - $this->visibilityManipulator->makeFinal($param); - $phpDocInfo->removeByName(self::TAGNAME); - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($param); - } - - return $class; - } - - /** - * @return ClassReflection[] - */ - private function resolveParentClassReflections(Scope $scope): array - { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return []; - } - - return $classReflection->getParents(); - } - - private function shouldSkip(Class_ $class, Scope $scope): bool - { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return true; - } - - if ($this->shouldSkipClass($class)) { - return true; - } - - $parents = $this->resolveParentClassReflections($scope); - - if ($class->isAnonymous()) { - return true; - } - - if ($class->isFinal()) { - return true; - } - - foreach ($parents as $parent) { - if ($parent->isFinal()) { - return true; - } - } - - $properties = $class->getProperties(); - - if ($this->shouldSkipConsumeTraitProperty($class)) { - return true; - } - - $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); - if (! $constructClassMethod instanceof ClassMethod) { - // no __construct means no property promotion, skip if class has no property defined - return $properties === []; - } - - $params = $constructClassMethod->getParams(); - if ($params === []) { - // no params means no property promotion, skip if class has no property defined - return $properties === []; - } - - return $this->shouldSkipParams($params); - } - - private function shouldSkipConsumeTraitProperty(Class_ $class): bool - { - $traitUses = $class->getTraitUses(); - foreach ($traitUses as $traitUse) { - foreach ($traitUse->traits as $trait) { - $traitName = $trait->toString(); - - // trait not autoloaded - if (! $this->reflectionProvider->hasClass($traitName)) { - return true; - } - - $traitClassReflection = $this->reflectionProvider->getClass($traitName); - $nativeReflection = $traitClassReflection->getNativeReflection(); - - if ($this->hasFinalProperty($nativeReflection->getProperties())) { - return true; - } - } - } - - return false; - } - - /** - * @param ReflectionProperty[] $properties - */ - private function hasFinalProperty(array $properties): bool - { - foreach ($properties as $property) { - if (! $property->isFinal()) { - return true; - } - } - - return false; - } - - private function shouldSkipClass(Class_ $class): bool - { - // need to have test fixture once feature added to nikic/PHP-Parser - if ($this->visibilityManipulator->hasVisibility($class, Visibility::FINAL)) { - return true; - } - - if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, AttributeName::ALLOW_DYNAMIC_PROPERTIES)) { - return true; - } - - return $class->extends instanceof FullyQualified && ! $this->reflectionProvider->hasClass( - $class->extends->toString() - ); - } - - /** - * @param Param[] $params - */ - private function shouldSkipParams(array $params): bool - { - foreach ($params as $param) { - // has non-final property promotion - if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { - return true; - } - - // type is missing, invalid syntax - if ($param->type === null) { - return true; - } - } - return false; - } -} diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index aa7eaf31cdb..cdbfc2b4e99 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -7,9 +7,20 @@ use PhpParser\Node; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; -use Rector\Php85\NodeManipulator\FinalPromotionManipulator; +use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProvider; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\Comments\NodeDocBlock\DocBlockUpdater; +use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; +use Rector\Php81\Enum\AttributeName; +use Rector\PHPStan\ScopeFetcher; +use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractRector; +use Rector\ValueObject\MethodName; use Rector\ValueObject\PhpVersionFeature; +use Rector\ValueObject\Visibility; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -20,8 +31,17 @@ */ final class FinalPropertyPromotionRector extends AbstractRector implements MinPhpVersionInterface { + /** + * @var string + */ + private const TAGNAME = 'final'; + public function __construct( - private readonly FinalPromotionManipulator $finalPromotionManipulator, + private VisibilityManipulator $visibilityManipulator, + private PhpAttributeAnalyzer $phpAttributeAnalyzer, + private ReflectionProvider $reflectionProvider, + private readonly DocBlockUpdater $docBlockUpdater, + private readonly PhpDocInfoFactory $phpDocInfoFactory, ) { } @@ -55,12 +75,104 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { + $scope = ScopeFetcher::fetch($node); + if ($this->shouldSkip($node, $scope)) { + return null; + } + + $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); - return $this->finalPromotionManipulator->process($node); + if (! $constructClassMethod instanceof ClassMethod) { + return null; + } + + foreach ($constructClassMethod->getParams() as $param) { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($param); + + if (! $phpDocInfo->hasByName(self::TAGNAME)) { + continue; + } + $this->visibilityManipulator->makeFinal($param); + $phpDocInfo->removeByName(self::TAGNAME); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($param); + } + + return $node; } public function provideMinPhpVersion(): int { return PhpVersionFeature::FINAL_PROPERTY_PROMOTION; } + + /** + * @return ClassReflection[] + */ + private function resolveParentClassReflections(Scope $scope): array + { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return []; + } + + return $classReflection->getParents(); + } + + private function shouldSkip(Class_ $class, Scope $scope): bool + { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return true; + } + + if ($this->shouldSkipClass($class)) { + return true; + } + + $parents = $this->resolveParentClassReflections($scope); + + if ($class->isFinal()) { + return true; + } + + $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); + if (! $constructClassMethod instanceof ClassMethod) { + return false; + } + $params = $constructClassMethod->getParams(); + + return $this->shouldSkipParams($params); + } + + private function shouldSkipClass(Class_ $class): bool + { + if ($this->visibilityManipulator->hasVisibility($class, Visibility::FINAL)) { + return true; + } + + if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, AttributeName::ALLOW_DYNAMIC_PROPERTIES)) { + return true; + } + + return false; + } + + /** + * @param Param[] $params + */ + private function shouldSkipParams(array $params): bool + { + foreach ($params as $param) { + // has non-final property promotion + if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { + return true; + } + + // type is missing, invalid syntax + if ($param->type === null) { + return true; + } + } + return false; + } } From bdc8376bda4854a86c2ec3cd43f695e972520b9a Mon Sep 17 00:00:00 2001 From: Arshid Date: Tue, 9 Sep 2025 14:58:34 +0530 Subject: [PATCH 12/24] FinalPropertyPromotionRector --- rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index cdbfc2b4e99..775929b3428 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -10,7 +10,6 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ReflectionProvider; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; @@ -39,7 +38,6 @@ final class FinalPropertyPromotionRector extends AbstractRector implements MinPh public function __construct( private VisibilityManipulator $visibilityManipulator, private PhpAttributeAnalyzer $phpAttributeAnalyzer, - private ReflectionProvider $reflectionProvider, private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, ) { @@ -163,7 +161,6 @@ private function shouldSkipClass(Class_ $class): bool private function shouldSkipParams(array $params): bool { foreach ($params as $param) { - // has non-final property promotion if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { return true; } From be186eb9c59f50af78faa9ce857d9adfd536397a Mon Sep 17 00:00:00 2001 From: Arshid Date: Wed, 10 Sep 2025 15:30:53 +0530 Subject: [PATCH 13/24] FinalPropertyPromotionRector --- .../Fixture/non_typed_args.php.inc | 30 +++++++++++ .../Class_/FinalPropertyPromotionRector.php | 51 +++---------------- 2 files changed, 36 insertions(+), 45 deletions(-) create mode 100644 rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc new file mode 100644 index 00000000000..c13c732b82b --- /dev/null +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index 775929b3428..e78cf783617 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -8,13 +8,9 @@ use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; -use PHPStan\Analyser\Scope; -use PHPStan\Reflection\ClassReflection; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; -use Rector\Php81\Enum\AttributeName; -use Rector\PHPStan\ScopeFetcher; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractRector; use Rector\ValueObject\MethodName; @@ -73,8 +69,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $scope = ScopeFetcher::fetch($node); - if ($this->shouldSkip($node, $scope)) { + if ($this->shouldSkip($node)) { return null; } @@ -103,52 +98,23 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::FINAL_PROPERTY_PROMOTION; } - /** - * @return ClassReflection[] - */ - private function resolveParentClassReflections(Scope $scope): array - { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return []; - } - - return $classReflection->getParents(); - } - - private function shouldSkip(Class_ $class, Scope $scope): bool + private function shouldSkip(Class_ $class): bool { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return true; - } - - if ($this->shouldSkipClass($class)) { - return true; - } - - $parents = $this->resolveParentClassReflections($scope); - - if ($class->isFinal()) { - return true; - } - $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); if (! $constructClassMethod instanceof ClassMethod) { return false; } $params = $constructClassMethod->getParams(); - return $this->shouldSkipParams($params); - } + if ($this->shouldSkipParams($params)) { + return true; + } - private function shouldSkipClass(Class_ $class): bool - { if ($this->visibilityManipulator->hasVisibility($class, Visibility::FINAL)) { return true; } - if ($this->phpAttributeAnalyzer->hasPhpAttribute($class, AttributeName::ALLOW_DYNAMIC_PROPERTIES)) { + if ($class->isFinal()) { return true; } @@ -164,11 +130,6 @@ private function shouldSkipParams(array $params): bool if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { return true; } - - // type is missing, invalid syntax - if ($param->type === null) { - return true; - } } return false; } From aa1cd9e1edd1b5040990d1f8cc9c942738d449d8 Mon Sep 17 00:00:00 2001 From: Arshid Date: Wed, 10 Sep 2025 15:43:31 +0530 Subject: [PATCH 14/24] FinalPropertyPromotionRector --- rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index e78cf783617..4bf29e04257 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -10,7 +10,6 @@ use PhpParser\Node\Stmt\ClassMethod; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractRector; use Rector\ValueObject\MethodName; @@ -33,7 +32,6 @@ final class FinalPropertyPromotionRector extends AbstractRector implements MinPh public function __construct( private VisibilityManipulator $visibilityManipulator, - private PhpAttributeAnalyzer $phpAttributeAnalyzer, private readonly DocBlockUpdater $docBlockUpdater, private readonly PhpDocInfoFactory $phpDocInfoFactory, ) { From 78fb697bfcf9b09ec767731de305d939bb116b06 Mon Sep 17 00:00:00 2001 From: Arshid Date: Fri, 12 Sep 2025 19:29:34 +0530 Subject: [PATCH 15/24] FinalPropertyPromotionRector Check The Promotion --- .../Fixture/non_typed_args.php.inc | 15 --------------- .../Class_/FinalPropertyPromotionRector.php | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc index c13c732b82b..55cadaee7f4 100644 --- a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/non_typed_args.php.inc @@ -13,18 +13,3 @@ class FinalPropertyPromotion } ?> ------ - diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index 4bf29e04257..14afd155d6d 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -77,18 +77,31 @@ public function refactor(Node $node): ?Node return null; } + $hasChanged = false; foreach ($constructClassMethod->getParams() as $param) { + if (! $param->isPromoted()) { + continue; + } + + if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL)) { + continue; + } + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($param); if (! $phpDocInfo->hasByName(self::TAGNAME)) { continue; } + $hasChanged = true; $this->visibilityManipulator->makeFinal($param); $phpDocInfo->removeByName(self::TAGNAME); $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($param); } - - return $node; + + if($hasChanged){ + return $node; + } + return null; } public function provideMinPhpVersion(): int From b053cf76452e60c5a689326b77a3b32a90a29c67 Mon Sep 17 00:00:00 2001 From: Arshid Date: Fri, 12 Sep 2025 23:05:57 +0530 Subject: [PATCH 16/24] FinalPropertyPromotionRector Check The Promotion --- .../Class_/FinalPropertyPromotionRector.php | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index 14afd155d6d..84a0511bb6a 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -67,18 +67,25 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { + $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); + + if (! $constructClassMethod instanceof ClassMethod) { + return null; + } + if ($this->shouldSkip($node)) { return null; } - $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); + $hasChanged = false; - if (! $constructClassMethod instanceof ClassMethod) { + $params = $constructClassMethod->getParams(); + + if ($this->shouldSkipParams($params)) { return null; } - $hasChanged = false; - foreach ($constructClassMethod->getParams() as $param) { + foreach ($params as $param) { if (! $param->isPromoted()) { continue; } @@ -111,16 +118,6 @@ public function provideMinPhpVersion(): int private function shouldSkip(Class_ $class): bool { - $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); - if (! $constructClassMethod instanceof ClassMethod) { - return false; - } - $params = $constructClassMethod->getParams(); - - if ($this->shouldSkipParams($params)) { - return true; - } - if ($this->visibilityManipulator->hasVisibility($class, Visibility::FINAL)) { return true; } From 964ea930eee6e62660cc3bb558c64f4d74fdabfd Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 00:04:54 +0530 Subject: [PATCH 17/24] FinalPropertyPromotionRector.php --- .../Class_/FinalPropertyPromotionRector.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index 84a0511bb6a..f5ee610ca2d 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -78,13 +78,8 @@ public function refactor(Node $node): ?Node } $hasChanged = false; - $params = $constructClassMethod->getParams(); - if ($this->shouldSkipParams($params)) { - return null; - } - foreach ($params as $param) { if (! $param->isPromoted()) { continue; @@ -128,17 +123,4 @@ private function shouldSkip(Class_ $class): bool return false; } - - /** - * @param Param[] $params - */ - private function shouldSkipParams(array $params): bool - { - foreach ($params as $param) { - if ($this->visibilityManipulator->hasVisibility($param, Visibility::FINAL) && $param->isPromoted()) { - return true; - } - } - return false; - } } From 26d8d58861dc04bf29edb395d775c5db0a826c74 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 00:26:17 +0530 Subject: [PATCH 18/24] Update FinalPropertyPromotionRector.php --- .../Class_/FinalPropertyPromotionRector.php | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index f5ee610ca2d..f0b546a2b0b 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -73,7 +73,7 @@ public function refactor(Node $node): ?Node return null; } - if ($this->shouldSkip($node)) { + if ($class->isFinal()) { return null; } @@ -110,17 +110,4 @@ public function provideMinPhpVersion(): int { return PhpVersionFeature::FINAL_PROPERTY_PROMOTION; } - - private function shouldSkip(Class_ $class): bool - { - if ($this->visibilityManipulator->hasVisibility($class, Visibility::FINAL)) { - return true; - } - - if ($class->isFinal()) { - return true; - } - - return false; - } } From 1834fbfe9af87adf3cc0c5d0674af1a2198c3664 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 00:29:42 +0530 Subject: [PATCH 19/24] Update FinalPropertyPromotionRector.php --- rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index f0b546a2b0b..d396944773e 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -73,7 +73,7 @@ public function refactor(Node $node): ?Node return null; } - if ($class->isFinal()) { + if ($node->isFinal()) { return null; } From a68383bf5c1eabc4fbca3c1c11af60cd4c5c60c3 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 00:31:23 +0530 Subject: [PATCH 20/24] Update FinalPropertyPromotionRector.php --- .../Php85/Rector/Class_/FinalPropertyPromotionRector.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index d396944773e..5ffb5421c12 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -67,13 +67,13 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); - - if (! $constructClassMethod instanceof ClassMethod) { + if ($node->isFinal()) { return null; } + + $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); - if ($node->isFinal()) { + if (! $constructClassMethod instanceof ClassMethod) { return null; } From 1d7f092ed551dffcdc6521a29ff8ba97fecf7882 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 05:25:12 +0530 Subject: [PATCH 21/24] Update rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php Co-authored-by: Abdul Malik Ikhsan --- rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index 5ffb5421c12..2158b5c4bc5 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -39,7 +39,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Promotes constructor properties in final classes', [ + return new RuleDefinition('Add native final promoted properties in non-final class to avoid child to override the promoted properties based on `@final` tag', [ new CodeSample( <<<'CODE_SAMPLE' public function __construct( From 8d837c899a1d49fb082a425b3d255236cbc78123 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 09:07:01 +0530 Subject: [PATCH 22/24] Update rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php Co-authored-by: Abdul Malik Ikhsan --- rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index 2158b5c4bc5..c61753d69af 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -57,6 +57,9 @@ public function __construct( ]); } + /** + * @return array> + */ public function getNodeTypes(): array { return [Class_::class]; From 7407a58d3ef75e6131d54a808def1cdb48e71ef2 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 09:19:43 +0530 Subject: [PATCH 23/24] FinalPropertyPromotionRector --- rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php index c61753d69af..16aa2bb0aa6 100644 --- a/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php +++ b/rules/Php85/Rector/Class_/FinalPropertyPromotionRector.php @@ -73,6 +73,10 @@ public function refactor(Node $node): ?Node if ($node->isFinal()) { return null; } + + if ($node->isAnonymous()) { + return null; + } $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); From 57ba7f3a630df539dc6161a7092e658108d2a96e Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 13 Sep 2025 09:21:39 +0530 Subject: [PATCH 24/24] FinalPropertyPromotionRector --- .../Fixture/skip_anonymous_class.php.inc | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc index 90e97ea0abe..164e6e7fcd3 100644 --- a/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc +++ b/rules-tests/Php85/Rector/Class_/FinalPropertyPromotionRector/Fixture/skip_anonymous_class.php.inc @@ -9,16 +9,4 @@ $obj = new class { public string $id ){} }; -?> ------ - \ No newline at end of file