rector/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php

143 lines
4.8 KiB
PHP
Raw Normal View History

<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan\Scope;
use PhpParser\Node;
2018-08-04 20:32:01 +00:00
use PhpParser\Node\Stmt\Class_;
2019-03-10 23:47:43 +00:00
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
2019-05-12 08:19:38 +00:00
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
use PHPStan\Analyser\NodeScopeResolver as PHPStanNodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use PHPStan\Node\UnreachableStatementNode;
2019-03-10 23:47:43 +00:00
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
2019-08-05 16:52:55 +00:00
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
/**
* @inspired by https://github.com/silverstripe/silverstripe-upgrader/blob/532182b23e854d02e0b27e68ebc394f436de0682/src/UpgradeRule/PHP/Visitor/PHPStanScopeVisitor.php
* - https://github.com/silverstripe/silverstripe-upgrader/pull/57/commits/e5c7cfa166ad940d9d4ff69537d9f7608e992359#diff-5e0807bb3dc03d6a8d8b6ad049abd774
*/
final class NodeScopeResolver
{
/**
* @var PHPStanNodeScopeResolver
*/
private $phpStanNodeScopeResolver;
/**
* @var ScopeFactory
*/
private $scopeFactory;
/**
* @var Broker
*/
private $broker;
/**
* @var RemoveDeepChainMethodCallNodeVisitor
*/
private $removeDeepChainMethodCallNodeVisitor;
2019-05-12 08:19:38 +00:00
/**
2019-08-05 16:52:55 +00:00
* @var TraitNodeScopeCollector
2019-05-12 08:19:38 +00:00
*/
2019-08-05 16:52:55 +00:00
private $traitNodeScopeCollector;
public function __construct(
ScopeFactory $scopeFactory,
PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
Broker $broker,
2019-05-12 08:19:38 +00:00
RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor,
2019-08-05 16:52:55 +00:00
TraitNodeScopeCollector $traitNodeScopeCollector
) {
$this->scopeFactory = $scopeFactory;
$this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver;
$this->broker = $broker;
$this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor;
2019-08-05 16:52:55 +00:00
$this->traitNodeScopeCollector = $traitNodeScopeCollector;
}
/**
* @param Node[] $nodes
* @return Node[]
*/
public function processNodes(array $nodes, string $filePath): array
{
$this->removeDeepChainMethodCallNodes($nodes);
$scope = $this->scopeFactory->createFromFile($filePath);
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
$nodeCallback = function (Node $node, Scope $scope): void {
// the class reflection is resolved AFTER entering to class node
// so we need to get it from the first after this one
if ($node instanceof Class_ || $node instanceof Interface_) {
$scope = $this->resolveClassOrInterfaceScope($node, $scope);
2019-08-05 16:52:55 +00:00
}
// traversing trait inside class that is using it scope (from referenced) - the trait traversed by Rector is different (directly from parsed file)
if ($scope->isInTrait()) {
$traitName = $scope->getTraitReflection()->getName();
$this->traitNodeScopeCollector->addForTraitAndNode($traitName, $node, $scope);
return;
}
// special case for unreachable nodes
if ($node instanceof UnreachableStatementNode) {
$originalNode = $node->getOriginalStatement();
$originalNode->setAttribute(AttributeKey::IS_UNREACHABLE, true);
$originalNode->setAttribute(AttributeKey::SCOPE, $scope);
} else {
$node->setAttribute(AttributeKey::SCOPE, $scope);
}
};
$this->phpStanNodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);
2019-08-05 16:52:55 +00:00
return $nodes;
}
2018-12-16 13:24:31 +00:00
/**
* @param Node[] $nodes
*/
private function removeDeepChainMethodCallNodes(array $nodes): void
{
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor($this->removeDeepChainMethodCallNodeVisitor);
$nodeTraverser->traverse($nodes);
}
/**
* @param Class_|Interface_ $classOrInterfaceNode
*/
2019-05-12 08:19:38 +00:00
private function resolveClassOrInterfaceScope(Node $classOrInterfaceNode, Scope $scope): Scope
2019-03-10 23:47:43 +00:00
{
$className = $this->resolveClassName($classOrInterfaceNode);
$classReflection = $this->broker->getClass($className);
return $scope->enterClass($classReflection);
}
/**
2019-05-12 08:19:38 +00:00
* @param Class_|Interface_|Trait_ $classOrInterfaceNode
2019-03-10 23:47:43 +00:00
*/
private function resolveClassName(ClassLike $classOrInterfaceNode): string
{
if (isset($classOrInterfaceNode->namespacedName)) {
2019-03-10 23:47:43 +00:00
return (string) $classOrInterfaceNode->namespacedName;
}
2019-03-10 23:47:43 +00:00
if ($classOrInterfaceNode->name === null) {
2019-09-21 11:03:30 +00:00
throw new ShouldNotHappenException();
}
2019-03-10 23:47:43 +00:00
return $classOrInterfaceNode->name->toString();
}
}