From d280fd6ebef2a98fb9b25ff95fa3a1aadd6e368b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 12 May 2019 10:19:38 +0200 Subject: [PATCH] add trait scope without need for class --- config/services.yaml | 1 + .../NodeTypeResolver/src/NodeTypeResolver.php | 5 ++ .../src/PHPStan/Scope/NodeScopeResolver.php | 51 +++++++++++++++++-- .../Stub/ClassReflectionForUnusedTrait.php | 7 +++ .../FuncCall/StringifyStrNeedlesRector.php | 11 +--- .../Fixture/trait.php.inc | 29 +++++++++++ .../StringifyStrNeedlesRectorTest.php | 2 +- 7 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 packages/NodeTypeResolver/src/PHPStan/Scope/Stub/ClassReflectionForUnusedTrait.php create mode 100644 packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/trait.php.inc diff --git a/config/services.yaml b/config/services.yaml index 699359c8a771..1c66c6e03bb7 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -23,6 +23,7 @@ services: # value resolver Symfony\Component\Filesystem\Filesystem: ~ + Symplify\PackageBuilder\Reflection\PrivatesAccessor: ~ Symplify\PackageBuilder\FileSystem\FileSystem: ~ Symplify\PackageBuilder\FileSystem\FinderSanitizer: ~ diff --git a/packages/NodeTypeResolver/src/NodeTypeResolver.php b/packages/NodeTypeResolver/src/NodeTypeResolver.php index b2824fb3f9d1..918c8f5a4b79 100644 --- a/packages/NodeTypeResolver/src/NodeTypeResolver.php +++ b/packages/NodeTypeResolver/src/NodeTypeResolver.php @@ -24,6 +24,7 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; @@ -284,6 +285,10 @@ public function isArrayType(Node $node): bool public function getNodeStaticType(Node $node): ?Type { + if ($node instanceof Node\Scalar\String_) { + return new ConstantStringType($node->value); + } + /** @var Scope|null $nodeScope */ $nodeScope = $node->getAttribute(AttributeKey::SCOPE); if (! $node instanceof Expr || $nodeScope === null) { diff --git a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php index 04715c33a3ef..085cfd7075d5 100644 --- a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php +++ b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php @@ -6,13 +6,18 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Interface_; +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\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor; +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 @@ -40,16 +45,23 @@ final class NodeScopeResolver */ private $removeDeepChainMethodCallNodeVisitor; + /** + * @var PrivatesAccessor + */ + private $privatesAccessor; + public function __construct( ScopeFactory $scopeFactory, PHPStanNodeScopeResolver $phpStanNodeScopeResolver, Broker $broker, - RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor + RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor, + PrivatesAccessor $privatesAccessor ) { $this->scopeFactory = $scopeFactory; $this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver; $this->broker = $broker; $this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor; + $this->privatesAccessor = $privatesAccessor; } /** @@ -70,7 +82,9 @@ function (Node $node, Scope $scope): void { // 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 Class_ || $node instanceof Interface_) { - $scope = $this->resolveClassOrInterfaceNode($node, $scope); + $scope = $this->resolveClassOrInterfaceScope($node, $scope); + } elseif ($node instanceof Trait_) { + $scope = $this->resolveTraitScope($node, $scope); } $node->setAttribute(AttributeKey::SCOPE, $scope); @@ -93,17 +107,16 @@ private function removeDeepChainMethodCallNodes(array $nodes): void /** * @param Class_|Interface_ $classOrInterfaceNode */ - private function resolveClassOrInterfaceNode(Node $classOrInterfaceNode, Scope $scope): Scope + private function resolveClassOrInterfaceScope(Node $classOrInterfaceNode, Scope $scope): Scope { $className = $this->resolveClassName($classOrInterfaceNode); - $classReflection = $this->broker->getClass($className); return $scope->enterClass($classReflection); } /** - * @param Class_|Interface_ $classOrInterfaceNode + * @param Class_|Interface_|Trait_ $classOrInterfaceNode */ private function resolveClassName(ClassLike $classOrInterfaceNode): string { @@ -117,4 +130,32 @@ private function resolveClassName(ClassLike $classOrInterfaceNode): string 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'); + if ($scopeContext->getClassReflection() !== null) { + return $scope->enterTrait($traitReflection); + } + + // we need to emulate class reflection, because PHPStan is unable to analyze 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); + $this->privatesAccessor->setPrivateProperty($scopeContext, 'classReflection', null); + + return $traitScope; + } } diff --git a/packages/NodeTypeResolver/src/PHPStan/Scope/Stub/ClassReflectionForUnusedTrait.php b/packages/NodeTypeResolver/src/PHPStan/Scope/Stub/ClassReflectionForUnusedTrait.php new file mode 100644 index 000000000000..021e7d74a566 --- /dev/null +++ b/packages/NodeTypeResolver/src/PHPStan/Scope/Stub/ClassReflectionForUnusedTrait.php @@ -0,0 +1,7 @@ +args[1]->value; - $nodeScope = $needleArgNode->getAttribute(AttributeKey::SCOPE); - if ($nodeScope === null) { - throw new ShouldNotHappenException(); - } - - if ($nodeScope->getType($needleArgNode) instanceof ConstantStringType) { + $nodeStaticType = $this->getStaticType($needleArgNode); + if ($nodeStaticType instanceof ConstantStringType) { return null; } diff --git a/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/trait.php.inc b/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/trait.php.inc new file mode 100644 index 000000000000..1d97434381d6 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/Fixture/trait.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/StringifyStrNeedlesRectorTest.php b/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/StringifyStrNeedlesRectorTest.php index 78e6eac3673f..8d940e1c35bf 100644 --- a/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/StringifyStrNeedlesRectorTest.php +++ b/packages/Php/tests/Rector/FuncCall/StringifyStrNeedlesRector/StringifyStrNeedlesRectorTest.php @@ -9,7 +9,7 @@ final class StringifyStrNeedlesRectorTest extends AbstractRectorTestCase { public function test(): void { - $this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']); + $this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc', __DIR__ . '/Fixture/trait.php.inc']); } public function getRectorClass(): string