Skip to content

Commit

Permalink
Bleeding edge - API class const fetch rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 26, 2022
1 parent 7aa19e0 commit 9e00725
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 0 deletions.
4 changes: 4 additions & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -82,6 +84,8 @@ rules:
- PHPStan\Rules\Whitespace\FileWhitespaceRule

services:
-
class: PHPStan\Rules\Api\ApiClassConstFetchRule
-
class: PHPStan\Rules\Api\ApiInstanceofRule
-
Expand Down
75 changes: 75 additions & 0 deletions src/Rules/Api/ApiClassConstFetchRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Api;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function count;
use function sprintf;

/**
* @implements Rule<Node\Expr\ClassConstFetch>
*/
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];
}

}
41 changes: 41 additions & 0 deletions tests/PHPStan/Rules/Api/ApiClassConstFetchRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Api;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use function sprintf;

/**
* @extends RuleTestCase<ApiClassConstFetchRule>
*/
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,
],
]);
}

}
17 changes: 17 additions & 0 deletions tests/PHPStan/Rules/Api/data/class-const-fetch-in-phpstan.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace PHPStan\ClassConstFetch;

use PHPStan\Command\AnalyseCommand;
use PHPStan\Reflection\ClassReflection;

class Foo
{

public function doFoo()
{
echo ClassReflection::class;
echo AnalyseCommand::class;
}

}
17 changes: 17 additions & 0 deletions tests/PHPStan/Rules/Api/data/class-const-fetch-out-of-phpstan.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\ClassConstFetch;

use PHPStan\Command\AnalyseCommand;
use PHPStan\Reflection\ClassReflection;

class Foo
{

public function doFoo()
{
echo ClassReflection::class;
echo AnalyseCommand::class;
}

}

0 comments on commit 9e00725

Please sign in to comment.