@@ -47,23 +47,39 @@ public function process(File $file, $position)
47
47
}
48
48
49
49
list ($ functionStart , $ functionEnd ) = PhpcsHelpers::functionBoundaries ($ file , $ position );
50
- if (!$ functionStart < 0 || $ functionEnd <= 0 ) {
50
+
51
+ if (($ functionStart < 0 ) || ($ functionEnd <= 0 )) {
51
52
return ;
52
53
}
53
54
54
55
list (
55
56
$ hasNonVoidReturnType ,
56
57
$ hasVoidReturnType ,
57
58
$ hasNoReturnType ,
58
- $ hasNullable
59
+ $ hasNullableReturn ,
60
+ $ returnsGenerator
59
61
) = $ this ->returnTypeInfo ($ file , $ position );
60
62
61
63
list ($ nonVoidReturnCount , $ voidReturnCount , $ nullReturnCount ) = PhpcsHelpers::countReturns (
62
64
$ file ,
63
65
$ position
64
66
);
65
67
66
- if ($ hasNullable ) {
68
+ $ yieldCount = $ this ->countYield ($ functionStart , $ functionEnd , $ file );
69
+
70
+ if ($ yieldCount || $ returnsGenerator ) {
71
+ $ this ->maybeGeneratorErrors (
72
+ $ yieldCount ,
73
+ $ returnsGenerator ,
74
+ $ nonVoidReturnCount ,
75
+ $ file ,
76
+ $ position
77
+ );
78
+
79
+ return ;
80
+ }
81
+
82
+ if ($ hasNullableReturn ) {
67
83
$ voidReturnCount -= $ nullReturnCount ;
68
84
}
69
85
@@ -96,6 +112,7 @@ private function maybeErrors(
96
112
File $ file ,
97
113
int $ position
98
114
) {
115
+
99
116
if ($ hasNonVoidReturnType && ($ nonVoidReturnCount === 0 || $ voidReturnCount > 0 )) {
100
117
$ msg = 'Return type with ' ;
101
118
$ file ->addError (
@@ -134,25 +151,100 @@ private function maybeErrors(
134
151
}
135
152
}
136
153
154
+ /**
155
+ * @param int $yieldCount
156
+ * @param bool $returnsGenerator
157
+ * @param int $nonVoidReturnCount
158
+ * @param File $file
159
+ * @param int $position
160
+ */
161
+ private function maybeGeneratorErrors (
162
+ int $ yieldCount ,
163
+ bool $ returnsGenerator ,
164
+ int $ nonVoidReturnCount ,
165
+ File $ file ,
166
+ int $ position
167
+ ) {
168
+
169
+ if ($ nonVoidReturnCount > 1 ) {
170
+ $ file ->addWarning (
171
+ 'A generator should only contain a single return point. ' ,
172
+ $ position ,
173
+ 'InvalidGeneratorManyReturns '
174
+ );
175
+ }
176
+
177
+ if ($ yieldCount && $ returnsGenerator ) {
178
+ return ;
179
+ }
180
+
181
+ if (!$ yieldCount ) {
182
+ $ file ->addError (
183
+ 'Found a generator return type in non-yielding function. ' ,
184
+ $ position ,
185
+ 'GeneratorReturnTypeWithoutYield '
186
+ );
187
+
188
+ return ;
189
+ }
190
+
191
+ if (!$ nonVoidReturnCount ) {
192
+ $ file ->addWarning (
193
+ 'Found a function that yield values but missing Generator return type. ' ,
194
+ $ position ,
195
+ 'NoGeneratorReturnType '
196
+ );
197
+
198
+ return ;
199
+ }
200
+
201
+ $ returnType = $ this ->returnTypeContent ($ file , $ position );
202
+ if ($ returnType === 'Traversable ' || $ returnType === 'Iterator ' ) {
203
+ return ;
204
+ }
205
+
206
+ $ file ->addError (
207
+ 'Found a function that yield values but declare a return type different than Generator. ' ,
208
+ $ position ,
209
+ 'IncorrectReturnTypeForGenerator '
210
+ );
211
+ }
212
+
137
213
/**
138
214
* @param File $file
139
215
* @param int $functionPosition
140
- * @return array
216
+ * @return string
141
217
*/
142
- private function returnTypeInfo (File $ file , int $ functionPosition ): array
218
+ private function returnTypeContent (File $ file , int $ functionPosition ): string
143
219
{
144
220
$ tokens = $ file ->getTokens ();
145
- $ functionToken = $ tokens [$ functionPosition ];
146
-
147
221
$ returnTypeToken = $ file ->findNext (
148
222
[T_RETURN_TYPE ],
149
223
$ functionPosition + 3 , // 3: open parenthesis, close parenthesis, colon
150
- ($ functionToken ['scope_opener ' ] ?? 0 ) - 1
224
+ ($ tokens [ $ functionPosition ] ['scope_opener ' ] ?? 0 ) - 1
151
225
);
152
226
153
227
$ returnType = $ tokens [$ returnTypeToken ] ?? null ;
154
- if ($ returnType && $ returnType ['code ' ] !== T_RETURN_TYPE ) {
155
- return [false , false , true , false ];
228
+ if (!$ returnType || $ returnType ['code ' ] !== T_RETURN_TYPE ) {
229
+ return '' ;
230
+ }
231
+
232
+ return ltrim ($ returnType ['content ' ] ?? '' , '\\' );
233
+ }
234
+
235
+ /**
236
+ * @param File $file
237
+ * @param int $functionPosition
238
+ * @return array
239
+ */
240
+ private function returnTypeInfo (File $ file , int $ functionPosition ): array
241
+ {
242
+ $ tokens = $ file ->getTokens ();
243
+
244
+ $ returnTypeContent = $ this ->returnTypeContent ($ file , $ functionPosition );
245
+
246
+ if (!$ returnTypeContent ) {
247
+ return [false , false , true , false , false ];
156
248
}
157
249
158
250
$ start = $ tokens [$ functionPosition ]['parenthesis_closer ' ] + 1 ;
@@ -168,10 +260,11 @@ private function returnTypeInfo(File $file, int $functionPosition): array
168
260
}
169
261
}
170
262
171
- $ hasNonVoidReturnType = $ returnType ['content ' ] !== 'void ' ;
172
- $ hasVoidReturnType = $ returnType ['content ' ] === 'void ' ;
263
+ $ hasNonVoidReturnType = $ returnTypeContent !== 'void ' ;
264
+ $ hasVoidReturnType = $ returnTypeContent === 'void ' ;
265
+ $ returnsGenerator = $ returnTypeContent === 'Generator ' ;
173
266
174
- return [$ hasNonVoidReturnType , $ hasVoidReturnType , false , $ hasNullable ];
267
+ return [$ hasNonVoidReturnType , $ hasVoidReturnType , false , $ hasNullable, $ returnsGenerator ];
175
268
}
176
269
177
270
/**
@@ -214,4 +307,26 @@ private function areNullableReturnTypesSupported(): bool
214
307
215
308
return $ min && version_compare ($ min , '7.1 ' , '>= ' );
216
309
}
310
+
311
+ /**
312
+ * @param int $functionStart
313
+ * @param int $functionEnd
314
+ * @return int
315
+ */
316
+ private function countYield (int $ functionStart , int $ functionEnd , File $ file ): int
317
+ {
318
+ $ count = 0 ;
319
+ $ tokens = $ file ->getTokens ();
320
+ for ($ i = $ functionStart + 1 ; $ i < $ functionEnd ; $ i ++) {
321
+ if ($ tokens [$ i ]['code ' ] === T_CLOSURE ) {
322
+ $ i = $ tokens [$ i ]['scope_closer ' ];
323
+ continue ;
324
+ }
325
+ if ($ tokens [$ i ]['code ' ] === T_YIELD || $ tokens [$ i ]['code ' ] === T_YIELD_FROM ) {
326
+ $ count ++;
327
+ }
328
+ }
329
+
330
+ return $ count ;
331
+ }
217
332
}
0 commit comments