2019-10-13 05:59:52 +00:00
< ? php
2021-05-09 20:15:43 +00:00
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
2021-10-03 08:23:40 +00:00
use RectorPrefix20211003\Nette\Utils\Strings ;
2018-08-03 18:41:30 +00:00
use PhpParser\Node ;
2021-08-16 08:19:28 +00:00
use PhpParser\Node\Name\FullyQualified ;
2021-06-22 17:07:57 +00:00
use PhpParser\Node\Stmt ;
2018-08-04 20:32:01 +00:00
use PhpParser\Node\Stmt\Class_ ;
2018-08-05 16:12:11 +00:00
use PhpParser\Node\Stmt\Interface_ ;
2021-07-04 18:27:51 +00:00
use PhpParser\Node\Stmt\Trait_ ;
2018-11-06 23:05:21 +00:00
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 ;
2020-08-27 10:19:22 +00:00
use PHPStan\Analyser\Scope ;
2021-10-02 22:25:11 +00:00
use PHPStan\BetterReflection\Reflection\Exception\NotAnInterfaceReflection ;
2021-06-26 18:33:28 +00:00
use PHPStan\BetterReflection\Reflector\ClassReflector ;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator ;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator ;
2019-10-02 20:13:37 +00:00
use PHPStan\Node\UnreachableStatementNode ;
2020-12-21 02:12:42 +00:00
use PHPStan\Reflection\ClassReflection ;
2020-01-13 13:34:18 +00:00
use PHPStan\Reflection\ReflectionProvider ;
2021-08-19 04:03:26 +00:00
use PHPStan\Type\ObjectType ;
2020-09-18 10:06:01 +00:00
use Rector\Caching\Detector\ChangedFilesDetector ;
2020-04-01 01:55:44 +00:00
use Rector\Caching\FileSystem\DependencyResolver ;
2020-02-06 21:48:18 +00:00
use Rector\Core\Exception\ShouldNotHappenException ;
2021-08-16 08:19:28 +00:00
use Rector\Core\PhpParser\Node\BetterNodeFinder ;
2021-07-04 13:13:54 +00:00
use Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator ;
2021-06-26 18:33:28 +00:00
use Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator ;
2019-04-13 09:20:27 +00:00
use Rector\NodeTypeResolver\Node\AttributeKey ;
2019-08-05 16:52:55 +00:00
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector ;
2018-11-06 23:05:21 +00:00
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor ;
2021-10-03 08:23:40 +00:00
use RectorPrefix20211003\Symplify\PackageBuilder\Reflection\PrivatesAccessor ;
2020-04-19 17:56:05 +00:00
use Symplify\SmartFileSystem\SmartFileInfo ;
2021-10-02 22:25:11 +00:00
use Throwable ;
2018-08-03 18:41:30 +00:00
/**
* @ inspired by https :// github . com / silverstripe / silverstripe - upgrader / blob / 532182 b23e854d02e0b27e68ebc394f436de0682 / 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
2018-08-03 18:41:30 +00:00
{
2020-09-23 09:16:40 +00:00
/**
* @ var string
2020-10-29 18:04:33 +00:00
* @ see https :// regex101 . com / r / aXsCkK / 1
2020-09-23 09:16:40 +00:00
*/
2021-05-09 20:15:43 +00:00
private const ANONYMOUS_CLASS_START_REGEX = '#^AnonymousClass(\\w+)#' ;
2021-10-02 22:25:11 +00:00
/**
* @ var string
* @ see https :// regex101 . com / r / AIA24M / 1
*/
private const NOT_AN_INTERFACE_EXCEPTION_REGEX = '#^Provided node ".*" is not interface, but "class"$#' ;
2018-08-03 18:41:30 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\Caching\Detector\ChangedFilesDetector
2018-08-03 18:41:30 +00:00
*/
2021-05-10 23:39:21 +00:00
private $changedFilesDetector ;
2018-08-05 15:11:56 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\Caching\FileSystem\DependencyResolver
2018-08-05 15:11:56 +00:00
*/
2021-05-10 23:39:21 +00:00
private $dependencyResolver ;
2018-11-06 23:05:21 +00:00
/**
2021-08-23 00:20:32 +00:00
* @ var \PHPStan\Analyser\NodeScopeResolver
2018-11-06 23:05:21 +00:00
*/
2021-05-10 23:39:21 +00:00
private $nodeScopeResolver ;
2019-05-12 08:19:38 +00:00
/**
2021-08-23 00:20:32 +00:00
* @ var \PHPStan\Reflection\ReflectionProvider
2019-05-12 08:19:38 +00:00
*/
2021-05-10 23:39:21 +00:00
private $reflectionProvider ;
2020-04-01 01:55:44 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor
2020-04-01 01:55:44 +00:00
*/
2021-05-10 23:39:21 +00:00
private $removeDeepChainMethodCallNodeVisitor ;
2020-04-01 01:55:44 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\NodeTypeResolver\PHPStan\Scope\ScopeFactory
2020-04-01 01:55:44 +00:00
*/
2021-05-10 23:39:21 +00:00
private $scopeFactory ;
2020-04-01 01:55:44 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector
2020-04-01 01:55:44 +00:00
*/
2021-05-10 23:39:21 +00:00
private $traitNodeScopeCollector ;
2021-06-25 17:38:41 +00:00
/**
2021-08-23 00:20:32 +00:00
* @ var \Symplify\PackageBuilder\Reflection\PrivatesAccessor
2021-06-25 17:38:41 +00:00
*/
2021-06-26 18:33:28 +00:00
private $privatesAccessor ;
/**
* @ var \Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator
*/
private $renamedClassesSourceLocator ;
2021-07-04 13:13:54 +00:00
/**
* @ var \Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator
*/
private $parentAttributeSourceLocator ;
2021-08-16 08:19:28 +00:00
/**
* @ var \Rector\Core\PhpParser\Node\BetterNodeFinder
*/
private $betterNodeFinder ;
2021-10-03 08:23:40 +00:00
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 , \Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector $traitNodeScopeCollector , \RectorPrefix20211003\Symplify\PackageBuilder\Reflection\PrivatesAccessor $privatesAccessor , \Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator $renamedClassesSourceLocator , \Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator $parentAttributeSourceLocator , \Rector\Core\PhpParser\Node\BetterNodeFinder $betterNodeFinder )
2021-05-09 20:15:43 +00:00
{
2021-05-10 23:39:21 +00:00
$this -> changedFilesDetector = $changedFilesDetector ;
$this -> dependencyResolver = $dependencyResolver ;
2020-06-16 19:43:45 +00:00
$this -> nodeScopeResolver = $nodeScopeResolver ;
2020-01-13 13:34:18 +00:00
$this -> reflectionProvider = $reflectionProvider ;
2018-11-06 23:05:21 +00:00
$this -> removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor ;
2021-05-10 23:39:21 +00:00
$this -> scopeFactory = $scopeFactory ;
$this -> traitNodeScopeCollector = $traitNodeScopeCollector ;
2021-06-26 18:33:28 +00:00
$this -> privatesAccessor = $privatesAccessor ;
$this -> renamedClassesSourceLocator = $renamedClassesSourceLocator ;
2021-07-04 13:13:54 +00:00
$this -> parentAttributeSourceLocator = $parentAttributeSourceLocator ;
2021-08-16 08:19:28 +00:00
$this -> betterNodeFinder = $betterNodeFinder ;
2018-08-03 18:41:30 +00:00
}
/**
2021-06-22 17:07:57 +00:00
* @ param Stmt [] $nodes
* @ return Stmt []
2018-08-03 18:41:30 +00:00
*/
2021-05-10 22:23:08 +00:00
public function processNodes ( array $nodes , \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo ) : array
2018-08-03 18:41:30 +00:00
{
2018-11-06 23:05:21 +00:00
$this -> removeDeepChainMethodCallNodes ( $nodes );
2020-04-19 17:56:05 +00:00
$scope = $this -> scopeFactory -> createFromFile ( $smartFileInfo );
2018-11-06 23:05:21 +00:00
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
2021-05-10 22:23:08 +00:00
$nodeCallback = function ( \PhpParser\Node $node , \PHPStan\Analyser\Scope $scope ) : void {
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 ()) {
2021-07-04 18:27:51 +00:00
// has just entereted trait, to avoid adding it for ever ynode
$parentNode = $node -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: PARENT_NODE );
if ( $parentNode instanceof \PhpParser\Node\Stmt\Trait_ ) {
/** @var ClassReflection $classReflection */
$classReflection = $scope -> getTraitReflection ();
$traitName = $classReflection -> getName ();
$this -> traitNodeScopeCollector -> addForTrait ( $traitName , $scope );
}
2019-08-05 16:52:55 +00:00
return ;
2018-08-03 18:41:30 +00:00
}
2021-03-18 01:48:44 +00:00
// the class reflection is resolved AFTER entering to class node
// so we need to get it from the first after this one
2021-05-10 22:23:08 +00:00
if ( $node instanceof \PhpParser\Node\Stmt\Class_ || $node instanceof \PhpParser\Node\Stmt\Interface_ ) {
2021-03-18 01:48:44 +00:00
/** @var Scope $scope */
$scope = $this -> resolveClassOrInterfaceScope ( $node , $scope );
}
2019-10-02 20:13:37 +00:00
// special case for unreachable nodes
2021-05-10 22:23:08 +00:00
if ( $node instanceof \PHPStan\Node\UnreachableStatementNode ) {
2019-10-02 20:13:37 +00:00
$originalNode = $node -> getOriginalStatement ();
2021-05-10 22:23:08 +00:00
$originalNode -> setAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: IS_UNREACHABLE , \true );
$originalNode -> setAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: SCOPE , $scope );
2019-10-02 20:13:37 +00:00
} else {
2021-05-10 22:23:08 +00:00
$node -> setAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: SCOPE , $scope );
2019-10-02 20:13:37 +00:00
}
2020-09-16 18:11:35 +00:00
};
2021-06-26 18:33:28 +00:00
$this -> decoratePHPStanNodeScopeResolverWithRenamedClassSourceLocator ( $this -> nodeScopeResolver );
2021-08-16 08:19:28 +00:00
return $this -> processNodesWithMixinHandling ( $smartFileInfo , $nodes , $scope , $nodeCallback );
}
/**
* @ param Stmt [] $nodes
* @ return Stmt []
*/
private function processNodesWithMixinHandling ( \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo , array $nodes , \PHPStan\Analyser\MutatingScope $mutatingScope , callable $nodeCallback ) : array
{
2021-08-19 04:03:26 +00:00
if ( $this -> isMixinInSource ( $nodes )) {
return $nodes ;
2021-08-15 21:29:57 +00:00
}
2021-10-02 22:25:11 +00:00
try {
$this -> nodeScopeResolver -> processNodes ( $nodes , $mutatingScope , $nodeCallback );
} catch ( \Throwable $throwable ) {
if ( ! $throwable instanceof \PHPStan\BetterReflection\Reflection\Exception\NotAnInterfaceReflection ) {
throw $throwable ;
}
2021-10-03 08:23:40 +00:00
if ( ! \RectorPrefix20211003\Nette\Utils\Strings :: match ( $throwable -> getMessage (), self :: NOT_AN_INTERFACE_EXCEPTION_REGEX )) {
2021-10-02 22:25:11 +00:00
throw $throwable ;
}
}
2021-08-16 08:19:28 +00:00
$this -> resolveAndSaveDependentFiles ( $nodes , $mutatingScope , $smartFileInfo );
2019-08-05 16:52:55 +00:00
return $nodes ;
2018-08-03 18:41:30 +00:00
}
2021-08-16 08:19:28 +00:00
/**
* @ param Node [] $nodes
*/
2021-08-19 04:03:26 +00:00
private function isMixinInSource ( array $nodes ) : bool
2021-08-16 08:19:28 +00:00
{
2021-08-19 04:03:26 +00:00
return ( bool ) $this -> betterNodeFinder -> findFirst ( $nodes , function ( \PhpParser\Node $node ) : bool {
if ( ! $node instanceof \PhpParser\Node\Name\FullyQualified && ! $node instanceof \PhpParser\Node\Stmt\Class_ ) {
2021-08-16 08:19:28 +00:00
return \false ;
}
2021-08-19 04:03:26 +00:00
if ( $node instanceof \PhpParser\Node\Stmt\Class_ && $node -> isAnonymous ()) {
2021-08-16 08:19:28 +00:00
return \false ;
}
2021-08-19 04:03:26 +00:00
$className = $node instanceof \PhpParser\Node\Name\FullyQualified ? $node -> toString () : $node -> namespacedName -> toString ();
return $this -> isCircularMixin ( $className );
});
}
private function isCircularMixin ( string $className ) : bool
{
// fix error in parallel test
// use function_exists on purpose as using reflectionProvider broke the test in parallel
if ( \function_exists ( $className )) {
return \false ;
}
$hasClass = $this -> reflectionProvider -> hasClass ( $className );
if ( ! $hasClass ) {
return \false ;
}
$classReflection = $this -> reflectionProvider -> getClass ( $className );
if ( $classReflection -> isBuiltIn ()) {
return \false ;
}
foreach ( $classReflection -> getMixinTags () as $mixinTag ) {
$type = $mixinTag -> getType ();
if ( ! $type instanceof \PHPStan\Type\ObjectType ) {
2021-08-16 08:19:28 +00:00
return \false ;
}
2021-08-19 04:03:26 +00:00
if ( $type -> getClassName () === $className ) {
return \true ;
2021-08-16 08:19:28 +00:00
}
2021-08-19 04:03:26 +00:00
if ( $this -> isCircularMixin ( $type -> getClassName ())) {
return \true ;
2021-08-16 18:30:22 +00:00
}
2021-08-19 04:03:26 +00:00
}
return \false ;
2021-08-16 08:19:28 +00:00
}
2018-12-16 13:24:31 +00:00
/**
* @ param Node [] $nodes
*/
2021-05-09 20:15:43 +00:00
private function removeDeepChainMethodCallNodes ( array $nodes ) : void
2018-12-16 13:24:31 +00:00
{
2021-05-10 22:23:08 +00:00
$nodeTraverser = new \PhpParser\NodeTraverser ();
2018-12-16 13:24:31 +00:00
$nodeTraverser -> addVisitor ( $this -> removeDeepChainMethodCallNodeVisitor );
$nodeTraverser -> traverse ( $nodes );
}
2018-08-10 15:17:09 +00:00
/**
2021-08-23 00:20:32 +00:00
* @ param \PhpParser\Node\Stmt\Class_ | \PhpParser\Node\Stmt\Interface_ $classLike
2018-08-10 15:17:09 +00:00
*/
2021-05-30 10:12:56 +00:00
private function resolveClassOrInterfaceScope ( $classLike , \PHPStan\Analyser\Scope $scope ) : \PHPStan\Analyser\Scope
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
2021-10-03 08:23:40 +00:00
if ( $classLike instanceof \PhpParser\Node\Stmt\Class_ && \RectorPrefix20211003\Nette\Utils\Strings :: match ( $className , self :: ANONYMOUS_CLASS_START_REGEX )) {
2020-08-27 10:19:22 +00:00
$classReflection = $this -> reflectionProvider -> getAnonymousClassReflection ( $classLike , $scope );
2021-05-09 20:15:43 +00:00
} elseif ( ! $this -> reflectionProvider -> hasClass ( $className )) {
2021-03-07 14:27:17 +00:00
return $scope ;
2021-03-18 01:48:44 +00:00
} else {
$classReflection = $this -> reflectionProvider -> getClass ( $className );
2020-07-26 09:00:23 +00:00
}
2020-08-27 10:19:22 +00:00
/** @var MutatingScope $scope */
return $scope -> enterClass ( $classReflection );
2019-03-10 23:47:43 +00:00
}
/**
2021-08-23 00:20:32 +00:00
* @ param \PhpParser\Node\Stmt\Class_ | \PhpParser\Node\Stmt\Interface_ $classLike
2019-03-10 23:47:43 +00:00
*/
2021-05-30 10:12:56 +00:00
private function resolveClassName ( $classLike ) : string
2018-08-10 15:17:09 +00:00
{
2021-05-09 20:15:43 +00:00
if ( \property_exists ( $classLike , 'namespacedName' )) {
2020-02-07 07:46:29 +00:00
return ( string ) $classLike -> namespacedName ;
2018-08-10 15:17:09 +00:00
}
2020-02-07 07:46:29 +00:00
if ( $classLike -> name === null ) {
2021-05-10 22:23:08 +00:00
throw new \Rector\Core\Exception\ShouldNotHappenException ();
2018-08-10 15:17:09 +00:00
}
2020-02-07 07:46:29 +00:00
return $classLike -> name -> toString ();
2018-08-10 15:17:09 +00:00
}
2021-06-22 17:07:57 +00:00
/**
* @ 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 );
}
2021-06-25 17:38:41 +00:00
/**
* 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 .
*/
2021-06-26 18:33:28 +00:00
private function decoratePHPStanNodeScopeResolverWithRenamedClassSourceLocator ( \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver ) : void
2021-06-25 17:38:41 +00:00
{
2021-06-26 18:33:28 +00:00
// 1. get PHPStan locator
/** @var ClassReflector $classReflector */
$classReflector = $this -> privatesAccessor -> getPrivateProperty ( $nodeScopeResolver , 'classReflector' );
/** @var SourceLocator $sourceLocator */
$sourceLocator = $this -> privatesAccessor -> getPrivateProperty ( $classReflector , 'sourceLocator' );
// 2. get Rector locator
2021-07-04 13:13:54 +00:00
$aggregateSourceLocator = new \PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator ([ $sourceLocator , $this -> renamedClassesSourceLocator , $this -> parentAttributeSourceLocator ]);
2021-06-26 18:33:28 +00:00
$this -> privatesAccessor -> setPrivateProperty ( $classReflector , 'sourceLocator' , $aggregateSourceLocator );
2021-06-25 17:38:41 +00:00
}
2018-08-03 18:41:30 +00:00
}