Skip to content

Commit a3c6cad

Browse files
committed
RestrictedInternalClassNameUsageExtension - report static method call on internal subclass
1 parent 975afcd commit a3c6cad

6 files changed

+88
-12
lines changed

src/Rules/ClassNameUsageLocation.php

+15-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace PHPStan\Rules;
44

5-
use function array_key_exists;
5+
use PHPStan\Reflection\ExtendedMethodReflection;
66
use function sprintf;
77
use function ucfirst;
88

@@ -39,26 +39,26 @@ final class ClassNameUsageLocation
3939
public const PHPDOC_TAG_TEMPLATE_BOUND = 'templateBound';
4040
public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'templateDefault';
4141

42-
/** @var array<string, self> */
43-
public static array $registry = [];
44-
4542
/**
4643
* @param self::* $value
44+
* @param mixed[] $data
4745
*/
48-
private function __construct(public readonly string $value)
46+
private function __construct(public readonly string $value, public readonly array $data)
4947
{
5048
}
5149

5250
/**
5351
* @param self::* $value
52+
* @param mixed[] $data
5453
*/
55-
public static function from(string $value): self
54+
public static function from(string $value, array $data = []): self
5655
{
57-
if (array_key_exists($value, self::$registry)) {
58-
return self::$registry[$value];
59-
}
56+
return new self($value, $data);
57+
}
6058

61-
return self::$registry[$value] = new self($value);
59+
public function getMethod(): ?ExtendedMethodReflection
60+
{
61+
return $this->data['method'] ?? null;
6262
}
6363

6464
public function createMessage(string $part): string
@@ -111,6 +111,11 @@ public function createMessage(string $part): string
111111
case self::PHPDOC_TAG_REQUIRE_IMPLEMENTS:
112112
return sprintf('PHPDoc tag @phpstan-require-implements references %s.', $part);
113113
case self::STATIC_METHOD_CALL:
114+
$method = $this->getMethod();
115+
if ($method !== null) {
116+
return sprintf('Call to static method %s() on %s.', $method->getName(), $part);
117+
}
118+
114119
return sprintf('Call to static method on %s.', $part);
115120
case self::PHPDOC_TAG_TEMPLATE_BOUND:
116121
return sprintf('PHPDoc tag @template bound references %s.', $part);

src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ public function isRestrictedClassNameUsage(
3434
}
3535

3636
if ($location->value === ClassNameUsageLocation::STATIC_METHOD_CALL) {
37-
return null;
37+
$method = $location->getMethod();
38+
if ($method !== null) {
39+
if ($method->isInternal()->yes() || $method->getDeclaringClass()->isInternal()) {
40+
return null;
41+
}
42+
}
3843
}
3944

4045
return RestrictedUsage::create(

src/Rules/Methods/StaticMethodCallCheck.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,16 @@ public function check(
140140
];
141141
}
142142

143+
$locationData = [];
144+
$locationClassReflection = $this->reflectionProvider->getClass($className);
145+
if ($locationClassReflection->hasMethod($methodName)) {
146+
$locationData['method'] = $locationClassReflection->getMethod($methodName, $scope);
147+
}
148+
143149
$errors = $this->classCheck->checkClassNames(
144150
$scope,
145151
[new ClassNameNodePair($className, $class)],
146-
ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_METHOD_CALL),
152+
ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_METHOD_CALL, $locationData),
147153
);
148154

149155
$classType = $scope->resolveTypeByName($class);

tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,14 @@ public function testRule(): void
5656
]);
5757
}
5858

59+
public function testStaticMethodCallOnInternalSubclass(): void
60+
{
61+
$this->analyse([__DIR__ . '/data/static-method-call-on-internal-subclass.php'], [
62+
[
63+
'Call to static method doBar() of internal class StaticMethodCallOnInternalSubclassOne\Bar from outside its root namespace StaticMethodCallOnInternalSubclassOne.',
64+
34,
65+
],
66+
]);
67+
}
68+
5969
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace StaticMethodCallOnInternalSubclassOne {
4+
5+
class Foo
6+
{
7+
public static function doFoo(): void
8+
{
9+
10+
}
11+
}
12+
13+
/**
14+
* @internal
15+
*/
16+
class Bar extends Foo
17+
{
18+
19+
public static function doBar(): void
20+
{
21+
22+
}
23+
24+
}
25+
26+
}
27+
28+
namespace StaticMethodCallOnInternalSubclassTwo {
29+
30+
use StaticMethodCallOnInternalSubclassOne\Bar;
31+
32+
function (): void {
33+
Bar::doFoo();
34+
Bar::doBar();
35+
};
36+
}

tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php

+14
Original file line numberDiff line numberDiff line change
@@ -899,4 +899,18 @@ public function testDynamicCall(): void
899899
]);
900900
}
901901

902+
public function testRestrictedInternalClassNameUsage(): void
903+
{
904+
$this->checkThisOnly = false;
905+
$this->checkExplicitMixed = true;
906+
$this->checkImplicitMixed = true;
907+
908+
$this->analyse([__DIR__ . '/../InternalTag/data/static-method-call-on-internal-subclass.php'], [
909+
[
910+
'Call to static method doFoo() on internal class StaticMethodCallOnInternalSubclassOne\Bar.',
911+
33,
912+
],
913+
]);
914+
}
915+
902916
}

0 commit comments

Comments
 (0)