From bf1c958df9819dffc4640c3f1610f0f95bb993c1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 28 Oct 2025 10:49:23 +0100 Subject: [PATCH 1/7] PHPUnit 9.x/10.x does not at all support named arguments from data-providers --- src/Rules/PHPUnit/DataProviderDataRule.php | 9 ++-- src/Rules/PHPUnit/PHPUnitVersion.php | 8 +++ .../PHPUnit/DataProviderDataRuleTest.php | 53 ++++++++++++++----- .../PHPUnit/data/data-provider-named-args.php | 32 +++++++++++ 4 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 tests/Rules/PHPUnit/data/data-provider-named-args.php 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..bc9b4fab 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -8,6 +8,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -19,21 +20,22 @@ class DataProviderDataRuleTest extends RuleTestCase 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 */ ]; @@ -176,30 +178,26 @@ public function testRule(): void public function testRulePhp8(): void { if (PHP_VERSION_ID < 80000) { - self::markTestSkipped(); + self::markTestSkipped('PHPUnit11 requires PHP 8.0.'); } $this->phpunitVersion = 10; $this->analyse([__DIR__ . '/data/data-provider-data-named.php'], [ [ - 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, int given.', + 'Parameter #1 $expectedResult of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, int given.', 44 ], [ - 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, false given.', + 'Parameter #1 $expectedResult 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().', + 'Parameter #1 $si of method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar() expects int, string given.', 58 ], [ - 'Parameter $si of method DataProviderDataTestPhp8\TestWrongTypeInArrayShapeIterable::testBar() expects int, string given.', + 'Parameter #1 $si of method DataProviderDataTestPhp8\TestWrongTypeInArrayShapeIterable::testBar() expects int, string given.', 79 ], ]); @@ -274,7 +272,36 @@ public function testTrimmingArgs(): void ]); } - /** + public function testNamedArgumentsInDataProviders(): void + { + $this->phpunitVersion = 10; + + $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], [ + [ + '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 + ], + ]); + } + + public function testNamedArgumentsInDataProvidersPhpUnit11OrNewer(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('PHPUnit11 requires PHP 8.0.'); + } + + $this->phpunitVersion = 11; + + $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], [ + ]); + } + + + /** * @return string[] */ public static function getAdditionalConfigFiles(): array 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, + ]; + } +} + From 4b700e51a923e9ddbdbcfb0e94a07250ece125c2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Oct 2025 08:03:52 +0100 Subject: [PATCH 2/7] cleanup --- .../PHPUnit/DataProviderDataRuleTest.php | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index bc9b4fab..4b0ae850 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -8,6 +8,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; +use PHPUnit\Framework\Attributes\DataProvider; use const PHP_VERSION_ID; /** @@ -15,7 +16,7 @@ */ class DataProviderDataRuleTest extends RuleTestCase { - private int $phpunitVersion; + private ?int $phpunitVersion; protected function getRule(): Rule { @@ -272,36 +273,44 @@ public function testTrimmingArgs(): void ]); } - public function testNamedArgumentsInDataProviders(): void + static public function provideNamedArgumentVersions(): iterable { - $this->phpunitVersion = 10; - - $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], [ - [ - '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 - ], - ]); + return [ + [null], + [10], + [11], + ]; } - public function testNamedArgumentsInDataProvidersPhpUnit11OrNewer(): void + /** + * @dataProvider provideNamedArgumentVersions + */ + #[DataProvider('provideNamedArgumentVersions')] + public function testNamedArgumentsInDataProviders(?int $phpunitVersion): void { - if (PHP_VERSION_ID < 80000) { - self::markTestSkipped('PHPUnit11 requires PHP 8.0.'); - } + $this->phpunitVersion = $phpunitVersion; - $this->phpunitVersion = 11; + if ($phpunitVersion >= 11) { + $errors = []; + $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], [ + ]); + } 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'], [ - ]); + $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], $errors); } - - /** + /** * @return string[] */ public static function getAdditionalConfigFiles(): array From b4cebc841661ec25f11d3f4f55db53574cdf52c6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Oct 2025 08:06:50 +0100 Subject: [PATCH 3/7] Update DataProviderDataRuleTest.php --- tests/Rules/PHPUnit/DataProviderDataRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index 4b0ae850..d2907fec 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -179,7 +179,7 @@ public function testRule(): void public function testRulePhp8(): void { if (PHP_VERSION_ID < 80000) { - self::markTestSkipped('PHPUnit11 requires PHP 8.0.'); + self::markTestSkipped(); } $this->phpunitVersion = 10; From 8f1b71755f304326089b214d65fce2e4d44afd04 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Oct 2025 08:11:26 +0100 Subject: [PATCH 4/7] refactor --- .../PHPUnit/DataProviderDataRuleTest.php | 73 ++++++++++++++----- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index d2907fec..c295cd41 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -9,6 +9,7 @@ use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use const PHP_VERSION_ID; /** @@ -176,32 +177,64 @@ public function testRule(): void ]); } - public function testRulePhp8(): void + + /** + * @dataProvider provideNamedArgumentVersions + */ + #[DataProvider('provideNamedArgumentVersions')] + 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 #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 - ], - ]); + 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); } From 59ee09456edb4d1b74ac533cfe15b8180d02bfb0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Oct 2025 08:12:30 +0100 Subject: [PATCH 5/7] Update DataProviderDataRuleTest.php --- tests/Rules/PHPUnit/DataProviderDataRuleTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index c295cd41..750a3a7c 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -179,9 +179,9 @@ public function testRule(): void /** - * @dataProvider provideNamedArgumentVersions + * @dataProvider provideNamedArgumentPHPUnitVersions */ - #[DataProvider('provideNamedArgumentVersions')] + #[DataProvider('provideNamedArgumentPHPUnitVersions')] public function testRulePhp8(?int $phpunitVersion): void { if (PHP_VERSION_ID < 80000) { @@ -306,7 +306,7 @@ public function testTrimmingArgs(): void ]); } - static public function provideNamedArgumentVersions(): iterable + static public function provideNamedArgumentPHPUnitVersions(): iterable { return [ [null], @@ -316,9 +316,9 @@ static public function provideNamedArgumentVersions(): iterable } /** - * @dataProvider provideNamedArgumentVersions + * @dataProvider provideNamedArgumentPHPUnitVersions */ - #[DataProvider('provideNamedArgumentVersions')] + #[DataProvider('provideNamedArgumentPHPUnitVersions')] public function testNamedArgumentsInDataProviders(?int $phpunitVersion): void { $this->phpunitVersion = $phpunitVersion; From 71bc2c86aad19be675ae9df7d25c1306e27a2914 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Oct 2025 08:21:25 +0100 Subject: [PATCH 6/7] fix --- tests/Rules/PHPUnit/DataProviderDataRuleTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index 750a3a7c..de2f7b7f 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -324,9 +324,11 @@ public function testNamedArgumentsInDataProviders(?int $phpunitVersion): void $this->phpunitVersion = $phpunitVersion; if ($phpunitVersion >= 11) { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('PHPUnit11 requires PHP 8.0+'); + } + $errors = []; - $this->analyse([__DIR__ . '/data/data-provider-named-args.php'], [ - ]); } else { $errors = [ [ From edf98b893861b363fe6be109b931c57c51e0bdae Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Oct 2025 08:24:18 +0100 Subject: [PATCH 7/7] Update DataProviderDataRuleTest.php --- .../Rules/PHPUnit/DataProviderDataRuleTest.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php index de2f7b7f..cca88e7f 100644 --- a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -308,11 +308,14 @@ public function testTrimmingArgs(): void static public function provideNamedArgumentPHPUnitVersions(): iterable { - return [ - [null], - [10], - [11], - ]; + 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+ + } } /** @@ -324,10 +327,6 @@ public function testNamedArgumentsInDataProviders(?int $phpunitVersion): void $this->phpunitVersion = $phpunitVersion; if ($phpunitVersion >= 11) { - if (PHP_VERSION_ID < 80000) { - self::markTestSkipped('PHPUnit11 requires PHP 8.0+'); - } - $errors = []; } else { $errors = [