44
55use PhpParser \Node ;
66use PhpParser \Node \Expr \StaticCall ;
7- use PhpParser \Node \Name ;
8- use PHPStan \Analyser \NullsafeOperatorHelper ;
97use PHPStan \Analyser \Scope ;
108use PHPStan \Internal \SprintfHelper ;
11- use PHPStan \Reflection \MethodReflection ;
12- use PHPStan \Reflection \Native \NativeMethodReflection ;
139use PHPStan \Reflection \ParametersAcceptorSelector ;
14- use PHPStan \Reflection \Php \PhpMethodReflection ;
15- use PHPStan \Reflection \ReflectionProvider ;
16- use PHPStan \Rules \ClassCaseSensitivityCheck ;
17- use PHPStan \Rules \ClassNameNodePair ;
1810use PHPStan \Rules \FunctionCallParametersCheck ;
19- use PHPStan \Rules \RuleErrorBuilder ;
20- use PHPStan \Rules \RuleLevelHelper ;
21- use PHPStan \Type \ErrorType ;
22- use PHPStan \Type \Generic \GenericClassStringType ;
23- use PHPStan \Type \ObjectWithoutClassType ;
24- use PHPStan \Type \StringType ;
25- use PHPStan \Type \ThisType ;
26- use PHPStan \Type \Type ;
27- use PHPStan \Type \TypeCombinator ;
28- use PHPStan \Type \TypeUtils ;
29- use PHPStan \Type \TypeWithClassName ;
30- use PHPStan \Type \VerbosityLevel ;
3111
3212/**
3313 * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\StaticCall>
3414 */
3515class CallStaticMethodsRule implements \PHPStan \Rules \Rule
3616{
3717
38- private \ PHPStan \ Reflection \ ReflectionProvider $ reflectionProvider ;
18+ private StaticMethodCallCheck $ methodCallCheck ;
3919
40- private \PHPStan \Rules \FunctionCallParametersCheck $ check ;
41-
42- private \PHPStan \Rules \RuleLevelHelper $ ruleLevelHelper ;
43-
44- private \PHPStan \Rules \ClassCaseSensitivityCheck $ classCaseSensitivityCheck ;
45-
46- private bool $ checkFunctionNameCase ;
47-
48- private bool $ reportMagicMethods ;
20+ private FunctionCallParametersCheck $ parametersCheck ;
4921
5022 public function __construct (
51- ReflectionProvider $ reflectionProvider ,
52- FunctionCallParametersCheck $ check ,
53- RuleLevelHelper $ ruleLevelHelper ,
54- ClassCaseSensitivityCheck $ classCaseSensitivityCheck ,
55- bool $ checkFunctionNameCase ,
56- bool $ reportMagicMethods
23+ StaticMethodCallCheck $ methodCallCheck ,
24+ FunctionCallParametersCheck $ parametersCheck
5725 )
5826 {
59- $ this ->reflectionProvider = $ reflectionProvider ;
60- $ this ->check = $ check ;
61- $ this ->ruleLevelHelper = $ ruleLevelHelper ;
62- $ this ->classCaseSensitivityCheck = $ classCaseSensitivityCheck ;
63- $ this ->checkFunctionNameCase = $ checkFunctionNameCase ;
64- $ this ->reportMagicMethods = $ reportMagicMethods ;
27+ $ this ->methodCallCheck = $ methodCallCheck ;
28+ $ this ->parametersCheck = $ parametersCheck ;
6529 }
6630
6731 public function getNodeType (): string
@@ -76,200 +40,23 @@ public function processNode(Node $node, Scope $scope): array
7640 }
7741 $ methodName = $ node ->name ->name ;
7842
79- $ class = $ node ->class ;
80- $ errors = [];
81- $ isAbstract = false ;
82- if ($ class instanceof Name) {
83- $ className = (string ) $ class ;
84- $ lowercasedClassName = strtolower ($ className );
85- if (in_array ($ lowercasedClassName , ['self ' , 'static ' ], true )) {
86- if (!$ scope ->isInClass ()) {
87- return [
88- RuleErrorBuilder::message (sprintf (
89- 'Calling %s::%s() outside of class scope. ' ,
90- $ className ,
91- $ methodName
92- ))->build (),
93- ];
94- }
95- $ classType = $ scope ->resolveTypeByName ($ class );
96- } elseif ($ lowercasedClassName === 'parent ' ) {
97- if (!$ scope ->isInClass ()) {
98- return [
99- RuleErrorBuilder::message (sprintf (
100- 'Calling %s::%s() outside of class scope. ' ,
101- $ className ,
102- $ methodName
103- ))->build (),
104- ];
105- }
106- $ currentClassReflection = $ scope ->getClassReflection ();
107- if ($ currentClassReflection ->getParentClass () === null ) {
108- return [
109- RuleErrorBuilder::message (sprintf (
110- '%s::%s() calls parent::%s() but %s does not extend any class. ' ,
111- $ scope ->getClassReflection ()->getDisplayName (),
112- $ scope ->getFunctionName (),
113- $ methodName ,
114- $ scope ->getClassReflection ()->getDisplayName ()
115- ))->build (),
116- ];
117- }
118-
119- if ($ scope ->getFunctionName () === null ) {
120- throw new \PHPStan \ShouldNotHappenException ();
121- }
122-
123- $ classType = $ scope ->resolveTypeByName ($ class );
124- } else {
125- if (!$ this ->reflectionProvider ->hasClass ($ className )) {
126- if ($ scope ->isInClassExists ($ className )) {
127- return [];
128- }
129-
130- return [
131- RuleErrorBuilder::message (sprintf (
132- 'Call to static method %s() on an unknown class %s. ' ,
133- $ methodName ,
134- $ className
135- ))->discoveringSymbolsTip ()->build (),
136- ];
137- } else {
138- $ errors = $ this ->classCaseSensitivityCheck ->checkClassNames ([new ClassNameNodePair ($ className , $ class )]);
139- }
140-
141- $ classType = $ scope ->resolveTypeByName ($ class );
142- }
143-
144- $ classReflection = $ classType ->getClassReflection ();
145- if ($ classReflection !== null && $ classReflection ->hasNativeMethod ($ methodName ) && $ lowercasedClassName !== 'static ' ) {
146- $ nativeMethodReflection = $ classReflection ->getNativeMethod ($ methodName );
147- if ($ nativeMethodReflection instanceof PhpMethodReflection || $ nativeMethodReflection instanceof NativeMethodReflection) {
148- $ isAbstract = $ nativeMethodReflection ->isAbstract ();
149- }
150- }
151- } else {
152- $ classTypeResult = $ this ->ruleLevelHelper ->findTypeToCheck (
153- $ scope ,
154- NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope ($ scope , $ class ),
155- sprintf ('Call to static method %s() on an unknown class %%s. ' , SprintfHelper::escapeFormatString ($ methodName )),
156- static function (Type $ type ) use ($ methodName ): bool {
157- return $ type ->canCallMethods ()->yes () && $ type ->hasMethod ($ methodName )->yes ();
158- }
159- );
160- $ classType = $ classTypeResult ->getType ();
161- if ($ classType instanceof ErrorType) {
162- return $ classTypeResult ->getUnknownClassErrors ();
163- }
43+ [$ errors , $ method ] = $ this ->methodCallCheck ->check ($ scope , $ methodName , $ node ->class );
44+ if ($ method === null ) {
45+ return $ errors ;
16446 }
16547
166- if ($ classType instanceof GenericClassStringType) {
167- $ classType = $ classType ->getGenericType ();
168- if (!(new ObjectWithoutClassType ())->isSuperTypeOf ($ classType )->yes ()) {
169- return [];
170- }
171- } elseif ((new StringType ())->isSuperTypeOf ($ classType )->yes ()) {
172- return [];
173- }
174-
175- $ typeForDescribe = $ classType ;
176- if ($ classType instanceof ThisType) {
177- $ typeForDescribe = $ classType ->getStaticObjectType ();
178- }
179- $ classType = TypeCombinator::remove ($ classType , new StringType ());
180-
181- if (!$ classType ->canCallMethods ()->yes ()) {
182- return array_merge ($ errors , [
183- RuleErrorBuilder::message (sprintf (
184- 'Cannot call static method %s() on %s. ' ,
185- $ methodName ,
186- $ typeForDescribe ->describe (VerbosityLevel::typeOnly ())
187- ))->build (),
188- ]);
189- }
190-
191- if (!$ classType ->hasMethod ($ methodName )->yes ()) {
192- if (!$ this ->reportMagicMethods ) {
193- $ directClassNames = TypeUtils::getDirectClassNames ($ classType );
194- foreach ($ directClassNames as $ className ) {
195- if (!$ this ->reflectionProvider ->hasClass ($ className )) {
196- continue ;
197- }
198-
199- $ classReflection = $ this ->reflectionProvider ->getClass ($ className );
200- if ($ classReflection ->hasNativeMethod ('__callStatic ' )) {
201- return [];
202- }
203- }
204- }
205-
206- return array_merge ($ errors , [
207- RuleErrorBuilder::message (sprintf (
208- 'Call to an undefined static method %s::%s(). ' ,
209- $ typeForDescribe ->describe (VerbosityLevel::typeOnly ()),
210- $ methodName
211- ))->build (),
212- ]);
213- }
214-
215- $ method = $ classType ->getMethod ($ methodName , $ scope );
216- if (!$ method ->isStatic ()) {
217- $ function = $ scope ->getFunction ();
218- if (
219- !$ function instanceof MethodReflection
220- || $ function ->isStatic ()
221- || !$ scope ->isInClass ()
222- || (
223- $ classType instanceof TypeWithClassName
224- && $ scope ->getClassReflection ()->getName () !== $ classType ->getClassName ()
225- && !$ scope ->getClassReflection ()->isSubclassOf ($ classType ->getClassName ())
226- )
227- ) {
228- return array_merge ($ errors , [
229- RuleErrorBuilder::message (sprintf (
230- 'Static call to instance method %s::%s(). ' ,
231- $ method ->getDeclaringClass ()->getDisplayName (),
232- $ method ->getName ()
233- ))->build (),
234- ]);
235- }
236- }
237-
238- if (!$ scope ->canCallMethod ($ method )) {
239- $ errors = array_merge ($ errors , [
240- RuleErrorBuilder::message (sprintf (
241- 'Call to %s %s %s() of class %s. ' ,
242- $ method ->isPrivate () ? 'private ' : 'protected ' ,
243- $ method ->isStatic () ? 'static method ' : 'method ' ,
244- $ method ->getName (),
245- $ method ->getDeclaringClass ()->getDisplayName ()
246- ))->build (),
247- ]);
248- }
249-
250- if ($ isAbstract ) {
251- return [
252- RuleErrorBuilder::message (sprintf (
253- 'Cannot call abstract%s method %s::%s(). ' ,
254- $ method ->isStatic () ? ' static ' : '' ,
255- $ method ->getDeclaringClass ()->getDisplayName (),
256- $ method ->getName ()
257- ))->build (),
258- ];
259- }
260-
261- $ lowercasedMethodName = SprintfHelper::escapeFormatString (sprintf (
48+ $ displayMethodName = SprintfHelper::escapeFormatString (sprintf (
26249 '%s %s ' ,
263- $ method ->isStatic () ? 'static method ' : 'method ' ,
50+ $ method ->isStatic () ? 'Static method ' : 'Method ' ,
26451 $ method ->getDeclaringClass ()->getDisplayName () . ':: ' . $ method ->getName () . '() '
26552 ));
266- $ displayMethodName = SprintfHelper::escapeFormatString (sprintf (
53+ $ lowercasedMethodName = SprintfHelper::escapeFormatString (sprintf (
26754 '%s %s ' ,
268- $ method ->isStatic () ? 'Static method ' : 'Method ' ,
55+ $ method ->isStatic () ? 'static method ' : 'method ' ,
26956 $ method ->getDeclaringClass ()->getDisplayName () . ':: ' . $ method ->getName () . '() '
27057 ));
27158
272- $ errors = array_merge ($ errors , $ this ->check ->check (
59+ $ errors = array_merge ($ errors , $ this ->parametersCheck ->check (
27360 ParametersAcceptorSelector::selectFromArgs (
27461 $ scope ,
27562 $ node ->getArgs (),
@@ -295,17 +82,6 @@ static function (Type $type) use ($methodName): bool {
29582 ]
29683 ));
29784
298- if (
299- $ this ->checkFunctionNameCase
300- && $ method ->getName () !== $ methodName
301- ) {
302- $ errors [] = RuleErrorBuilder::message (sprintf (
303- 'Call to %s with incorrect case: %s ' ,
304- $ lowercasedMethodName ,
305- $ methodName
306- ))->build ();
307- }
308-
30985 return $ errors ;
31086 }
31187
0 commit comments