Skip to content

Commit cf0771d

Browse files
committed
Call RestrictedMethodUsageExtension for static method calls
1 parent f7027e9 commit cf0771d

8 files changed

+411
-8
lines changed

Diff for: conf/config.neon

+1
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ rules:
219219
- PHPStan\Rules\Debug\DumpTypeRule
220220
- PHPStan\Rules\Debug\FileAssertRule
221221
- PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule
222+
- PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule
222223

223224
conditionalTags:
224225
PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule:

Diff for: phpstan-baseline.neon

+30
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ parameters:
108108
count: 1
109109
path: src/Command/CommandHelper.php
110110

111+
-
112+
message: '#^Call to static method expand\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#'
113+
identifier: staticMethod.internalClass
114+
count: 2
115+
path: src/Command/CommandHelper.php
116+
111117
-
112118
message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#'
113119
identifier: argument.type
@@ -120,6 +126,18 @@ parameters:
120126
count: 1
121127
path: src/Command/CommandHelper.php
122128

129+
-
130+
message: '#^Call to static method escape\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#'
131+
identifier: staticMethod.internalClass
132+
count: 4
133+
path: src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php
134+
135+
-
136+
message: '#^Call to static method escape\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#'
137+
identifier: staticMethod.internalClass
138+
count: 5
139+
path: src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php
140+
123141
-
124142
message: '#^Parameter \#1 \$headers \(array\<string\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#'
125143
identifier: method.childParameterType
@@ -144,6 +162,18 @@ parameters:
144162
count: 1
145163
path: src/Command/ErrorsConsoleStyle.php
146164

165+
-
166+
message: '#^Call to static method expand\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#'
167+
identifier: staticMethod.internalClass
168+
count: 1
169+
path: src/DependencyInjection/ContainerFactory.php
170+
171+
-
172+
message: '#^Call to static method merge\(\) of internal class Nette\\Schema\\Helpers from outside its root namespace Nette\.$#'
173+
identifier: staticMethod.internalClass
174+
count: 2
175+
path: src/DependencyInjection/ContainerFactory.php
176+
147177
-
148178
message: '#^Variable method call on Nette\\Schema\\Elements\\AnyOf\|Nette\\Schema\\Elements\\Structure\|Nette\\Schema\\Elements\\Type\.$#'
149179
identifier: method.dynamicName

Diff for: src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php

+29-8
Original file line numberDiff line numberDiff line change
@@ -47,37 +47,58 @@ private function buildRestrictedUsage(ExtendedMethodReflection $methodReflection
4747
if (!$isMethodInternal) {
4848
return RestrictedUsage::create(
4949
sprintf(
50-
'Call to method %s() of internal %s %s.',
50+
'Call to %smethod %s() of internal %s %s.',
51+
$methodReflection->isStatic() ? 'static ' : '',
5152
$methodReflection->getName(),
5253
strtolower($methodReflection->getDeclaringClass()->getClassTypeDescription()),
5354
$methodReflection->getDeclaringClass()->getDisplayName(),
5455
),
55-
sprintf('method.internal%s', $methodReflection->getDeclaringClass()->getClassTypeDescription()),
56+
sprintf(
57+
'%s.internal%s',
58+
$methodReflection->isStatic() ? 'staticMethod' : 'method',
59+
$methodReflection->getDeclaringClass()->getClassTypeDescription(),
60+
),
5661
);
5762
}
5863

5964
return RestrictedUsage::create(
60-
sprintf('Call to internal method %s::%s().', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName()),
61-
'method.internal',
65+
sprintf(
66+
'Call to internal %smethod %s::%s().',
67+
$methodReflection->isStatic() ? 'static ' : '',
68+
$methodReflection->getDeclaringClass()->getDisplayName(),
69+
$methodReflection->getName(),
70+
),
71+
sprintf('%s.internal', $methodReflection->isStatic() ? 'staticMethod' : 'method'),
6272
);
6373
}
6474

6575
if (!$isMethodInternal) {
6676
return RestrictedUsage::create(
6777
sprintf(
68-
'Call to method %s() of internal %s %s from outside its root namespace %s.',
78+
'Call to %smethod %s() of internal %s %s from outside its root namespace %s.',
79+
$methodReflection->isStatic() ? 'static ' : '',
6980
$methodReflection->getName(),
7081
strtolower($methodReflection->getDeclaringClass()->getClassTypeDescription()),
7182
$methodReflection->getDeclaringClass()->getDisplayName(),
7283
$namespace,
7384
),
74-
sprintf('method.internal%s', $methodReflection->getDeclaringClass()->getClassTypeDescription()),
85+
sprintf(
86+
'%s.internal%s',
87+
$methodReflection->isStatic() ? 'staticMethod' : 'method',
88+
$methodReflection->getDeclaringClass()->getClassTypeDescription(),
89+
),
7590
);
7691
}
7792

