diff --git a/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/Fixture/array_null_collection.php.inc b/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/Fixture/array_null_collection.php.inc index 2bb0cf8c..31a85e79 100644 --- a/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/Fixture/array_null_collection.php.inc +++ b/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/Fixture/array_null_collection.php.inc @@ -1,6 +1,6 @@ +----- + diff --git a/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/NarrowPropertyUnionToCollectionRectorTest.php b/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/NarrowPropertyUnionToCollectionRectorTest.php index e457b250..c8eb225e 100644 --- a/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/NarrowPropertyUnionToCollectionRectorTest.php +++ b/rules-tests/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector/NarrowPropertyUnionToCollectionRectorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Doctrine\Tests\TypedCollections\Rector\NarrowPropertyUnionToCollectionRector; +namespace Rector\Doctrine\Tests\TypedCollections\Rector\Property\NarrowPropertyUnionToCollectionRector; use Iterator; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php b/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php index 66444471..7ae3fe55 100644 --- a/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php +++ b/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php @@ -6,20 +6,24 @@ use Doctrine\Common\Collections\Collection; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; +use PhpParser\Node\UnionType; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; -use PHPStan\Reflection\ClassReflection; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; +use Rector\Doctrine\Enum\DoctrineClass; use Rector\Doctrine\TypedCollections\DocBlockProcessor\UnionCollectionTagValueNodeNarrower; -use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see \Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\NarrowParamUnionToCollectionRectorTest + * @see \Rector\Doctrine\Tests\TypedCollections\Rector\Property\NarrowPropertyUnionToCollectionRector\NarrowPropertyUnionToCollectionRectorTest */ final class NarrowPropertyUnionToCollectionRector extends AbstractRector { @@ -33,7 +37,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Narrow union type to Collection type in property docblock', + 'Narrow union type to Collection type in property docblock and native type declaration', [ new CodeSample( <<<'CODE_SAMPLE' @@ -66,48 +70,99 @@ class SomeClass public function getNodeTypes(): array { - return [Property::class]; + return [Class_::class]; } /** - * @param Property $node + * @param Class_ $node */ - public function refactor(Node $node): ?Property + public function refactor(Node $node): ?Class_ { - if ($node->isAbstract()) { - return null; + $hasChanged = false; + foreach ($node->getProperties() as $property) { + if ($property->isAbstract()) { + continue; + } + + if ($this->refactorPropertyDocBlock($property)) { + $hasChanged = true; + } + + if ($this->refactorNativePropertyType($property)) { + $hasChanged = true; + } } - $scope = ScopeFetcher::fetch($node); - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return null; + if ($hasChanged) { + return $node; } - if ($classReflection->isInterface()) { - return null; + return null; + } + + private function hasNativeTypeCollection(Property $property): bool + { + if (! $property->type instanceof Name) { + return false; } - $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + return $this->isName($property->type, Collection::class); + } - $varTagValueNode = $propertyPhpDocInfo->getVarTagValueNode(); - if (! $varTagValueNode instanceof VarTagValueNode) { - return null; + private function isCollectionName(Node $node): bool + { + if (! $node instanceof Name) { + return false; + } + + return $this->isName($node, DoctrineClass::COLLECTION); + } + + private function refactorNativePropertyType(Property $property): bool + { + if (! $property->type instanceof UnionType) { + return false; } - $hasNativeCollectionType = false; - if ($node->type instanceof Name && $this->isName($node->type, Collection::class)) { - $hasNativeCollectionType = true; + foreach ($property->type->types as $uniontedType) { + if (! $this->isCollectionName($uniontedType)) { + continue; + } + + // narrow to pure collection + $property->type = new FullyQualified(DoctrineClass::COLLECTION); + + // remove default, as will be defined in constructor by another rule + if ($property->props[0]->default instanceof Expr) { + $property->props[0]->default = null; + } + + return true; + } + + return false; + } + + private function refactorPropertyDocBlock(Property $property): bool + { + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property); + if (! $propertyPhpDocInfo instanceof PhpDocInfo) { + return false; } - $hasChanged = $this->unionCollectionTagValueNodeNarrower->narrow($varTagValueNode, $hasNativeCollectionType); + $varTagValueNode = $propertyPhpDocInfo->getVarTagValueNode(); - if ($hasChanged === false) { - return null; + if (! $varTagValueNode instanceof VarTagValueNode) { + return false; } - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + $hasNativeCollectionType = $this->hasNativeTypeCollection($property); + + if ($this->unionCollectionTagValueNodeNarrower->narrow($varTagValueNode, $hasNativeCollectionType)) { + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); + return true; + } - return $node; + return false; } }