rector/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php

235 lines
12 KiB
PHP
Raw Normal View History

2019-10-13 05:59:52 +00:00
<?php
declare (strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan\Scope;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
2018-08-04 20:32:01 +00:00
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
2020-04-01 01:55:44 +00:00
use PHPStan\AnalysedCodeException;
2019-12-08 21:00:38 +00:00
use PHPStan\Analyser\MutatingScope;
2020-06-16 19:43:45 +00:00
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\ScopeContext;
use PHPStan\BetterReflection\Reflector\Reflector;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Node\UnreachableStatementNode;
use PHPStan\Reflection\BetterReflection\Reflector\MemoizingReflector;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Caching\Detector\ChangedFilesDetector;
2020-04-01 01:55:44 +00:00
use Rector\Caching\FileSystem\DependencyResolver;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator;
use Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator;
use Rector\Core\Util\StringUtils;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
use RectorPrefix20220512\Symplify\PackageBuilder\Reflection\PrivatesAccessor;
2020-04-19 17:56:05 +00:00
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @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
*/
2020-06-16 19:43:45 +00:00
final class PHPStanNodeScopeResolver
{
/**
* @var string
* @see https://regex101.com/r/aXsCkK/1
*/
private const ANONYMOUS_CLASS_START_REGEX = '#^AnonymousClass(\\w+)#';
/**
* @var string
*/
private const CONTEXT = 'context';
/**
* @readonly
* @var \Rector\Caching\Detector\ChangedFilesDetector
*/
private $changedFilesDetector;
/**
* @readonly
* @var \Rector\Caching\FileSystem\DependencyResolver
*/
private $dependencyResolver;
/**
* @readonly
* @var \PHPStan\Analyser\NodeScopeResolver
*/
private $nodeScopeResolver;
2019-05-12 08:19:38 +00:00
/**
* @readonly
* @var \PHPStan\Reflection\ReflectionProvider
2019-05-12 08:19:38 +00:00
*/
private $reflectionProvider;
2020-04-01 01:55:44 +00:00
/**
* @readonly
* @var \Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor
2020-04-01 01:55:44 +00:00
*/
private $removeDeepChainMethodCallNodeVisitor;
2020-04-01 01:55:44 +00:00
/**
* @readonly
* @var \Rector\NodeTypeResolver\PHPStan\Scope\ScopeFactory
2020-04-01 01:55:44 +00:00
*/
private $scopeFactory;
/**
* @readonly
* @var \Symplify\PackageBuilder\Reflection\PrivatesAccessor
*/
private $privatesAccessor;
/**
* @readonly
* @var \Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator
*/
private $renamedClassesSourceLocator;
/**
* @readonly
* @var \Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator
*/
private $parentAttributeSourceLocator;
public function __construct(\Rector\Caching\Detector\ChangedFilesDetector $changedFilesDetector, \Rector\Caching\FileSystem\DependencyResolver $dependencyResolver, \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver, \PHPStan\Reflection\ReflectionProvider $reflectionProvider, \Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor, \Rector\NodeTypeResolver\PHPStan\Scope\ScopeFactory $scopeFactory, \RectorPrefix20220512\Symplify\PackageBuilder\Reflection\PrivatesAccessor $privatesAccessor, \Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator $renamedClassesSourceLocator, \Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator $parentAttributeSourceLocator)
{
$this->changedFilesDetector = $changedFilesDetector;
$this->dependencyResolver = $dependencyResolver;
2020-06-16 19:43:45 +00:00
$this->nodeScopeResolver = $nodeScopeResolver;
$this->reflectionProvider = $reflectionProvider;
$this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor;
$this->scopeFactory = $scopeFactory;
$this->privatesAccessor = $privatesAccessor;
$this->renamedClassesSourceLocator = $renamedClassesSourceLocator;
$this->parentAttributeSourceLocator = $parentAttributeSourceLocator;
}
/**
* @param Stmt[] $stmts
* @return Stmt[]
*/
public function processNodes(array $stmts, \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo) : array
{
$this->removeDeepChainMethodCallNodes($stmts);
2020-04-19 17:56:05 +00:00
$scope = $this->scopeFactory->createFromFile($smartFileInfo);
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
$nodeCallback = function (\PhpParser\Node $node, \PHPStan\Analyser\MutatingScope $mutatingScope) use(&$nodeCallback) : void {
if ($node instanceof \PhpParser\Node\Stmt\Foreach_) {
// decorate value as well
$node->valueVar->setAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE, $mutatingScope);
}
if ($node instanceof \PhpParser\Node\Stmt\Trait_) {
$traitName = $this->resolveClassName($node);
$traitReflectionClass = $this->reflectionProvider->getClass($traitName);
$traitScope = clone $mutatingScope;
$scopeContext = $this->privatesAccessor->getPrivatePropertyOfClass($traitScope, self::CONTEXT, \PHPStan\Analyser\ScopeContext::class);
$traitContext = clone $scopeContext;
$this->privatesAccessor->setPrivatePropertyOfClass($traitContext, 'classReflection', $traitReflectionClass, \PHPStan\Reflection\ClassReflection::class);
$this->privatesAccessor->setPrivatePropertyOfClass($traitScope, self::CONTEXT, $traitContext, \PHPStan\Analyser\ScopeContext::class);
$this->nodeScopeResolver->processNodes($node->stmts, $traitScope, $nodeCallback);
2019-08-05 16:52:55 +00:00
return;
}
// 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 \PhpParser\Node\Stmt\Class_ || $node instanceof \PhpParser\Node\Stmt\Interface_) {
/** @var MutatingScope $mutatingScope */
$mutatingScope = $this->resolveClassOrInterfaceScope($node, $mutatingScope);
}
// special case for unreachable nodes
if ($node instanceof \PHPStan\Node\UnreachableStatementNode) {
$originalNode = $node->getOriginalStatement();
$originalNode->setAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::IS_UNREACHABLE, \true);
$originalNode->setAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE, $mutatingScope);
} else {
$node->setAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE, $mutatingScope);
}
};
$this->decoratePHPStanNodeScopeResolverWithRenamedClassSourceLocator($this->nodeScopeResolver);
return $this->processNodesWithDependentFiles($smartFileInfo, $stmts, $scope, $nodeCallback);
}
/**
* @param Stmt[] $stmts
* @param callable(Node $node, MutatingScope $scope): void $nodeCallback
* @return Stmt[]
*/
private function processNodesWithDependentFiles(\Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo, array $stmts, \PHPStan\Analyser\MutatingScope $mutatingScope, callable $nodeCallback) : array
{
$this->nodeScopeResolver->processNodes($stmts, $mutatingScope, $nodeCallback);
$this->resolveAndSaveDependentFiles($stmts, $mutatingScope, $smartFileInfo);
return $stmts;
}
2018-12-16 13:24:31 +00:00
/**
* @param Node[] $nodes
*/
private function removeDeepChainMethodCallNodes(array $nodes) : void
2018-12-16 13:24:31 +00:00
{
$nodeTraverser = new \PhpParser\NodeTraverser();
2018-12-16 13:24:31 +00:00
$nodeTraverser->addVisitor($this->removeDeepChainMethodCallNodeVisitor);
$nodeTraverser->traverse($nodes);
}
/**
* @param \PhpParser\Node\Stmt\Class_|\PhpParser\Node\Stmt\Interface_ $classLike
*/
private function resolveClassOrInterfaceScope($classLike, \PHPStan\Analyser\MutatingScope $mutatingScope) : \PHPStan\Analyser\MutatingScope
2020-08-27 10:19:22 +00:00
{
2020-07-26 09:00:23 +00:00
$className = $this->resolveClassName($classLike);
// is anonymous class? - not possible to enter it since PHPStan 0.12.33, see https://github.com/phpstan/phpstan-src/commit/e87fb0ec26f9c8552bbeef26a868b1e5d8185e91
if ($classLike instanceof \PhpParser\Node\Stmt\Class_ && \Rector\Core\Util\StringUtils::isMatch($className, self::ANONYMOUS_CLASS_START_REGEX)) {
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($classLike, $mutatingScope);
} elseif (!$this->reflectionProvider->hasClass($className)) {
return $mutatingScope;
} else {
$classReflection = $this->reflectionProvider->getClass($className);
2020-07-26 09:00:23 +00:00
}
return $mutatingScope->enterClass($classReflection);
2019-03-10 23:47:43 +00:00
}
/**
* @param \PhpParser\Node\Stmt\Class_|\PhpParser\Node\Stmt\Interface_|\PhpParser\Node\Stmt\Trait_ $classLike
2019-03-10 23:47:43 +00:00
*/
private function resolveClassName($classLike) : string
{
if ($classLike->namespacedName instanceof \PhpParser\Node\Name) {
return (string) $classLike->namespacedName;
}
if ($classLike->name === null) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
return $classLike->name->toString();
}
/**
* @param Stmt[] $stmts
*/
private function resolveAndSaveDependentFiles(array $stmts, \PHPStan\Analyser\MutatingScope $mutatingScope, \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo) : void
{
$dependentFiles = [];
foreach ($stmts as $stmt) {
try {
$nodeDependentFiles = $this->dependencyResolver->resolveDependencies($stmt, $mutatingScope);
$dependentFiles = \array_merge($dependentFiles, $nodeDependentFiles);
} catch (\PHPStan\AnalysedCodeException $exception) {
// @ignoreException
}
}
$this->changedFilesDetector->addFileWithDependencies($smartFileInfo, $dependentFiles);
}
/**
* In case PHPStan tried to parse a file with missing class, it fails.
* But sometimes we want to rename old class that is missing with Rector..
*
* That's why we have to skip fatal errors of PHPStan caused by missing class,
* so Rector can fix it first. Then run Rector again to refactor code with new classes.
*/
private function decoratePHPStanNodeScopeResolverWithRenamedClassSourceLocator(\PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver) : void
{
// 1. get PHPStan locator
/** @var MemoizingReflector $classReflector */
$classReflector = $this->privatesAccessor->getPrivatePropertyOfClass($nodeScopeResolver, 'reflector', \PHPStan\BetterReflection\Reflector\Reflector::class);
$reflector = $this->privatesAccessor->getPrivatePropertyOfClass($classReflector, 'reflector', \PHPStan\BetterReflection\Reflector\Reflector::class);
/** @var SourceLocator $sourceLocator */
$sourceLocator = $this->privatesAccessor->getPrivatePropertyOfClass($reflector, 'sourceLocator', \PHPStan\BetterReflection\SourceLocator\Type\SourceLocator::class);
// 2. get Rector locator
$aggregateSourceLocator = new \PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator([$sourceLocator, $this->renamedClassesSourceLocator, $this->parentAttributeSourceLocator]);
$this->privatesAccessor->setPrivatePropertyOfClass($reflector, 'sourceLocator', $aggregateSourceLocator, \PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator::class);
}
}