@@ -46,9 +46,12 @@ public function process(File $phpCsFile, $stackPointer): void
4646 $ type [] = $ tokens [$ previousIndex ]['content ' ];
4747 $ previousIndex = $ phpCsFile ->findPrevious (Tokens::$ emptyTokens , $ previousIndex - 1 , null , true );
4848 }
49+ $ type = array_reverse ($ type );
4950
5051 // Skip these checks for typed ones for now
5152 if ($ type ) {
53+ $ this ->checkTyped ($ phpCsFile , $ stackPointer , $ type );
54+
5255 return ;
5356 }
5457
@@ -69,93 +72,7 @@ public function process(File $phpCsFile, $stackPointer): void
6972 return ;
7073 }
7174
72- /** @var int $docBlockStartIndex */
73- $ docBlockStartIndex = $ tokens [$ docBlockEndIndex ]['comment_opener ' ];
74-
75- $ defaultValueType = $ this ->findDefaultValueType ($ phpCsFile , $ stackPointer );
76-
77- $ varIndex = null ;
78- for ($ i = $ docBlockStartIndex + 1 ; $ i < $ docBlockEndIndex ; $ i ++) {
79- if ($ tokens [$ i ]['type ' ] !== 'T_DOC_COMMENT_TAG ' ) {
80- continue ;
81- }
82- if (!in_array ($ tokens [$ i ]['content ' ], ['@var ' ], true )) {
83- continue ;
84- }
85-
86- $ varIndex = $ i ;
87- }
88-
89- if (!$ varIndex ) {
90- $ this ->handleMissingVar ($ phpCsFile , $ docBlockEndIndex , $ docBlockStartIndex , $ defaultValueType );
91-
92- return ;
93- }
94-
95- $ classNameIndex = $ varIndex + 2 ;
96-
97- if ($ tokens [$ classNameIndex ]['type ' ] !== 'T_DOC_COMMENT_STRING ' ) {
98- $ this ->handleMissingVarType ($ phpCsFile , $ varIndex , $ defaultValueType );
99-
100- return ;
101- }
102-
103- $ content = $ tokens [$ classNameIndex ]['content ' ];
104-
105- $ appendix = '' ;
106- $ spaceIndex = strpos ($ content , ' ' );
107- if ($ spaceIndex ) {
108- $ appendix = substr ($ content , $ spaceIndex );
109- $ content = substr ($ content , 0 , $ spaceIndex );
110- }
111-
112- if (!$ content ) {
113- $ error = 'Doc Block type for property annotation @var missing ' ;
114- if ($ defaultValueType ) {
115- $ error .= ', type ` ' . $ defaultValueType . '` detected ' ;
116- }
117- $ phpCsFile ->addError ($ error , $ stackPointer , 'VarTypeEmpty ' );
118-
119- return ;
120- }
121-
122- $ comment = trim ($ appendix );
123- if (mb_substr ($ comment , 0 , 1 ) === '$ ' ) {
124- $ phpCsFile ->addError ('$var declaration only valid/needed inside inline doc blocks. ' , $ stackPointer , 'CommentInvalid ' );
125- }
126-
127- if ($ defaultValueType === null ) {
128- return ;
129- }
130-
131- $ parts = explode ('| ' , $ content );
132- if (in_array ($ defaultValueType , $ parts , true )) {
133- return ;
134- }
135- if ($ defaultValueType === 'array ' && ($ this ->containsTypeArray ($ parts ) || $ this ->containsTypeArray ($ parts , 'list ' ))) {
136- return ;
137- }
138- if ($ defaultValueType === 'false ' && in_array ('bool ' , $ parts , true )) {
139- return ;
140- }
141-
142- if ($ defaultValueType === 'false ' ) {
143- $ defaultValueType = 'bool ' ;
144- }
145-
146- if (count ($ parts ) > 1 || $ defaultValueType === 'null ' ) {
147- $ fix = $ phpCsFile ->addFixableError ('Doc Block type for property annotation @var incorrect, type ` ' . $ defaultValueType . '` missing ' , $ stackPointer , 'VarTypeMissing ' );
148- if ($ fix ) {
149- $ phpCsFile ->fixer ->replaceToken ($ classNameIndex , implode ('| ' , $ parts ) . '| ' . $ defaultValueType . $ appendix );
150- }
151-
152- return ;
153- }
154-
155- $ fix = $ phpCsFile ->addFixableError ('Doc Block type ` ' . $ content . '` for property annotation @var incorrect, type ` ' . $ defaultValueType . '` expected ' , $ stackPointer , 'VarTypeIncorrect ' );
156- if ($ fix ) {
157- $ phpCsFile ->fixer ->replaceToken ($ classNameIndex , $ defaultValueType . $ appendix );
158- }
75+ $ this ->handle ($ phpCsFile , $ docBlockEndIndex , $ stackPointer );
15976 }
16077
16178 /**
@@ -287,4 +204,187 @@ protected function handleMissingVarType(File $phpCsFile, int $varIndex, ?string
287204
288205 $ phpCsFile ->fixer ->addContent ($ varIndex , ' ' . $ defaultValueType );
289206 }
207+
208+ /**
209+ * @param \PHP_CodeSniffer\Files\File $phpCsFile
210+ * @param int $stackPointer
211+ * @param array<string> $types
212+ *
213+ * @return void
214+ */
215+ protected function checkTyped (File $ phpCsFile , int $ stackPointer , array $ types ): void
216+ {
217+ foreach ($ types as $ key => $ value ) {
218+ if ($ value === '? ' ) {
219+ unset($ types [$ key ]);
220+ $ types [] = 'null ' ;
221+ }
222+ if ($ value === '| ' ) {
223+ unset($ types [$ key ]);
224+ }
225+ }
226+
227+ $ docBlockEndIndex = $ this ->findRelatedDocBlock ($ phpCsFile , $ stackPointer );
228+
229+ if (!$ docBlockEndIndex ) {
230+ return ;
231+ }
232+
233+ $ this ->handle ($ phpCsFile , $ docBlockEndIndex , $ stackPointer , $ types );
234+ }
235+
236+ /**
237+ * @param \PHP_CodeSniffer\Files\File $phpCsFile
238+ * @param int $docBlockEndIndex
239+ * @param int $stackPointer
240+ * @param array<string> $types
241+ *
242+ * @return void
243+ */
244+ protected function handle (File $ phpCsFile , int $ docBlockEndIndex , int $ stackPointer , array $ types = []): void
245+ {
246+ $ tokens = $ phpCsFile ->getTokens ();
247+
248+ /** @var int $docBlockStartIndex */
249+ $ docBlockStartIndex = $ tokens [$ docBlockEndIndex ]['comment_opener ' ];
250+
251+ $ defaultValueType = $ this ->findDefaultValueType ($ phpCsFile , $ stackPointer );
252+
253+ $ varIndex = null ;
254+ for ($ i = $ docBlockStartIndex + 1 ; $ i < $ docBlockEndIndex ; $ i ++) {
255+ if ($ tokens [$ i ]['type ' ] !== 'T_DOC_COMMENT_TAG ' ) {
256+ continue ;
257+ }
258+ if (!in_array ($ tokens [$ i ]['content ' ], ['@var ' ], true )) {
259+ continue ;
260+ }
261+
262+ $ varIndex = $ i ;
263+ }
264+
265+ if (!$ varIndex ) {
266+ if ($ types ) {
267+ return ;
268+ }
269+
270+ $ this ->handleMissingVar ($ phpCsFile , $ docBlockEndIndex , $ docBlockStartIndex , $ defaultValueType );
271+
272+ return ;
273+ }
274+
275+ $ classNameIndex = $ varIndex + 2 ;
276+
277+ if ($ tokens [$ classNameIndex ]['type ' ] !== 'T_DOC_COMMENT_STRING ' ) {
278+ $ this ->handleMissingVarType ($ phpCsFile , $ varIndex , $ defaultValueType );
279+
280+ return ;
281+ }
282+
283+ $ content = $ tokens [$ classNameIndex ]['content ' ];
284+ if (str_contains ($ content , '{ ' ) || str_contains ($ content , '< ' )) {
285+ return ;
286+ }
287+
288+ $ appendix = '' ;
289+ $ spaceIndex = strpos ($ content , ' ' );
290+ if ($ spaceIndex ) {
291+ $ appendix = substr ($ content , $ spaceIndex );
292+ $ content = substr ($ content , 0 , $ spaceIndex );
293+ }
294+
295+ if (!$ content ) {
296+ $ error = 'Doc Block type for property annotation @var missing ' ;
297+ if ($ defaultValueType ) {
298+ $ error .= ', type ` ' . $ defaultValueType . '` detected ' ;
299+ }
300+ $ phpCsFile ->addError ($ error , $ stackPointer , 'VarTypeEmpty ' );
301+
302+ return ;
303+ }
304+
305+ $ comment = trim ($ appendix );
306+ if (mb_substr ($ comment , 0 , 1 ) === '$ ' ) {
307+ $ phpCsFile ->addError ('$var declaration only valid/needed inside inline doc blocks. ' , $ stackPointer , 'CommentInvalid ' );
308+ }
309+
310+ $ this ->handleDefaultValue ($ phpCsFile , $ stackPointer , $ defaultValueType , $ content , $ appendix , $ classNameIndex );
311+ $ this ->handleTypes ($ phpCsFile , $ stackPointer , $ types , $ content , $ appendix , $ classNameIndex );
312+ }
313+
314+ /**
315+ * @param \PHP_CodeSniffer\Files\File $phpCsFile
316+ * @param int $stackPointer
317+ * @param string|null $defaultValueType
318+ * @param string $content
319+ * @param string $appendix
320+ * @param int $classNameIndex
321+ *
322+ * @return void
323+ */
324+ protected function handleDefaultValue (
325+ File $ phpCsFile ,
326+ int $ stackPointer ,
327+ ?string $ defaultValueType ,
328+ string $ content ,
329+ string $ appendix ,
330+ int $ classNameIndex ,
331+ ): void {
332+ if ($ defaultValueType === null ) {
333+ return ;
334+ }
335+
336+ $ parts = explode ('| ' , $ content );
337+
338+ if (in_array ($ defaultValueType , $ parts , true )) {
339+ return ;
340+ }
341+ if ($ defaultValueType === 'array ' && ($ this ->containsTypeArray ($ parts ) || $ this ->containsTypeArray ($ parts , 'list ' ))) {
342+ return ;
343+ }
344+ if ($ defaultValueType === 'false ' && in_array ('bool ' , $ parts , true )) {
345+ return ;
346+ }
347+
348+ if ($ defaultValueType === 'false ' ) {
349+ $ defaultValueType = 'bool ' ;
350+ }
351+
352+ if (count ($ parts ) > 1 || $ defaultValueType === 'null ' ) {
353+ $ fix = $ phpCsFile ->addFixableError ('Doc Block type for property annotation @var incorrect, type ` ' . $ defaultValueType . '` missing ' , $ stackPointer , 'VarTypeMissing ' );
354+ if ($ fix ) {
355+ $ phpCsFile ->fixer ->replaceToken ($ classNameIndex , implode ('| ' , $ parts ) . '| ' . $ defaultValueType . $ appendix );
356+ }
357+
358+ return ;
359+ }
360+
361+ $ fix = $ phpCsFile ->addFixableError ('Doc Block type ` ' . $ content . '` for property annotation @var incorrect, type ` ' . $ defaultValueType . '` expected ' , $ stackPointer , 'VarTypeIncorrect ' );
362+ if ($ fix ) {
363+ $ phpCsFile ->fixer ->replaceToken ($ classNameIndex , $ defaultValueType . $ appendix );
364+ }
365+ }
366+
367+ /**
368+ * @param \PHP_CodeSniffer\Files\File $phpCsFile
369+ * @param int $stackPointer
370+ * @param array<string> $types
371+ * @param mixed $content
372+ * @param string $appendix
373+ * @param int $classNameIndex
374+ *
375+ * @return void
376+ */
377+ protected function handleTypes (File $ phpCsFile , int $ stackPointer , array $ types , mixed $ content , string $ appendix , int $ classNameIndex ): void
378+ {
379+ foreach ($ types as $ type ) {
380+ if (str_contains ($ content , $ type )) {
381+ continue ;
382+ }
383+
384+ $ fix = $ phpCsFile ->addFixableError ('Doc Block type ` ' . $ content . '` for property annotation @var incorrect, type ` ' . $ type . '` missing ' , $ stackPointer , 'VarTypeMissing ' );
385+ if ($ fix ) {
386+ $ phpCsFile ->fixer ->replaceToken ($ classNameIndex , $ content . '| ' . $ type . $ appendix );
387+ }
388+ }
389+ }
290390}
0 commit comments