diff --git a/rules-tests/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector/Fixture/remove_from_property_docblock.php.inc b/rules-tests/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector/Fixture/remove_from_property_docblock.php.inc new file mode 100644 index 00000000..dcd84c5e --- /dev/null +++ b/rules-tests/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector/Fixture/remove_from_property_docblock.php.inc @@ -0,0 +1,43 @@ + + */ + private Collection $collection; + + public function __construct() + { + $this->collection = new ArrayCollection([]); + } +} + +?> +----- + + */ + private Collection $collection; + + public function __construct() + { + $this->collection = new ArrayCollection([]); + } +} + +?> diff --git a/rules/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector.php b/rules/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector.php index 3bb2bb97..42d94f16 100644 --- a/rules/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector.php +++ b/rules/TypedCollections/Rector/ClassMethod/RemoveNullFromNullableCollectionTypeRector.php @@ -5,8 +5,15 @@ namespace Rector\Doctrine\TypedCollections\Rector\ClassMethod; use PhpParser\Node; +use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Property; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\Doctrine\Enum\DoctrineClass; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; use Rector\Rector\AbstractRector; @@ -19,7 +26,9 @@ final class RemoveNullFromNullableCollectionTypeRector extends AbstractRector { public function __construct( - private readonly TestsNodeAnalyzer $testsNodeAnalyzer + private readonly TestsNodeAnalyzer $testsNodeAnalyzer, + private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly DocBlockUpdater $docBlockUpdater, ) { } @@ -64,25 +73,34 @@ public function setItems(Collection $items): void public function getNodeTypes(): array { - return [ClassMethod::class]; + return [ClassMethod::class, Property::class]; } /** - * @param ClassMethod $node + * @param ClassMethod|Property $node */ - public function refactor(Node $node): ClassMethod|null + public function refactor(Node $node): ClassMethod|Property|null { - if (count($node->params) !== 1) { + if ($node instanceof Property) { + return $this->refactorProperty($node); + } + + return $this->refactorClassMethod($node); + } + + private function refactorClassMethod(ClassMethod $classMethod): null|ClassMethod + { + if (count($classMethod->params) !== 1) { return null; } - if ($this->testsNodeAnalyzer->isInTestClass($node)) { + if ($this->testsNodeAnalyzer->isInTestClass($classMethod)) { return null; } $hasChanged = false; - foreach ($node->params as $param) { + foreach ($classMethod->params as $param) { if (! $param->type instanceof NullableType) { continue; } @@ -97,9 +115,50 @@ public function refactor(Node $node): ClassMethod|null } if ($hasChanged) { - return $node; + return $classMethod; } return null; } + + private function refactorProperty(Property $property): ?Property + { + if (! $this->hasNativeCollectionType($property)) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($property); + if (! $phpDocInfo instanceof PhpDocInfo) { + return null; + } + + $varTagValueNode = $phpDocInfo->getVarTagValueNode(); + if (! $varTagValueNode instanceof VarTagValueNode) { + return null; + } + + // remove nullable if has one + if (! $varTagValueNode->type instanceof NullableTypeNode) { + return null; + } + + // unwrap nullable type + $varTagValueNode->type = $varTagValueNode->type->type; + + $phpDocInfo->removeByType(VarTagValueNode::class); + $phpDocInfo->addTagValueNode($varTagValueNode); + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); + + return $property; + } + + private function hasNativeCollectionType(Property $property): bool + { + if (! $property->type instanceof Name) { + return false; + } + + return $this->isName($property->type, DoctrineClass::COLLECTION); + } }