@@ -18,7 +18,7 @@ class FullyQualifiedClassNameInDocBlockSniff extends AbstractSniff {
18
18
*/
19
19
public static $ whitelistedTypes = [
20
20
'string ' , 'int ' , 'integer ' , 'float ' , 'bool ' , 'boolean ' , 'resource ' , 'null ' , 'void ' , 'callable ' ,
21
- 'array ' , 'mixed ' , 'object ' , 'false ' , 'true ' , 'self ' , 'static ' , '$this ' ,
21
+ 'array ' , 'iterable ' , ' mixed ' , 'object ' , 'false ' , 'true ' , 'self ' , 'static ' , '$this ' ,
22
22
];
23
23
24
24
/**
@@ -76,15 +76,16 @@ public function process(File $phpCsFile, $stackPointer) {
76
76
continue ;
77
77
}
78
78
79
- $ classNames = explode ('| ' , $ content );
80
- $ this ->fixClassNames ($ phpCsFile , $ classNameIndex , $ classNames , $ appendix );
79
+ $ types = $ this ->parseTypes ($ content );
80
+
81
+ $ this ->fixClassNames ($ phpCsFile , $ classNameIndex , $ types , $ appendix );
81
82
}
82
83
}
83
84
84
85
/**
85
86
* @param \PHP_CodeSniffer\Files\File $phpCsFile
86
87
* @param int $classNameIndex
87
- * @param array $classNames
88
+ * @param string[] $classNames
88
89
* @param string $appendix
89
90
*
90
91
* @return void
@@ -113,20 +114,35 @@ protected function fixClassNames(File $phpCsFile, $classNameIndex, array $classN
113
114
* @param int $classNameIndex
114
115
* @param string[] $classNames
115
116
*
116
- * @return array
117
+ * @return string[]
117
118
*/
118
119
protected function generateClassNameMap (File $ phpCsFile , $ classNameIndex , array &$ classNames ) {
119
120
$ result = [];
120
121
121
122
foreach ($ classNames as $ key => $ className ) {
122
- if (strpos ($ className , '\\' ) !== false ) {
123
- continue ;
124
- }
125
123
$ arrayOfObject = 0 ;
126
124
while (substr ($ className , -2 ) === '[] ' ) {
127
125
$ arrayOfObject ++;
128
126
$ className = substr ($ className , 0 , -2 );
129
127
}
128
+
129
+ if (preg_match ('#^\((.+)\)# ' , $ className , $ matches )) {
130
+ $ subClassNames = explode ('| ' , $ matches [1 ]);
131
+ $ newClassName = '( ' . $ this ->generateClassNameMapForUnionType ($ phpCsFile , $ classNameIndex , $ className , $ subClassNames ) . ') ' ;
132
+ if ($ newClassName === $ className ) {
133
+ continue ;
134
+ }
135
+
136
+ $ classNames [$ key ] = $ newClassName . ($ arrayOfObject ? str_repeat ('[] ' , $ arrayOfObject ) : '' );
137
+ $ result [$ className . ($ arrayOfObject ? str_repeat ('[] ' , $ arrayOfObject ) : '' )] = $ classNames [$ key ];
138
+
139
+ continue ;
140
+ }
141
+
142
+ if (strpos ($ className , '\\' ) !== false ) {
143
+ continue ;
144
+ }
145
+
130
146
if (in_array ($ className , static ::$ whitelistedTypes , true )) {
131
147
continue ;
132
148
}
@@ -226,30 +242,8 @@ protected function getNamespace(File $phpCsFile) {
226
242
227
243
/**
228
244
* @param \PHP_CodeSniffer\Files\File $phpCsFile
229
- * @param int $stackPointer
230
245
*
231
- * @return int|null Stackpointer value of docblock end tag, or null if cannot be found
232
- */
233
- protected function findRelatedDocBlock (File $ phpCsFile , $ stackPointer ) {
234
- $ tokens = $ phpCsFile ->getTokens ();
235
-
236
- $ line = $ tokens [$ stackPointer ]['line ' ];
237
- $ beginningOfLine = $ stackPointer ;
238
- while (!empty ($ tokens [$ beginningOfLine - 1 ]) && $ tokens [$ beginningOfLine - 1 ]['line ' ] === $ line ) {
239
- $ beginningOfLine --;
240
- }
241
-
242
- if (!empty ($ tokens [$ beginningOfLine - 2 ]) && $ tokens [$ beginningOfLine - 2 ]['type ' ] === 'T_DOC_COMMENT_CLOSE_TAG ' ) {
243
- return $ beginningOfLine - 2 ;
244
- }
245
-
246
- return null ;
247
- }
248
-
249
- /**
250
- * @param \PHP_CodeSniffer\Files\File $phpCsFile
251
- *
252
- * @return array
246
+ * @return string[]
253
247
*/
254
248
protected function parseUseStatements (File $ phpCsFile ) {
255
249
$ useStatements = [];
@@ -286,4 +280,61 @@ protected function parseUseStatements(File $phpCsFile) {
286
280
return $ useStatements ;
287
281
}
288
282
283
+ /**
284
+ * @param string $content
285
+ *
286
+ * @return string[]
287
+ */
288
+ protected function parseTypes ($ content ) {
289
+ preg_match_all ('#\(.+\)# ' , $ content , $ matches );
290
+ if (!$ matches [0 ]) {
291
+ return explode ('| ' , $ content );
292
+ }
293
+ $ unionTypes = $ matches [0 ];
294
+ $ map = [];
295
+ foreach ($ unionTypes as $ i => $ unionType ) {
296
+ $ content = str_replace ($ unionType , '{{t ' . $ i . '}} ' , $ content );
297
+ $ map ['{{t ' . $ i . '}} ' ] = $ unionType ;
298
+ }
299
+
300
+ $ types = explode ('| ' , $ content );
301
+ foreach ($ types as $ k => $ type ) {
302
+ $ types [$ k ] = str_replace (array_keys ($ map ), array_values ($ map ), $ type );
303
+ }
304
+
305
+ return $ types ;
306
+ }
307
+
308
+ /**
309
+ * @param \PHP_CodeSniffer\Files\File $phpCsFile
310
+ * @param int $classNameIndex
311
+ * @param string $className
312
+ * @param string[] $subClassNames
313
+ *
314
+ * @return string
315
+ */
316
+ protected function generateClassNameMapForUnionType (File $ phpCsFile , $ classNameIndex , $ className , array $ subClassNames ) {
317
+ foreach ($ subClassNames as $ i => $ subClassName ) {
318
+ if (strpos ($ subClassName , '\\' ) !== false ) {
319
+ continue ;
320
+ }
321
+
322
+ if (in_array ($ subClassName , static ::$ whitelistedTypes , true )) {
323
+ continue ;
324
+ }
325
+ $ useStatement = $ this ->findUseStatementForClassName ($ phpCsFile , $ subClassName );
326
+ if (!$ useStatement ) {
327
+ $ message = 'Invalid typehint `%s` ' ;
328
+ if (substr ($ subClassName , 0 , 1 ) === '$ ' ) {
329
+ $ message = 'The typehint seems to be missing for `%s` ' ;
330
+ }
331
+ $ phpCsFile ->addError (sprintf ($ message , $ subClassName ), $ classNameIndex , 'ClassNameInvalid ' );
332
+ continue ;
333
+ }
334
+ $ subClassNames [$ i ] = $ useStatement ;
335
+ }
336
+
337
+ return implode ('| ' , $ subClassNames );
338
+ }
339
+
289
340
}
0 commit comments