Skip to content

Commit b9827cf

Browse files
authored
Discover data providers from other classes
1 parent 008f5da commit b9827cf

File tree

3 files changed

+70
-18
lines changed

3 files changed

+70
-18
lines changed

src/Rules/PHPUnit/DataProviderHelper.php

+50-13
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,32 @@
55
use PHPStan\Analyser\Scope;
66
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
77
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
8+
use PHPStan\Reflection\ClassReflection;
89
use PHPStan\Reflection\MissingMethodFromReflectionException;
10+
use PHPStan\Reflection\ReflectionProvider;
911
use PHPStan\Rules\RuleError;
1012
use PHPStan\Rules\RuleErrorBuilder;
1113
use function array_merge;
14+
use function count;
15+
use function explode;
1216
use function preg_match;
1317
use function sprintf;
1418

1519
class DataProviderHelper
1620
{
1721

22+
/**
23+
* Reflection provider.
24+
*
25+
* @var ReflectionProvider
26+
*/
27+
private $reflectionProvider;
28+
29+
public function __construct(ReflectionProvider $reflectionProvider)
30+
{
31+
$this->reflectionProvider = $reflectionProvider;
32+
}
33+
1834
/**
1935
* @return array<PhpDocTagNode>
2036
*/
@@ -48,57 +64,61 @@ public function processDataProvider(
4864
bool $deprecationRulesInstalled
4965
): array
5066
{
51-
$dataProviderName = $this->getDataProviderName($phpDocTag);
52-
if ($dataProviderName === null) {
53-
// Missing name is already handled in NoMissingSpaceInMethodAnnotationRule
67+
$dataProviderValue = $this->getDataProviderValue($phpDocTag);
68+
if ($dataProviderValue === null) {
69+
// Missing value is already handled in NoMissingSpaceInMethodAnnotationRule
5470
return [];
5571
}
5672

57-
$classReflection = $scope->getClassReflection();
73+
[$classReflection, $method] = $this->parseDataProviderValue($scope, $dataProviderValue);
5874
if ($classReflection === null) {
59-
// Should not happen
60-
return [];
75+
$error = RuleErrorBuilder::message(sprintf(
76+
'@dataProvider %s related class not found.',
77+
$dataProviderValue
78+
))->build();
79+
80+
return [$error];
6181
}
6282

6383
try {
64-
$dataProviderMethodReflection = $classReflection->getNativeMethod($dataProviderName);
84+
$dataProviderMethodReflection = $classReflection->getNativeMethod($method);
6585
} catch (MissingMethodFromReflectionException $missingMethodFromReflectionException) {
6686
$error = RuleErrorBuilder::message(sprintf(
6787
'@dataProvider %s related method not found.',
68-
$dataProviderName
88+
$dataProviderValue
6989
))->build();
7090

7191
return [$error];
7292
}
7393

7494
$errors = [];
7595

76-
if ($checkFunctionNameCase && $dataProviderName !== $dataProviderMethodReflection->getName()) {
96+
if ($checkFunctionNameCase && $method !== $dataProviderMethodReflection->getName()) {
7797
$errors[] = RuleErrorBuilder::message(sprintf(
7898
'@dataProvider %s related method is used with incorrect case: %s.',
79-
$dataProviderName,
99+
$dataProviderValue,
80100
$dataProviderMethodReflection->getName()
81101
))->build();
82102
}
83103

84104
if (!$dataProviderMethodReflection->isPublic()) {
85105
$errors[] = RuleErrorBuilder::message(sprintf(
86106
'@dataProvider %s related method must be public.',
87-
$dataProviderName
107+
$dataProviderValue
88108
))->build();
89109
}
90110

91111
if ($deprecationRulesInstalled && !$dataProviderMethodReflection->isStatic()) {
92112
$errors[] = RuleErrorBuilder::message(sprintf(
93113
'@dataProvider %s related method must be static.',
94-
$dataProviderName
114+
$dataProviderValue
95115
))->build();
96116
}
97117

98118
return $errors;
99119
}
100120

101-
private function getDataProviderName(PhpDocTagNode $phpDocTag): ?string
121+
private function getDataProviderValue(PhpDocTagNode $phpDocTag): ?string
102122
{
103123
if (preg_match('/^[^ \t]+/', (string) $phpDocTag->value, $matches) !== 1) {
104124
return null;
@@ -107,4 +127,21 @@ private function getDataProviderName(PhpDocTagNode $phpDocTag): ?string
107127
return $matches[0];
108128
}
109129

130+
/**
131+
* @return array{ClassReflection|null, string}
132+
*/
133+
private function parseDataProviderValue(Scope $scope, string $dataProviderValue): array
134+
{
135+
$parts = explode('::', $dataProviderValue, 2);
136+
if (count($parts) <= 1) {
137+
return [$scope->getClassReflection(), $dataProviderValue];
138+
}
139+
140+
if ($this->reflectionProvider->hasClass($parts[0])) {
141+
return [$this->reflectionProvider->getClass($parts[0]), $parts[1]];
142+
}
143+
144+
return [null, $dataProviderValue];
145+
}
146+
110147
}

tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php

+11-5
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ class DataProviderDeclarationRuleTest extends RuleTestCase
1414

1515
protected function getRule(): Rule
1616
{
17+
$reflection = $this->createReflectionProvider();
18+
1719
return new DataProviderDeclarationRule(
18-
new DataProviderHelper(),
20+
new DataProviderHelper($reflection),
1921
self::getContainer()->getByType(FileTypeMapper::class),
2022
true,
2123
true
@@ -27,19 +29,23 @@ public function testRule(): void
2729
$this->analyse([__DIR__ . '/data/data-provider-declaration.php'], [
2830
[
2931
'@dataProvider providebaz related method is used with incorrect case: provideBaz.',
30-
13,
32+
14,
3133
],
3234
[
3335
'@dataProvider provideQux related method must be static.',
34-
13,
36+
14,
3537
],
3638
[
3739
'@dataProvider provideQuux related method must be public.',
38-
13,
40+
14,
3941
],
4042
[
4143
'@dataProvider provideNonExisting related method not found.',
42-
66,
44+
68,
45+
],
46+
[
47+
'@dataProvider NonExisting::provideNonExisting related class not found.',
48+
68,
4349
],
4450
]);
4551
}

tests/Rules/PHPUnit/data/data-provider-declaration.php

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class FooTestCase extends \PHPUnit\Framework\TestCase
99
* @dataProvider providebaz
1010
* @dataProvider provideQux
1111
* @dataProvider provideQuux
12+
* @dataProvider \ExampleTestCase\BarTestCase::provideToOtherClass
1213
*/
1314
public function testIsNotFoo(string $subject): void
1415
{
@@ -61,10 +62,18 @@ class BarTestCase extends \PHPUnit\Framework\TestCase
6162

6263
/**
6364
* @dataProvider provideNonExisting
65+
* @dataProvider NonExisting::provideNonExisting
6466
* @dataProvider provideCorge
6567
*/
6668
public function testIsNotBar(string $subject): void
6769
{
6870
self::assertNotSame('bar', $subject);
6971
}
72+
73+
public static function provideToOtherClass(): iterable
74+
{
75+
return [
76+
['toOtherClass'],
77+
];
78+
}
7079
}

0 commit comments

Comments
 (0)