mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-25 20:23:49 +01:00
129 lines
4.7 KiB
PHP
129 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare (strict_types=1);
|
|
namespace Rector\Defluent\NodeAnalyzer;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Expr\MethodCall;
|
|
use PhpParser\Node\Expr\StaticCall;
|
|
use PhpParser\Node\Name;
|
|
use PHPStan\Type\MixedType;
|
|
use PHPStan\Type\Type;
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
use Rector\NodeTypeResolver\NodeTypeResolver;
|
|
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
|
/**
|
|
* Utils for chain of MethodCall Node:
|
|
* "$this->methodCall()->chainedMethodCall()"
|
|
*/
|
|
final class FluentChainMethodCallNodeAnalyzer
|
|
{
|
|
/**
|
|
* @var \Rector\NodeNameResolver\NodeNameResolver
|
|
*/
|
|
private $nodeNameResolver;
|
|
/**
|
|
* @var \Rector\NodeTypeResolver\NodeTypeResolver
|
|
*/
|
|
private $nodeTypeResolver;
|
|
public function __construct(\Rector\NodeNameResolver\NodeNameResolver $nodeNameResolver, \Rector\NodeTypeResolver\NodeTypeResolver $nodeTypeResolver)
|
|
{
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
|
$this->nodeTypeResolver = $nodeTypeResolver;
|
|
}
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function collectMethodCallNamesInChain(\PhpParser\Node\Expr\MethodCall $desiredMethodCall) : array
|
|
{
|
|
$methodCalls = $this->collectAllMethodCallsInChain($desiredMethodCall);
|
|
$methodNames = [];
|
|
foreach ($methodCalls as $methodCall) {
|
|
$methodName = $this->nodeNameResolver->getName($methodCall->name);
|
|
if ($methodName === null) {
|
|
continue;
|
|
}
|
|
$methodNames[] = $methodName;
|
|
}
|
|
return $methodNames;
|
|
}
|
|
/**
|
|
* @return MethodCall[]
|
|
*/
|
|
public function collectAllMethodCallsInChain(\PhpParser\Node\Expr\MethodCall $methodCall) : array
|
|
{
|
|
$chainMethodCalls = [$methodCall];
|
|
// traverse up
|
|
$currentNode = $methodCall->var;
|
|
while ($currentNode instanceof \PhpParser\Node\Expr\MethodCall) {
|
|
$chainMethodCalls[] = $currentNode;
|
|
$currentNode = $currentNode->var;
|
|
}
|
|
// traverse down
|
|
if (\count($chainMethodCalls) === 1) {
|
|
$currentNode = $methodCall->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE);
|
|
while ($currentNode instanceof \PhpParser\Node\Expr\MethodCall) {
|
|
$chainMethodCalls[] = $currentNode;
|
|
$currentNode = $currentNode->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE);
|
|
}
|
|
}
|
|
return $chainMethodCalls;
|
|
}
|
|
/**
|
|
* Checks "$this->someMethod()->anotherMethod()"
|
|
*
|
|
* @param string[] $methods
|
|
*/
|
|
public function isTypeAndChainCalls(\PhpParser\Node $node, \PHPStan\Type\Type $type, array $methods) : bool
|
|
{
|
|
if (!$node instanceof \PhpParser\Node\Expr\MethodCall) {
|
|
return \false;
|
|
}
|
|
$rootMethodCall = $this->resolveRootMethodCall($node);
|
|
if (!$rootMethodCall instanceof \PhpParser\Node\Expr\MethodCall) {
|
|
return \false;
|
|
}
|
|
$rootMethodCallVarType = $this->nodeTypeResolver->getType($rootMethodCall->var);
|
|
if (!$rootMethodCallVarType instanceof \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType) {
|
|
return \false;
|
|
}
|
|
// node chaining is in reverse order than code
|
|
$methods = \array_reverse($methods);
|
|
foreach ($methods as $method) {
|
|
if (!$this->nodeNameResolver->isName($node->name, $method)) {
|
|
return \false;
|
|
}
|
|
$node = $node->var;
|
|
}
|
|
$variableType = $this->nodeTypeResolver->getType($node);
|
|
if ($variableType instanceof \PHPStan\Type\MixedType) {
|
|
return \false;
|
|
}
|
|
return $variableType->isSuperTypeOf($type)->yes();
|
|
}
|
|
/**
|
|
* @return \PhpParser\Node\Expr|\PhpParser\Node\Name
|
|
*/
|
|
public function resolveRootExpr(\PhpParser\Node\Expr\MethodCall $methodCall)
|
|
{
|
|
$callerNode = $methodCall->var;
|
|
while ($callerNode instanceof \PhpParser\Node\Expr\MethodCall || $callerNode instanceof \PhpParser\Node\Expr\StaticCall) {
|
|
$callerNode = $callerNode instanceof \PhpParser\Node\Expr\StaticCall ? $callerNode->class : $callerNode->var;
|
|
}
|
|
return $callerNode;
|
|
}
|
|
public function resolveRootMethodCall(\PhpParser\Node\Expr\MethodCall $methodCall) : ?\PhpParser\Node\Expr\MethodCall
|
|
{
|
|
$callerNode = $methodCall->var;
|
|
while ($callerNode instanceof \PhpParser\Node\Expr\MethodCall && $callerNode->var instanceof \PhpParser\Node\Expr\MethodCall) {
|
|
$callerNode = $callerNode->var;
|
|
}
|
|
if ($callerNode instanceof \PhpParser\Node\Expr\MethodCall) {
|
|
return $callerNode;
|
|
}
|
|
return null;
|
|
}
|
|
}
|