22
33namespace PHPStan \Rules \PHPUnit ;
44
5+ use PhpParser \Comment \Doc ;
56use PhpParser \Modifiers ;
67use PhpParser \Node \Attribute ;
78use PhpParser \Node \Expr \ClassConstFetch ;
1920use PHPStan \Rules \IdentifierRuleError ;
2021use PHPStan \Rules \RuleErrorBuilder ;
2122use PHPStan \Type \FileTypeMapper ;
23+ use ReflectionMethod ;
2224use function array_merge ;
2325use function count ;
2426use function explode ;
27+ use function method_exists ;
2528use function preg_match ;
2629use function sprintf ;
2730
2831class DataProviderHelper
2932{
3033
31- /**
32- * Reflection provider.
33- *
34- */
3534private ReflectionProvider $ reflectionProvider ;
3635
37- /**
38- * The file type mapper.
39- *
40- */
4136private FileTypeMapper $ fileTypeMapper ;
4237
4338private Parser $ parser ;
@@ -58,56 +53,23 @@ public function __construct(
5853}
5954
6055/**
56+ * @param ReflectionMethod|ClassMethod $node
57+ *
6158 * @return iterable<array{ClassReflection|null, string, int}>
6259 */
6360public function getDataProviderMethods (
6461Scope $ scope ,
65- ClassMethod $ node ,
62+ $ node ,
6663ClassReflection $ classReflection
6764): iterable
6865{
69- $ docComment = $ node ->getDocComment ();
70- if ($ docComment !== null ) {
71- $ methodPhpDoc = $ this ->fileTypeMapper ->getResolvedPhpDoc (
72- $ scope ->getFile (),
73- $ classReflection ->getName (),
74- $ scope ->isInTrait () ? $ scope ->getTraitReflection ()->getName () : null ,
75- $ node ->name ->toString (),
76- $ docComment ->getText (),
77- );
78- foreach ($ this ->getDataProviderAnnotations ($ methodPhpDoc ) as $ annotation ) {
79- $ dataProviderValue = $ this ->getDataProviderAnnotationValue ($ annotation );
80- if ($ dataProviderValue === null ) {
81- // Missing value is already handled in NoMissingSpaceInMethodAnnotationRule
82- continue ;
83- }
84-
85- $ dataProviderMethod = $ this ->parseDataProviderAnnotationValue ($ scope , $ dataProviderValue );
86- $ dataProviderMethod [] = $ node ->getStartLine ();
87-
88- yield $ dataProviderValue => $ dataProviderMethod ;
89- }
90- }
66+ yield from $ this ->yieldDataProviderAnnotations ($ node , $ scope , $ classReflection );
9167
9268if (!$ this ->phpunit10OrNewer ) {
9369return ;
9470}
9571
96- foreach ($ node ->attrGroups as $ attrGroup ) {
97- foreach ($ attrGroup ->attrs as $ attr ) {
98- $ dataProviderMethod = null ;
99- if ($ attr ->name ->toLowerString () === 'phpunit \\framework \\attributes \\dataprovider ' ) {
100- $ dataProviderMethod = $ this ->parseDataProviderAttribute ($ attr , $ classReflection );
101- } elseif ($ attr ->name ->toLowerString () === 'phpunit \\framework \\attributes \\dataproviderexternal ' ) {
102- $ dataProviderMethod = $ this ->parseDataProviderExternalAttribute ($ attr );
103- }
104- if ($ dataProviderMethod === null ) {
105- continue ;
106- }
107-
108- yield from $ dataProviderMethod ;
109- }
110- }
72+ yield from $ this ->yieldDataProviderAttributes ($ node , $ classReflection );
11173}
11274
11375/**
@@ -306,4 +268,91 @@ private function parseDataProviderAttribute(Attribute $attribute, ClassReflectio
306268];
307269}
308270
271+ /**
272+ * @param ReflectionMethod|ClassMethod $node
273+ *
274+ * @return iterable<array{ClassReflection|null, string, int}>
275+ */
276+ private function yieldDataProviderAttributes ($ node , ClassReflection $ classReflection ): iterable
277+ {
278+ if (
279+ $ node instanceof ReflectionMethod
280+ ) {
281+ /** @phpstan-ignore function.alreadyNarrowedType */
282+ if (!method_exists ($ node , 'getAttributes ' )) {
283+ return ;
284+ }
285+
286+ foreach ($ node ->getAttributes ('PHPUnit\Framework\Attributes\DataProvider ' ) as $ attr ) {
287+ $ args = $ attr ->getArguments ();
288+ if (count ($ args ) !== 1 ) {
289+ continue ;
290+ }
291+
292+ $ startLine = $ node ->getStartLine ();
293+ if ($ startLine === false ) {
294+ $ startLine = -1 ;
295+ }
296+
297+ yield [$ classReflection , $ args [0 ], $ startLine ];
298+ }
299+
300+ return ;
301+ }
302+
303+ foreach ($ node ->attrGroups as $ attrGroup ) {
304+ foreach ($ attrGroup ->attrs as $ attr ) {
305+ $ dataProviderMethod = null ;
306+ if ($ attr ->name ->toLowerString () === 'phpunit \\framework \\attributes \\dataprovider ' ) {
307+ $ dataProviderMethod = $ this ->parseDataProviderAttribute ($ attr , $ classReflection );
308+ } elseif ($ attr ->name ->toLowerString () === 'phpunit \\framework \\attributes \\dataproviderexternal ' ) {
309+ $ dataProviderMethod = $ this ->parseDataProviderExternalAttribute ($ attr );
310+ }
311+ if ($ dataProviderMethod === null ) {
312+ continue ;
313+ }
314+
315+ yield from $ dataProviderMethod ;
316+ }
317+ }
318+ }
319+
320+ /**
321+ * @param ReflectionMethod|ClassMethod $node
322+ *
323+ * @return iterable<array{ClassReflection|null, string, int}>
324+ */
325+ private function yieldDataProviderAnnotations ($ node , Scope $ scope , ClassReflection $ classReflection ): iterable
326+ {
327+ $ docComment = $ node ->getDocComment ();
328+ if ($ docComment === null || $ docComment === false ) {
329+ return ;
330+ }
331+
332+ $ methodPhpDoc = $ this ->fileTypeMapper ->getResolvedPhpDoc (
333+ $ scope ->getFile (),
334+ $ classReflection ->getName (),
335+ $ scope ->isInTrait () ? $ scope ->getTraitReflection ()->getName () : null ,
336+ $ node instanceof ClassMethod ? $ node ->name ->toString () : $ node ->getName (),
337+ $ docComment instanceof Doc ? $ docComment ->getText () : $ docComment ,
338+ );
339+ foreach ($ this ->getDataProviderAnnotations ($ methodPhpDoc ) as $ annotation ) {
340+ $ dataProviderValue = $ this ->getDataProviderAnnotationValue ($ annotation );
341+ if ($ dataProviderValue === null ) {
342+ // Missing value is already handled in NoMissingSpaceInMethodAnnotationRule
343+ continue ;
344+ }
345+
346+ $ startLine = $ node ->getStartLine ();
347+ if ($ startLine === false ) {
348+ $ startLine = -1 ;
349+ }
350+
351+ $ dataProviderMethod = $ this ->parseDataProviderAnnotationValue ($ scope , $ dataProviderValue );
352+ $ dataProviderMethod [] = $ startLine ;
353+
354+ yield $ dataProviderValue => $ dataProviderMethod ;
355+ }
356+ }
357+
309358}
0 commit comments