Skip to content

Commit 1f54e5a

Browse files
committed
Bleeding edge - report restricted @internal class name usage
1 parent 7d448c7 commit 1f54e5a

10 files changed

+309
-24
lines changed

conf/config.level0.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ rules:
103103
- PHPStan\Rules\Variables\UnsetRule
104104
- PHPStan\Rules\Whitespace\FileWhitespaceRule
105105

106+
conditionalTags:
107+
PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension:
108+
phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag%
109+
106110
services:
107111
-
108112
class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
@@ -290,3 +294,6 @@ services:
290294
currentWorkingDirectory: %currentWorkingDirectory%
291295
tags:
292296
- phpstan.rules.rule
297+
298+
-
299+
class: PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension

conf/config.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,10 @@ services:
19881988
tags:
19891989
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
19901990

1991+
-
1992+
class: PHPStan\Type\PHPStan\ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension
1993+
tags:
1994+
- phpstan.broker.dynamicMethodReturnTypeExtension
19911995
-
19921996
class: PHPStan\Type\ClosureTypeFactory
19931997
arguments:

phpstan-baseline.neon

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,12 +750,30 @@ parameters:
750750
count: 1
751751
path: src/Testing/LevelsTestCase.php
752752

753+
-
754+
message: '#^Catching internal class PHPUnit\\Framework\\AssertionFailedError\.$#'
755+
identifier: catch.internalClass
756+
count: 2
757+
path: src/Testing/LevelsTestCase.php
758+
759+
-
760+
message: '#^Return type references internal class PHPUnit\\Framework\\AssertionFailedError\.$#'
761+
identifier: return.internalClass
762+
count: 1
763+
path: src/Testing/LevelsTestCase.php
764+
753765
-
754766
message: '#^Anonymous function has an unused use \$container\.$#'
755767
identifier: closure.unusedUse
756768
count: 1
757769
path: src/Testing/PHPStanTestCase.php
758770

771+
-
772+
message: '#^Catching internal class PHPUnit\\Framework\\ExpectationFailedException\.$#'
773+
identifier: catch.internalClass
774+
count: 1
775+
path: src/Testing/PHPStanTestCase.php
776+
759777
-
760778
message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#'
761779
identifier: phpstanApi.instanceofType
@@ -1470,6 +1488,12 @@ parameters:
14701488
count: 4
14711489
path: src/Type/ObjectWithoutClassType.php
14721490

1491+
-
1492+
message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#'
1493+
identifier: phpstanApi.runtimeReflection
1494+
count: 1
1495+
path: src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php
1496+
14731497
-
14741498
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#'
14751499
identifier: phpstanApi.instanceofType
@@ -1932,6 +1956,24 @@ parameters:
19321956
count: 1
19331957
path: tests/PHPStan/Node/FileNodeTest.php
19341958

1959+
-
1960+
message: '#^Access to constant on internal class InternalAnnotations\\InternalFoo\.$#'
1961+
identifier: classConstant.internalClass
1962+
count: 1
1963+
path: tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php
1964+
1965+
-
1966+
message: '#^Access to constant on internal interface InternalAnnotations\\InternalFooInterface\.$#'
1967+
identifier: classConstant.internalInterface
1968+
count: 1
1969+
path: tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php
1970+
1971+
-
1972+
message: '#^Access to constant on internal trait InternalAnnotations\\InternalFooTrait\.$#'
1973+
identifier: classConstant.internalTrait
1974+
count: 1
1975+
path: tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php
1976+
19351977
-
19361978
message: '#^PHPDoc tag @var with type string is not subtype of type class\-string\.$#'
19371979
identifier: varTag.type
@@ -1944,18 +1986,48 @@ parameters:
19441986
count: 1
19451987
path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php
19461988

1989+
-
1990+
message: '#^Instanceof references internal interface PHPUnit\\Exception\.$#'
1991+
identifier: instanceof.internalInterface
1992+
count: 1
1993+
path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php
1994+
19471995
-
19481996
message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#'
19491997
identifier: phpstanApi.constructor
19501998
count: 1
19511999
path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php
19522000

