diff --git a/src/Rules/PHPUnit/DataProviderHelper.php b/src/Rules/PHPUnit/DataProviderHelper.php index e88f293..a224cb7 100644 --- a/src/Rules/PHPUnit/DataProviderHelper.php +++ b/src/Rules/PHPUnit/DataProviderHelper.php @@ -5,16 +5,32 @@ use PHPStan\Analyser\Scope; use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MissingMethodFromReflectionException; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; use function array_merge; +use function count; +use function explode; use function preg_match; use function sprintf; class DataProviderHelper { + /** + * Reflection provider. + * + * @var ReflectionProvider + */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + /** * @return array */ @@ -48,24 +64,28 @@ public function processDataProvider( bool $deprecationRulesInstalled ): array { - $dataProviderName = $this->getDataProviderName($phpDocTag); - if ($dataProviderName === null) { - // Missing name is already handled in NoMissingSpaceInMethodAnnotationRule + $dataProviderValue = $this->getDataProviderValue($phpDocTag); + if ($dataProviderValue === null) { + // Missing value is already handled in NoMissingSpaceInMethodAnnotationRule return []; } - $classReflection = $scope->getClassReflection(); + [$classReflection, $method] = $this->parseDataProviderValue($scope, $dataProviderValue); if ($classReflection === null) { - // Should not happen - return []; + $error = RuleErrorBuilder::message(sprintf( + '@dataProvider %s related class not found.', + $dataProviderValue + ))->build(); + + return [$error]; } try { - $dataProviderMethodReflection = $classReflection->getNativeMethod($dataProviderName); + $dataProviderMethodReflection = $classReflection->getNativeMethod($method); } catch (MissingMethodFromReflectionException $missingMethodFromReflectionException) { $error = RuleErrorBuilder::message(sprintf( '@dataProvider %s related method not found.', - $dataProviderName + $dataProviderValue ))->build(); return [$error]; @@ -73,10 +93,10 @@ public function processDataProvider( $errors = []; - if ($checkFunctionNameCase && $dataProviderName !== $dataProviderMethodReflection->getName()) { + if ($checkFunctionNameCase && $method !== $dataProviderMethodReflection->getName()) { $errors[] = RuleErrorBuilder::message(sprintf( '@dataProvider %s related method is used with incorrect case: %s.', - $dataProviderName, + $dataProviderValue, $dataProviderMethodReflection->getName() ))->build(); } @@ -84,21 +104,21 @@ public function processDataProvider( if (!$dataProviderMethodReflection->isPublic()) { $errors[] = RuleErrorBuilder::message(sprintf( '@dataProvider %s related method must be public.', - $dataProviderName + $dataProviderValue ))->build(); } if ($deprecationRulesInstalled && !$dataProviderMethodReflection->isStatic()) { $errors[] = RuleErrorBuilder::message(sprintf( '@dataProvider %s related method must be static.', - $dataProviderName + $dataProviderValue ))->build(); } return $errors; } - private function getDataProviderName(PhpDocTagNode $phpDocTag): ?string + private function getDataProviderValue(PhpDocTagNode $phpDocTag): ?string { if (preg_match('/^[^ \t]+/', (string) $phpDocTag->value, $matches) !== 1) { return null; @@ -107,4 +127,21 @@ private function getDataProviderName(PhpDocTagNode $phpDocTag): ?string return $matches[0]; } + /** + * @return array{ClassReflection|null, string} + */ + private function parseDataProviderValue(Scope $scope, string $dataProviderValue): array + { + $parts = explode('::', $dataProviderValue, 2); + if (count($parts) <= 1) { + return [$scope->getClassReflection(), $dataProviderValue]; + } + + if ($this->reflectionProvider->hasClass($parts[0])) { + return [$this->reflectionProvider->getClass($parts[0]), $parts[1]]; + } + + return [null, $dataProviderValue]; + } + } diff --git a/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php b/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php index cfd27ae..04dad8d 100644 --- a/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php @@ -14,8 +14,10 @@ class DataProviderDeclarationRuleTest extends RuleTestCase protected function getRule(): Rule { + $reflection = $this->createReflectionProvider(); + return new DataProviderDeclarationRule( - new DataProviderHelper(), + new DataProviderHelper($reflection), self::getContainer()->getByType(FileTypeMapper::class), true, true @@ -27,19 +29,23 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/data-provider-declaration.php'], [ [ '@dataProvider providebaz related method is used with incorrect case: provideBaz.', - 13, + 14, ], [ '@dataProvider provideQux related method must be static.', - 13, + 14, ], [ '@dataProvider provideQuux related method must be public.', - 13, + 14, ], [ '@dataProvider provideNonExisting related method not found.', - 66, + 68, + ], + [ + '@dataProvider NonExisting::provideNonExisting related class not found.', + 68, ], ]); } diff --git a/tests/Rules/PHPUnit/data/data-provider-declaration.php b/tests/Rules/PHPUnit/data/data-provider-declaration.php index 2690d02..23af826 100644 --- a/tests/Rules/PHPUnit/data/data-provider-declaration.php +++ b/tests/Rules/PHPUnit/data/data-provider-declaration.php @@ -9,6 +9,7 @@ class FooTestCase extends \PHPUnit\Framework\TestCase * @dataProvider providebaz * @dataProvider provideQux * @dataProvider provideQuux + * @dataProvider \ExampleTestCase\BarTestCase::provideToOtherClass */ public function testIsNotFoo(string $subject): void { @@ -61,10 +62,18 @@ class BarTestCase extends \PHPUnit\Framework\TestCase /** * @dataProvider provideNonExisting + * @dataProvider NonExisting::provideNonExisting * @dataProvider provideCorge */ public function testIsNotBar(string $subject): void { self::assertNotSame('bar', $subject); } + + public static function provideToOtherClass(): iterable + { + return [ + ['toOtherClass'], + ]; + } }