rector/packages/Defluent/NodeAnalyzer/FluentChainMethodCallNodeAnalyzer.php
2021-10-26 00:34:35 +00:00

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;
}
}