22
33namespace PHPStan \Rules \PHPUnit ;
44
5+ use Composer \Semver \Constraint \ConstraintInterface ;
6+ use Composer \Semver \VersionParser ;
57use PhpParser \Node ;
68use PHPStan \Analyser \Scope ;
79use PHPStan \Node \InClassMethodNode ;
10+ use PHPStan \Php \PhpVersion ;
811use PHPStan \Rules \Rule ;
912use PHPStan \Rules \RuleErrorBuilder ;
1013use PHPUnit \Framework \TestCase ;
14+ use UnexpectedValueException ;
1115use function count ;
1216use function is_numeric ;
1317use function method_exists ;
1923class AttributeRequiresPhpVersionRule implements Rule
2024{
2125
26+ private ConstraintInterface $ phpstanVersionConstraint ;
27+
2228 private PHPUnitVersion $ PHPUnitVersion ;
2329
2430 private TestMethodsHelper $ testMethodsHelper ;
@@ -31,12 +37,16 @@ class AttributeRequiresPhpVersionRule implements Rule
3137 public function __construct (
3238 PHPUnitVersion $ PHPUnitVersion ,
3339 TestMethodsHelper $ testMethodsHelper ,
34- bool $ deprecationRulesInstalled
40+ bool $ deprecationRulesInstalled ,
41+ PhpVersion $ phpVersion
3542 )
3643 {
3744 $ this ->PHPUnitVersion = $ PHPUnitVersion ;
3845 $ this ->testMethodsHelper = $ testMethodsHelper ;
3946 $ this ->deprecationRulesInstalled = $ deprecationRulesInstalled ;
47+
48+ $ parser = new VersionParser ();
49+ $ this ->phpstanVersionConstraint = $ parser ->parseConstraints ($ phpVersion ->getVersionString ());
4050 }
4151
4252 public function getNodeType (): string
@@ -62,35 +72,57 @@ public function processNode(Node $node, Scope $scope): array
6272 }
6373
6474 $ errors = [];
75+ $ parser = new VersionParser ();
6576 foreach ($ reflectionMethod ->getAttributes ('PHPUnit\Framework\Attributes\RequiresPhp ' ) as $ attr ) {
6677 $ args = $ attr ->getArguments ();
6778 if (count ($ args ) !== 1 ) {
6879 continue ;
6980 }
7081
7182 if (
72- ! is_numeric ($ args [0 ])
83+ is_numeric ($ args [0 ])
7384 ) {
85+ if ($ this ->PHPUnitVersion ->requiresPhpversionAttributeWithOperator ()->yes ()) {
86+ $ errors [] = RuleErrorBuilder::message (
87+ sprintf ('Version requirement is missing operator. ' ),
88+ )
89+ ->identifier ('phpunit.attributeRequiresPhpVersion ' )
90+ ->build ();
91+ } elseif (
92+ $ this ->deprecationRulesInstalled
93+ && $ this ->PHPUnitVersion ->deprecatesPhpversionAttributeWithoutOperator ()->yes ()
94+ ) {
95+ $ errors [] = RuleErrorBuilder::message (
96+ sprintf ('Version requirement without operator is deprecated. ' ),
97+ )
98+ ->identifier ('phpunit.attributeRequiresPhpVersion ' )
99+ ->build ();
100+ }
101+
74102 continue ;
75103 }
76104
77- if ($ this ->PHPUnitVersion ->requiresPhpversionAttributeWithOperator ()->yes ()) {
105+ try {
106+ $ testPhpVersionConstraint = $ parser ->parseConstraints ($ args [0 ]);
107+ } catch (UnexpectedValueException $ e ) {
78108 $ errors [] = RuleErrorBuilder::message (
79- sprintf ('Version requirement is missing operator. ' ),
80- )
81- ->identifier ('phpunit.attributeRequiresPhpVersion ' )
82- ->build ();
83- } elseif (
84- $ this ->deprecationRulesInstalled
85- && $ this ->PHPUnitVersion ->deprecatesPhpversionAttributeWithoutOperator ()->yes ()
86- ) {
87- $ errors [] = RuleErrorBuilder::message (
88- sprintf ('Version requirement without operator is deprecated. ' ),
109+ sprintf ($ e ->getMessage ()),
89110 )
90111 ->identifier ('phpunit.attributeRequiresPhpVersion ' )
91112 ->build ();
113+
114+ continue ;
115+ }
116+
117+ if ($ this ->phpstanVersionConstraint ->matches ($ testPhpVersionConstraint )) {
118+ continue ;
92119 }
93120
121+ $ errors [] = RuleErrorBuilder::message (
122+ sprintf ('Version requirement will always evaluate to false. ' ),
123+ )
124+ ->identifier ('phpunit.attributeRequiresPhpVersion ' )
125+ ->build ();
94126 }
95127
96128 return $ errors ;
0 commit comments