Skip to content

Commit 83cc69f

Browse files
committed
Rewrite ApiClassConstFetchRule as a restricted class constant usage extension
Nonexistent constants are no longer reported, ClassConstantRule already reports them. Amp-Thread-ID: https://ampcode.com/threads/T-01a1bf5f-ca34-7733-8886-c850b9908584
1 parent 7c155a9 commit 83cc69f

4 files changed

Lines changed: 87 additions & 127 deletions

File tree

‎src/Rules/Api/ApiClassConstFetchRule.php‎

Lines changed: 0 additions & 118 deletions
This file was deleted.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Api;
4+
5+
use PHPStan\Analyser\Scope;
6+
use PHPStan\DependencyInjection\AutowiredService;
7+
use PHPStan\Reflection\ClassConstantReflection;
8+
use PHPStan\Reflection\ClassReflection;
9+
use PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageExtension;
10+
use PHPStan\Rules\RestrictedUsage\RestrictedUsage;
11+
use function count;
12+
use function sprintf;
13+
use function str_contains;
14+
15+
#[AutowiredService]
16+
final class ApiClassConstantUsageExtension implements RestrictedClassConstantUsageExtension
17+
{
18+
19+
public function __construct(private ApiRuleHelper $apiRuleHelper)
20+
{
21+
}
22+
23+
public function isRestrictedClassConstantUsage(
24+
ClassConstantReflection $constantReflection,
25+
Scope $scope,
26+
): ?RestrictedUsage
27+
{
28+
$declaringClass = $constantReflection->getDeclaringClass();
29+
if (!$this->apiRuleHelper->isPhpStanCode($scope, $declaringClass->getName(), $declaringClass->getFileName())) {
30+
return null;
31+
}
32+
33+
if ($this->isClassCovered($declaringClass)) {
34+
return null;
35+
}
36+
37+
if ($constantReflection->getName() === 'class') {
38+
// Foo::class is fine when the class has a method covered by the backward compatibility promise
39+
foreach ($declaringClass->getNativeReflection()->getMethods() as $methodReflection) {
40+
$methodDocComment = $methodReflection->getDocComment();
41+
if ($methodDocComment !== false && str_contains($methodDocComment, '@api')) {
42+
return null;
43+
}
44+
}
45+
} else {
46+
$constantDocComment = $constantReflection->getDocComment();
47+
if ($constantDocComment !== null && str_contains($constantDocComment, '@api')) {
48+
return null;
49+
}
50+
}
51+
52+
return RestrictedUsage::create(
53+
sprintf(
54+
'Accessing %s::%s is not covered by backward compatibility promise. The class might change in a minor PHPStan version.',
55+
$declaringClass->getDisplayName(),
56+
$constantReflection->getName(),
57+
),
58+
'phpstanApi.classConstant',
59+
sprintf(
60+
"If you think it should be covered by backward compatibility promise, open a discussion:\n %s\n\n See also:\n https://phpstan.org/developing-extensions/backward-compatibility-promise",
61+
'https://github.com/phpstan/phpstan/discussions',
62+
),
63+
);
64+
}
65+
66+
private function isClassCovered(ClassReflection $classReflection): bool
67+
{
68+
$docBlock = $classReflection->getResolvedPhpDoc();
69+
if ($docBlock === null) {
70+
return false;
71+
}
72+
73+
foreach ($docBlock->getPhpDocNodes() as $phpDocNode) {
74+
if (count($phpDocNode->getTagsByName('@api')) > 0) {
75+
return true;
76+
}
77+
}
78+
79+
return false;
80+
}
81+
82+
}

tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php renamed to tests/PHPStan/Rules/Api/ApiClassConstantUsageExtensionTest.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
namespace PHPStan\Rules\Api;
44

5+
use PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageRule;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78
use function sprintf;
89

910
/**
10-
* @extends RuleTestCase<ApiClassConstFetchRule>
11+
* @extends RuleTestCase<RestrictedClassConstantUsageRule>
1112
*/
12-
class ApiClassConstFetchRuleTest extends RuleTestCase
13+
class ApiClassConstantUsageExtensionTest extends RuleTestCase
1314
{
1415

1516
protected function getRule(): Rule
1617
{
17-
return new ApiClassConstFetchRule(new ApiRuleHelper(), self::createReflectionProvider());
18+
return self::getContainer()->getByType(RestrictedClassConstantUsageRule::class);
1819
}
1920

2021
public function testRuleInPhpStan(): void
@@ -35,11 +36,6 @@ public function testRuleOutOfPhpStan(): void
3536
19,
3637
$tip,
3738
],
38-
[
39-
'Accessing PHPStan\Analyser\NodeScopeResolver::FOO is not covered by backward compatibility promise. The class might change in a minor PHPStan version.',
40-
23,
41-
$tip,
42-
],
4339
[
4440
'Accessing PHPStan\Turbo\TurboExtensionSelector::MINIMUM_PHP_VERSION_ID is not covered by backward compatibility promise. The class might change in a minor PHPStan version.',
4541
28,

‎tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function doFoo()
2020
echo NodeScopeResolver::class;
2121
echo ScopeFactory::class;
2222

23-
echo NodeScopeResolver::FOO;
23+
echo NodeScopeResolver::FOO; // nonexistent constants are reported by ClassConstantRule
2424

2525
echo Scope::SUPERGLOBAL_VARIABLES;
2626
echo MutatingScope::SUPERGLOBAL_VARIABLES;

0 commit comments

Comments
 (0)