2001+
-
2002+
message: '#^Catching internal class PHPUnit\\Framework\\AssertionFailedError\.$#'
2003+
identifier: catch.internalClass
2004+
count: 1
2005+
path: tests/PHPStan/Rules/WarningEmittingRuleTest.php
2006+
19532007
-
19542008
message: '#^Call to method getComparisonFailure\(\) of internal class PHPUnit\\Framework\\ExpectationFailedException from outside its root namespace PHPUnit\.$#'
19552009
identifier: method.internalClass
19562010
count: 2
19572011
path: tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php
19582012

2013+
-
2014+
message: '#^Catching internal class PHPUnit\\Framework\\ExpectationFailedException\.$#'
2015+
identifier: catch.internalClass
2016+
count: 1
2017+
path: tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php
2018+
2019+
-
2020+
message: '#^Access to constant on internal class PHPUnit\\Framework\\AssertionFailedError\.$#'
2021+
identifier: classConstant.internalClass
2022+
count: 1
2023+
path: tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
2024+
2025+
-
2026+
message: '#^Catching internal class PHPUnit\\Framework\\AssertionFailedError\.$#'
2027+
identifier: catch.internalClass
2028+
count: 1
2029+
path: tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
2030+
19592031
-
19602032
message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Generic\\TemplateType is always PHPStan\\Type\\Generic\\TemplateMixedType but it''s error\-prone and dangerous\.$#'
19612033
identifier: phpstanApi.varTagAssumption

