Skip to content

Commit 39950c7

Browse files
authored
Preparational refactoring for DataProviderDataRule
1 parent c54a269 commit 39950c7

File tree

5 files changed

+163
-77
lines changed

5 files changed

+163
-77
lines changed

extension.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ services:
4949
class: PHPStan\Rules\PHPUnit\CoversHelper
5050
-
5151
class: PHPStan\Rules\PHPUnit\AnnotationHelper
52+
-
53+
class: PHPStan\Rules\PHPUnit\PHPUnitVersionDetector
54+
5255
-
5356
class: PHPStan\Rules\PHPUnit\DataProviderHelper
5457
factory: @PHPStan\Rules\PHPUnit\DataProviderHelperFactory::create()

phpstan.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ includes:
77
- phpstan-baseline.neon
88

99
parameters:
10+
reportUnmatchedIgnoredErrors: false
11+
1012
excludePaths:
1113
- tests/*/data/*
1214
ignoreErrors:

src/Rules/PHPUnit/DataProviderHelper.php

Lines changed: 95 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\PHPUnit;
44

5+
use PhpParser\Comment\Doc;
56
use PhpParser\Modifiers;
67
use PhpParser\Node\Attribute;
78
use PhpParser\Node\Expr\ClassConstFetch;
@@ -19,25 +20,19 @@
1920
use PHPStan\Rules\IdentifierRuleError;
2021
use PHPStan\Rules\RuleErrorBuilder;
2122
use PHPStan\Type\FileTypeMapper;
23+
use ReflectionMethod;
2224
use function array_merge;
2325
use function count;
2426
use function explode;
27+
use function method_exists;
2528
use function preg_match;
2629
use function sprintf;
2730

2831
class DataProviderHelper
2932
{
3033

31-
/**
32-
* Reflection provider.
33-
*
34-
*/
3534
private ReflectionProvider $reflectionProvider;
3635

37-
/**
38-
* The file type mapper.
39-
*
40-
*/
4136
private FileTypeMapper $fileTypeMapper;
4237

4338
private 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
*/
6360
public function getDataProviderMethods(
6461
Scope $scope,
65-
ClassMethod $node,
62+
$node,
6663
ClassReflection $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

9268
if (!$this->phpunit10OrNewer) {
9369
return;
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
}

src/Rules/PHPUnit/DataProviderHelperFactory.php

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@
55
use PHPStan\Parser\Parser;
66
use PHPStan\Reflection\ReflectionProvider;
77
use PHPStan\Type\FileTypeMapper;
8-
use PHPUnit\Framework\TestCase;
9-
use function dirname;
10-
use function explode;
11-
use function file_get_contents;
12-
use function is_file;
13-
use function json_decode;
148

159
class DataProviderHelperFactory
1610
{
@@ -21,43 +15,24 @@ class DataProviderHelperFactory
2115

2216
private Parser $parser;
2317

18+
private PHPUnitVersionDetector $PHPUnitVersionDetector;
19+
2420
public function __construct(
2521
ReflectionProvider $reflectionProvider,
2622
FileTypeMapper $fileTypeMapper,
27-
Parser $parser
23+
Parser $parser,
24+
PHPUnitVersionDetector $PHPUnitVersionDetector
2825
)
2926
{
3027
$this->reflectionProvider = $reflectionProvider;
3128
$this->fileTypeMapper = $fileTypeMapper;
3229
$this->parser = $parser;
30+
$this->PHPUnitVersionDetector = $PHPUnitVersionDetector;
3331
}
3432

3533
public function create(): DataProviderHelper
3634
{
37-
$phpUnit10OrNewer = false;
38-
if ($this->reflectionProvider->hasClass(TestCase::class)) {
39-
$testCase = $this->reflectionProvider->getClass(TestCase::class);
40-
$file = $testCase->getFileName();
41-
if ($file !== null) {
42-
$phpUnitRoot = dirname($file, 3);
43-
$phpUnitComposer = $phpUnitRoot . '/composer.json';
44-
if (is_file($phpUnitComposer)) {
45-
$composerJson = @file_get_contents($phpUnitComposer);
46-
if ($composerJson !== false) {
47-
$json = json_decode($composerJson, true);
48-
$version = $json['extra']['branch-alias']['dev-main'] ?? null;
49-
if ($version !== null) {
50-
$majorVersion = (int) explode('.', $version)[0];
51-
if ($majorVersion >= 10) {
52-
$phpUnit10OrNewer = true;
53-
}
54-
}
55-
}
56-
}
57-
}
58-
}
59-
60-
return new DataProviderHelper($this->reflectionProvider, $this->fileTypeMapper, $this->parser, $phpUnit10OrNewer);
35+
return new DataProviderHelper($this->reflectionProvider, $this->fileTypeMapper, $this->parser, $this->PHPUnitVersionDetector->isPHPUnit10OrNewer());
6136
}
6237

6338
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PHPStan\Reflection\ReflectionProvider;
6+
use PHPUnit\Framework\TestCase;
7+
use function dirname;
8+
use function explode;
9+
use function file_get_contents;
10+
use function is_file;
11+
use function json_decode;
12+
13+
class PHPUnitVersionDetector
14+
{
15+
16+
private ?bool $is10OrNewer = null;
17+
18+
private ReflectionProvider $reflectionProvider;
19+
20+
public function __construct(ReflectionProvider $reflectionProvider)
21+
{
22+
$this->reflectionProvider = $reflectionProvider;
23+
}
24+
25+
public function isPHPUnit10OrNewer(): bool
26+
{
27+
if ($this->is10OrNewer !== null) {
28+
return $this->is10OrNewer;
29+
}
30+
31+
$this->is10OrNewer = false;
32+
if ($this->reflectionProvider->hasClass(TestCase::class)) {
33+
$testCase = $this->reflectionProvider->getClass(TestCase::class);
34+
$file = $testCase->getFileName();
35+
if ($file !== null) {
36+
$phpUnitRoot = dirname($file, 3);
37+
$phpUnitComposer = $phpUnitRoot . '/composer.json';
38+
if (is_file($phpUnitComposer)) {
39+
$composerJson = @file_get_contents($phpUnitComposer);
40+
if ($composerJson !== false) {
41+
$json = json_decode($composerJson, true);
42+
$version = $json['extra']['branch-alias']['dev-main'] ?? null;
43+
if ($version !== null) {
44+
$majorVersion = (int) explode('.', $version)[0];
45+
if ($majorVersion >= 10) {
46+
$this->is10OrNewer = true;
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
return $this->is10OrNewer;
55+
}
56+
57+
}

0 commit comments

Comments
 (0)