4
4
5
5
use PhpParser \Node ;
6
6
use PHPStan \Analyser \Scope ;
7
+ use PHPStan \DependencyInjection \AutowiredParameter ;
7
8
use PHPStan \DependencyInjection \RegisteredRule ;
9
+ use PHPStan \DependencyInjection \Type \OperatorTypeSpecifyingExtensionRegistryProvider ;
10
+ use PHPStan \Rules \IdentifierRuleError ;
8
11
use PHPStan \Rules \Rule ;
9
12
use PHPStan \Rules \RuleErrorBuilder ;
10
13
use PHPStan \Rules \RuleLevelHelper ;
28
31
final class InvalidComparisonOperationRule implements Rule
29
32
{
30
33
31
- public function __construct (private RuleLevelHelper $ ruleLevelHelper )
34
+ public function __construct (
35
+ private RuleLevelHelper $ ruleLevelHelper ,
36
+ private OperatorTypeSpecifyingExtensionRegistryProvider $ operatorTypeSpecifyingExtensionRegistryProvider ,
37
+ #[AutowiredParameter(ref: '%featureToggles.checkExtensionsForComparisonOperators% ' )]
38
+ private bool $ checkExtensionsForComparisonOperators ,
39
+ )
32
40
{
33
41
}
34
42
@@ -55,6 +63,22 @@ public function processNode(Node $node, Scope $scope): array
55
63
return [];
56
64
}
57
65
66
+ $ result = $ this ->operatorTypeSpecifyingExtensionRegistryProvider ->getRegistry ()->callOperatorTypeSpecifyingExtensions (
67
+ $ node ,
68
+ $ scope ->getType ($ node ->left ),
69
+ $ scope ->getType ($ node ->right ),
70
+ );
71
+
72
+ if ($ result !== null ) {
73
+ if (! $ result instanceof ErrorType) {
74
+ return [];
75
+ }
76
+
77
+ if ($ this ->checkExtensionsForComparisonOperators ) {
78
+ return $ this ->createError ($ node , $ scope );
79
+ }
80
+ }
81
+
58
82
if (
59
83
($ this ->isNumberType ($ scope , $ node ->left ) && (
60
84
$ this ->isPossiblyNullableObjectType ($ scope , $ node ->right ) || $ this ->isPossiblyNullableArrayType ($ scope , $ node ->right )
@@ -63,43 +87,7 @@ public function processNode(Node $node, Scope $scope): array
63
87
$ this ->isPossiblyNullableObjectType ($ scope , $ node ->left ) || $ this ->isPossiblyNullableArrayType ($ scope , $ node ->left )
64
88
))
65
89
) {
66
- switch (get_class ($ node )) {
67
- case Node \Expr \BinaryOp \Equal::class:
68
- $ nodeType = 'equal ' ;
69
- break ;
70
- case Node \Expr \BinaryOp \NotEqual::class:
71
- $ nodeType = 'notEqual ' ;
72
- break ;
73
- case Node \Expr \BinaryOp \Greater::class:
74
- $ nodeType = 'greater ' ;
75
- break ;
76
- case Node \Expr \BinaryOp \GreaterOrEqual::class:
77
- $ nodeType = 'greaterOrEqual ' ;
78
- break ;
79
- case Node \Expr \BinaryOp \Smaller::class:
80
- $ nodeType = 'smaller ' ;
81
- break ;
82
- case Node \Expr \BinaryOp \SmallerOrEqual::class:
83
- $ nodeType = 'smallerOrEqual ' ;
84
- break ;
85
- case Node \Expr \BinaryOp \Spaceship::class:
86
- $ nodeType = 'spaceship ' ;
87
- break ;
88
- default :
89
- throw new ShouldNotHappenException ();
90
- }
91
-
92
- return [
93
- RuleErrorBuilder::message (sprintf (
94
- 'Comparison operation "%s" between %s and %s results in an error. ' ,
95
- $ node ->getOperatorSigil (),
96
- $ scope ->getType ($ node ->left )->describe (VerbosityLevel::value ()),
97
- $ scope ->getType ($ node ->right )->describe (VerbosityLevel::value ()),
98
- ))
99
- ->line ($ node ->left ->getStartLine ())
100
- ->identifier (sprintf ('%s.invalid ' , $ nodeType ))
101
- ->build (),
102
- ];
90
+ return $ this ->createError ($ node , $ scope );
103
91
}
104
92
105
93
return [];
@@ -166,4 +154,46 @@ private function isPossiblyNullableArrayType(Scope $scope, Node\Expr $expr): boo
166
154
return !($ type instanceof ErrorType) && $ type ->isArray ()->yes ();
167
155
}
168
156
157
+ /** @return list<IdentifierRuleError> */
158
+ private function createError (Node \Expr \BinaryOp $ node , Scope $ scope ): array
159
+ {
160
+ switch (get_class ($ node )) {
161
+ case Node \Expr \BinaryOp \Equal::class:
162
+ $ nodeType = 'equal ' ;
163
+ break ;
164
+ case Node \Expr \BinaryOp \NotEqual::class:
165
+ $ nodeType = 'notEqual ' ;
166
+ break ;
167
+ case Node \Expr \BinaryOp \Greater::class:
168
+ $ nodeType = 'greater ' ;
169
+ break ;
170
+ case Node \Expr \BinaryOp \GreaterOrEqual::class:
171
+ $ nodeType = 'greaterOrEqual ' ;
172
+ break ;
173
+ case Node \Expr \BinaryOp \Smaller::class:
174
+ $ nodeType = 'smaller ' ;
175
+ break ;
176
+ case Node \Expr \BinaryOp \SmallerOrEqual::class:
177
+ $ nodeType = 'smallerOrEqual ' ;
178
+ break ;
179
+ case Node \Expr \BinaryOp \Spaceship::class:
180
+ $ nodeType = 'spaceship ' ;
181
+ break ;
182
+ default :
183
+ throw new ShouldNotHappenException ();
184
+ }
185
+
186
+ return [
187
+ RuleErrorBuilder::message (sprintf (
188
+ 'Comparison operation "%s" between %s and %s results in an error. ' ,
189
+ $ node ->getOperatorSigil (),
190
+ $ scope ->getType ($ node ->left )->describe (VerbosityLevel::value ()),
191
+ $ scope ->getType ($ node ->right )->describe (VerbosityLevel::value ()),
192
+ ))
193
+ ->line ($ node ->left ->getStartLine ())
194
+ ->identifier (sprintf ('%s.invalid ' , $ nodeType ))
195
+ ->build (),
196
+ ];
197
+ }
198
+
169
199
}
0 commit comments