Skip to content

Commit 5c89d74

Browse files
authored
Ignore missingType.iterableValue for data-providers
1 parent 1fbc321 commit 5c89d74

File tree

9 files changed

+157
-10
lines changed

9 files changed

+157
-10
lines changed

extension.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
parameters:
22
phpunit:
33
convertUnionToIntersectionType: true
4+
checkDataProviderData: %featureToggles.bleedingEdge%
45
additionalConstructors:
56
- PHPUnit\Framework\TestCase::setUp
67
earlyTerminatingMethodCalls:
@@ -24,6 +25,7 @@ parameters:
2425
parametersSchema:
2526
phpunit: structure([
2627
convertUnionToIntersectionType: bool()
28+
checkDataProviderData: bool(),
2729
])
2830

2931
services:
@@ -67,6 +69,11 @@ services:
6769
arguments:
6870
parser: @defaultAnalysisParser
6971

72+
-
73+
class: PHPStan\Type\PHPUnit\DataProviderReturnTypeIgnoreExtension
74+
7075
conditionalTags:
7176
PHPStan\PhpDoc\PHPUnit\MockObjectTypeNodeResolverExtension:
7277
phpstan.phpDoc.typeNodeResolverExtension: %phpunit.convertUnionToIntersectionType%
78+
PHPStan\Type\PHPUnit\DataProviderReturnTypeIgnoreExtension:
79+
phpstan.ignoreErrorExtension: %phpunit.checkDataProviderData%

rules.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ conditionalTags:
1414
phpstan.rules.rule: [%strictRulesInstalled%, %featureToggles.bleedingEdge%]
1515

1616
PHPStan\Rules\PHPUnit\DataProviderDataRule:
17-
phpstan.rules.rule: %featureToggles.bleedingEdge%
17+
phpstan.rules.rule: %phpunit.checkDataProviderData%
1818

1919
services:
2020
-

src/Rules/PHPUnit/DataProviderDataRule.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Rules\Rule;
99
use PHPStan\Type\ObjectType;
1010
use PHPStan\Type\Type;
11-
use PHPUnit\Framework\TestCase;
1211
use function array_slice;
1312
use function count;
1413
use function max;
@@ -62,10 +61,7 @@ public function processNode(Node $node, Scope $scope): array
6261

