@@ -52,6 +52,13 @@ final class SpecialTypeResolver
5252 */
5353 private static array $ fileNamespaces = [];
5454
55+ /**
56+ * In-memory cache of class trait use statement docblocks keyed by class FQCN.
57+ *
58+ * @var array<string, array<int, string>>
59+ */
60+ private static array $ classTraitUseDocs = [];
61+
5562 /**
5663 * Validates strict object identity ($value === $thisObj) when the return type node specifies $this.
5764 */
@@ -692,19 +699,54 @@ private static function resolveConstantKeyValue(string $fqcn, string $constName)
692699 }
693700
694701 /**
695- * Seeds the in-memory cache directly from StreamWrapper to prevent double file reads and re-parsing .
702+ * Seeds file metadata directly from StreamWrapper.
696703 *
697704 * @param array<string, string> $imports
705+ * @param array<string, array<int, string>> $classTraitUseDocs
698706 */
699- public static function seedFileMetadata (string $ fileName , string $ namespace , array $ imports ): void
707+ public static function seedFileMetadata (string $ fileName , string $ namespace , array $ imports, array $ classTraitUseDocs = [] ): void
700708 {
701709 if ($ fileName !== '' ) {
702710 $ fileName = str_replace ('\\' , '/ ' , $ fileName );
703711 self ::$ fileNamespaces [$ fileName ] = $ namespace ;
704712 self ::$ fileUseImports [$ fileName ] = $ imports ;
713+ foreach ($ classTraitUseDocs as $ className => $ docs ) {
714+ self ::$ classTraitUseDocs [$ className ] = $ docs ;
715+ }
705716 }
706717 }
707718
719+ /**
720+ * Returns all inline trait use statement docblock strings declared inside a class.
721+ *
722+ * @return array<int, string>
723+ */
724+ public static function getClassTraitUseDocs (string $ className ): array
725+ {
726+ if (isset (self ::$ classTraitUseDocs [$ className ])) {
727+ return self ::$ classTraitUseDocs [$ className ];
728+ }
729+
730+ if (! class_exists ($ className ) && ! trait_exists ($ className )) {
731+ return self ::$ classTraitUseDocs [$ className ] = [];
732+ }
733+
734+ try {
735+ $ ref = new \ReflectionClass ($ className );
736+ $ fileName = $ ref ->getFileName ();
737+ if ($ fileName !== false && file_exists ($ fileName )) {
738+ $ source = file_get_contents ($ fileName );
739+ if ($ source !== false ) {
740+ self ::parseFileMetadata ($ fileName , $ source );
741+ }
742+ }
743+ } catch (\Throwable $ e ) {
744+ // Silently ignore reflection errors
745+ }
746+
747+ return self ::$ classTraitUseDocs [$ className ] ?? [];
748+ }
749+
708750 /**
709751 * Returns use imports for the declaring file of a Reflection object.
710752 *
@@ -939,7 +981,7 @@ private static function isBuiltInTypeKeyword(string $name): bool
939981 }
940982
941983 /**
942- * Parses the AST of a PHP file once to extract both namespace and use import statements .
984+ * Parses the AST of a PHP file once to extract namespace, use imports, and class trait use docblocks .
943985 */
944986 private static function parseFileMetadata (string $ fileName , string $ source ): void
945987 {
@@ -995,6 +1037,16 @@ private static function parseFileMetadata(string $fileName, string $source): voi
9951037 $ alias = $ use ->getAlias ()->toString ();
9961038 $ imports [$ alias ] = $ fqcn ;
9971039 }
1040+ } elseif ($ stmt instanceof Stmt \Class_ && $ stmt ->name !== null ) {
1041+ $ className = $ namespace !== '' ? $ namespace . '\\' . $ stmt ->name ->toString () : $ stmt ->name ->toString ();
1042+ foreach ($ stmt ->stmts as $ classStmt ) {
1043+ if ($ classStmt instanceof Stmt \TraitUse) {
1044+ $ doc = $ classStmt ->getDocComment ();
1045+ if ($ doc !== null ) {
1046+ self ::$ classTraitUseDocs [$ className ][] = $ doc ->getText ();
1047+ }
1048+ }
1049+ }
9981050 }
9991051 }
10001052
0 commit comments