mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-09 20:52:23 +00:00
Improve trait node scope resolving
This commit is contained in:
parent
59222aa086
commit
a4302b44be
|
@ -15,7 +15,6 @@
|
|||
"doctrine/inflector": "^1.3",
|
||||
"jean85/pretty-package-versions": "^1.2",
|
||||
"jetbrains/phpstorm-stubs": "^2019.1",
|
||||
"nette/di": "^3.0",
|
||||
"nette/robot-loader": "^3.1",
|
||||
"nette/utils": "^2.5|^3.0",
|
||||
"nikic/php-parser": "^4.2.2",
|
||||
|
|
|
@ -27,3 +27,8 @@ trait SkipTraitCalledMethod
|
|||
$entity->getName();
|
||||
}
|
||||
}
|
||||
|
||||
class SkipTraitCalledMethodAddict
|
||||
{
|
||||
use SkipTraitCalledMethod;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedDoctrineEntityMethodAndPropertyRector\Fixture;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedDoctrineEntityMethodAndPropertyRector\Source\SomeEntityProvider;
|
||||
|
||||
class SomeClassOnlyUsingTrait
|
||||
{
|
||||
use SkipTraitComplex;
|
||||
}
|
||||
|
||||
trait SkipTraitComplex
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$provider = self::getProvider();
|
||||
|
||||
$entities = $provider->provideMultiple([]);
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$entity->getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getProvider(): SomeStatusEntityProvider
|
||||
{
|
||||
return new SomeStatusEntityProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SomeStatusEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $status;
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
|
||||
class SomeStatusEntityProvider
|
||||
{
|
||||
/**
|
||||
* @return SomeStatusEntity[]
|
||||
*/
|
||||
public function provideMultiple($items): array
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,11 @@ namespace Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedDoctrineEntityMethodAn
|
|||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Rector\DeadCode\Tests\Rector\Class_\RemoveUnusedDoctrineEntityMethodAndPropertyRector\Source\SomeEntityProvider;
|
||||
|
||||
class SkipTraitDocTypedAddict
|
||||
{
|
||||
use SkipTraitDocTyped;
|
||||
}
|
||||
|
||||
trait SkipTraitDocTyped
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -18,6 +18,7 @@ final class RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest extends Abstra
|
|||
__DIR__ . '/Fixture/skip_id_and_system.php.inc',
|
||||
__DIR__ . '/Fixture/skip_trait_called_method.php.inc',
|
||||
__DIR__ . '/Fixture/skip_trait_doc_typed.php.inc',
|
||||
__DIR__ . '/Fixture/skip_trait_complex.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
extensions:
|
||||
- Rector\NodeTypeResolver\PHPStanOverride\DependencyInjection\ReplaceNodeScopeResolverClassCompilerExtension
|
|
@ -27,8 +27,6 @@ final class PHPStanServicesFactory
|
|||
$additionalConfigFiles[] = $phpstanPhpunitExtensionConfig;
|
||||
}
|
||||
|
||||
$additionalConfigFiles[] = __DIR__ . '/../../config/phpstan_services_override.neon';
|
||||
|
||||
$this->container = $containerFactory->create(sys_get_temp_dir(), $additionalConfigFiles, []);
|
||||
}
|
||||
|
||||
|
|
|
@ -459,6 +459,7 @@ final class NodeTypeResolver
|
|||
}
|
||||
|
||||
$type = $nodeScope->getType($staticCall);
|
||||
|
||||
if ($type instanceof ObjectType) {
|
||||
return [$type->getClassName()];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PHPStan\Collector;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Node\VirtualNode;
|
||||
use Rector\PhpParser\Printer\BetterStandardPrinter;
|
||||
|
||||
final class TraitNodeScopeCollector
|
||||
{
|
||||
/**
|
||||
* @var BetterStandardPrinter
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @var Scope[]
|
||||
*/
|
||||
private $scopeByTraitNodeHash = [];
|
||||
|
||||
public function __construct(BetterStandardPrinter $betterStandardPrinter)
|
||||
{
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
}
|
||||
|
||||
public function addForTraitAndNode(string $traitName, Node $node, Scope $scope): void
|
||||
{
|
||||
if ($node instanceof VirtualNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$traitNodeHash = $this->createHash($traitName, $node);
|
||||
|
||||
// probably set from another class
|
||||
if (isset($this->scopeByTraitNodeHash[$traitNodeHash])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scopeByTraitNodeHash[$traitNodeHash] = $scope;
|
||||
}
|
||||
|
||||
public function getScopeForTraitAndNode(string $traitName, Node $node): ?Scope
|
||||
{
|
||||
$traitNodeHash = $this->createHash($traitName, $node);
|
||||
|
||||
return $this->scopeByTraitNodeHash[$traitNodeHash] ?? null;
|
||||
}
|
||||
|
||||
private function createHash(string $traitName, Node $node): string
|
||||
{
|
||||
$printedNode = $this->betterStandardPrinter->print($node);
|
||||
|
||||
return sha1($traitName . $printedNode);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Rector\NodeTypeResolver\PHPStan\Scope;
|
||||
|
||||
use Closure;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
|
@ -11,15 +10,11 @@ use PhpParser\Node\Stmt\Trait_;
|
|||
use PhpParser\NodeTraverser;
|
||||
use PHPStan\Analyser\NodeScopeResolver as PHPStanNodeScopeResolver;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Analyser\ScopeContext;
|
||||
use PHPStan\Broker\Broker;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
|
||||
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
|
||||
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ScopeTraitNodeVisitor;
|
||||
use Rector\NodeTypeResolver\PHPStan\Scope\Stub\ClassReflectionForUnusedTrait;
|
||||
use ReflectionClass;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
||||
|
||||
/**
|
||||
* @inspired by https://github.com/silverstripe/silverstripe-upgrader/blob/532182b23e854d02e0b27e68ebc394f436de0682/src/UpgradeRule/PHP/Visitor/PHPStanScopeVisitor.php
|
||||
|
@ -48,29 +43,22 @@ final class NodeScopeResolver
|
|||
private $removeDeepChainMethodCallNodeVisitor;
|
||||
|
||||
/**
|
||||
* @var PrivatesAccessor
|
||||
* @var TraitNodeScopeCollector
|
||||
*/
|
||||
private $privatesAccessor;
|
||||
|
||||
/**
|
||||
* @var ScopeTraitNodeVisitor
|
||||
*/
|
||||
private $scopeTraitNodeVisitor;
|
||||
private $traitNodeScopeCollector;
|
||||
|
||||
public function __construct(
|
||||
ScopeFactory $scopeFactory,
|
||||
PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
|
||||
Broker $broker,
|
||||
RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor,
|
||||
PrivatesAccessor $privatesAccessor,
|
||||
ScopeTraitNodeVisitor $scopeTraitNodeVisitor
|
||||
TraitNodeScopeCollector $traitNodeScopeCollector
|
||||
) {
|
||||
$this->scopeFactory = $scopeFactory;
|
||||
$this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver;
|
||||
$this->broker = $broker;
|
||||
$this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor;
|
||||
$this->privatesAccessor = $privatesAccessor;
|
||||
$this->scopeTraitNodeVisitor = $scopeTraitNodeVisitor;
|
||||
$this->traitNodeScopeCollector = $traitNodeScopeCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,8 +78,14 @@ final class NodeScopeResolver
|
|||
// 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);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
$traitName = $scope->getTraitReflection()->getName();
|
||||
$this->traitNodeScopeCollector->addForTraitAndNode($traitName, $node, $scope);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$node->setAttribute(AttributeKey::SCOPE, $scope);
|
||||
|
@ -99,7 +93,7 @@ final class NodeScopeResolver
|
|||
|
||||
$this->phpStanNodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);
|
||||
|
||||
return $this->resolveScopeInTrait($nodes, $nodeCallback);
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,45 +132,4 @@ final class NodeScopeResolver
|
|||
|
||||
return $classOrInterfaceNode->name->toString();
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
// we need to emulate class reflection, because PHPStan is unable to analyze bare trait without it
|
||||
$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);
|
||||
|
||||
// clear stub
|
||||
$this->privatesAccessor->setPrivateProperty($scopeContext, 'classReflection', null);
|
||||
|
||||
return $traitScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
* @return Node[]
|
||||
*/
|
||||
private function resolveScopeInTrait(array $nodes, Closure $nodeCallback): array
|
||||
{
|
||||
$traitNodeTraverser = new NodeTraverser();
|
||||
|
||||
$this->scopeTraitNodeVisitor->setNodeCallback($nodeCallback);
|
||||
$traitNodeTraverser->addVisitor($this->scopeTraitNodeVisitor);
|
||||
|
||||
return $traitNodeTraverser->traverse($nodes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor;
|
||||
|
||||
use Closure;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PHPStan\Analyser\NodeScopeResolver;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
/**
|
||||
* Adds scope to all nodes inside trait even without class that is using it (that what PHPStan needs to add a scope to them)
|
||||
*/
|
||||
final class ScopeTraitNodeVisitor extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var NodeScopeResolver
|
||||
*/
|
||||
private $nodeScopeResolver;
|
||||
|
||||
/**
|
||||
* @var Closure
|
||||
*/
|
||||
private $nodeCallback;
|
||||
|
||||
public function __construct(NodeScopeResolver $nodeScopeResolver)
|
||||
{
|
||||
$this->nodeScopeResolver = $nodeScopeResolver;
|
||||
}
|
||||
|
||||
public function setNodeCallback(Closure $nodeCallback): void
|
||||
{
|
||||
$this->nodeCallback = $nodeCallback;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node): ?Node
|
||||
{
|
||||
if ($this->nodeCallback === null) {
|
||||
throw new ShouldNotHappenException(sprintf(
|
||||
'Set "$nodeCallback" property via "setNodeCallback()" on "%s" first',
|
||||
self::class
|
||||
));
|
||||
}
|
||||
|
||||
if (! $node instanceof Trait_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Scope|null $traitScope */
|
||||
$traitScope = $node->getAttribute(AttributeKey::SCOPE);
|
||||
if ($traitScope === null) {
|
||||
throw new ShouldNotHappenException(sprintf('A trait "%s" is missing a scope', (string) $node->name));
|
||||
}
|
||||
|
||||
$this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $traitScope, $this->nodeCallback);
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PHPStan\Scope\Stub;
|
||||
|
||||
final class ClassReflectionForUnusedTrait
|
||||
{
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PHPStanOverride\Analyser;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PHPStan\Analyser\NameScope;
|
||||
use PHPStan\Analyser\NodeScopeResolver;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Analyser\TypeSpecifier;
|
||||
use PHPStan\Broker\Broker;
|
||||
use PHPStan\File\FileHelper;
|
||||
use PHPStan\Parser\Parser;
|
||||
use PHPStan\PhpDoc\PhpDocStringResolver;
|
||||
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
|
||||
use PHPStan\PhpDoc\Tag\ParamTag;
|
||||
use PHPStan\Type\FileTypeMapper;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
/**
|
||||
* This services is not used in Rector directly,
|
||||
* but replaces a services in PHPStan container.
|
||||
*/
|
||||
final class StandaloneTraitAwarePHPStanNodeScopeResolver extends NodeScopeResolver
|
||||
{
|
||||
/**
|
||||
* @var PhpDocStringResolver
|
||||
*/
|
||||
private $phpDocStringResolver;
|
||||
|
||||
/**
|
||||
* @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[])
|
||||
*/
|
||||
public function __construct(
|
||||
Broker $broker,
|
||||
Parser $parser,
|
||||
FileTypeMapper $fileTypeMapper,
|
||||
FileHelper $fileHelper,
|
||||
TypeSpecifier $typeSpecifier,
|
||||
bool $polluteScopeWithLoopInitialAssignments,
|
||||
bool $polluteCatchScopeWithTryAssignments,
|
||||
bool $polluteScopeWithAlwaysIterableForeach,
|
||||
array $earlyTerminatingMethodCalls,
|
||||
bool $allowVarTagAboveStatements,
|
||||
PhpDocStringResolver $phpDocStringResolver
|
||||
) {
|
||||
parent::__construct($broker, $parser, $fileTypeMapper, $fileHelper, $typeSpecifier, $polluteScopeWithLoopInitialAssignments, $polluteCatchScopeWithTryAssignments, $polluteScopeWithAlwaysIterableForeach, $earlyTerminatingMethodCalls, $allowVarTagAboveStatements);
|
||||
|
||||
$this->phpDocStringResolver = $phpDocStringResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPhpDocs(Scope $scope, FunctionLike $functionLike): array
|
||||
{
|
||||
if (! $this->isClassMethodInTrait($functionLike) || $functionLike->getDocComment() === null) {
|
||||
return parent::getPhpDocs($scope, $functionLike);
|
||||
}
|
||||
|
||||
// special case for traits
|
||||
$phpDocString = $functionLike->getDocComment()->getText();
|
||||
$nameScope = $this->createNameScope($functionLike);
|
||||
|
||||
$resolvedPhpDocs = $this->phpDocStringResolver->resolve($phpDocString, $nameScope);
|
||||
|
||||
return $this->convertResolvedPhpDocToArray($resolvedPhpDocs, $functionLike, $scope);
|
||||
}
|
||||
|
||||
private function isClassMethodInTrait(FunctionLike $functionLike): bool
|
||||
{
|
||||
if ($functionLike instanceof Function_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classNode = $functionLike->getAttribute(AttributeKey::CLASS_NODE);
|
||||
|
||||
return $classNode instanceof Trait_;
|
||||
}
|
||||
|
||||
private function createNameScope(FunctionLike $functionLike): NameScope
|
||||
{
|
||||
$namespace = $functionLike->getAttribute(AttributeKey::NAMESPACE_NAME);
|
||||
|
||||
/** @var Node\Stmt\Use_[] $useNodes */
|
||||
$useNodes = $functionLike->getAttribute(AttributeKey::USE_NODES) ?? [];
|
||||
$uses = [];
|
||||
foreach ($useNodes as $useNode) {
|
||||
foreach ($useNode->uses as $useUserNode) {
|
||||
$useImport = $useUserNode->name->toString();
|
||||
|
||||
/** @var string $alias */
|
||||
$alias = $useUserNode->alias ? (string) $useUserNode->alias : Strings::after($useImport, '\\', -1);
|
||||
|
||||
$phpstanAlias = strtolower($alias);
|
||||
$uses[$phpstanAlias] = $useImport;
|
||||
}
|
||||
}
|
||||
|
||||
$className = $functionLike->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
return new NameScope($namespace, $uses, $className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy pasted from last part of @see \PHPStan\Analyser\NodeScopeResolver::getPhpDocs()
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function convertResolvedPhpDocToArray(
|
||||
ResolvedPhpDocBlock $resolvedPhpDocBlock,
|
||||
FunctionLike $functionLike,
|
||||
Scope $scope
|
||||
): array {
|
||||
$phpDocParameterTypes = $this->resolvePhpDocParameterTypes($resolvedPhpDocBlock);
|
||||
|
||||
$nativeReturnType = $scope->getFunctionType($functionLike->getReturnType(), false, false);
|
||||
|
||||
$phpDocThrowType = $resolvedPhpDocBlock->getThrowsTag() !== null ? $resolvedPhpDocBlock->getThrowsTag()->getType() : null;
|
||||
|
||||
$deprecatedDescription = $resolvedPhpDocBlock->getDeprecatedTag() !== null ? $resolvedPhpDocBlock->getDeprecatedTag()->getMessage() : null;
|
||||
|
||||
return [
|
||||
$phpDocParameterTypes,
|
||||
$this->resolvePhpDocReturnType($resolvedPhpDocBlock, $nativeReturnType),
|
||||
$phpDocThrowType,
|
||||
$deprecatedDescription,
|
||||
$resolvedPhpDocBlock->isDeprecated(),
|
||||
$resolvedPhpDocBlock->isInternal(),
|
||||
$resolvedPhpDocBlock->isFinal(),
|
||||
];
|
||||
}
|
||||
|
||||
private function resolvePhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDocBlock, Type $nativeReturnType): ?Type
|
||||
{
|
||||
if ($resolvedPhpDocBlock->getReturnTag() !== null && (
|
||||
$nativeReturnType->isSuperTypeOf($resolvedPhpDocBlock->getReturnTag()->getType())->yes()
|
||||
)) {
|
||||
return $resolvedPhpDocBlock->getReturnTag()->getType();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
private function resolvePhpDocParameterTypes(ResolvedPhpDocBlock $resolvedPhpDocBlock): array
|
||||
{
|
||||
return array_map(static function (ParamTag $tag): Type {
|
||||
return $tag->getType();
|
||||
}, $resolvedPhpDocBlock->getParamTags());
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PHPStanOverride\DependencyInjection;
|
||||
|
||||
use Nette\DI\CompilerExtension;
|
||||
use Nette\DI\Definitions\Reference;
|
||||
use Nette\DI\Definitions\ServiceDefinition;
|
||||
use Nette\DI\Definitions\Statement;
|
||||
use PHPStan\Analyser\NodeScopeResolver;
|
||||
use PHPStan\PhpDoc\PhpDocStringResolver;
|
||||
use Rector\NodeTypeResolver\PHPStanOverride\Analyser\StandaloneTraitAwarePHPStanNodeScopeResolver;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
||||
|
||||
/**
|
||||
* This services is not used in Rector directly,
|
||||
* but replaces a services in PHPStan container.
|
||||
*/
|
||||
final class ReplaceNodeScopeResolverClassCompilerExtension extends CompilerExtension
|
||||
{
|
||||
/**
|
||||
* @var PrivatesAccessor
|
||||
*/
|
||||
private $privatesAccessor;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->privatesAccessor = new PrivatesAccessor();
|
||||
}
|
||||
|
||||
public function beforeCompile(): void
|
||||
{
|
||||
/** @var ServiceDefinition $nodeScopeResolver */
|
||||
$nodeScopeResolver = $this->getContainerBuilder()->getDefinitionByType(NodeScopeResolver::class);
|
||||
|
||||
// @see https://github.com/nette/di/blob/47bf203c9ae0f3ccf51de9e5ea309a1cdff4d5e9/src/DI/Definitions/ServiceDefinition.php
|
||||
/** @var Statement $factory */
|
||||
$factory = $this->privatesAccessor->getPrivateProperty($nodeScopeResolver, 'factory');
|
||||
|
||||
$serviceArguments = $factory->arguments;
|
||||
// new extra dependency
|
||||
$serviceArguments['phpDocStringResolver'] = new Reference(PhpDocStringResolver::class);
|
||||
$serviceArguments['allowVarTagAboveStatements'] = true;
|
||||
|
||||
$nodeScopeResolver->setFactory(StandaloneTraitAwarePHPStanNodeScopeResolver::class, $serviceArguments);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Rector\NodeTypeResolver\PerNodeTypeResolver;
|
|||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\ThisType;
|
||||
|
@ -13,6 +14,7 @@ use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInte
|
|||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\StaticTypeToStringResolver;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
|
||||
|
@ -38,14 +40,21 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface, NodeTy
|
|||
*/
|
||||
private $nodeTypeResolver;
|
||||
|
||||
/**
|
||||
* @var TraitNodeScopeCollector
|
||||
*/
|
||||
private $traitNodeScopeCollector;
|
||||
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
StaticTypeToStringResolver $staticTypeToStringResolver,
|
||||
NameResolver $nameResolver
|
||||
NameResolver $nameResolver,
|
||||
TraitNodeScopeCollector $traitNodeScopeCollector
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->staticTypeToStringResolver = $staticTypeToStringResolver;
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->traitNodeScopeCollector = $traitNodeScopeCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,33 +102,31 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface, NodeTy
|
|||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
}
|
||||
|
||||
private function resolveNodeScope(Node $variableNode): ?Scope
|
||||
private function resolveNodeScope(Node $node): ?Scope
|
||||
{
|
||||
/** @var Scope|null $nodeScope */
|
||||
$nodeScope = $variableNode->getAttribute(AttributeKey::SCOPE);
|
||||
if ($nodeScope instanceof Scope) {
|
||||
return $nodeScope;
|
||||
$nodeScope = $node->getAttribute(AttributeKey::SCOPE);
|
||||
|
||||
// is node in trait
|
||||
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if ($classNode instanceof Trait_) {
|
||||
/** @var string $traitName */
|
||||
$traitName = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
$traitNodeScope = $this->traitNodeScopeCollector->getScopeForTraitAndNode($traitName, $node);
|
||||
}
|
||||
|
||||
$parentNode = $variableNode->getAttribute(AttributeKey::PARENT_NODE);
|
||||
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($parentNode instanceof Node) {
|
||||
$nodeScope = $parentNode->getAttribute(AttributeKey::SCOPE);
|
||||
if ($nodeScope instanceof Scope) {
|
||||
return $nodeScope;
|
||||
}
|
||||
$parentNodeScope = $parentNode->getAttribute(AttributeKey::SCOPE);
|
||||
}
|
||||
|
||||
// get nearest variable scope
|
||||
$method = $variableNode->getAttribute(AttributeKey::METHOD_NODE);
|
||||
$method = $node->getAttribute(AttributeKey::METHOD_NODE);
|
||||
if ($method instanceof Node) {
|
||||
$nodeScope = $method->getAttribute(AttributeKey::SCOPE);
|
||||
if ($nodeScope instanceof Scope) {
|
||||
return $nodeScope;
|
||||
}
|
||||
$methodNodeScope = $method->getAttribute(AttributeKey::SCOPE);
|
||||
}
|
||||
|
||||
// unknown scope
|
||||
return null;
|
||||
return $nodeScope ?? $traitNodeScope ?? $parentNodeScope ?? $methodNodeScope ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -186,4 +186,3 @@ parameters:
|
|||
# symfony future compatibility
|
||||
- '#Call to an undefined static method Symfony\\Component\\EventDispatcher\\EventDispatcher\:\:__construct\(\)#'
|
||||
- '#Rector\\EventDispatcher\\AutowiredEventDispatcher\:\:__construct\(\) calls parent constructor but parent does not have one#'
|
||||
- '#Method Rector\\NodeTypeResolver\\PHPStanOverride\\Analyser\\StandaloneTraitAwarePHPStanNodeScopeResolver\:\:getPhpDocs\(\) should return array\(array<PHPStan\\Type\\Type\>, PHPStan\\Type\\Type\|null, PHPStan\\Type\\Type\|null, string\|null, bool, bool, bool\) but returns array#'
|
||||
|
|
|
@ -124,14 +124,6 @@ final class ParsedNodesByType
|
|||
return $this->simpleParsedNodesByType[New_::class] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StaticCall[]
|
||||
*/
|
||||
public function getStaticCallNodes(): array
|
||||
{
|
||||
return $this->simpleParsedNodesByType[StaticCall::class] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to circular reference
|
||||
* @required
|
||||
|
@ -536,7 +528,6 @@ final class ParsedNodesByType
|
|||
}
|
||||
|
||||
$methodName = $this->nameResolver->getName($classMethod);
|
||||
|
||||
$this->methodsByType[$className][$methodName] = $classMethod;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user