From c92cf512e83992a9bbe100e9d7c83068bd8ac15d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 6 Jul 2026 20:18:33 +0200 Subject: [PATCH 1/6] AttributeVersionRequirementHelper: Improve error messages --- .../AttributeVersionRequirementHelper.php | 22 ++++++++++++---- ...tributeRequiresPhpVersionRangeRuleTest.php | 10 +++---- .../AttributeRequiresPhpVersionRuleTest.php | 26 +++++++++---------- ...assAttributeRequiresPhpVersionRuleTest.php | 6 ++--- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php index 7e72e44..b801289 100644 --- a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php +++ b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php @@ -11,6 +11,9 @@ use PHPStan\Php\PhpMinorVersionIterator; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\ShouldNotHappenException; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\VerbosityLevel; use function count; use function is_numeric; use function preg_match; @@ -77,7 +80,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array if ($this->warnAboutIncompleteVersion($versionRequirement)) { $errors[] = RuleErrorBuilder::message( - sprintf('Version requirement is incomplete.'), + sprintf('Version requirement %s is incomplete.', $versionRequirement), ) ->identifier('phpunit.attributeRequiresPhpVersion') ->build(); @@ -129,8 +132,17 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array } } + if (count($pharIoVersions) < 2) { + throw new ShouldNotHappenException(); + } + $errors[] = RuleErrorBuilder::message( - sprintf('Version requirement will always evaluate to false.'), + sprintf( + 'Version requirement %s does not match %s...%s.', + $versionRequirement, + $pharIoVersions[0]->getVersionString(), + $pharIoVersions[count($pharIoVersions) - 1]->getVersionString() + ), ) ->identifier('phpunit.attributeRequiresPhpVersion') ->build(); @@ -140,7 +152,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array if ($this->PHPUnitVersion->requiresPhpversionAttributeWithOperator()->yes()) { $errors[] = RuleErrorBuilder::message( - sprintf('Version requirement is missing operator.'), + sprintf('Version requirement %s is missing operator.', $versionRequirement), ) ->identifier('phpunit.attributeRequiresPhpVersion') ->build(); @@ -149,7 +161,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array && $this->PHPUnitVersion->deprecatesPhpversionAttributeWithoutOperator()->yes() ) { $errors[] = RuleErrorBuilder::message( - sprintf('Version requirement without operator is deprecated.'), + sprintf('Version requirement %s without operator is deprecated.', $versionRequirement), ) ->identifier('phpunit.attributeRequiresPhpVersion') ->build(); @@ -159,7 +171,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array } /** - * @return Version[] + * @return list */ private function getAnalyzedPhpVersions(): array { diff --git a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php index ceb05b2..1ddfbe9 100644 --- a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php +++ b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php @@ -17,23 +17,23 @@ public function testPhpVersionMismatch(): void { $this->analyse([__DIR__ . '/data/requires-php-version-mismatch.php'], [ [ - 'Version requirement will always evaluate to false.', + 'Version requirement < 7.0 does not match 8.2.0...8.4.0.', 20, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement ^5.0 does not match 8.2.0...8.4.0.', 28, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement ~5.0 does not match 8.2.0...8.4.0.', 36, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement 5.* does not match 8.2.0...8.4.0.', 44, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement 8.5.* does not match 8.2.0...8.4.0.', 76, ], ]); diff --git a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php index 548ad99..6e73eee 100644 --- a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php +++ b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php @@ -53,7 +53,7 @@ public function testRuleOnPHPUnit124DeprecationsOn(): void $this->analyse([__DIR__ . '/data/requires-php-version.php'], [ [ - 'Version requirement without operator is deprecated.', + 'Version requirement 8.0 without operator is deprecated.', 12, ], ]); @@ -75,7 +75,7 @@ public function testRuleOnPHPUnit13(): void $this->analyse([__DIR__ . '/data/requires-php-version.php'], [ [ - 'Version requirement is missing operator.', + 'Version requirement 8.0 is missing operator.', 12, ], ]); @@ -91,35 +91,35 @@ public function testPhpVersionMismatch(): void [ // errors because https://github.com/sebastianbergmann/phpunit/issues/6451 // the test assumes PHP_VERSION_ID 80500 and the constraint only has 2 digits - 'Version requirement will always evaluate to false.', + 'Version requirement <= 8.5 does not match 8.5.0...8.5.99.', 12, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement < 7.0 does not match 8.5.0...8.5.99.', 20, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement ^5.0 does not match 8.5.0...8.5.99.', 28, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement ~5.0 does not match 8.5.0...8.5.99.', 36, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement 5.* does not match 8.5.0...8.5.99.', 44, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement <= 8.4 does not match 8.5.0...8.5.99.', 52, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement <= 8.5 does not match 8.5.0...8.5.99.', 60, ], [ - 'Version requirement will always evaluate to false.', + 'Version requirement 8.3.* does not match 8.5.0...8.5.99.', 68, ], ]); @@ -158,11 +158,11 @@ public function testWarnAboutIncompleteVersion(): void $this->analyse([__DIR__ . '/data/requires-php-version.php'], [ [ - 'Version requirement is incomplete.', + 'Version requirement 8.0 is incomplete.', 12, ], [ - 'Version requirement is incomplete.', + 'Version requirement >=8.0 is incomplete.', 20, ], ]); @@ -177,7 +177,7 @@ public function testWarnAboutIncompletePhpunitVersion(): void $this->analyse([__DIR__ . '/data/requires-phpunit-version.php'], [ [ - 'Version requirement is incomplete.', + 'Version requirement 11.0 is incomplete.', 12, ], ]); diff --git a/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php b/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php index 0e66178..b778eb4 100644 --- a/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php +++ b/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php @@ -26,11 +26,11 @@ public function testWarnAboutIncompleteVersion(): void $this->analyse([__DIR__ . '/data/requires-php-version-on-class.php'], [ [ - 'Version requirement will always evaluate to false.', + 'Version requirement < 7.0 does not match 8.5.0...8.5.99.', 10, ], [ - 'Version requirement is incomplete.', + 'Version requirement < 7.0 is incomplete.', 10, ], ]); @@ -44,7 +44,7 @@ public function testWarnAboutIncompletePhpunitVersion(): void $this->analyse([__DIR__ . '/data/requires-phpunit-version.php'], [ [ - 'Version requirement is incomplete.', + 'Version requirement >=11.0 is incomplete.', 18, ], ]); From 06fb2c88edd4902ba806b37960c6d999a0de6917 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 6 Jul 2026 20:18:41 +0200 Subject: [PATCH 2/6] cs --- src/Rules/PHPUnit/AttributeVersionRequirementHelper.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php index b801289..4a882f9 100644 --- a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php +++ b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php @@ -12,8 +12,6 @@ use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\IntegerRangeType; -use PHPStan\Type\VerbosityLevel; use function count; use function is_numeric; use function preg_match; @@ -141,7 +139,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array 'Version requirement %s does not match %s...%s.', $versionRequirement, $pharIoVersions[0]->getVersionString(), - $pharIoVersions[count($pharIoVersions) - 1]->getVersionString() + $pharIoVersions[count($pharIoVersions) - 1]->getVersionString(), ), ) ->identifier('phpunit.attributeRequiresPhpVersion') From 78ecac8574dfe0b5a2e86450a1dbc59da085cf24 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 6 Jul 2026 20:22:03 +0200 Subject: [PATCH 3/6] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 6eca2cf..cc64cfb 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -30,8 +30,8 @@ jobs: composer install OUTPUT=$(../bashunit -a exit_code "1" "vendor/bin/phpstan analyze test.php --error-format=raw") echo "$OUTPUT" - ../bashunit -a contains 'test.php:12:Version requirement will always evaluate to false.' "$OUTPUT" - ../bashunit -a contains 'test.php:32:Version requirement will always evaluate to false.' "$OUTPUT" + ../bashunit -a contains 'test.php:12:Version requirement <=8.0.0 does not match 8.1.0...8.5.99.' "$OUTPUT" + ../bashunit -a contains 'test.php:32:Version requirement ^11.0.0 does not match 12.5.0...12.5.99.' "$OUTPUT" steps: - name: Harden the runner (Audit all outbound calls) From 011ea594f8679b373ba8933704508db9ec6afd7b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 11 Jul 2026 07:37:19 +0200 Subject: [PATCH 4/6] more precise "is incomplete message" --- src/Rules/PHPUnit/AttributeVersionRequirementHelper.php | 2 +- tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php | 6 +++--- .../PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php index 4a882f9..153fe10 100644 --- a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php +++ b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php @@ -78,7 +78,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array if ($this->warnAboutIncompleteVersion($versionRequirement)) { $errors[] = RuleErrorBuilder::message( - sprintf('Version requirement %s is incomplete.', $versionRequirement), + sprintf('Version requirement %s is incomplete. Expect a version composed of major, minor and patch.', $versionRequirement), ) ->identifier('phpunit.attributeRequiresPhpVersion') ->build(); diff --git a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php index 6e73eee..50661e2 100644 --- a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php +++ b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php @@ -158,11 +158,11 @@ public function testWarnAboutIncompleteVersion(): void $this->analyse([__DIR__ . '/data/requires-php-version.php'], [ [ - 'Version requirement 8.0 is incomplete.', + 'Version requirement 8.0 is incomplete. Expect a version composed of major, minor and patch.', 12, ], [ - 'Version requirement >=8.0 is incomplete.', + 'Version requirement >=8.0 is incomplete. Expect a version composed of major, minor and patch.', 20, ], ]); @@ -177,7 +177,7 @@ public function testWarnAboutIncompletePhpunitVersion(): void $this->analyse([__DIR__ . '/data/requires-phpunit-version.php'], [ [ - 'Version requirement 11.0 is incomplete.', + 'Version requirement 11.0 is incomplete. Expect a version composed of major, minor and patch.', 12, ], ]); diff --git a/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php b/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php index b778eb4..e1b1036 100644 --- a/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php +++ b/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php @@ -30,7 +30,7 @@ public function testWarnAboutIncompleteVersion(): void 10, ], [ - 'Version requirement < 7.0 is incomplete.', + 'Version requirement < 7.0 is incomplete. Expect a version composed of major, minor and patch.', 10, ], ]); @@ -44,7 +44,7 @@ public function testWarnAboutIncompletePhpunitVersion(): void $this->analyse([__DIR__ . '/data/requires-phpunit-version.php'], [ [ - 'Version requirement >=11.0 is incomplete.', + 'Version requirement >=11.0 is incomplete. Expect a version composed of major, minor and patch.', 18, ], ]); From dc08e8422aa4bae6c275df7019074991c32d0438 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 11 Jul 2026 07:47:21 +0200 Subject: [PATCH 5/6] add tip --- src/Rules/PHPUnit/AttributeVersionRequirementHelper.php | 7 +++++++ .../PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php | 7 +++++++ .../PHPUnit/AttributeRequiresPhpVersionRuleTest.php | 9 +++++++++ .../PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php | 3 +++ 4 files changed, 26 insertions(+) diff --git a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php index 153fe10..7afafe6 100644 --- a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php +++ b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php @@ -134,6 +134,12 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array throw new ShouldNotHappenException(); } + if (strpos($attr->getName(), 'RequiresPhpunit') !== false) { + $tip = 'PHPUnit version inferred from composer.json. requirements'; + } else { + $tip = 'PHP version for analysis inferred from NEON config phpVersion or composer.json requirements. Invoke PHPStan with -vvv to get more details.'; + } + $errors[] = RuleErrorBuilder::message( sprintf( 'Version requirement %s does not match %s...%s.', @@ -142,6 +148,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array $pharIoVersions[count($pharIoVersions) - 1]->getVersionString(), ), ) + ->tip($tip) ->identifier('phpunit.attributeRequiresPhpVersion') ->build(); diff --git a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php index 1ddfbe9..85ff775 100644 --- a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php +++ b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRangeRuleTest.php @@ -15,26 +15,33 @@ final class AttributeRequiresPhpVersionRangeRuleTest extends RuleTestCase public function testPhpVersionMismatch(): void { + $tip = 'PHP version for analysis inferred from NEON config phpVersion or composer.json requirements. Invoke PHPStan with -vvv to get more details.'; + $this->analyse([__DIR__ . '/data/requires-php-version-mismatch.php'], [ [ 'Version requirement < 7.0 does not match 8.2.0...8.4.0.', 20, + $tip, ], [ 'Version requirement ^5.0 does not match 8.2.0...8.4.0.', 28, + $tip, ], [ 'Version requirement ~5.0 does not match 8.2.0...8.4.0.', 36, + $tip, ], [ 'Version requirement 5.* does not match 8.2.0...8.4.0.', 44, + $tip, ], [ 'Version requirement 8.5.* does not match 8.2.0...8.4.0.', 76, + $tip, ], ]); } diff --git a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php index 50661e2..adad73b 100644 --- a/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php +++ b/tests/Rules/PHPUnit/AttributeRequiresPhpVersionRuleTest.php @@ -87,40 +87,49 @@ public function testPhpVersionMismatch(): void $this->phpunitMinorVersion = 4; $this->deprecationRulesInstalled = false; + $tip = 'PHP version for analysis inferred from NEON config phpVersion or composer.json requirements. Invoke PHPStan with -vvv to get more details.'; $this->analyse([__DIR__ . '/data/requires-php-version-mismatch.php'], [ [ // errors because https://github.com/sebastianbergmann/phpunit/issues/6451 // the test assumes PHP_VERSION_ID 80500 and the constraint only has 2 digits 'Version requirement <= 8.5 does not match 8.5.0...8.5.99.', 12, + $tip, ], [ 'Version requirement < 7.0 does not match 8.5.0...8.5.99.', 20, + $tip, ], [ 'Version requirement ^5.0 does not match 8.5.0...8.5.99.', 28, + $tip, ], [ 'Version requirement ~5.0 does not match 8.5.0...8.5.99.', 36, + $tip, ], [ 'Version requirement 5.* does not match 8.5.0...8.5.99.', 44, + $tip, ], [ 'Version requirement <= 8.4 does not match 8.5.0...8.5.99.', 52, + $tip, ], [ 'Version requirement <= 8.5 does not match 8.5.0...8.5.99.', 60, + $tip, ], [ 'Version requirement 8.3.* does not match 8.5.0...8.5.99.', 68, + $tip, ], ]); } diff --git a/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php b/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php index e1b1036..f07b3a6 100644 --- a/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php +++ b/tests/Rules/PHPUnit/ClassAttributeRequiresPhpVersionRuleTest.php @@ -24,10 +24,13 @@ public function testWarnAboutIncompleteVersion(): void $this->phpunitMinorVersion = 5; $this->warnAboutIncompleteVersion = true; + $tip = 'PHP version for analysis inferred from NEON config phpVersion or composer.json requirements. Invoke PHPStan with -vvv to get more details.'; + $this->analyse([__DIR__ . '/data/requires-php-version-on-class.php'], [ [ 'Version requirement < 7.0 does not match 8.5.0...8.5.99.', 10, + $tip, ], [ 'Version requirement < 7.0 is incomplete. Expect a version composed of major, minor and patch.', From 7ab3e2f5ce5a52db75f5518563d606006c05b8df Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 11 Jul 2026 16:50:28 +0200 Subject: [PATCH 6/6] fix typo --- src/Rules/PHPUnit/AttributeVersionRequirementHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php index 7afafe6..fc29d48 100644 --- a/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php +++ b/src/Rules/PHPUnit/AttributeVersionRequirementHelper.php @@ -135,7 +135,7 @@ public function checkVersionRequirement(array $attributes, Scope $scope): array } if (strpos($attr->getName(), 'RequiresPhpunit') !== false) { - $tip = 'PHPUnit version inferred from composer.json. requirements'; + $tip = 'PHPUnit version inferred from composer.json requirements.'; } else { $tip = 'PHP version for analysis inferred from NEON config phpVersion or composer.json requirements. Invoke PHPStan with -vvv to get more details.'; }