55namespace Rector \Doctrine \TypedCollections \Rector \ClassMethod ;
66
77use PhpParser \Node ;
8+ use PhpParser \Node \Name ;
89use PhpParser \Node \NullableType ;
910use PhpParser \Node \Stmt \ClassMethod ;
11+ use PhpParser \Node \Stmt \Property ;
12+ use PHPStan \PhpDocParser \Ast \PhpDoc \VarTagValueNode ;
13+ use PHPStan \PhpDocParser \Ast \Type \NullableTypeNode ;
14+ use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfo ;
15+ use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfoFactory ;
16+ use Rector \Comments \NodeDocBlock \DocBlockUpdater ;
1017use Rector \Doctrine \Enum \DoctrineClass ;
1118use Rector \PHPUnit \NodeAnalyzer \TestsNodeAnalyzer ;
1219use Rector \Rector \AbstractRector ;
1926final class RemoveNullFromNullableCollectionTypeRector extends AbstractRector
2027{
2128 public function __construct (
22- private readonly TestsNodeAnalyzer $ testsNodeAnalyzer
29+ private readonly TestsNodeAnalyzer $ testsNodeAnalyzer ,
30+ private readonly PhpDocInfoFactory $ phpDocInfoFactory ,
31+ private readonly DocBlockUpdater $ docBlockUpdater ,
2332 ) {
2433 }
2534
@@ -64,25 +73,34 @@ public function setItems(Collection $items): void
6473
6574 public function getNodeTypes (): array
6675 {
67- return [ClassMethod::class];
76+ return [ClassMethod::class, Property::class ];
6877 }
6978
7079 /**
71- * @param ClassMethod $node
80+ * @param ClassMethod|Property $node
7281 */
73- public function refactor (Node $ node ): ClassMethod |null
82+ public function refactor (Node $ node ): ClassMethod |Property | null
7483 {
75- if (count ($ node ->params ) !== 1 ) {
84+ if ($ node instanceof Property) {
85+ return $ this ->refactorProperty ($ node );
86+ }
87+
88+ return $ this ->refactorClassMethod ($ node );
89+ }
90+
91+ private function refactorClassMethod (ClassMethod $ classMethod ): null |ClassMethod
92+ {
93+ if (count ($ classMethod ->params ) !== 1 ) {
7694 return null ;
7795 }
7896
79- if ($ this ->testsNodeAnalyzer ->isInTestClass ($ node )) {
97+ if ($ this ->testsNodeAnalyzer ->isInTestClass ($ classMethod )) {
8098 return null ;
8199 }
82100
83101 $ hasChanged = false ;
84102
85- foreach ($ node ->params as $ param ) {
103+ foreach ($ classMethod ->params as $ param ) {
86104 if (! $ param ->type instanceof NullableType) {
87105 continue ;
88106 }
@@ -97,9 +115,50 @@ public function refactor(Node $node): ClassMethod|null
97115 }
98116
99117 if ($ hasChanged ) {
100- return $ node ;
118+ return $ classMethod ;
101119 }
102120
103121 return null ;
104122 }
123+
124+ private function refactorProperty (Property $ property ): ?Property
125+ {
126+ if (! $ this ->hasNativeCollectionType ($ property )) {
127+ return null ;
128+ }
129+
130+ $ phpDocInfo = $ this ->phpDocInfoFactory ->createFromNode ($ property );
131+ if (! $ phpDocInfo instanceof PhpDocInfo) {
132+ return null ;
133+ }
134+
135+ $ varTagValueNode = $ phpDocInfo ->getVarTagValueNode ();
136+ if (! $ varTagValueNode instanceof VarTagValueNode) {
137+ return null ;
138+ }
139+
140+ // remove nullable if has one
141+ if (! $ varTagValueNode ->type instanceof NullableTypeNode) {
142+ return null ;
143+ }
144+
145+ // unwrap nullable type
146+ $ varTagValueNode ->type = $ varTagValueNode ->type ->type ;
147+
148+ $ phpDocInfo ->removeByType (VarTagValueNode::class);
149+ $ phpDocInfo ->addTagValueNode ($ varTagValueNode );
150+
151+ $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ property );
152+
153+ return $ property ;
154+ }
155+
156+ private function hasNativeCollectionType (Property $ property ): bool
157+ {
158+ if (! $ property ->type instanceof Name) {
159+ return false ;
160+ }
161+
162+ return $ this ->isName ($ property ->type , DoctrineClass::COLLECTION );
163+ }
105164}
0 commit comments