7893
return RestrictedUsage::create(
79-
sprintf('Call to internal method %s::%s() from outside its root namespace %s.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $namespace),
80-
'method.internal',
94+
sprintf(
95+
'Call to internal %smethod %s::%s() from outside its root namespace %s.',
96+
$methodReflection->isStatic() ? 'static ' : '',
97+
$methodReflection->getDeclaringClass()->getDisplayName(),
98+
$methodReflection->getName(),
99+
$namespace,
100+
),
101+
sprintf('%s.internal', $methodReflection->isStatic() ? 'staticMethod' : 'method'),
81102
);
82103
}
83104

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\RestrictedUsage;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Identifier;
7+
use PhpParser\Node\Name;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\DependencyInjection\Container;
10+
use PHPStan\Reflection\ReflectionProvider;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use PHPStan\Rules\RuleLevelHelper;
14+
use PHPStan\Type\ErrorType;
15+
use PHPStan\Type\Type;
16+
17+
/**
18+
* @implements Rule<Node\Expr\StaticCall>
19+
*/
20+
final class RestrictedStaticMethodUsageRule implements Rule
21+
{
22+
23+
public function __construct(
24+
private Container $container,
25+
private ReflectionProvider $reflectionProvider,
26+
private RuleLevelHelper $ruleLevelHelper,
27+
)
28+
{
29+
}
30+
31+
public function getNodeType(): string
32+
{
33+
return Node\Expr\StaticCall::class;
34+
}
35+
36+
/**
37+
* @api
38+
*/
39+
public function processNode(Node $node, Scope $scope): array
40+
{
41+
if (!$node->name instanceof Identifier) {
42+
return [];
43+
}
44+
45+
/** @var RestrictedMethodUsageExtension[] $extensions */
46+
$extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG);
47+
if ($extensions === []) {
48+
return [];
49+
}
50+
51+
$methodName = $node->name->name;
52+
$referencedClasses = [];
53+
54+
if ($node->class instanceof Name) {
55+
$referencedClasses[] = $scope->resolveName($node->class);
56+
} else {
57+
$classTypeResult = $this->ruleLevelHelper->findTypeToCheck(
58+
$scope,
59+
$node->class,
60+
'', // We don't care about the error message
61+
static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(),
62+
);
63+
64+
if ($classTypeResult->getType() instanceof ErrorType) {
65+
return [];
66+
}
67+
68+
$referencedClasses = $classTypeResult->getReferencedClasses();
69+
}
70+
71+
$errors = [];
72+
foreach ($referencedClasses as $referencedClass) {
73+
if (!$this->reflectionProvider->hasClass($referencedClass)) {
74+
continue;
75+
}
76+
77+
$classReflection = $this->reflectionProvider->getClass($referencedClass);
78+
if (!$classReflection->hasMethod($methodName)) {
79+
continue;
80+
}
81+
82+
$methodReflection = $classReflection->getMethod($methodName, $scope);
83+
foreach ($extensions as $extension) {
84+
$restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope);
85+
if ($restrictedUsage === null) {
86+
continue;
87+
}
88+
89+
$errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage)
90+
->identifier($restrictedUsage->identifier)
91+
->build();
92+
}
93+
}
94+
95+
return $errors;
96+
}
97+
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\InternalTag;
4+
5+
use PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
9+
/**
10+
* @extends RuleTestCase<RestrictedStaticMethodUsageRule>
11+
*/
12+
class RestrictedInternalStaticMethodUsageExtensionTest extends RuleTestCase
13+
{
14+
15+
protected function getRule(): Rule
16+
{
17+
return self::getContainer()->getByType(RestrictedStaticMethodUsageRule::class);
18+
}
19+
20+
public function testRule(): void
21+
{
22+
$this->analyse([__DIR__ . '/data/static-method-internal-tag.php'], [
23+
[
24+
'Call to internal static method StaticMethodInternalTagOne\Foo::doInternal() from outside its root namespace StaticMethodInternalTagOne.',
25+
58,
26+
],
27+
[
28+
'Call to static method doFoo() of internal class StaticMethodInternalTagOne\FooInternal from outside its root namespace StaticMethodInternalTagOne.',
29+
63,
30+
],
31+
[
32+
'Call to internal static method StaticMethodInternalTagOne\Foo::doInternal() from outside its root namespace StaticMethodInternalTagOne.',
33+
71,
34+
],
35+
36+
[
37+
'Call to static method doFoo() of internal class StaticMethodInternalTagOne\FooInternal from outside its root namespace StaticMethodInternalTagOne.',
38+
76,
39+
],
40+
[
41+
'Call to internal static method FooWithInternalStaticMethodWithoutNamespace::doInternal().',
42+
107,
43+
],
44+
[
45+
'Call to static method doFoo() of internal class FooInternalStaticWithoutNamespace.',
46+
112,
47+
],
48+
[
49+
'Call to internal static method FooWithInternalStaticMethodWithoutNamespace::doInternal().',
50+
120,
51+
],
52+
[
53+
'Call to static method doFoo() of internal class FooInternalStaticWithoutNamespace.',
54+
125,
55+
],
56+
]);
57+
}
58+
59+
}

0 commit comments

Comments
 (0)