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-05-30 07:54:06 +00:00
use RectorPrefix20210530\Nette\Utils\Strings ;
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 ;
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 ;
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 ;
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 ;
use Rector\Core\Configuration\Configuration ;
2020-02-06 21:48:18 +00:00
use Rector\Core\Exception\ShouldNotHappenException ;
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-05-30 07:54:06 +00:00
use RectorPrefix20210530\Symfony\Component\Console\Style\SymfonyStyle ;
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+)#' ;
2020-04-26 00:57:47 +00:00
/**
* @ var string []
*/
private $dependentFiles = [];
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-03 18:41:30 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\Core\Configuration\Configuration
2018-08-03 18:41:30 +00:00
*/
2021-05-10 23:39:21 +00:00
private $configuration ;
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-05-10 23:39:21 +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-05-10 23:39:21 +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 \Symfony\Component\Console\Style\SymfonyStyle
2020-04-01 01:55:44 +00:00
*/
2021-05-10 23:39:21 +00:00
private $symfonyStyle ;
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-05-30 07:54:06 +00:00
public function __construct ( \Rector\Caching\Detector\ChangedFilesDetector $changedFilesDetector , \Rector\Core\Configuration\Configuration $configuration , \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 , \RectorPrefix20210530\Symfony\Component\Console\Style\SymfonyStyle $symfonyStyle , \Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector $traitNodeScopeCollector )
2021-05-09 20:15:43 +00:00
{
2021-05-10 23:39:21 +00:00
$this -> changedFilesDetector = $changedFilesDetector ;
$this -> configuration = $configuration ;
$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 ;
2020-04-01 01:55:44 +00:00
$this -> symfonyStyle = $symfonyStyle ;
2021-05-10 23:39:21 +00:00
$this -> traitNodeScopeCollector = $traitNodeScopeCollector ;
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
*/
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 );
2020-04-01 01:55:44 +00:00
$this -> dependentFiles = [];
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 ()) {
2020-12-21 02:12:42 +00:00
/** @var ClassReflection $classReflection */
2020-11-16 17:50:38 +00:00
$classReflection = $scope -> getTraitReflection ();
$traitName = $classReflection -> getName ();
2019-08-05 16:52:55 +00:00
$this -> traitNodeScopeCollector -> addForTraitAndNode ( $traitName , $node , $scope );
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
};
foreach ( $nodes as $node ) {
2020-04-01 01:55:44 +00:00
$this -> resolveDependentFiles ( $node , $scope );
2020-09-16 18:11:35 +00:00
}
2019-12-08 21:00:38 +00:00
/** @var MutatingScope $scope */
2020-06-16 19:43:45 +00:00
$this -> nodeScopeResolver -> processNodes ( $nodes , $scope , $nodeCallback );
2020-04-01 01:55:44 +00:00
$this -> reportCacheDebugAndSaveDependentFiles ( $smartFileInfo , $this -> dependentFiles );
2019-08-05 16:52:55 +00:00
return $nodes ;
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
/**
2020-07-26 09:00:23 +00:00
* @ param Class_ | Interface_ $classLike
2018-08-10 15:17:09 +00:00
*/
2021-05-10 22:23:08 +00:00
private function resolveClassOrInterfaceScope ( \PhpParser\Node\Stmt\ClassLike $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-05-30 07:54:06 +00:00
if ( $classLike instanceof \PhpParser\Node\Stmt\Class_ && \RectorPrefix20210530\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-05-10 22:23:08 +00:00
private function resolveDependentFiles ( \PhpParser\Node $node , \PHPStan\Analyser\MutatingScope $mutatingScope ) : void
2020-04-26 00:57:47 +00:00
{
2021-05-09 20:15:43 +00:00
if ( ! $this -> configuration -> isCacheEnabled ()) {
2020-04-26 00:57:47 +00:00
return ;
}
try {
2021-03-21 21:31:18 +00:00
$dependentFiles = $this -> dependencyResolver -> resolveDependencies ( $node , $mutatingScope );
2020-11-16 23:32:24 +00:00
foreach ( $dependentFiles as $dependentFile ) {
2020-04-26 00:57:47 +00:00
$this -> dependentFiles [] = $dependentFile ;
}
2021-05-29 22:10:59 +00:00
} catch ( \PHPStan\AnalysedCodeException $exception ) {
2020-04-26 00:57:47 +00:00
// @ignoreException
}
}
/**
* @ param string [] $dependentFiles
*/
2021-05-10 22:23:08 +00:00
private function reportCacheDebugAndSaveDependentFiles ( \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo , array $dependentFiles ) : void
2020-04-26 00:57:47 +00:00
{
2021-05-09 20:15:43 +00:00
if ( ! $this -> configuration -> isCacheEnabled ()) {
2020-04-26 00:57:47 +00:00
return ;
}
$this -> reportCacheDebug ( $smartFileInfo , $dependentFiles );
// save for cache
$this -> changedFilesDetector -> addFileWithDependencies ( $smartFileInfo , $dependentFiles );
}
2019-03-10 23:47:43 +00:00
/**
2020-02-07 07:46:29 +00:00
* @ param Class_ | Interface_ | Trait_ $classLike
2019-03-10 23:47:43 +00:00
*/
2021-05-10 22:23:08 +00:00
private function resolveClassName ( \PhpParser\Node\Stmt\ClassLike $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
}
2020-10-11 14:17:43 +00:00
/**
* @ param string [] $dependentFiles
*/
2021-05-10 22:23:08 +00:00
private function reportCacheDebug ( \Symplify\SmartFileSystem\SmartFileInfo $smartFileInfo , array $dependentFiles ) : void
2020-04-01 01:55:44 +00:00
{
2021-05-09 20:15:43 +00:00
if ( ! $this -> configuration -> isCacheDebug ()) {
2020-04-01 01:55:44 +00:00
return ;
}
2021-05-09 20:15:43 +00:00
$message = \sprintf ( '[debug] %d dependencies for "%s" file' , \count ( $dependentFiles ), $smartFileInfo -> getRealPath ());
2020-07-05 23:57:19 +00:00
$this -> symfonyStyle -> note ( $message );
2020-04-01 01:55:44 +00:00
if ( $dependentFiles !== []) {
$this -> symfonyStyle -> listing ( $dependentFiles );
}
}
2018-08-03 18:41:30 +00:00
}