@@ -218,8 +218,7 @@ private function getNameScopeMap(string $fileName): array
218218 */
219219 private function createResolvedPhpDocMap (string $ fileName ): array
220220 {
221- $ phpDocNodeMap = $ this ->createPhpDocNodeMap ($ fileName , null , $ fileName , [], $ fileName );
222- $ nameScopeMap = $ this ->createNameScopeMap ($ fileName , null , null , [], $ fileName , $ phpDocNodeMap );
221+ [/*$phpDocNodeMap*/ , $ nameScopeMap ] = $ this ->createPhpDocNodeMap ($ fileName , null , null , [], $ fileName );
223222 $ resolvedNameScopeMap = [];
224223
225224 try {
@@ -240,211 +239,13 @@ private function createResolvedPhpDocMap(string $fileName): array
240239
241240 /**
242241 * @param array<string, string> $traitMethodAliases
243- * @return array<string, PhpDocNode>
242+ * @return array{array <string, PhpDocNode>, (callable(): NameScope)[]}
244243 */
245244 private function createPhpDocNodeMap (string $ fileName , ?string $ lookForTrait , ?string $ traitUseClass , array $ traitMethodAliases , string $ originalClassFileName ): array
246245 {
247246 /** @var array<string, PhpDocNode> $phpDocNodeMap */
248247 $ phpDocNodeMap = [];
249248
250- /** @var string[] $classStack */
251- $ classStack = [];
252- if ($ lookForTrait !== null && $ traitUseClass !== null ) {
253- $ classStack [] = $ traitUseClass ;
254- }
255- $ namespace = null ;
256-
257- $ traitFound = false ;
258-
259- /** @var array<string|null> $functionStack */
260- $ functionStack = [];
261- $ this ->processNodes (
262- $ this ->phpParser ->parseFile ($ fileName ),
263- function (Node $ node ) use ($ fileName , $ lookForTrait , &$ traitFound , $ traitMethodAliases , $ originalClassFileName , &$ phpDocNodeMap , &$ classStack , &$ namespace , &$ functionStack ): ?int {
264- if ($ node instanceof Node \Stmt \ClassLike) {
265- if ($ traitFound && $ fileName === $ originalClassFileName ) {
266- return self ::SKIP_NODE ;
267- }
268-
269- if ($ lookForTrait !== null && !$ traitFound ) {
270- if (!$ node instanceof Node \Stmt \Trait_) {
271- return self ::SKIP_NODE ;
272- }
273- if ((string ) $ node ->namespacedName !== $ lookForTrait ) {
274- return self ::SKIP_NODE ;
275- }
276-
277- $ traitFound = true ;
278- $ functionStack [] = null ;
279- } else {
280- if ($ node ->name === null ) {
281- if (!$ node instanceof Node \Stmt \Class_) {
282- throw new ShouldNotHappenException ();
283- }
284-
285- $ className = $ this ->anonymousClassNameHelper ->getAnonymousClassName ($ node , $ fileName );
286- } elseif ($ node instanceof Node \Stmt \Class_ && $ node ->isAnonymous ()) {
287- $ className = $ node ->name ->name ;
288- } else {
289- if ($ traitFound ) {
290- return self ::SKIP_NODE ;
291- }
292- $ className = ltrim (sprintf ('%s \\%s ' , $ namespace , $ node ->name ->name ), '\\' );
293- }
294- $ classStack [] = $ className ;
295- $ functionStack [] = null ;
296- }
297- } elseif ($ node instanceof Node \Stmt \ClassMethod) {
298- if (array_key_exists ($ node ->name ->name , $ traitMethodAliases )) {
299- $ functionStack [] = $ traitMethodAliases [$ node ->name ->name ];
300- } else {
301- $ functionStack [] = $ node ->name ->name ;
302- }
303- } elseif ($ node instanceof Node \Stmt \Function_) {
304- $ functionStack [] = ltrim (sprintf ('%s \\%s ' , $ namespace , $ node ->name ->name ), '\\' );
305- } elseif ($ node instanceof Node \PropertyHook) {
306- $ propertyName = $ node ->getAttribute ('propertyName ' );
307- if ($ propertyName !== null ) {
308- $ functionStack [] = sprintf ('$%s::%s ' , $ propertyName , $ node ->name ->toString ());
309- }
310- }
311-
312- $ className = array_last ($ classStack ) ?? null ;
313- $ functionName = array_last ($ functionStack ) ?? null ;
314-
315- if ($ node instanceof Node \Stmt \ClassLike || $ node instanceof Node \Stmt \ClassMethod || $ node instanceof Node \Stmt \Function_) {
316- $ docComment = GetLastDocComment::forNode ($ node );
317- if ($ docComment !== null ) {
318- $ nameScopeKey = $ this ->getNameScopeKey ($ originalClassFileName , $ className , $ lookForTrait , $ functionName );
319- $ phpDocNodeMap [$ nameScopeKey ] = $ this ->phpDocStringResolver ->resolve ($ docComment );
320- }
321-
322- return null ;
323- } elseif ($ node instanceof Node \PropertyHook) {
324- $ propertyName = $ node ->getAttribute ('propertyName ' );
325- if ($ propertyName !== null ) {
326- $ docComment = GetLastDocComment::forNode ($ node );
327- if ($ docComment !== null ) {
328- $ nameScopeKey = $ this ->getNameScopeKey ($ originalClassFileName , $ className , $ lookForTrait , $ functionName );
329- $ phpDocNodeMap [$ nameScopeKey ] = $ this ->phpDocStringResolver ->resolve ($ docComment );
330- }
331- }
332-
333- return null ;
334- }
335-
336- if ($ node instanceof Node \Stmt \Namespace_) {
337- $ namespace = $ node ->name !== null ? (string ) $ node ->name : null ;
338- } elseif ($ node instanceof Node \Stmt \TraitUse) {
339- $ traitMethodAliases = [];
340- foreach ($ node ->adaptations as $ traitUseAdaptation ) {
341- if (!$ traitUseAdaptation instanceof Node \Stmt \TraitUseAdaptation \Alias) {
342- continue ;
343- }
344-
345- if ($ traitUseAdaptation ->newName === null ) {
346- continue ;
347- }
348-
349- $ methodName = $ traitUseAdaptation ->method ->toString ();
350- $ newTraitName = $ traitUseAdaptation ->newName ->toString ();
351-
352- if ($ traitUseAdaptation ->trait === null ) {
353- foreach ($ node ->traits as $ traitName ) {
354- $ traitMethodAliases [$ traitName ->toString ()][$ methodName ] = $ newTraitName ;
355- }
356- continue ;
357- }
358-
359- $ traitMethodAliases [$ traitUseAdaptation ->trait ->toString ()][$ methodName ] = $ newTraitName ;
360- }
361-
362- foreach ($ node ->traits as $ traitName ) {
363- /** @var class-string $traitName */
364- $ traitName = (string ) $ traitName ;
365- $ reflectionProvider = $ this ->reflectionProviderProvider ->getReflectionProvider ();
366- if (!$ reflectionProvider ->hasClass ($ traitName )) {
367- continue ;
368- }
369-
370- $ traitReflection = $ reflectionProvider ->getClass ($ traitName );
371- if (!$ traitReflection ->isTrait ()) {
372- continue ;
373- }
374- if ($ traitReflection ->getFileName () === null ) {
375- continue ;
376- }
377- if (!is_file ($ traitReflection ->getFileName ())) {
378- continue ;
379- }
380-
381- $ className = array_last ($ classStack ) ?? null ;
382- if ($ className === null ) {
383- throw new ShouldNotHappenException ();
384- }
385-
386- $ phpDocNodeMap = array_merge ($ phpDocNodeMap , $ this ->createPhpDocNodeMap (
387- $ traitReflection ->getFileName (),
388- $ traitName ,
389- $ className ,
390- $ traitMethodAliases [$ traitName ] ?? [],
391- $ originalClassFileName ,
392- ));
393- }
394- }
395-
396- return null ;
397- },
398- static function (Node $ node ) use (&$ namespace , &$ functionStack , &$ classStack ): void {
399- if ($ node instanceof Node \Stmt \ClassLike) {
400- if (count ($ classStack ) === 0 ) {
401- throw new ShouldNotHappenException ();
402- }
403- array_pop ($ classStack );
404-
405- if (count ($ functionStack ) === 0 ) {
406- throw new ShouldNotHappenException ();
407- }
408-
409- array_pop ($ functionStack );
410- } elseif ($ node instanceof Node \Stmt \Namespace_) {
411- $ namespace = null ;
412- } elseif ($ node instanceof Node \Stmt \ClassMethod || $ node instanceof Node \Stmt \Function_) {
413- if (count ($ functionStack ) === 0 ) {
414- throw new ShouldNotHappenException ();
415- }
416-
417- array_pop ($ functionStack );
418- } elseif ($ node instanceof Node \PropertyHook) {
419- $ propertyName = $ node ->getAttribute ('propertyName ' );
420- if ($ propertyName !== null ) {
421- if (count ($ functionStack ) === 0 ) {
422- throw new ShouldNotHappenException ();
423- }
424-
425- array_pop ($ functionStack );
426- }
427- }
428- },
429- );
430-
431- return $ phpDocNodeMap ;
432- }
433-
434- /**
435- * @param array<string, string> $traitMethodAliases
436- * @param array<string, PhpDocNode> $phpDocNodeMap
437- * @return (callable(): NameScope)[]
438- */
439- private function createNameScopeMap (
440- string $ fileName ,
441- ?string $ lookForTrait ,
442- ?string $ traitUseClass ,
443- array $ traitMethodAliases ,
444- string $ originalClassFileName ,
445- array $ phpDocNodeMap ,
446- ): array
447- {
448249 /** @var (callable(): NameScope)[] $nameScopeMap */
449250 $ nameScopeMap = [];
450251
@@ -470,7 +271,7 @@ private function createNameScopeMap(
470271 $ constUses = [];
471272 $ this ->processNodes (
472273 $ this ->phpParser ->parseFile ($ fileName ),
473- function (Node $ node ) use ($ fileName , $ lookForTrait , $ phpDocNodeMap , &$ traitFound , $ traitMethodAliases , $ originalClassFileName , &$ nameScopeMap , &$ classStack , &$ typeAliasStack , &$ namespace , &$ functionStack , &$ uses , &$ typeMapStack , &$ constUses ): ?int {
274+ function (Node $ node ) use ($ fileName , $ lookForTrait , &$ traitFound , $ traitMethodAliases , $ originalClassFileName , &$ phpDocNodeMap , & $ nameScopeMap , &$ typeMapStack , &$ typeAliasStack , &$ classStack , &$ namespace , &$ functionStack , &$ uses , &$ constUses ): ?int {
474275 if ($ node instanceof Node \Stmt \ClassLike) {
475276 if ($ traitFound && $ fileName === $ originalClassFileName ) {
476277 return self ::SKIP_NODE ;
@@ -485,12 +286,6 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun
485286 }
486287
487288 $ traitFound = true ;
488- $ traitNameScopeKey = $ this ->getNameScopeKey ($ originalClassFileName , $ classStack [count ($ classStack ) - 1 ] ?? null , $ lookForTrait , null );
489- if (array_key_exists ($ traitNameScopeKey , $ phpDocNodeMap )) {
490- $ typeAliasStack [] = $ this ->getTypeAliasesMap ($ phpDocNodeMap [$ traitNameScopeKey ]);
491- } else {
492- $ typeAliasStack [] = [];
493- }
494289 $ functionStack [] = null ;
495290 } else {
496291 if ($ node ->name === null ) {
@@ -508,12 +303,6 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun
508303 $ className = ltrim (sprintf ('%s \\%s ' , $ namespace , $ node ->name ->name ), '\\' );
509304 }
510305 $ classStack [] = $ className ;
511- $ classNameScopeKey = $ this ->getNameScopeKey ($ originalClassFileName , $ className , $ lookForTrait , null );
512- if (array_key_exists ($ classNameScopeKey , $ phpDocNodeMap )) {
513- $ typeAliasStack [] = $ this ->getTypeAliasesMap ($ phpDocNodeMap [$ classNameScopeKey ]);
514- } else {
515- $ typeAliasStack [] = [];
516- }
517306 $ functionStack [] = null ;
518307 }
519308 } elseif ($ node instanceof Node \Stmt \ClassMethod) {
@@ -536,9 +325,13 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun
536325 $ nameScopeKey = $ this ->getNameScopeKey ($ originalClassFileName , $ className , $ lookForTrait , $ functionName );
537326
538327 if ($ node instanceof Node \Stmt \ClassLike || $ node instanceof Node \Stmt \ClassMethod || $ node instanceof Node \Stmt \Function_) {
539- // property hook skipped on purpose, it does not support @template
540- if (array_key_exists ($ nameScopeKey , $ phpDocNodeMap )) {
541- $ phpDocNode = $ phpDocNodeMap [$ nameScopeKey ];
328+ $ docComment = GetLastDocComment::forNode ($ node );
329+ if ($ docComment !== null ) {
330+ $ phpDocNode = $ this ->phpDocStringResolver ->resolve ($ docComment );
331+ $ phpDocNodeMap [$ nameScopeKey ] = $ phpDocNode ;
332+ if ($ node instanceof Node \Stmt \ClassLike) {
333+ $ typeAliasStack [] = $ this ->getTypeAliasesMap ($ phpDocNode );
334+ }
542335 $ typeMapStack [] = function () use ($ namespace , $ uses , $ className , $ lookForTrait , $ functionName , $ phpDocNode , $ typeMapStack , $ typeAliasStack , $ constUses ): TemplateTypeMap {
543336 $ typeMapCb = array_last ($ typeMapStack ) ?? null ;
544337 $ currentTypeMap = $ typeMapCb !== null ? $ typeMapCb () : null ;
@@ -559,12 +352,24 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun
559352 $ templateTypeMap ->getTypes (),
560353 ));
561354 };
355+ } elseif ($ node instanceof Node \Stmt \ClassLike) {
356+ $ typeAliasStack [] = [];
562357 }
563358 }
564359
565360 $ typeMapCb = array_last ($ typeMapStack ) ?? null ;
566361 $ typeAliasesMap = array_last ($ typeAliasStack ) ?? [];
567362
363+ if ($ node instanceof Node \PropertyHook) {
364+ $ propertyName = $ node ->getAttribute ('propertyName ' );
365+ if ($ propertyName !== null ) {
366+ $ docComment = GetLastDocComment::forNode ($ node );
367+ if ($ docComment !== null ) {
368+ $ phpDocNodeMap [$ nameScopeKey ] = $ this ->phpDocStringResolver ->resolve ($ docComment );
369+ }
370+ }
371+ }
372+
568373 if (
569374 (
570375 $ node instanceof Node \PropertyHook
@@ -594,8 +399,9 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun
594399 }
595400
596401 if ($ node instanceof Node \Stmt \ClassLike || $ node instanceof Node \Stmt \ClassMethod || $ node instanceof Node \Stmt \Function_) {
402+ $ docComment = GetLastDocComment::forNode ($ node );
597403 // property hook skipped on purpose, it does not support @template
598- if (array_key_exists ( $ nameScopeKey , $ phpDocNodeMap ) ) {
404+ if ($ docComment !== null ) {
599405 return self ::POP_TYPE_MAP_STACK ;
600406 }
601407
@@ -676,14 +482,14 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun
676482 throw new ShouldNotHappenException ();
677483 }
678484
679- $ traitPhpDocMap = $ this ->createNameScopeMap (
485+ [ $ tmpPhpDocNodeMap , $ traitPhpDocMap] = $ this ->createPhpDocNodeMap (
680486 $ traitReflection ->getFileName (),
681487 $ traitName ,
682488 $ className ,
683489 $ traitMethodAliases [$ traitName ] ?? [],
684490 $ originalClassFileName ,
685- $ phpDocNodeMap ,
686491 );
492+ $ phpDocNodeMap = array_merge ($ phpDocNodeMap , $ tmpPhpDocNodeMap );
687493 $ finalTraitPhpDocMap = [];
688494 foreach ($ traitPhpDocMap as $ nameScopeTraitKey => $ callback ) {
689495 $ finalTraitPhpDocMap [$ nameScopeTraitKey ] = function () use ($ callback , $ traitReflection , $ fileName , $ className , $ lookForTrait , $ useDocComment ): NameScope {
@@ -787,7 +593,7 @@ static function (Node $node, $callbackResult) use (&$namespace, &$functionStack,
787593 throw new ShouldNotHappenException ();
788594 }
789595
790- return $ nameScopeMap ;
596+ return [ $ phpDocNodeMap , $ nameScopeMap] ;
791597 }
792598
793599 /**
0 commit comments