diff --git a/extension.neon b/extension.neon index c6ce9bf4..dcf43818 100644 --- a/extension.neon +++ b/extension.neon @@ -1,6 +1,7 @@ parameters: phpunit: convertUnionToIntersectionType: true + checkDataProviderData: %featureToggles.bleedingEdge% additionalConstructors: - PHPUnit\Framework\TestCase::setUp earlyTerminatingMethodCalls: @@ -24,6 +25,7 @@ parameters: parametersSchema: phpunit: structure([ convertUnionToIntersectionType: bool() + checkDataProviderData: bool(), ]) services: @@ -67,6 +69,11 @@ services: arguments: parser: @defaultAnalysisParser + - + class: PHPStan\Type\PHPUnit\DataProviderReturnTypeIgnoreExtension + conditionalTags: PHPStan\PhpDoc\PHPUnit\MockObjectTypeNodeResolverExtension: phpstan.phpDoc.typeNodeResolverExtension: %phpunit.convertUnionToIntersectionType% + PHPStan\Type\PHPUnit\DataProviderReturnTypeIgnoreExtension: + phpstan.ignoreErrorExtension: %phpunit.checkDataProviderData% diff --git a/rules.neon b/rules.neon index 63e10b47..8272f47a 100644 --- a/rules.neon +++ b/rules.neon @@ -14,7 +14,7 @@ conditionalTags: phpstan.rules.rule: [%strictRulesInstalled%, %featureToggles.bleedingEdge%] PHPStan\Rules\PHPUnit\DataProviderDataRule: - phpstan.rules.rule: %featureToggles.bleedingEdge% + phpstan.rules.rule: %phpunit.checkDataProviderData% services: - diff --git a/src/Rules/PHPUnit/DataProviderDataRule.php b/src/Rules/PHPUnit/DataProviderDataRule.php index 45a7fa16..6f250fee 100644 --- a/src/Rules/PHPUnit/DataProviderDataRule.php +++ b/src/Rules/PHPUnit/DataProviderDataRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPUnit\Framework\TestCase; use function array_slice; use function count; use function max; @@ -62,10 +61,7 @@ public function processNode(Node $node, Scope $scope): array $method = $scope->getFunction(); $classReflection = $scope->getClassReflection(); - if ( - $classReflection === null - || !$classReflection->is(TestCase::class) - ) { + if ($classReflection === null) { return []; } diff --git a/src/Rules/PHPUnit/DataProviderHelper.php b/src/Rules/PHPUnit/DataProviderHelper.php index 64137c76..b468dfcb 100644 --- a/src/Rules/PHPUnit/DataProviderHelper.php +++ b/src/Rules/PHPUnit/DataProviderHelper.php @@ -53,23 +53,23 @@ public function __construct( } /** - * @param ReflectionMethod|ClassMethod $node + * @param ReflectionMethod|ClassMethod $testMethod * * @return iterable */ public function getDataProviderMethods( Scope $scope, - $node, + $testMethod, ClassReflection $classReflection ): iterable { - yield from $this->yieldDataProviderAnnotations($node, $scope, $classReflection); + yield from $this->yieldDataProviderAnnotations($testMethod, $scope, $classReflection); if (!$this->phpunit10OrNewer) { return; } - yield from $this->yieldDataProviderAttributes($node, $classReflection); + yield from $this->yieldDataProviderAttributes($testMethod, $classReflection); } /** diff --git a/src/Rules/PHPUnit/TestMethodsHelper.php b/src/Rules/PHPUnit/TestMethodsHelper.php index d0984442..67b888e7 100644 --- a/src/Rules/PHPUnit/TestMethodsHelper.php +++ b/src/Rules/PHPUnit/TestMethodsHelper.php @@ -6,6 +6,7 @@ use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\FileTypeMapper; +use PHPUnit\Framework\TestCase; use ReflectionMethod; use function str_starts_with; use function strtolower; @@ -31,6 +32,10 @@ public function __construct( */ public function getTestMethods(ClassReflection $classReflection, Scope $scope): array { + if (!$classReflection->is(TestCase::class)) { + return []; + } + $testMethods = []; foreach ($classReflection->getNativeReflection()->getMethods() as $reflectionMethod) { if (!$reflectionMethod->isPublic()) { diff --git a/src/Type/PHPUnit/DataProviderReturnTypeIgnoreExtension.php b/src/Type/PHPUnit/DataProviderReturnTypeIgnoreExtension.php new file mode 100644 index 00000000..be6af678 --- /dev/null +++ b/src/Type/PHPUnit/DataProviderReturnTypeIgnoreExtension.php @@ -0,0 +1,56 @@ +testMethodsHelper = $testMethodsHelper; + $this->dataProviderHelper = $dataProviderHelper; + } + + public function shouldIgnore(Error $error, Node $node, Scope $scope): bool + { + if ($error->getIdentifier() !== 'missingType.iterableValue') { + return false; + } + + if (!$scope->isInClass()) { + return false; + } + $classReflection = $scope->getClassReflection(); + + $methodReflection = $scope->getFunction(); + if ($methodReflection === null) { + return false; + } + + $testMethods = $this->testMethodsHelper->getTestMethods($classReflection, $scope); + foreach ($testMethods as $testMethod) { + foreach ($this->dataProviderHelper->getDataProviderMethods($scope, $testMethod, $classReflection) as [, $providerMethodName]) { + if ($providerMethodName === $methodReflection->getName()) { + return true; + } + } + } + + return false; + } + +} diff --git a/tests/Type/PHPUnit/DataProviderReturnTypeIgnoreExtensionTest.php b/tests/Type/PHPUnit/DataProviderReturnTypeIgnoreExtensionTest.php new file mode 100644 index 00000000..fb5b927a --- /dev/null +++ b/tests/Type/PHPUnit/DataProviderReturnTypeIgnoreExtensionTest.php @@ -0,0 +1,38 @@ + + */ +class DataProviderReturnTypeIgnoreExtensionTest extends RuleTestCase { + protected function getRule(): Rule + { + /** @phpstan-ignore phpstanApi.classConstant */ + $rule = self::getContainer()->getByType(MissingMethodReturnTypehintRule::class); + + return $rule; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/data-provider-iterable-value.php'], [ + [ + 'Method DataProviderIterableValueTest\Foo::notADataProvider() return type has no value type specified in iterable type iterable.', + 32, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type' + ], + ]); + } + + static public function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/data/data-provider-iterable-value.neon' + ]; + } +} diff --git a/tests/Type/PHPUnit/data/data-provider-iterable-value.neon b/tests/Type/PHPUnit/data/data-provider-iterable-value.neon new file mode 100644 index 00000000..eed12a5b --- /dev/null +++ b/tests/Type/PHPUnit/data/data-provider-iterable-value.neon @@ -0,0 +1,6 @@ +parameters: + phpunit: + checkDataProviderData: true + +includes: + - ../../../../extension.neon diff --git a/tests/Type/PHPUnit/data/data-provider-iterable-value.php b/tests/Type/PHPUnit/data/data-provider-iterable-value.php new file mode 100644 index 00000000..613d3b14 --- /dev/null +++ b/tests/Type/PHPUnit/data/data-provider-iterable-value.php @@ -0,0 +1,39 @@ +