1414use PhpParser \Node \Stmt ;
1515use PhpParser \NodeAbstract ;
1616use PhpParser \NodeVisitorAbstract ;
17- use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocNode ;
18- use PHPStan \PhpDocParser \Ast \Type \ArrayTypeNode ;
19- use PHPStan \PhpDocParser \Ast \Type \GenericTypeNode ;
20- use PHPStan \PhpDocParser \Ast \Type \TypeNode ;
2117
2218/**
2319 * This class is used to collect type information from dockblocks, in particular
@@ -68,51 +64,39 @@ public function enterNode(Node $node): void
6864
6965 $ this ->resolveFunctionTypes ($ node );
7066
71- $ this ->resolveParamTypes ($ node );
67+ $ this ->resolvePropertyTypes ($ node );
7268 }
7369
74- private function resolveParamTypes (Node $ node ): void
70+ private function resolvePropertyTypes (Node $ node ): void
7571 {
7672 if (!($ node instanceof Stmt \Property)) {
7773 return ;
7874 }
7975
80- $ phpDocNode = $ this ->parseDocblock ($ node );
76+ $ docblock = $ this ->parseDocblock ($ node );
8177
82- if (null === $ phpDocNode ) {
78+ if (null === $ docblock ) {
8379 return ;
8480 }
8581
86- if ( $ this -> isNodeOfTypeArray ( $ node )) {
87- $ arrayItemType = null ;
82+ $ arrayItemType = $ docblock -> getVarTagTypes ();
83+ $ arrayItemType = array_pop ( $ arrayItemType ) ;
8884
89- foreach ($ phpDocNode ->getVarTagValues () as $ tagValue ) {
90- $ arrayItemType = $ this ->getArrayItemType ($ tagValue ->type );
91- }
92-
93- if (null !== $ arrayItemType ) {
94- $ node ->type = $ this ->resolveName (new Name ($ arrayItemType ), Stmt \Use_::TYPE_NORMAL );
95-
96- return ;
97- }
98- }
85+ if (null !== $ arrayItemType ) {
86+ $ node ->type = $ this ->resolveName (new Name ($ arrayItemType ), Stmt \Use_::TYPE_NORMAL );
9987
100- foreach ($ phpDocNode ->getVarTagValues () as $ tagValue ) {
101- $ type = $ this ->resolveName (new Name ((string ) $ tagValue ->type ), Stmt \Use_::TYPE_NORMAL );
102- $ node ->type = $ type ;
103- break ;
88+ return ;
10489 }
10590
10691 if ($ this ->parseCustomAnnotations && !($ node ->type instanceof FullyQualified)) {
107- foreach ($ phpDocNode ->getTags () as $ tagValue ) {
108- if ('@ ' === $ tagValue ->name [0 ] && !str_contains ($ tagValue ->name , '@var ' )) {
109- $ customTag = str_replace ('@ ' , '' , $ tagValue ->name );
110- $ type = $ this ->resolveName (new Name ($ customTag ), Stmt \Use_::TYPE_NORMAL );
111- $ node ->type = $ type ;
112-
113- break ;
114- }
92+ $ doctrineAnnotations = $ docblock ->getDoctrineLikeAnnotationTypes ();
93+ $ doctrineAnnotations = array_shift ($ doctrineAnnotations );
94+
95+ if (null === $ doctrineAnnotations ) {
96+ return ;
11597 }
98+
99+ $ node ->type = $ this ->resolveName (new Name ($ doctrineAnnotations ), Stmt \Use_::TYPE_NORMAL );
116100 }
117101 }
118102
@@ -127,38 +111,41 @@ private function resolveFunctionTypes(Node $node): void
127111 return ;
128112 }
129113
130- $ phpDocNode = $ this ->parseDocblock ($ node );
114+ $ docblock = $ this ->parseDocblock ($ node );
131115
132- if (null === $ phpDocNode ) { // no docblock, nothing to do
116+ if (null === $ docblock ) { // no docblock, nothing to do
133117 return ;
134118 }
135119
120+ // extract param types from param tags
136121 foreach ($ node ->params as $ param ) {
137- if (!$ this ->isNodeOfTypeArray ($ param )) { // not an array, nothing to do
122+ if (!$ this ->isTypeArray ($ param ->type )) { // not an array, nothing to do
123+ continue ;
124+ }
125+
126+ if (!($ param ->var instanceof Expr \Variable) || !\is_string ($ param ->var ->name )) {
138127 continue ;
139128 }
140129
141- foreach ($ phpDocNode ->getParamTagValues () as $ phpDocParam ) {
142- if ($ param ->var instanceof Expr \Variable && \is_string ($ param ->var ->name ) && $ phpDocParam ->parameterName === ('$ ' .$ param ->var ->name )) {
143- $ arrayItemType = $ this ->getArrayItemType ($ phpDocParam ->type );
130+ $ type = $ docblock ->getParamTagTypesByName ('$ ' .$ param ->var ->name );
144131
145- if (null !== $ arrayItemType ) {
146- $ param ->type = $ this ->resolveName (new Name ($ arrayItemType ), Stmt \Use_::TYPE_NORMAL );
147- }
148- }
132+ if (null === $ type ) {
133+ continue ;
149134 }
135+
136+ $ param ->type = $ this ->resolveName (new Name ($ type ), Stmt \Use_::TYPE_NORMAL );
150137 }
151138
152- if ($ node ->returnType instanceof Node \Identifier && 'array ' === $ node ->returnType ->name ) {
153- $ arrayItemType = null ;
139+ // extract return type from return tag
140+ if ($ this ->isTypeArray ($ node ->returnType )) {
141+ $ type = $ docblock ->getReturnTagTypes ();
142+ $ type = array_pop ($ type );
154143
155- foreach ( $ phpDocNode -> getReturnTagValues () as $ tagValue ) {
156- $ arrayItemType = $ this -> getArrayItemType ( $ tagValue -> type ) ;
144+ if ( null === $ type ) {
145+ return ;
157146 }
158147
159- if (null !== $ arrayItemType ) {
160- $ node ->returnType = $ this ->resolveName (new Name ($ arrayItemType ), Stmt \Use_::TYPE_NORMAL );
161- }
148+ $ node ->returnType = $ this ->resolveName (new Name ($ type ), Stmt \Use_::TYPE_NORMAL );
162149 }
163150 }
164151
@@ -178,14 +165,6 @@ private function resolveName(Name $name, int $type): Name
178165 return $ resolvedName ;
179166 }
180167
181- // unqualified names inside a namespace cannot be resolved at compile-time
182- // add the namespaced version of the name as an attribute
183- $ name ->setAttribute ('namespacedName ' , FullyQualified::concat (
184- $ this ->nameContext ->getNamespace (),
185- $ name ,
186- $ name ->getAttributes ()
187- ));
188-
189168 return $ name ;
190169 }
191170
@@ -218,7 +197,7 @@ private function addAlias(Node\UseItem $use, int $type, ?Name $prefix = null): v
218197 );
219198 }
220199
221- private function parseDocblock (NodeAbstract $ node ): ?PhpDocNode
200+ private function parseDocblock (NodeAbstract $ node ): ?Docblock
222201 {
223202 if (null === $ node ->getDocComment ()) {
224203 return null ;
@@ -231,38 +210,10 @@ private function parseDocblock(NodeAbstract $node): ?PhpDocNode
231210 }
232211
233212 /**
234- * @param Node\Param|Stmt\Property $node
213+ * @param Node\Identifier|Name|Node\ComplexType|null $type
235214 */
236- private function isNodeOfTypeArray ($ node ): bool
237- {
238- return null !== $ node ->type && isset ($ node ->type ->name ) && 'array ' === $ node ->type ->name ;
239- }
240-
241- private function getArrayItemType (TypeNode $ typeNode ): ?string
215+ private function isTypeArray ($ type ): bool
242216 {
243- $ arrayItemType = null ;
244-
245- if ($ typeNode instanceof GenericTypeNode) {
246- if (1 === \count ($ typeNode ->genericTypes )) {
247- // this handles list<ClassName>
248- $ arrayItemType = (string ) $ typeNode ->genericTypes [0 ];
249- } elseif (2 === \count ($ typeNode ->genericTypes )) {
250- // this handles array<int, ClassName>
251- $ arrayItemType = (string ) $ typeNode ->genericTypes [1 ];
252- }
253- }
254-
255- if ($ typeNode instanceof ArrayTypeNode) {
256- // this handles ClassName[]
257- $ arrayItemType = (string ) $ typeNode ->type ;
258- }
259-
260- $ validFqcn = '/^[a-zA-Z_\x7f-\xff \\\\][a-zA-Z0-9_\x7f-\xff \\\\]*[a-zA-Z0-9_\x7f-\xff]$/ ' ;
261-
262- if (null !== $ arrayItemType && !(bool ) preg_match ($ validFqcn , $ arrayItemType )) {
263- return null ;
264- }
265-
266- return $ arrayItemType ;
217+ return null !== $ type && isset ($ type ->name ) && 'array ' === $ type ->name ;
267218 }
268219}
0 commit comments