diff --git a/src/Rules/PHPUnit/DataProviderDataRule.php b/src/Rules/PHPUnit/DataProviderDataRule.php index 6f250fee..ce994676 100644 --- a/src/Rules/PHPUnit/DataProviderDataRule.php +++ b/src/Rules/PHPUnit/DataProviderDataRule.php @@ -23,13 +23,17 @@ class DataProviderDataRule implements Rule private DataProviderHelper $dataProviderHelper; + private PHPUnitVersion $PHPUnitVersion; + public function __construct( TestMethodsHelper $testMethodsHelper, - DataProviderHelper $dataProviderHelper + DataProviderHelper $dataProviderHelper, + PHPUnitVersion $PHPUnitVersion ) { $this->testMethodsHelper = $testMethodsHelper; $this->dataProviderHelper = $dataProviderHelper; + $this->PHPUnitVersion = $PHPUnitVersion; } public function getNodeType(): string @@ -153,11 +157,10 @@ private function arrayItemsToArgs(Type $array, int $numberOfParameters): ?array return null; } - if (count($key) === 0) { + if (count($key) === 0 || !$this->PHPUnitVersion->supportsNamedArgumentsInDataProvider()->yes()) { $arg = new Node\Arg(new TypeExpr($valueType)); $args[] = $arg; continue; - } $arg = new Node\Arg( diff --git a/src/Rules/PHPUnit/PHPUnitVersion.php b/src/Rules/PHPUnit/PHPUnitVersion.php index 932deedd..b7259a84 100644 --- a/src/Rules/PHPUnit/PHPUnitVersion.php +++ b/src/Rules/PHPUnit/PHPUnitVersion.php @@ -38,4 +38,12 @@ public function requiresStaticDataProviders(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->majorVersion >= 10); } + public function supportsNamedArgumentsInDataProvider(): TrinaryLogic + { + if ($this->majorVersion === null) { + return TrinaryLogic::createMaybe(); + } + return TrinaryLogic::createFromBoolean($this->majorVersion >= 11); + } + } diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index e3f80a3b..cca88e7f 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -8,32 +8,36 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; +use const PHP_VERSION_ID; /** * @extends RuleTestCase */ class DataProviderDataRuleTest extends RuleTestCase { - private int $phpunitVersion; + private ?int $phpunitVersion; protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); + $phpunitVersion = new PHPUnitVersion($this->phpunitVersion); /** @var list> $rules */ $rules = [ new DataProviderDataRule( new TestMethodsHelper( self::getContainer()->getByType(FileTypeMapper::class), - new PHPUnitVersion($this->phpunitVersion) + $phpunitVersion ), new DataProviderHelper( $reflectionProvider, self::getContainer()->getByType(FileTypeMapper::class), self::getContainer()->getService('defaultAnalysisParser'), - new PHPUnitVersion($this->phpunitVersion) + $phpunitVersion ), - + $phpunitVersion, ), self::getContainer()->getByType(CallMethodsRule::class) /** @phpstan-ignore phpstanApi.classConstant */ ]; @@ -173,36 +177,64 @@ public function testRule(): void ]); } - public function testRulePhp8(): void + + /** + * @dataProvider provideNamedArgumentPHPUnitVersions + */ + #[DataProvider('provideNamedArgumentPHPUnitVersions')] + public function testRulePhp8(?int $phpunitVersion): void { if (PHP_VERSION_ID < 80000) { self::markTestSkipped(); } - $this->phpunitVersion = 10; + $this->phpunitVersion = $phpunitVersion; - $this->analyse([__DIR__ . '/data/data-provider-data-named.php'], [ - [ - 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, int given.', - 44 - ], - [ - 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, false given.', - 44 - ], - [ - 'Unknown parameter $wrong in call to method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar().', - 58 - ], - [ - 'Missing parameter $si (int) in call to method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar().', - 58 - ], - [ - 'Parameter $si of method DataProviderDataTestPhp8\TestWrongTypeInArrayShapeIterable::testBar() expects int, string given.', - 79 - ], - ]); + if ($phpunitVersion >= 11) { + $errors = [ + [ + 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, int given.', + 44 + ], + [ + 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, false given.', + 44 + ], + [ + 'Unknown parameter $wrong in call to method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar().', + 58 + ], + [ + 'Missing parameter $si (int) in call to method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar().', + 58 + ], + [ + 'Parameter $si of method DataProviderDataTestPhp8\TestWrongTypeInArrayShapeIterable::testBar() expects int, string given.', + 79 + ], + ]; + } else { + $errors = [ + [ + 'Parameter #1 $expectedResult of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, int given.', + 44 + ], + [ + 'Parameter #1 $expectedResult of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, false given.', + 44 + ], + [ + 'Parameter #1 $si of method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar() expects int, string given.', + 58 + ], + [ + 'Parameter #1 $si of method DataProviderDataTestPhp8\TestWrongTypeInArrayShapeIterable::testBar() expects int, string given.', + 79 + ], + ]; + } + + $this->analyse([__DIR__ . '/data/data-provider-data-named.php'], $errors); } @@ -274,6 +306,44 @@ public function testTrimmingArgs(): void ]); } + static public function provideNamedArgumentPHPUnitVersions(): iterable + { + yield [null]; // unknown phpunit version + + if (PHP_VERSION_ID >= 80100) { + yield [10]; // PHPUnit 10.x requires PHP 8.1+ + } + if (PHP_VERSION_ID >= 80200) { + yield [11]; // PHPUnit 11.x requires PHP 8.2+ + } + } + + /** + * @dataProvider provideNamedArgumentPHPUnitVersions + */ + #[DataProvider('provideNamedArgumentPHPUnitVersions')] + public function testNamedArgumentsInDataProviders(?int $phpunitVersion): void + { + $this->phpunitVersion = $phpunitVersion; + + if ($phpunitVersion >= 11) { + $errors = []; + } else { + $errors = [ + [ + 'Parameter #1 $int of method DataProviderNamedArgs\FooTest::testFoo() expects int, string given.', + 26 + ], + [ + 'Parameter #2 $string of method DataProviderNamedArgs\FooTest::testFoo() expects string, int given.', + 26 + ], + ]; + } + + $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], $errors); + } + /** * @return string[] */ diff --git a/tests/Rules/PHPUnit/data/data-provider-named-args.php b/tests/Rules/PHPUnit/data/data-provider-named-args.php new file mode 100644 index 00000000..094ea3b1 --- /dev/null +++ b/tests/Rules/PHPUnit/data/data-provider-named-args.php @@ -0,0 +1,32 @@ +assertTrue(true); + } + + public static function dataProvider(): iterable + { + yield 'even' => [ + 'int' => 50, + 'string' => 'abc', + ]; + + yield 'odd' => [ + 'string' => 'def', + 'int' => 51, + ]; + } +} +