6
6
7
7
use Doctrine \Common \Collections \Collection ;
8
8
use PhpParser \Node ;
9
+ use PhpParser \Node \Expr ;
9
10
use PhpParser \Node \Name ;
11
+ use PhpParser \Node \Name \FullyQualified ;
12
+ use PhpParser \Node \Stmt \Class_ ;
10
13
use PhpParser \Node \Stmt \Property ;
14
+ use PhpParser \Node \UnionType ;
11
15
use PHPStan \PhpDocParser \Ast \PhpDoc \VarTagValueNode ;
12
- use PHPStan \ Reflection \ ClassReflection ;
16
+ use Rector \ BetterPhpDocParser \ PhpDocInfo \ PhpDocInfo ;
13
17
use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfoFactory ;
14
18
use Rector \Comments \NodeDocBlock \DocBlockUpdater ;
19
+ use Rector \Doctrine \Enum \DoctrineClass ;
15
20
use Rector \Doctrine \TypedCollections \DocBlockProcessor \UnionCollectionTagValueNodeNarrower ;
16
- use Rector \PHPStan \ScopeFetcher ;
17
21
use Rector \Rector \AbstractRector ;
18
22
use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
19
23
use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
20
24
21
25
/**
22
- * @see \Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\NarrowParamUnionToCollectionRectorTest
26
+ * @see \Rector\Doctrine\Tests\TypedCollections\Rector\Property\NarrowPropertyUnionToCollectionRector\NarrowPropertyUnionToCollectionRectorTest
23
27
*/
24
28
final class NarrowPropertyUnionToCollectionRector extends AbstractRector
25
29
{
@@ -33,7 +37,7 @@ public function __construct(
33
37
public function getRuleDefinition (): RuleDefinition
34
38
{
35
39
return new RuleDefinition (
36
- 'Narrow union type to Collection type in property docblock ' ,
40
+ 'Narrow union type to Collection type in property docblock and native type declaration ' ,
37
41
[
38
42
new CodeSample (
39
43
<<<'CODE_SAMPLE'
@@ -66,48 +70,99 @@ class SomeClass
66
70
67
71
public function getNodeTypes (): array
68
72
{
69
- return [Property ::class];
73
+ return [Class_ ::class];
70
74
}
71
75
72
76
/**
73
- * @param Property $node
77
+ * @param Class_ $node
74
78
*/
75
- public function refactor (Node $ node ): ?Property
79
+ public function refactor (Node $ node ): ?Class_
76
80
{
77
- if ($ node ->isAbstract ()) {
78
- return null ;
81
+ $ hasChanged = false ;
82
+ foreach ($ node ->getProperties () as $ property ) {
83
+ if ($ property ->isAbstract ()) {
84
+ continue ;
85
+ }
86
+
87
+ if ($ this ->refactorPropertyDocBlock ($ property )) {
88
+ $ hasChanged = true ;
89
+ }
90
+
91
+ if ($ this ->refactorNativePropertyType ($ property )) {
92
+ $ hasChanged = true ;
93
+ }
79
94
}
80
95
81
- $ scope = ScopeFetcher::fetch ($ node );
82
- $ classReflection = $ scope ->getClassReflection ();
83
- if (! $ classReflection instanceof ClassReflection) {
84
- return null ;
96
+ if ($ hasChanged ) {
97
+ return $ node ;
85
98
}
86
99
87
- if ($ classReflection ->isInterface ()) {
88
- return null ;
100
+ return null ;
101
+ }
102
+
103
+ private function hasNativeTypeCollection (Property $ property ): bool
104
+ {
105
+ if (! $ property ->type instanceof Name) {
106
+ return false ;
89
107
}
90
108
91
- $ propertyPhpDocInfo = $ this ->phpDocInfoFactory ->createFromNodeOrEmpty ($ node );
109
+ return $ this ->isName ($ property ->type , Collection::class);
110
+ }
92
111
93
- $ varTagValueNode = $ propertyPhpDocInfo ->getVarTagValueNode ();
94
- if (! $ varTagValueNode instanceof VarTagValueNode) {
95
- return null ;
112
+ private function isCollectionName (Node $ node ): bool
113
+ {
114
+ if (! $ node instanceof Name) {
115
+ return false ;
116
+ }
117
+
118
+ return $ this ->isName ($ node , DoctrineClass::COLLECTION );
119
+ }
120
+
121
+ private function refactorNativePropertyType (Property $ property ): bool
122
+ {
123
+ if (! $ property ->type instanceof UnionType) {
124
+ return false ;
96
125
}
97
126
98
- $ hasNativeCollectionType = false ;
99
- if ($ node ->type instanceof Name && $ this ->isName ($ node ->type , Collection::class)) {
100
- $ hasNativeCollectionType = true ;
127
+ foreach ($ property ->type ->types as $ uniontedType ) {
128
+ if (! $ this ->isCollectionName ($ uniontedType )) {
129
+ continue ;
130
+ }
131
+
132
+ // narrow to pure collection
133
+ $ property ->type = new FullyQualified (DoctrineClass::COLLECTION );
134
+
135
+ // remove default, as will be defined in constructor by another rule
136
+ if ($ property ->props [0 ]->default instanceof Expr) {
137
+ $ property ->props [0 ]->default = null ;
138
+ }
139
+
140
+ return true ;
141
+ }
142
+
143
+ return false ;
144
+ }
145
+
146
+ private function refactorPropertyDocBlock (Property $ property ): bool
147
+ {
148
+ $ propertyPhpDocInfo = $ this ->phpDocInfoFactory ->createFromNode ($ property );
149
+ if (! $ propertyPhpDocInfo instanceof PhpDocInfo) {
150
+ return false ;
101
151
}
102
152
103
- $ hasChanged = $ this -> unionCollectionTagValueNodeNarrower -> narrow ( $ varTagValueNode , $ hasNativeCollectionType );
153
+ $ varTagValueNode = $ propertyPhpDocInfo -> getVarTagValueNode ( );
104
154
105
- if ($ hasChanged === false ) {
106
- return null ;
155
+ if (! $ varTagValueNode instanceof VarTagValueNode ) {
156
+ return false ;
107
157
}
108
158
109
- $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ node );
159
+ $ hasNativeCollectionType = $ this ->hasNativeTypeCollection ($ property );
160
+
161
+ if ($ this ->unionCollectionTagValueNodeNarrower ->narrow ($ varTagValueNode , $ hasNativeCollectionType )) {
162
+ $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ property );
163
+ return true ;
164
+ }
110
165
111
- return $ node ;
166
+ return false ;
112
167
}
113
168
}
0 commit comments