2018-08-03 18:41:30 +00:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
2018-08-06 12:10:16 +00:00
|
|
|
namespace Rector\NodeTypeResolver\PHPStan\Scope;
|
2018-08-03 18:41:30 +00:00
|
|
|
|
2019-08-03 19:41:46 +00:00
|
|
|
use Closure;
|
2018-08-03 18:41:30 +00:00
|
|
|
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;
|
2018-08-05 16:12:11 +00:00
|
|
|
use PhpParser\Node\Stmt\Interface_;
|
2019-05-12 08:19:38 +00:00
|
|
|
use PhpParser\Node\Stmt\Trait_;
|
2018-11-06 23:05:21 +00:00
|
|
|
use PhpParser\NodeTraverser;
|
2018-08-06 12:10:16 +00:00
|
|
|
use PHPStan\Analyser\NodeScopeResolver as PHPStanNodeScopeResolver;
|
2018-08-03 18:41:30 +00:00
|
|
|
use PHPStan\Analyser\Scope;
|
2019-05-12 08:19:38 +00:00
|
|
|
use PHPStan\Analyser\ScopeContext;
|
2018-08-03 18:41:30 +00:00
|
|
|
use PHPStan\Broker\Broker;
|
2019-03-10 23:47:43 +00:00
|
|
|
use Rector\Exception\ShouldNotHappenException;
|
2019-04-13 09:20:27 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2018-11-06 23:05:21 +00:00
|
|
|
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
|
2019-08-03 19:41:46 +00:00
|
|
|
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ScopeTraitNodeVisitor;
|
2019-05-12 08:19:38 +00:00
|
|
|
use Rector\NodeTypeResolver\PHPStan\Scope\Stub\ClassReflectionForUnusedTrait;
|
|
|
|
use ReflectionClass;
|
|
|
|
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
2018-08-03 18:41:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
2018-08-06 12:10:16 +00:00
|
|
|
final class NodeScopeResolver
|
2018-08-03 18:41:30 +00:00
|
|
|
{
|
|
|
|
/**
|
2018-08-06 12:10:16 +00:00
|
|
|
* @var PHPStanNodeScopeResolver
|
2018-08-03 18:41:30 +00:00
|
|
|
*/
|
2018-08-06 12:10:16 +00:00
|
|
|
private $phpStanNodeScopeResolver;
|
2018-08-03 18:41:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var ScopeFactory
|
|
|
|
*/
|
2018-08-06 10:31:17 +00:00
|
|
|
private $scopeFactory;
|
2018-08-03 18:41:30 +00:00
|
|
|
|
2018-08-05 15:11:56 +00:00
|
|
|
/**
|
2018-08-06 10:31:17 +00:00
|
|
|
* @var Broker
|
2018-08-05 15:11:56 +00:00
|
|
|
*/
|
2018-08-06 10:31:17 +00:00
|
|
|
private $broker;
|
2018-08-05 15:11:56 +00:00
|
|
|
|
2018-11-06 23:05:21 +00:00
|
|
|
/**
|
|
|
|
* @var RemoveDeepChainMethodCallNodeVisitor
|
|
|
|
*/
|
|
|
|
private $removeDeepChainMethodCallNodeVisitor;
|
|
|
|
|
2019-05-12 08:19:38 +00:00
|
|
|
/**
|
|
|
|
* @var PrivatesAccessor
|
|
|
|
*/
|
|
|
|
private $privatesAccessor;
|
|
|
|
|
2019-08-03 19:41:46 +00:00
|
|
|
/**
|
|
|
|
* @var ScopeTraitNodeVisitor
|
|
|
|
*/
|
|
|
|
private $scopeTraitNodeVisitor;
|
|
|
|
|
2018-08-03 18:41:30 +00:00
|
|
|
public function __construct(
|
2018-08-06 10:31:17 +00:00
|
|
|
ScopeFactory $scopeFactory,
|
2018-08-06 12:10:16 +00:00
|
|
|
PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
|
2018-11-06 23:05:21 +00:00
|
|
|
Broker $broker,
|
2019-05-12 08:19:38 +00:00
|
|
|
RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor,
|
2019-08-03 19:41:46 +00:00
|
|
|
PrivatesAccessor $privatesAccessor,
|
|
|
|
ScopeTraitNodeVisitor $scopeTraitNodeVisitor
|
2018-08-03 18:41:30 +00:00
|
|
|
) {
|
2018-08-06 10:31:17 +00:00
|
|
|
$this->scopeFactory = $scopeFactory;
|
2018-08-06 12:10:16 +00:00
|
|
|
$this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver;
|
2018-08-06 10:31:17 +00:00
|
|
|
$this->broker = $broker;
|
2018-11-06 23:05:21 +00:00
|
|
|
$this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor;
|
2019-05-12 08:19:38 +00:00
|
|
|
$this->privatesAccessor = $privatesAccessor;
|
2019-08-03 19:41:46 +00:00
|
|
|
$this->scopeTraitNodeVisitor = $scopeTraitNodeVisitor;
|
2018-08-03 18:41:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-08-05 08:58:42 +00:00
|
|
|
* @param Node[] $nodes
|
|
|
|
* @return Node[]
|
2018-08-03 18:41:30 +00:00
|
|
|
*/
|
2018-08-13 15:29:31 +00:00
|
|
|
public function processNodes(array $nodes, string $filePath): array
|
2018-08-03 18:41:30 +00:00
|
|
|
{
|
2018-11-06 23:05:21 +00:00
|
|
|
$this->removeDeepChainMethodCallNodes($nodes);
|
|
|
|
|
2018-08-15 21:43:17 +00:00
|
|
|
$this->phpStanNodeScopeResolver->setAnalysedFiles([$filePath]);
|
2019-08-05 07:37:13 +00:00
|
|
|
$scope = $this->scopeFactory->createFromFile($filePath);
|
2018-08-04 16:58:52 +00:00
|
|
|
|
2018-11-06 23:05:21 +00:00
|
|
|
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
|
2019-08-03 19:41:46 +00:00
|
|
|
$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);
|
|
|
|
} elseif ($node instanceof Trait_) {
|
|
|
|
$scope = $this->resolveTraitScope($node, $scope);
|
2018-08-03 18:41:30 +00:00
|
|
|
}
|
2019-08-03 19:41:46 +00:00
|
|
|
|
|
|
|
$node->setAttribute(AttributeKey::SCOPE, $scope);
|
|
|
|
};
|
|
|
|
|
|
|
|
$this->phpStanNodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);
|
|
|
|
|
2019-08-05 07:37:13 +00:00
|
|
|
return $this->resolveScopeInTrait($nodes, $nodeCallback);
|
2018-08-03 18:41:30 +00:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-08-10 15:17:09 +00:00
|
|
|
/**
|
|
|
|
* @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
|
2018-08-10 15:17:09 +00:00
|
|
|
{
|
|
|
|
if (isset($classOrInterfaceNode->namespacedName)) {
|
2019-03-10 23:47:43 +00:00
|
|
|
return (string) $classOrInterfaceNode->namespacedName;
|
2018-08-10 15:17:09 +00:00
|
|
|
}
|
|
|
|
|
2019-03-10 23:47:43 +00:00
|
|
|
if ($classOrInterfaceNode->name === null) {
|
|
|
|
throw new ShouldNotHappenException();
|
2018-08-10 15:17:09 +00:00
|
|
|
}
|
|
|
|
|
2019-03-10 23:47:43 +00:00
|
|
|
return $classOrInterfaceNode->name->toString();
|
2018-08-10 15:17:09 +00:00
|
|
|
}
|
2019-05-12 08:19:38 +00:00
|
|
|
|
|
|
|
private function resolveTraitScope(Trait_ $trait, Scope $scope): Scope
|
|
|
|
{
|
|
|
|
$traitName = $this->resolveClassName($trait);
|
|
|
|
$traitReflection = $this->broker->getClass($traitName);
|
|
|
|
|
|
|
|
/** @var ScopeContext $scopeContext */
|
|
|
|
$scopeContext = $this->privatesAccessor->getPrivateProperty($scope, 'context');
|
|
|
|
|
2019-08-05 07:37:13 +00:00
|
|
|
// we need to emulate class reflection, because PHPStan is unable to analyze bare trait without it
|
2019-05-12 08:19:38 +00:00
|
|
|
$classReflection = new ReflectionClass(ClassReflectionForUnusedTrait::class);
|
|
|
|
$phpstanClassReflection = $this->broker->getClassFromReflection(
|
|
|
|
$classReflection,
|
|
|
|
ClassReflectionForUnusedTrait::class,
|
|
|
|
null
|
|
|
|
);
|
|
|
|
|
|
|
|
// set stub
|
|
|
|
$this->privatesAccessor->setPrivateProperty($scopeContext, 'classReflection', $phpstanClassReflection);
|
|
|
|
|
|
|
|
$traitScope = $scope->enterTrait($traitReflection);
|
2019-08-05 07:37:13 +00:00
|
|
|
|
|
|
|
// clear stub
|
2019-05-12 08:19:38 +00:00
|
|
|
$this->privatesAccessor->setPrivateProperty($scopeContext, 'classReflection', null);
|
|
|
|
|
|
|
|
return $traitScope;
|
|
|
|
}
|
2019-08-03 19:41:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Node[] $nodes
|
2019-08-05 07:37:13 +00:00
|
|
|
* @return Node[]
|
2019-08-03 19:41:46 +00:00
|
|
|
*/
|
2019-08-05 07:37:13 +00:00
|
|
|
private function resolveScopeInTrait(array $nodes, Closure $nodeCallback): array
|
2019-08-03 19:41:46 +00:00
|
|
|
{
|
|
|
|
$traitNodeTraverser = new NodeTraverser();
|
|
|
|
|
|
|
|
$this->scopeTraitNodeVisitor->setNodeCallback($nodeCallback);
|
|
|
|
$traitNodeTraverser->addVisitor($this->scopeTraitNodeVisitor);
|
|
|
|
|
2019-08-05 07:37:13 +00:00
|
|
|
return $traitNodeTraverser->traverse($nodes);
|
2019-08-03 19:41:46 +00:00
|
|
|
}
|
2018-08-03 18:41:30 +00:00
|
|
|
}
|