src/Rules/ClassNameCheck.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function __construct(
2626
public function checkClassNames(
2727
Scope $scope,
2828
array $pairs,
29-
ClassNameUsageLocation $location,
29+
?ClassNameUsageLocation $location,
3030
bool $checkClassCaseSensitivity = true,
3131
): array
3232
{
@@ -41,6 +41,10 @@ public function checkClassNames(
4141
$errors[] = $error;
4242
}
4343

44+
if ($location === null) {
45+
return $errors;
46+
}
47+
4448
/** @var RestrictedClassNameUsageExtension[] $extensions */
4549
$extensions = $this->container->getServicesByTag(RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG);
4650
if ($extensions === []) {

src/Rules/ClassNameUsageLocation.php

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,46 @@
33
namespace PHPStan\Rules;
44

55
use function array_key_exists;
6+
use function sprintf;
7+
use function ucfirst;
68

79
final class ClassNameUsageLocation
810
{
911

1012
public const TRAIT_USE = 'traitUse';
11-
public const STATIC_PROPERTY_ACCESS = 'staticPropertyAccess';
12-
public const PHPDOC_TAG_ASSERT = 'phpDocTagAssert';
13+
public const STATIC_PROPERTY_ACCESS = 'staticProperty';
14+
public const PHPDOC_TAG_ASSERT = 'assert';
1315
public const ATTRIBUTE = 'attribute';
14-
public const EXCEPTION_CATCH = 'exceptionCatch';
15-
public const CLASS_CONSTANT_ACCESS = 'classConstantAccess';
16+
public const EXCEPTION_CATCH = 'catch';
17+
public const CLASS_CONSTANT_ACCESS = 'classConstant';
1618
public const CLASS_IMPLEMENTS = 'classImplements';
1719
public const ENUM_IMPLEMENTS = 'enumImplements';
1820
public const INTERFACE_EXTENDS = 'interfaceExtends';
1921
public const CLASS_EXTENDS = 'classExtends';
2022
public const INSTANCEOF = 'instanceof';
21-
public const PROPERTY_TYPE = 'propertyType';
22-
public const USE_STATEMENT = 'use';
23-
public const PARAMETER_TYPE = 'parameterType';
24-
public const RETURN_TYPE = 'returnType';
25-
public const PHPDOC_TAG_SELF_OUT = 'phpDocTagSelfOut';
26-
public const PHPDOC_TAG_VAR = 'phpDocTagVar';
23+
public const PROPERTY_TYPE = 'property';
24+
public const PARAMETER_TYPE = 'parameter';
25+
public const RETURN_TYPE = 'return';
26+
public const PHPDOC_TAG_SELF_OUT = 'selfOut';
27+
public const PHPDOC_TAG_VAR = 'varTag';
2728
public const INSTANTIATION = 'new';
2829
public const TYPE_ALIAS = 'typeAlias';
29-
public const PHPDOC_TAG_METHOD = 'phpDocTagMethod';
30-
public const PHPDOC_TAG_MIXIN = 'phpDocTagMixin';
31-
public const PHPDOC_TAG_PROPERTY = 'phpDocTagProperty';
32-
public const PHPDOC_TAG_REQUIRE_EXTENDS = 'phpDocTagRequireExtends';
33-
public const PHPDOC_TAG_REQUIRE_IMPLEMENTS = 'phpDocTagRequireImplements';
34-
public const STATIC_METHOD_CALL = 'staticMethodCall';
35-
public const PHPDOC_TAG_TEMPLATE_BOUND = 'phpDocTemplateBound';
36-
public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'phpDocTemplateDefault';
30+
public const PHPDOC_TAG_METHOD = 'methodTag';
31+
public const PHPDOC_TAG_MIXIN = 'mixin';
32+
public const PHPDOC_TAG_PROPERTY = 'propertyTag';
33+
public const PHPDOC_TAG_REQUIRE_EXTENDS = 'requireExtends';
34+
public const PHPDOC_TAG_REQUIRE_IMPLEMENTS = 'requireImplements';
35+
public const STATIC_METHOD_CALL = 'staticMethod';
36+
public const PHPDOC_TAG_TEMPLATE_BOUND = 'templateBound';
37+
public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'templateDefault';
3738

3839
/** @var array<string, self> */
3940
public static array $registry = [];
4041

4142
/**
4243
* @param self::* $value
4344
*/
44-
private function __construct(string $value) // @phpstan-ignore constructor.unusedParameter
45+
private function __construct(public readonly string $value)
4546
{
4647
}
4748

@@ -57,4 +58,86 @@ public static function from(string $value): self
5758
return self::$registry[$value] = new self($value);
5859
}
5960

61+
public function createMessage(string $part): string
62+
{
63+
switch ($this->value) {
64+
case self::TRAIT_USE:
65+
return sprintf('Usage of %s.', $part);
66+
case self::STATIC_PROPERTY_ACCESS:
67+
return sprintf('Access to static property on %s.', $part);
68+
case self::PHPDOC_TAG_ASSERT:
69+
return sprintf('Assert tag references %s.', $part);
70+
case self::ATTRIBUTE:
71+
return sprintf('Attribute references %s.', $part);
72+
case self::EXCEPTION_CATCH:
73+
return sprintf('Catching %s.', $part);
74+
case self::CLASS_CONSTANT_ACCESS:
75+
return sprintf('Access to constant on %s.', $part);
76+
case self::CLASS_IMPLEMENTS:
77+
return sprintf('Class implements %s.', $part);
78+
case self::ENUM_IMPLEMENTS:
79+
return sprintf('Enum implements %s.', $part);
80+
case self::INTERFACE_EXTENDS:
81+
return sprintf('Interface extends %s.', $part);
82+
case self::CLASS_EXTENDS:
83+
return sprintf('Class extends %s.', $part);
84+
case self::INSTANCEOF:
85+
return sprintf('Instanceof references %s.', $part);
86+
case self::PROPERTY_TYPE:
87+
return sprintf('Property references %s in its type.', $part);
88+
case self::PARAMETER_TYPE:
89+
return sprintf('Parameter references %s in its type.', $part);
90+
case self::RETURN_TYPE:
91+
return sprintf('Return type references %s.', $part);
92+
case self::PHPDOC_TAG_SELF_OUT:
93+
return sprintf('PHPDoc tag @phpstan-self-out references %s.', $part);
94+
case self::PHPDOC_TAG_VAR:
95+
return sprintf('PHPDoc tag @var references %s.', $part);
96+
case self::INSTANTIATION:
97+
return sprintf('Instantiating %s.', $part);
98+
case self::TYPE_ALIAS:
99+
return sprintf('Type alias references %s.', $part);
100+
case self::PHPDOC_TAG_METHOD:
101+
return sprintf('PHPDoc tag @method references %s.', $part);
102+
case self::PHPDOC_TAG_MIXIN:
103+
return sprintf('PHPDoc tag @mixin references %s.', $part);
104+
case self::PHPDOC_TAG_PROPERTY:
105+
return sprintf('PHPDoc tag @property references %s.', $part);
106+
case self::PHPDOC_TAG_REQUIRE_EXTENDS:
107+
return sprintf('PHPDoc tag @phpstan-require-extends references %s.', $part);
108+
case self::PHPDOC_TAG_REQUIRE_IMPLEMENTS:
109+
return sprintf('PHPDoc tag @phpstan-require-implements references %s.', $part);
110+
case self::STATIC_METHOD_CALL:
111+
return sprintf('Call to static method on %s.', $part);
112+
case self::PHPDOC_TAG_TEMPLATE_BOUND:
113+
return sprintf('PHPDoc tag @template bound references %s.', $part);
114+
case self::PHPDOC_TAG_TEMPLATE_DEFAULT:
115+
return sprintf('PHPDoc tag @template default references %s.', $part);
116+
}
117+
}
118+
119+
public function createIdentifier(string $secondPart): string
120+
{
121+
if ($this->value === self::CLASS_IMPLEMENTS) {
122+
return sprintf('class.implements%s', ucfirst($secondPart));
123+
}
124+
if ($this->value === self::ENUM_IMPLEMENTS) {
125+
return sprintf('enum.implements%s', ucfirst($secondPart));
126+
}
127+
if ($this->value === self::INTERFACE_EXTENDS) {
128+
return sprintf('interface.extends%s', ucfirst($secondPart));
129+
}
130+
if ($this->value === self::CLASS_EXTENDS) {
131+
return sprintf('class.extends%s', ucfirst($secondPart));
132+
}
133+
if ($this->value === self::PHPDOC_TAG_TEMPLATE_BOUND) {
134+
return sprintf('generics.%sBound', $secondPart);
135+
}
136+
if ($this->value === self::PHPDOC_TAG_TEMPLATE_DEFAULT) {
137+
return sprintf('generics.%sDefault', $secondPart);
138+
}
139+
140+
return sprintf('%s.%s', $this->value, $secondPart);
141+
}
142+
60143
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\InternalTag;
4+
5+
use PHPStan\Analyser\Scope;
6+
use PHPStan\Reflection\ClassReflection;
7+
use PHPStan\Rules\ClassNameUsageLocation;
8+
use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension;
9+
use PHPStan\Rules\RestrictedUsage\RestrictedUsage;
10+
use function sprintf;
11+
use function strtolower;
12+
13+
final class RestrictedInternalClassNameUsageExtension implements RestrictedClassNameUsageExtension
14+
{
15+
16+
public function __construct(
17+
private RestrictedInternalUsageHelper $helper,
18+
)
19+
{
20+
}
21+
22+
public function isRestrictedClassNameUsage(
23+
ClassReflection $classReflection,
24+
Scope $scope,
25+
ClassNameUsageLocation $location,
26+
): ?RestrictedUsage
27+
{
28+
if (!$classReflection->isInternal()) {
29+
return null;
30+
}
31+
32+
if (!$this->helper->shouldBeReported($scope, $classReflection->getName())) {
33+
return null;
34+
}
35+
36+
if ($location->value === ClassNameUsageLocation::STATIC_METHOD_CALL) {
37+
return null;
38+
}
39+
40+
return RestrictedUsage::create(
41+
$location->createMessage(sprintf('internal %s %s', strtolower($classReflection->getClassTypeDescription()), $classReflection->getDisplayName())),
42+
$location->createIdentifier(sprintf('internal%s', $classReflection->getClassTypeDescription())),
43+
);
44+
}
45+
46+
}

src/Rules/Namespaces/ExistingNamesInGroupUseRule.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Reflection\ReflectionProvider;
99
use PHPStan\Rules\ClassNameCheck;
1010
use PHPStan\Rules\ClassNameNodePair;
11-
use PHPStan\Rules\ClassNameUsageLocation;
1211
use PHPStan\Rules\IdentifierRuleError;
1312
use PHPStan\Rules\Rule;
1413
use PHPStan\Rules\RuleErrorBuilder;
@@ -128,7 +127,7 @@ private function checkClass(Scope $scope, Node\Name $name): ?IdentifierRuleError
128127
{
129128
$errors = $this->classCheck->checkClassNames($scope, [
130129
new ClassNameNodePair((string) $name, $name),
131-
], ClassNameUsageLocation::from(ClassNameUsageLocation::USE_STATEMENT));
130+
], null);
132131
if (count($errors) === 0) {
133132
return null;
134133
} elseif (count($errors) === 1) {

0 commit comments

Comments
 (0)