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
use PhpParser\Node ;
2022-05-11 16:53:20 +00:00
use PhpParser\Node\Name ;
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_ ;
2022-05-04 15:20:50 +00:00
use PhpParser\Node\Stmt\Foreach_ ;
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 ;
2022-01-11 15:49:28 +00:00
use PHPStan\Analyser\ScopeContext ;
use PHPStan\BetterReflection\Reflector\Reflector ;
2021-06-26 18:33:28 +00:00
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator ;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator ;
2019-10-02 20:13:37 +00:00
use PHPStan\Node\UnreachableStatementNode ;
2021-12-29 23:18:00 +00:00
use PHPStan\Reflection\BetterReflection\Reflector\MemoizingReflector ;
2022-01-11 15:49:28 +00:00
use PHPStan\Reflection\ClassReflection ;
2020-01-13 13:34:18 +00:00
use PHPStan\Reflection\ReflectionProvider ;
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-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 ;
2021-11-28 16:42:02 +00:00
use Rector\Core\Util\StringUtils ;
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 ;
2022-05-12 05:20:59 +00:00
use RectorPrefix20220512\Symplify\PackageBuilder\Reflection\PrivatesAccessor ;
2020-04-19 17:56:05 +00:00
use Symplify\SmartFileSystem\SmartFileInfo ;
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-11-25 19:08:52 +00:00
/**
* @ var string
*/
private const CONTEXT = 'context' ;
2018-08-03 18:41:30 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
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-12-04 12:47:17 +00:00
* @ readonly
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-12-04 12:47:17 +00:00
* @ readonly
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-12-04 12:47:17 +00:00
* @ readonly
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-12-04 12:47:17 +00:00
* @ readonly
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-12-04 12:47:17 +00:00
* @ readonly
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 ;
2021-06-25 17:38:41 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
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 ;
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-06-26 18:33:28 +00:00
* @ var \Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator
*/
private $renamedClassesSourceLocator ;
2021-07-04 13:13:54 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-07-04 13:13:54 +00:00
* @ var \Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator
*/
private $parentAttributeSourceLocator ;
2022-05-12 05:20:59 +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 , \RectorPrefix20220512\Symplify\PackageBuilder\Reflection\PrivatesAccessor $privatesAccessor , \Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator $renamedClassesSourceLocator , \Rector\Core\StaticReflection\SourceLocator\ParentAttributeSourceLocator $parentAttributeSourceLocator )
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 ;
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 ;
2018-08-03 18:41:30 +00:00
}
/**
2021-10-27 08:50:45 +00:00
* @ param Stmt [] $stmts
2021-06-22 17:07:57 +00:00
* @ return Stmt []
2018-08-03 18:41:30 +00:00
*/
2021-10-27 08:50:45 +00:00
public function processNodes ( array $stmts , \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo ) : array
2018-08-03 18:41:30 +00:00
{
2021-10-27 08:50:45 +00:00
$this -> removeDeepChainMethodCallNodes ( $stmts );
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
2022-04-10 08:30:09 +00:00
$nodeCallback = function ( \PhpParser\Node $node , \PHPStan\Analyser\MutatingScope $mutatingScope ) use ( & $nodeCallback ) : void {
2022-05-04 15:20:50 +00:00
if ( $node instanceof \PhpParser\Node\Stmt\Foreach_ ) {
// decorate value as well
$node -> valueVar -> setAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: SCOPE , $mutatingScope );
}
2021-10-04 21:24:17 +00:00
if ( $node instanceof \PhpParser\Node\Stmt\Trait_ ) {
$traitName = $this -> resolveClassName ( $node );
$traitReflectionClass = $this -> reflectionProvider -> getClass ( $traitName );
2022-04-10 08:30:09 +00:00
$traitScope = clone $mutatingScope ;
2022-01-11 15:49:28 +00:00
$scopeContext = $this -> privatesAccessor -> getPrivatePropertyOfClass ( $traitScope , self :: CONTEXT , \PHPStan\Analyser\ScopeContext :: class );
2021-12-29 23:48:39 +00:00
$traitContext = clone $scopeContext ;
2022-01-11 15:49:28 +00:00
$this -> privatesAccessor -> setPrivatePropertyOfClass ( $traitContext , 'classReflection' , $traitReflectionClass , \PHPStan\Reflection\ClassReflection :: class );
$this -> privatesAccessor -> setPrivatePropertyOfClass ( $traitScope , self :: CONTEXT , $traitContext , \PHPStan\Analyser\ScopeContext :: class );
2021-10-30 13:43:30 +00:00
$this -> nodeScopeResolver -> processNodes ( $node -> stmts , $traitScope , $nodeCallback );
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_ ) {
2022-04-10 08:30:09 +00:00
/** @var MutatingScope $mutatingScope */
$mutatingScope = $this -> resolveClassOrInterfaceScope ( $node , $mutatingScope );
2021-03-18 01:48:44 +00:00
}
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 );
2022-04-10 08:30:09 +00:00
$originalNode -> setAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: SCOPE , $mutatingScope );
2019-10-02 20:13:37 +00:00
} else {
2022-04-10 08:30:09 +00:00
$node -> setAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: SCOPE , $mutatingScope );
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-11-24 20:49:18 +00:00
return $this -> processNodesWithDependentFiles ( $smartFileInfo , $stmts , $scope , $nodeCallback );
2021-08-16 08:19:28 +00:00
}
/**
2021-10-27 08:50:45 +00:00
* @ param Stmt [] $stmts
2022-02-19 18:54:29 +00:00
* @ param callable ( Node $node , MutatingScope $scope ) : void $nodeCallback
2021-08-16 08:19:28 +00:00
* @ return Stmt []
*/
2021-11-24 20:49:18 +00:00
private function processNodesWithDependentFiles ( \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo , array $stmts , \PHPStan\Analyser\MutatingScope $mutatingScope , callable $nodeCallback ) : array
2021-08-16 08:19:28 +00:00
{
2021-11-24 20:49:18 +00:00
$this -> nodeScopeResolver -> processNodes ( $stmts , $mutatingScope , $nodeCallback );
2021-10-27 08:50:45 +00:00
$this -> resolveAndSaveDependentFiles ( $stmts , $mutatingScope , $smartFileInfo );
return $stmts ;
2018-08-03 18:41:30 +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-10-23 08:35:53 +00:00
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
2021-11-28 16:42:02 +00:00
if ( $classLike instanceof \PhpParser\Node\Stmt\Class_ && \Rector\Core\Util\StringUtils :: isMatch ( $className , self :: ANONYMOUS_CLASS_START_REGEX )) {
2021-10-04 21:24:17 +00:00
$classReflection = $this -> reflectionProvider -> getAnonymousClassReflection ( $classLike , $mutatingScope );
2021-05-09 20:15:43 +00:00
} elseif ( ! $this -> reflectionProvider -> hasClass ( $className )) {
2021-10-04 21:24:17 +00:00
return $mutatingScope ;
2021-03-18 01:48:44 +00:00
} else {
$classReflection = $this -> reflectionProvider -> getClass ( $className );
2020-07-26 09:00:23 +00:00
}
2021-10-04 21:24:17 +00:00
return $mutatingScope -> enterClass ( $classReflection );
2019-03-10 23:47:43 +00:00
}
/**
2021-10-04 21:24:17 +00:00
* @ param \PhpParser\Node\Stmt\Class_ | \PhpParser\Node\Stmt\Interface_ | \PhpParser\Node\Stmt\Trait_ $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
{
2022-05-11 16:53:20 +00:00
if ( $classLike -> namespacedName instanceof \PhpParser\Node\Name ) {
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
2021-12-29 23:18:00 +00:00
/** @var MemoizingReflector $classReflector */
2022-01-11 15:49:28 +00:00
$classReflector = $this -> privatesAccessor -> getPrivatePropertyOfClass ( $nodeScopeResolver , 'reflector' , \PHPStan\BetterReflection\Reflector\Reflector :: class );
$reflector = $this -> privatesAccessor -> getPrivatePropertyOfClass ( $classReflector , 'reflector' , \PHPStan\BetterReflection\Reflector\Reflector :: class );
2021-06-26 18:33:28 +00:00
/** @var SourceLocator $sourceLocator */
2022-01-11 15:49:28 +00:00
$sourceLocator = $this -> privatesAccessor -> getPrivatePropertyOfClass ( $reflector , 'sourceLocator' , \PHPStan\BetterReflection\SourceLocator\Type\SourceLocator :: class );
2021-06-26 18:33:28 +00:00
// 2. get Rector locator
2021-07-04 13:13:54 +00:00
$aggregateSourceLocator = new \PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator ([ $sourceLocator , $this -> renamedClassesSourceLocator , $this -> parentAttributeSourceLocator ]);
2022-01-11 15:49:28 +00:00
$this -> privatesAccessor -> setPrivatePropertyOfClass ( $reflector , 'sourceLocator' , $aggregateSourceLocator , \PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator :: class );
2021-06-25 17:38:41 +00:00
}
2018-08-03 18:41:30 +00:00
}