1414use PHPStan \PhpDocParser \Ast \PhpDoc \ParamTagValueNode ;
1515use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTagValueNode ;
1616use PHPStan \PhpDocParser \Ast \PhpDoc \ReturnTagValueNode ;
17- use PHPStan \PhpDocParser \Ast \PhpDoc \TypelessParamTagValueNode ;
1817use PHPStan \PhpDocParser \Ast \PhpDoc \VarTagValueNode ;
1918use PHPStan \PhpDocParser \Ast \Type \IdentifierTypeNode ;
2019use PHPStan \PhpDocParser \Ast \Type \NullableTypeNode ;
@@ -52,14 +51,25 @@ public function process(File $phpcsFile, $pointer): void
5251 $ tokens = $ phpcsFile ->getTokens ();
5352 $ docCommentContent = $ tokens [$ pointer ]['content ' ];
5453
55- /** @var \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode $valueNode */
54+ /** @var \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode $valueNode */
5655 $ valueNode = static ::getValueNode ($ tokens [$ pointer - 2 ]['content ' ], $ docCommentContent );
57- if ($ valueNode instanceof InvalidTagValueNode || $ valueNode instanceof TypelessParamTagValueNode) {
58- return ;
59- }
6056
6157 $ printer = new Printer ();
6258 $ before = $ printer ->print ($ valueNode );
59+
60+ // Check if the value node is invalid and handle it
61+ if ($ valueNode instanceof InvalidTagValueNode) {
62+ // Attempt to clean up and process invalid types
63+ $ fixedNode = $ this ->fixInvalidTagValueNode ($ valueNode );
64+ if ($ fixedNode ) {
65+ $ valueNode = $ fixedNode ;
66+ }
67+ }
68+
69+ if ($ valueNode instanceof InvalidTagValueNode) {
70+ return ;
71+ }
72+
6373 // Traverse and fix the nullable types
6474 $ this ->traversePhpDocNode ($ valueNode );
6575
@@ -78,6 +88,64 @@ public function process(File $phpcsFile, $pointer): void
7888 }
7989 }
8090
91+ /**
92+ * Attempt to fix an InvalidTagValueNode by parsing and correcting the types manually.
93+ *
94+ * @param \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode $invalidNode
95+ *
96+ * @return \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode|null
97+ */
98+ protected function fixInvalidTagValueNode (InvalidTagValueNode $ invalidNode ): ?PhpDocTagValueNode
99+ {
100+ $ value = $ invalidNode ->value ;
101+ $ rest = '' ;
102+ if (str_contains ($ value , '$ ' )) {
103+ $ string = trim (substr ($ value , 0 , (int )strpos ($ value , '$ ' )));
104+ $ rest = trim (substr ($ value , strlen ($ string )));
105+ $ value = $ string ;
106+ }
107+
108+ // Try to parse and correct the invalid node's type (e.g., `?string|null`)
109+ if (str_contains ($ value , '| ' )) {
110+ // Split the types
111+ $ types = explode ('| ' , $ value );
112+
113+ $ transformedTypes = [];
114+ $ hasNullable = false ;
115+
116+ foreach ($ types as $ type ) {
117+ $ type = trim ($ type );
118+
119+ // Handle `?Type` shorthand
120+ if (str_starts_with ($ type , '? ' )) {
121+ $ type = substr ($ type , 1 ); // Remove leading '?'
122+ $ transformedTypes [] = new IdentifierTypeNode ($ type );
123+ $ hasNullable = true ; // Mark as nullable
124+ } elseif (strtolower ($ type ) === 'null ' ) {
125+ // If 'null' is encountered, mark as nullable but don't add now
126+ $ hasNullable = true ;
127+ } else {
128+ $ transformedTypes [] = new IdentifierTypeNode ($ type );
129+ }
130+ }
131+
132+ // Add `null` at the end if the type is nullable
133+ if ($ hasNullable ) {
134+ $ transformedTypes [] = new IdentifierTypeNode ('null ' );
135+ }
136+
137+ // Create a new UnionTypeNode with the transformed types
138+ return new ParamTagValueNode (
139+ new UnionTypeNode ($ transformedTypes ),
140+ false ,
141+ $ rest ,
142+ '' ,
143+ );
144+ }
145+
146+ return null ;
147+ }
148+
81149 /**
82150 * Traverse and transform the PHPDoc AST.
83151 *
@@ -92,9 +160,11 @@ protected function traversePhpDocNode(PhpDocTagValueNode $phpDocNode): void
92160 || $ phpDocNode instanceof ReturnTagValueNode
93161 || $ phpDocNode instanceof VarTagValueNode
94162 ) {
95- // Traverse the type node recursively
163+ echo PHP_EOL . ' processing... ' . PHP_EOL ;
96164 $ phpDocNode ->type = $ this ->transformNullableType ($ phpDocNode ->type );
97165 }
166+
167+ echo PHP_EOL . PHP_EOL ;
98168 }
99169
100170 /**
@@ -116,39 +186,16 @@ protected function transformNullableType(TypeNode $typeNode): TypeNode
116186 ]);
117187 }
118188
119- // Recursively handle UnionTypeNode (e.g., `Type|null`)
189+ // Handle UnionTypeNode (e.g., `Type|null`)
120190 if ($ typeNode instanceof UnionTypeNode) {
121- // Traverse each type in the union and transform nullable types
122- foreach ($ typeNode ->types as & $ subType ) {
123- $ subType = $ this ->transformNullableType ($ subType );
191+ $ transformedTypes = [];
192+ foreach ($ typeNode ->types as $ subType ) {
193+ $ transformedTypes [] = $ this ->transformNullableType ($ subType ); // Recursively transform
124194 }
125195
126- return $ typeNode ;
127- }
128-
129- // Recursively handle other nodes that might contain nested types
130- if (property_exists ($ typeNode , 'types ' ) && is_array ($ typeNode ->types )) {
131- foreach ($ typeNode ->types as &$ subType ) {
132- $ subType = $ this ->transformNullableType ($ subType );
133- }
196+ return new UnionTypeNode ($ transformedTypes );
134197 }
135198
136199 return $ typeNode ;
137200 }
138-
139- /**
140- * @param array<string> $types
141- *
142- * @return bool
143- */
144- protected function containsShorthand (array $ types ): bool
145- {
146- foreach ($ types as $ type ) {
147- if (str_starts_with ($ type , '? ' )) {
148- return true ;
149- }
150- }
151-
152- return false ;
153- }
154201}
0 commit comments