Skip to content

Commit 480c516

Browse files
committed
Extracted part of CallStaticMethodsRule to be reusable
1 parent 5b37406 commit 480c516

File tree

6 files changed

+318
-254
lines changed

6 files changed

+318
-254
lines changed

conf/config.level0.neon

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ rules:
4949
- PHPStan\Rules\Keywords\ContinueBreakInLoopRule
5050
- PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule
5151
- PHPStan\Rules\Methods\CallMethodsRule
52+
- PHPStan\Rules\Methods\CallStaticMethodsRule
5253
- PHPStan\Rules\Methods\ExistingClassesInTypehintsRule
5354
- PHPStan\Rules\Methods\MethodCallableRule
5455
- PHPStan\Rules\Methods\MissingMethodImplementationRule
@@ -88,14 +89,6 @@ services:
8889
arguments:
8990
checkFunctionNameCase: %checkFunctionNameCase%
9091

91-
-
92-
class: PHPStan\Rules\Methods\CallStaticMethodsRule
93-
tags:
94-
- phpstan.rules.rule
95-
arguments:
96-
checkFunctionNameCase: %checkFunctionNameCase%
97-
reportMagicMethods: %reportMagicMethods%
98-
9992
-
10093
class: PHPStan\Rules\Constants\OverridingConstantRule
10194
arguments:

conf/config.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,12 @@ services:
808808
checkFunctionNameCase: %checkFunctionNameCase%
809809
reportMagicMethods: %reportMagicMethods%
810810

811+
-
812+
class: PHPStan\Rules\Methods\StaticMethodCallCheck
813+
arguments:
814+
checkFunctionNameCase: %checkFunctionNameCase%
815+
reportMagicMethods: %reportMagicMethods%
816+
811817
-
812818
# checked as part of OverridingMethodRule
813819
class: PHPStan\Rules\Methods\MethodSignatureRule

src/Rules/Methods/CallStaticMethodsRule.php

Lines changed: 14 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,28 @@
44

55
use PhpParser\Node;
66
use PhpParser\Node\Expr\StaticCall;
7-
use PhpParser\Node\Name;
8-
use PHPStan\Analyser\NullsafeOperatorHelper;
97
use PHPStan\Analyser\Scope;
108
use PHPStan\Internal\SprintfHelper;
11-
use PHPStan\Reflection\MethodReflection;
12-
use PHPStan\Reflection\Native\NativeMethodReflection;
139
use PHPStan\Reflection\ParametersAcceptorSelector;
14-
use PHPStan\Reflection\Php\PhpMethodReflection;
15-
use PHPStan\Reflection\ReflectionProvider;
16-
use PHPStan\Rules\ClassCaseSensitivityCheck;
17-
use PHPStan\Rules\ClassNameNodePair;
1810
use 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
*/
3515
class 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

src/Rules/Methods/MethodCallCheck.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public function __construct(
3636
$this->checkFunctionNameCase = $checkFunctionNameCase;
3737
$this->reportMagicMethods = $reportMagicMethods;
3838
}
39+
3940
/**
4041
* @return array{RuleError[], MethodReflection|null}
4142
*/

0 commit comments

Comments
 (0)