6362
$method = $scope->getFunction();
6463
$classReflection = $scope->getClassReflection();
65-
if (
66-
$classReflection === null
67-
|| !$classReflection->is(TestCase::class)
68-
) {
64+
if ($classReflection === null) {
6965
return [];
7066
}
7167

src/Rules/PHPUnit/DataProviderHelper.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,23 @@ public function __construct(
5353
}
5454

5555
/**
56-
* @param ReflectionMethod|ClassMethod $node
56+
* @param ReflectionMethod|ClassMethod $testMethod
5757
*
5858
* @return iterable<array{ClassReflection|null, string, int}>
5959
*/
6060
public function getDataProviderMethods(
6161
Scope $scope,
62-
$node,
62+
$testMethod,
6363
ClassReflection $classReflection
6464
): iterable
6565
{
66-
yield from $this->yieldDataProviderAnnotations($node, $scope, $classReflection);
66+
yield from $this->yieldDataProviderAnnotations($testMethod, $scope, $classReflection);
6767

6868
if (!$this->phpunit10OrNewer) {
6969
return;
7070
}
7171

72-
yield from $this->yieldDataProviderAttributes($node, $classReflection);
72+
yield from $this->yieldDataProviderAttributes($testMethod, $classReflection);
7373
}
7474

7575
/**

src/Rules/PHPUnit/TestMethodsHelper.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
77
use PHPStan\Reflection\ClassReflection;
88
use PHPStan\Type\FileTypeMapper;
9+
use PHPUnit\Framework\TestCase;
910
use ReflectionMethod;
1011
use function str_starts_with;
1112
use function strtolower;
@@ -31,6 +32,10 @@ public function __construct(
3132
*/
3233
public function getTestMethods(ClassReflection $classReflection, Scope $scope): array
3334
{
35+
if (!$classReflection->is(TestCase::class)) {
36+
return [];
37+
}
38+
3439
$testMethods = [];
3540
foreach ($classReflection->getNativeReflection()->getMethods() as $reflectionMethod) {
3641
if (!$reflectionMethod->isPublic()) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\PHPUnit;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Error;
7+
use PHPStan\Analyser\IgnoreErrorExtension;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\PHPUnit\DataProviderHelper;
10+
use PHPStan\Rules\PHPUnit\TestMethodsHelper;
11+
12+
final class DataProviderReturnTypeIgnoreExtension implements IgnoreErrorExtension
13+
{
14+
15+
private TestMethodsHelper $testMethodsHelper;
16+
17+
private DataProviderHelper $dataProviderHelper;
18+
19+
public function __construct(
20+
TestMethodsHelper $testMethodsHelper,
21+
DataProviderHelper $dataProviderHelper
22+
)
23+
{
24+
$this->testMethodsHelper = $testMethodsHelper;
25+
$this->dataProviderHelper = $dataProviderHelper;
26+
}
27+
28+
public function shouldIgnore(Error $error, Node $node, Scope $scope): bool
29+
{
30+
if ($error->getIdentifier() !== 'missingType.iterableValue') {
31+
return false;
32+
}
33+
34+
if (!$scope->isInClass()) {
35+
return false;
36+
}
37+
$classReflection = $scope->getClassReflection();
38+
39+
$methodReflection = $scope->getFunction();
40+
if ($methodReflection === null) {
41+
return false;
42+
}
43+
44+
$testMethods = $this->testMethodsHelper->getTestMethods($classReflection, $scope);
45+
foreach ($testMethods as $testMethod) {
46+
foreach ($this->dataProviderHelper->getDataProviderMethods($scope, $testMethod, $classReflection) as [, $providerMethodName]) {
47+
if ($providerMethodName === $methodReflection->getName()) {
48+
return true;
49+
}
50+
}
51+
}
52+
53+
return false;
54+
}
55+
56+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\PHPUnit;
4+
5+
use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<MissingMethodReturnTypehintRule>
11+
*/
12+
class DataProviderReturnTypeIgnoreExtensionTest extends RuleTestCase {
13+
protected function getRule(): Rule
14+
{
15+
/** @phpstan-ignore phpstanApi.classConstant */
16+
$rule = self::getContainer()->getByType(MissingMethodReturnTypehintRule::class);
17+
18+
return $rule;
19+
}
20+
21+
public function testRule(): void
22+
{
23+
$this->analyse([__DIR__ . '/data/data-provider-iterable-value.php'], [
24+
[
25+
'Method DataProviderIterableValueTest\Foo::notADataProvider() return type has no value type specified in iterable type iterable.',
26+
32,
27+
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type'
28+
],
29+
]);
30+
}
31+
32+
static public function getAdditionalConfigFiles(): array
33+
{
34+
return [
35+
__DIR__ . '/data/data-provider-iterable-value.neon'
36+
];
37+
}
38+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
parameters:
2+
phpunit:
3+
checkDataProviderData: true
4+
5+
includes:
6+
- ../../../../extension.neon
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace DataProviderIterableValueTest;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
class Foo extends TestCase {
8+
/**
9+
* @dataProvider dataProvider
10+
* @dataProvider dataProvider2
11+
*/
12+
public function testFoo():void {
13+
14+
}
15+
16+
public function dataProvider(): iterable {
17+
return [
18+
[1, 2],
19+
[3, 4],
20+
[5, 6],
21+
];
22+
}
23+
24+
public function dataProvider2(): iterable {
25+
$i = rand(0, 10);
26+
27+
return [
28+
[$i, 2],
29+
];
30+
}
31+
32+
public function notADataProvider(): iterable {
33+
return [
34+
[1, 2],
35+
[3, 4],
36+
[5, 6],
37+
];
38+
}
39+
}

0 commit comments

Comments
 (0)