From 9e007251ce61788f6a0319a53f1de6cf801ed233 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 26 Jun 2022 16:29:59 +0200 Subject: [PATCH] Bleeding edge - API class const fetch rule --- conf/config.level0.neon | 4 + src/Rules/Api/ApiClassConstFetchRule.php | 75 +++++++++++++++++++ .../Rules/Api/ApiClassConstFetchRuleTest.php | 41 ++++++++++ .../Api/data/class-const-fetch-in-phpstan.php | 17 +++++ .../data/class-const-fetch-out-of-phpstan.php | 17 +++++ 5 files changed, 154 insertions(+) create mode 100644 src/Rules/Api/ApiClassConstFetchRule.php create mode 100644 tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php create mode 100644 tests/PHPStan/Rules/Api/data/class-const-fetch-in-phpstan.php create mode 100644 tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 6f06e53df4..16d764c02c 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -12,6 +12,8 @@ conditionalTags: phpstan.rules.rule: %checkUninitializedProperties% PHPStan\Rules\Methods\ConsistentConstructorRule: phpstan.rules.rule: %featureToggles.consistentConstructor% + PHPStan\Rules\Api\ApiClassConstFetchRule: + phpstan.rules.rule: %featureToggles.runtimeReflectionRules% PHPStan\Rules\Api\ApiInstanceofRule: phpstan.rules.rule: %featureToggles.runtimeReflectionRules% PHPStan\Rules\Api\RuntimeReflectionFunctionRule: @@ -82,6 +84,8 @@ rules: - PHPStan\Rules\Whitespace\FileWhitespaceRule services: + - + class: PHPStan\Rules\Api\ApiClassConstFetchRule - class: PHPStan\Rules\Api\ApiInstanceofRule - diff --git a/src/Rules/Api/ApiClassConstFetchRule.php b/src/Rules/Api/ApiClassConstFetchRule.php new file mode 100644 index 0000000000..d7e148c387 --- /dev/null +++ b/src/Rules/Api/ApiClassConstFetchRule.php @@ -0,0 +1,75 @@ + + */ +class ApiClassConstFetchRule implements Rule +{ + + public function __construct( + private ApiRuleHelper $apiRuleHelper, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\ClassConstFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + + if (!$node->class instanceof Node\Name) { + return []; + } + + $className = $scope->resolveName($node->class); + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if (!$this->apiRuleHelper->isPhpStanCode($scope, $classReflection->getName(), $classReflection->getFileName())) { + return []; + } + + $ruleError = RuleErrorBuilder::message(sprintf( + 'Accessing %s::%s is not covered by backward compatibility promise. The class might change in a minor PHPStan version.', + $classReflection->getDisplayName(), + $node->name->toString(), + ))->tip(sprintf( + "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", + 'https://github.com/phpstan/phpstan/discussions', + ))->build(); + + $docBlock = $classReflection->getResolvedPhpDoc(); + if ($docBlock === null) { + return [$ruleError]; + } + + foreach ($docBlock->getPhpDocNodes() as $phpDocNode) { + $apiTags = $phpDocNode->getTagsByName('@api'); + if (count($apiTags) > 0) { + return []; + } + } + + return [$ruleError]; + } + +} diff --git a/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php new file mode 100644 index 0000000000..c69be145b9 --- /dev/null +++ b/tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php @@ -0,0 +1,41 @@ + + */ +class ApiClassConstFetchRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ApiClassConstFetchRule(new ApiRuleHelper(), $this->createReflectionProvider()); + } + + public function testRuleInPhpStan(): void + { + $this->analyse([__DIR__ . '/data/class-const-fetch-in-phpstan.php'], []); + } + + public function testRuleOutOfPhpStan(): void + { + $tip = sprintf( + "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", + 'https://github.com/phpstan/phpstan/discussions', + ); + + $this->analyse([__DIR__ . '/data/class-const-fetch-out-of-phpstan.php'], [ + [ + 'Accessing PHPStan\Command\AnalyseCommand::class is not covered by backward compatibility promise. The class might change in a minor PHPStan version.', + 14, + $tip, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Api/data/class-const-fetch-in-phpstan.php b/tests/PHPStan/Rules/Api/data/class-const-fetch-in-phpstan.php new file mode 100644 index 0000000000..2879583dad --- /dev/null +++ b/tests/PHPStan/Rules/Api/data/class-const-fetch-in-phpstan.php @@ -0,0 +1,17 @@ +