Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ services:

# value resolver
Symfony\Component\Filesystem\Filesystem: ~
Symplify\PackageBuilder\Reflection\PrivatesAccessor: ~

Symplify\PackageBuilder\FileSystem\FileSystem: ~
Symplify\PackageBuilder\FileSystem\FinderSanitizer: ~
Expand Down
5 changes: 5 additions & 0 deletions packages/NodeTypeResolver/src/NodeTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
51 changes: 46 additions & 5 deletions packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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);
Expand All @@ -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
{
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types=1);

namespace Rector\NodeTypeResolver\PHPStan\Scope\Stub;

final class ClassReflectionForUnusedTrait
{
}
11 changes: 2 additions & 9 deletions packages/Php/src/Rector/FuncCall/StringifyStrNeedlesRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
use PhpParser\Node;
use PhpParser\Node\Expr\Cast\String_;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Type\Constant\ConstantStringType;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
Expand Down Expand Up @@ -71,12 +68,8 @@ public function refactor(Node $node): ?Node
// is argument string?
$needleArgNode = $node->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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Rector\Php\Tests\Rector\FuncCall\StringifyStrNeedlesRector\Fixture;

trait ResourceHandler
{
protected function addResourceByType(string $path)
{
$local = !(strpos($path, 5) === 0);
$local = !(strpos($path, 'http') === 0);
}
}

?>
-----
<?php

namespace Rector\Php\Tests\Rector\FuncCall\StringifyStrNeedlesRector\Fixture;

trait ResourceHandler
{
protected function addResourceByType(string $path)
{
$local = !(strpos($path, (string) 5) === 0);
$local = !(strpos($path, 'http') === 0);
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down