From 5697bcf4f25eec5e0ff9ad8555e643ea3a3ef46f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 25 Jun 2026 07:26:33 +0700 Subject: [PATCH 1/4] [TypeDeclarationDocblocks] Skip child with parent property docblock on DocblockVarArrayFromPropertyDefaultsRector --- .../skip_child_with_parent_property_docblock.php.inc | 12 ++++++++++++ .../Source/BaseWithPropertyDocblock.php | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Source/BaseWithPropertyDocblock.php diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc new file mode 100644 index 00000000000..c40a194e09b --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc @@ -0,0 +1,12 @@ + diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Source/BaseWithPropertyDocblock.php b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Source/BaseWithPropertyDocblock.php new file mode 100644 index 00000000000..057baf89176 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Source/BaseWithPropertyDocblock.php @@ -0,0 +1,11 @@ + + */ + public array $myArray = ['a', 'b', 'c']; +} From 5ce5253eda57c56324b002847df2eb2cec057961 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 25 Jun 2026 07:27:07 +0700 Subject: [PATCH 2/4] [TypeDeclarationDocblocks] Skip child with parent property docblock on DocblockVarArrayFromPropertyDefaultsRector --- ..._docblock.php.inc => skip_already_defined_in_parent.php.inc} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/{skip_child_with_parent_property_docblock.php.inc => skip_already_defined_in_parent.php.inc} (79%) diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_already_defined_in_parent.php.inc similarity index 79% rename from rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc rename to rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_already_defined_in_parent.php.inc index c40a194e09b..3ddfc8599ee 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_child_with_parent_property_docblock.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_already_defined_in_parent.php.inc @@ -4,7 +4,7 @@ namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFr use Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Source\BaseWithPropertyDocblock; -final class SkipChildWithParentPropertyDocblock extends BaseWithPropertyDocblock +final class SkipAlreadyDefinedInParent extends BaseWithPropertyDocblock { public array $myArray = ['d', 'e', 'f']; } From 828a8af04dfe1853155199fa36dbe7a9881089af Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 25 Jun 2026 07:27:57 +0700 Subject: [PATCH 3/4] fix --- ...lockVarArrayFromPropertyDefaultsRector.php | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php index c1c617d8825..65004589add 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php @@ -4,12 +4,20 @@ namespace Rector\TypeDeclarationDocblocks\Rector\Class_; +use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Identifier; +use PhpParser\Node\PropertyItem; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Property; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\Php\PhpPropertyReflection; +use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Rector\AbstractRector; +use Rector\Reflection\ReflectionResolver; use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator; use Rector\TypeDeclarationDocblocks\TagNodeAnalyzer\UsefulArrayTagNodeAnalyzer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -23,7 +31,8 @@ final class DocblockVarArrayFromPropertyDefaultsRector extends AbstractRector public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator, - private readonly UsefulArrayTagNodeAnalyzer $usefulArrayTagNodeAnalyzer + private readonly UsefulArrayTagNodeAnalyzer $usefulArrayTagNodeAnalyzer, + private readonly ReflectionResolver $reflectionResolver ) { } @@ -90,6 +99,10 @@ public function refactor(Node $node): ?Node continue; } + if ($this->hasUsefulParentPropertyVarTag($node, $property, $propertyDefaultType)) { + continue; + } + if ($this->nodeDocblockTypeDecorator->decorateGenericIterableVarType( $propertyDefaultType, $propertyPhpDocInfo, @@ -105,4 +118,57 @@ public function refactor(Node $node): ?Node return $node; } + + private function hasUsefulParentPropertyVarTag(Class_ $class, Property $property, Type $propertyDefaultType): bool + { + $propertyName = $this->getName($property); + if ($propertyName === null) { + return false; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($class); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + foreach ($classReflection->getParents() as $parentClassReflection) { + if (! $parentClassReflection->hasNativeProperty($propertyName)) { + continue; + } + + $parentPropertyReflection = $parentClassReflection->getNativeProperty($propertyName); + if ($parentPropertyReflection->isPrivate()) { + continue; + } + + if (! $parentPropertyReflection->hasPhpDocType()) { + continue; + } + + $varTagValueNode = $this->resolveVarTagValueNode($parentPropertyReflection); + if (! $this->usefulArrayTagNodeAnalyzer->isUsefulArrayTag($varTagValueNode)) { + continue; + } + + if ($parentPropertyReflection->getPhpDocType()->isSuperTypeOf($propertyDefaultType)->yes()) { + return true; + } + } + + return false; + } + + private function resolveVarTagValueNode(PhpPropertyReflection $phpPropertyReflection): ?VarTagValueNode + { + $docComment = $phpPropertyReflection->getDocComment(); + if ($docComment === null) { + return null; + } + + $property = new Property(0, [new PropertyItem($phpPropertyReflection->getName())]); + $property->setDocComment(new Doc($docComment)); + + return $this->phpDocInfoFactory->createFromNodeOrEmpty($property) + ->getVarTagValueNode(); + } } From 90e92175d2ff3ce315082c360fb03322ac99e973 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 25 Jun 2026 18:53:34 +0700 Subject: [PATCH 4/4] use of scopefetcher --- ...ocblockVarArrayFromPropertyDefaultsRector.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php index 65004589add..88abd66c32a 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php @@ -16,8 +16,8 @@ use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; -use Rector\Reflection\ReflectionResolver; use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator; use Rector\TypeDeclarationDocblocks\TagNodeAnalyzer\UsefulArrayTagNodeAnalyzer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -31,8 +31,7 @@ final class DocblockVarArrayFromPropertyDefaultsRector extends AbstractRector public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator, - private readonly UsefulArrayTagNodeAnalyzer $usefulArrayTagNodeAnalyzer, - private readonly ReflectionResolver $reflectionResolver + private readonly UsefulArrayTagNodeAnalyzer $usefulArrayTagNodeAnalyzer ) { } @@ -126,7 +125,14 @@ private function hasUsefulParentPropertyVarTag(Class_ $class, Property $property return false; } - $classReflection = $this->reflectionResolver->resolveClassReflection($class); + // private property doesn't override parent property + if ($property->isPrivate()) { + return false; + } + + $scope = ScopeFetcher::fetch($class); + + $classReflection = $scope->getClassReflection(); if (! $classReflection instanceof ClassReflection) { return false; } @@ -138,7 +144,7 @@ private function hasUsefulParentPropertyVarTag(Class_ $class, Property $property $parentPropertyReflection = $parentClassReflection->getNativeProperty($propertyName); if ($parentPropertyReflection->isPrivate()) { - continue; + return false; } if (! $parentPropertyReflection->hasPhpDocType()) {