From d37535b511241cc4ba2146080af0cbaf6c6bd277 Mon Sep 17 00:00:00 2001 From: "ondrej@mirtes.cz" Date: Tue, 3 Nov 2026 09:58:14 +0000 Subject: [PATCH] SkipTestsWithRequiresPhpAttributeRule: do not report methods that already have #[RequiresPhp] The attribute is not repeatable, so suggesting another one is wrong. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a0d3-ad3e-7141-bcfe-124dc73d21ee --- .../SkipTestsWithRequiresPhpAttributeRule.php | 18 +++++++++++++++- ...pTestsWithRequiresPhpAttributeRuleTest.php | 2 +- .../Build/data/skip-tests-requires-php.php | 19 +++++++++++++++++ .../data/skip-tests-requires-php.php.fixed | 21 ++++++++++++++++++- .../PropertiesInInterfaceRuleTest.php | 8 +------ .../Properties/PropertyInClassRuleTest.php | 2 -- 6 files changed, 58 insertions(+), 12 deletions(-) diff --git a/build/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRule.php b/build/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRule.php index f7d8b1bcd15..8156e737566 100644 --- a/build/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRule.php +++ b/build/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRule.php @@ -14,6 +14,7 @@ use function array_values; use function count; use function get_class; +use function preg_match; use function sprintf; /** @@ -43,6 +44,21 @@ public function processNode(Node $node, Scope $scope): array return []; } + foreach ($originalNode->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toString() === 'PHPUnit\\Framework\\Attributes\\RequiresPhp') { + // RequiresPhp is not repeatable + return []; + } + } + } + + // the code downgrade for PHPUnit 9 turns #[RequiresPhp] into @requires PHP + $docComment = $originalNode->getDocComment(); + if ($docComment !== null && preg_match('/@requires\s+PHP\s/', $docComment->getText()) === 1) { + return []; + } + $firstStmt = $originalNode->stmts[0]; if (!$firstStmt instanceof Node\Stmt\If_) { return []; @@ -116,7 +132,7 @@ public function processNode(Node $node, Scope $scope): array $node->stmts = array_values($stmts); $node->attrGroups[] = new Node\AttributeGroup([ new Attribute(new Node\Name\FullyQualified('PHPUnit\\Framework\\Attributes\\RequiresPhp'), [ - new Node\Arg(new Node\Scalar\String_(sprintf('%s %s', $inverseBinaryOpSigil, $phpVersion->getVersionString()))), + new Node\Arg(new Node\Scalar\String_(sprintf('%s %d.%d.%d', $inverseBinaryOpSigil, $phpVersion->getMajorVersionId(), $phpVersion->getMinorVersionId(), $phpVersion->getPatchVersionId()))), ]), ]); diff --git a/tests/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRuleTest.php b/tests/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRuleTest.php index 0799102e3f4..6990cf53e07 100644 --- a/tests/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRuleTest.php +++ b/tests/PHPStan/Build/SkipTestsWithRequiresPhpAttributeRuleTest.php @@ -21,7 +21,7 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/skip-tests-requires-php.php'], [ [ 'Skip tests with #[RequiresPhp] attribute instead.', - 13, + 14, ], ]); } diff --git a/tests/PHPStan/Build/data/skip-tests-requires-php.php b/tests/PHPStan/Build/data/skip-tests-requires-php.php index 22128b00979..c138be0419b 100644 --- a/tests/PHPStan/Build/data/skip-tests-requires-php.php +++ b/tests/PHPStan/Build/data/skip-tests-requires-php.php @@ -2,6 +2,7 @@ namespace SkipTestsWithRequiresPhpAttributeRule; +use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\TestCase; use const PHP_VERSION_ID; @@ -29,6 +30,24 @@ public function testUnrelatedCondition(bool $a, bool $b): void } } + #[RequiresPhp('< 8.4.0')] + public function testVersionCheckWithExistingAttribute(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(); + } + } + + /** + * @requires PHP < 8.4.0 + */ + public function testVersionCheckWithExistingAnnotation(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(); + } + } + public function testUnrelatedComparison(int $a): void { if ($a < 80000) { diff --git a/tests/PHPStan/Build/data/skip-tests-requires-php.php.fixed b/tests/PHPStan/Build/data/skip-tests-requires-php.php.fixed index 5d501a47956..11df5d6549a 100644 --- a/tests/PHPStan/Build/data/skip-tests-requires-php.php.fixed +++ b/tests/PHPStan/Build/data/skip-tests-requires-php.php.fixed @@ -2,13 +2,14 @@ namespace SkipTestsWithRequiresPhpAttributeRule; +use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\TestCase; use const PHP_VERSION_ID; class FooTest extends TestCase { - #[\PHPUnit\Framework\Attributes\RequiresPhp('>= 8.0')] + #[\PHPUnit\Framework\Attributes\RequiresPhp('>= 8.0.0')] public function testVersionCheck(): void { } @@ -27,6 +28,24 @@ class FooTest extends TestCase } } + #[RequiresPhp('< 8.4.0')] + public function testVersionCheckWithExistingAttribute(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(); + } + } + + /** + * @requires PHP < 8.4.0 + */ + public function testVersionCheckWithExistingAnnotation(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(); + } + } + public function testUnrelatedComparison(int $a): void { if ($a < 80000) { diff --git a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php index af9a49dc8f9..6656c1fff3f 100644 --- a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php @@ -18,13 +18,9 @@ protected function getRule(): Rule return new PropertiesInInterfaceRule(); } + #[RequiresPhp('>= 8.0.0')] public function testPhpVersionNarrowedScope(): void { - // @phpstan-ignore phpstan.skipTestsRequiresPhp - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); - } - $errors = [ [ 'Interfaces can include properties only on PHP 8.4 and later.', @@ -44,7 +40,6 @@ public function testPhpVersionNarrowedScope(): void #[RequiresPhp('< 8.4.0')] public function testPhp83AndPropertiesInInterface(): void { - // @phpstan-ignore phpstan.skipTestsRequiresPhp if (PHP_VERSION_ID < 80000) { $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); } @@ -71,7 +66,6 @@ public function testPhp83AndPropertiesInInterface(): void #[RequiresPhp('< 8.4.0')] public function testPhp83AndPropertyHooksInInterface(): void { - // @phpstan-ignore phpstan.skipTestsRequiresPhp if (PHP_VERSION_ID < 80000) { $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); } diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 4c9748509bd..4895a5ac20e 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -44,7 +44,6 @@ public function testPhpVersionNarrowedScope(): void #[RequiresPhp('< 8.4.0')] public function testPhpLessThan84AndHookedPropertiesInClass(): void { - // @phpstan-ignore phpstan.skipTestsRequiresPhp if (PHP_VERSION_ID < 80000) { $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); } @@ -60,7 +59,6 @@ public function testPhpLessThan84AndHookedPropertiesInClass(): void #[RequiresPhp('>= 8.4.0')] public function testPhp84AndHookedPropertiesWithoutBodiesInClass(): void { - // @phpstan-ignore phpstan.skipTestsRequiresPhp if (PHP_VERSION_ID < 80000) { $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); }