Skip to content

Commit

Permalink
Bleeding edge - ApiInstanceofRule
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 17, 2022
1 parent ffc355d commit ff4d02d
Show file tree
Hide file tree
Showing 5 changed files with 160 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\ApiInstanceofRule:
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
PHPStan\Rules\Api\RuntimeReflectionFunctionRule:
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
PHPStan\Rules\Api\RuntimeReflectionInstantiationRule:
Expand Down Expand Up @@ -80,6 +82,8 @@ rules:
- PHPStan\Rules\Whitespace\FileWhitespaceRule

services:
-
class: PHPStan\Rules\Api\ApiInstanceofRule
-
class: PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule
-
Expand Down
71 changes: 71 additions & 0 deletions src/Rules/Api/ApiInstanceofRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?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\Instanceof_>
*/
class ApiInstanceofRule implements Rule
{

public function __construct(
private ApiRuleHelper $apiRuleHelper,
private ReflectionProvider $reflectionProvider,
)
{
}

public function getNodeType(): string
{
return Node\Expr\Instanceof_::class;
}

public function processNode(Node $node, Scope $scope): array
{
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(
'Asking about instanceof %s is not covered by backward compatibility promise. The %s might change in a minor PHPStan version.',
$classReflection->getDisplayName(),
$classReflection->isInterface() ? 'interface' : 'class',
))->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/ApiInstanceofRuleTest.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<ApiInstanceofRule>
*/
class ApiInstanceofRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new ApiInstanceofRule(new ApiRuleHelper(), $this->createReflectionProvider());
}

public function testRuleInPhpStan(): void
{
$this->analyse([__DIR__ . '/data/instanceof-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/instanceof-out-of-phpstan.php'], [
[
'Asking about instanceof PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator is not covered by backward compatibility promise. The class might change in a minor PHPStan version.',
17,
$tip,
],
]);
}

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

namespace PHPStan\TestInstanceof;

use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator;
use PHPStan\Reflection\ClassReflection;

class Foo
{

public function doFoo(object $o): void
{
if ($o instanceof ClassReflection) {

}

if ($o instanceof AutoloadSourceLocator) {

}
}

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

namespace App\TestInstanceof;

use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator;
use PHPStan\Reflection\ClassReflection;

class Foo
{

public function doFoo(object $o): void
{
if ($o instanceof ClassReflection) {

}

if ($o instanceof AutoloadSourceLocator) {

}
}

}

0 comments on commit ff4d02d

Please sign in to comment.