rector/rules/naming/src/Rector/Assign/RenameVariableToMatchMethodCallReturnTypeRector.php

263 lines
7.5 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Rector\Naming\Rector\Assign;
use PhpParser\Node;
2020-07-19 13:39:13 +00:00
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
2020-07-19 12:30:07 +00:00
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
2020-07-19 18:19:25 +00:00
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\Rector\AbstractRector;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\Naming\Guard\BreakingVariableRenameGuard;
use Rector\Naming\Matcher\VariableAndCallAssignMatcher;
use Rector\Naming\Naming\ExpectedNameResolver;
2020-07-19 17:24:46 +00:00
use Rector\Naming\NamingConvention\NamingConventionAnalyzer;
2020-07-19 17:42:16 +00:00
use Rector\Naming\PhpDoc\VarTagValueNodeRenamer;
use Rector\Naming\ValueObject\VariableAndCallAssign;
use Rector\Naming\VariableRenamer;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchMethodCallReturnTypeRector\RenameVariableToMatchMethodCallReturnTypeRectorTest
*/
final class RenameVariableToMatchMethodCallReturnTypeRector extends AbstractRector
{
2020-07-19 18:19:25 +00:00
/**
* @var string[]
*/
private const ALLOWED_PARENT_TYPES = [ClassLike::class];
/**
* @var ExpectedNameResolver
*/
private $expectedNameResolver;
/**
* @var VariableRenamer
*/
private $variableRenamer;
/**
* @var BreakingVariableRenameGuard
*/
private $breakingVariableRenameGuard;
/**
* @var FamilyRelationsAnalyzer
*/
private $familyRelationsAnalyzer;
/**
* @var VariableAndCallAssignMatcher
*/
private $variableAndCallAssignMatcher;
2020-07-19 17:24:46 +00:00
/**
* @var NamingConventionAnalyzer
*/
private $namingConventionAnalyzer;
2020-07-19 17:42:16 +00:00
/**
* @var VarTagValueNodeRenamer
*/
private $varTagValueNodeRenamer;
public function __construct(
BreakingVariableRenameGuard $breakingVariableRenameGuard,
2020-07-26 07:49:22 +00:00
ExpectedNameResolver $expectedNameResolver,
FamilyRelationsAnalyzer $familyRelationsAnalyzer,
2020-07-19 17:42:16 +00:00
NamingConventionAnalyzer $namingConventionAnalyzer,
2020-07-19 18:19:25 +00:00
VarTagValueNodeRenamer $varTagValueNodeRenamer,
2020-07-26 07:49:22 +00:00
VariableAndCallAssignMatcher $variableAndCallAssignMatcher,
VariableRenamer $variableRenamer
) {
$this->expectedNameResolver = $expectedNameResolver;
$this->variableRenamer = $variableRenamer;
$this->breakingVariableRenameGuard = $breakingVariableRenameGuard;
$this->familyRelationsAnalyzer = $familyRelationsAnalyzer;
$this->variableAndCallAssignMatcher = $variableAndCallAssignMatcher;
2020-07-19 17:24:46 +00:00
$this->namingConventionAnalyzer = $namingConventionAnalyzer;
2020-07-19 17:42:16 +00:00
$this->varTagValueNodeRenamer = $varTagValueNodeRenamer;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Rename variable to match method return type',
[
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
{
$a = $this->getRunner();
}
public function getRunner(): Runner
{
return new Runner();
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
{
$runner = $this->getRunner();
}
public function getRunner(): Runner
{
return new Runner();
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Assign::class];
}
/**
* @param Assign $node
*/
public function refactor(Node $node): ?Node
{
/** @var VariableAndCallAssign|null $variableAndCallAssign */
$variableAndCallAssign = $this->variableAndCallAssignMatcher->match($node);
if ($variableAndCallAssign === null) {
return null;
}
$call = $variableAndCallAssign->getCall();
if ($this->isMultipleCall($call)) {
return null;
}
$expectedName = $this->expectedNameResolver->resolveForCall($call);
2020-07-27 09:26:41 +00:00
if ($expectedName === null || $this->isName($node->var, $expectedName)) {
return null;
}
2020-07-19 17:42:16 +00:00
if ($this->shouldSkip($variableAndCallAssign, $expectedName)) {
return null;
}
2020-07-19 18:19:25 +00:00
$this->renameVariable($variableAndCallAssign, $expectedName);
return $node;
}
/**
* @param FuncCall|StaticCall|MethodCall $node
*/
private function isMultipleCall(Node $node): bool
{
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
while ($parentNode) {
$countUsed = count($this->betterNodeFinder->find($parentNode, function (Node $n) use ($node): bool {
if (get_class($node) !== get_class($n)) {
return false;
}
/** @var FuncCall|StaticCall|MethodCall $n */
$passedNode = clone $n;
/** @var FuncCall|StaticCall|MethodCall $node */
$usedNode = clone $node;
/** @var FuncCall|StaticCall|MethodCall $passedNode */
$passedNode->args = [];
/** @var FuncCall|StaticCall|MethodCall $usedNode */
$usedNode->args = [];
return $this->areNodesEqual($passedNode, $usedNode);
}));
if ($countUsed > 1) {
return true;
}
$parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE);
}
return false;
}
private function shouldSkip(VariableAndCallAssign $variableAndCallAssign, string $expectedName): bool
{
if ($this->namingConventionAnalyzer->isCallMatchingVariableName(
$variableAndCallAssign->getCall(),
$variableAndCallAssign->getVariableName(),
$expectedName
)) {
return true;
}
if ($this->isClassTypeWithChildren($variableAndCallAssign->getCall())) {
return true;
}
return $this->breakingVariableRenameGuard->shouldSkipVariable(
$variableAndCallAssign->getVariableName(),
$expectedName,
$variableAndCallAssign->getFunctionLike(),
$variableAndCallAssign->getVariable()
);
}
2020-07-19 18:19:25 +00:00
private function renameVariable(VariableAndCallAssign $variableAndCallAssign, string $expectedName): void
{
2020-07-19 17:42:16 +00:00
$this->varTagValueNodeRenamer->renameAssignVarTagVariableName(
$variableAndCallAssign->getAssign(),
$variableAndCallAssign->getVariableName(),
2020-07-19 18:19:25 +00:00
$expectedName
2020-07-19 17:42:16 +00:00
);
2020-07-19 09:53:35 +00:00
2020-07-19 17:42:16 +00:00
$this->variableRenamer->renameVariableInFunctionLike(
$variableAndCallAssign->getFunctionLike(),
$variableAndCallAssign->getAssign(),
$variableAndCallAssign->getVariableName(),
2020-07-19 18:19:25 +00:00
$expectedName
2020-07-19 17:42:16 +00:00
);
}
2020-07-19 13:39:13 +00:00
/**
2020-07-19 17:24:46 +00:00
* @param StaticCall|MethodCall|FuncCall $expr
2020-07-19 13:39:13 +00:00
*/
2020-07-19 17:24:46 +00:00
private function isClassTypeWithChildren(Expr $expr): bool
2020-07-19 13:39:13 +00:00
{
2020-07-19 17:24:46 +00:00
$callStaticType = $this->getStaticType($expr);
$callStaticType = $this->typeUnwrapper->unwrapNullableType($callStaticType);
2020-07-19 18:19:25 +00:00
2020-07-19 17:24:46 +00:00
if (! $callStaticType instanceof TypeWithClassName) {
return false;
2020-07-19 13:39:13 +00:00
}
2020-07-19 18:19:25 +00:00
if (in_array($callStaticType->getClassName(), self::ALLOWED_PARENT_TYPES, true)) {
return false;
}
2020-07-19 17:24:46 +00:00
return $this->familyRelationsAnalyzer->isParentClass($callStaticType->getClassName());
2020-07-19 13:39:13 +00:00
}
}