2020-07-13 21:13:40 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Rector\Naming\Rector\Assign;
|
|
|
|
|
|
|
|
use PhpParser\Comment\Doc;
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Expr\ArrowFunction;
|
|
|
|
use PhpParser\Node\Expr\Assign;
|
|
|
|
use PhpParser\Node\Expr\Closure;
|
|
|
|
use PhpParser\Node\Expr\ClosureUse;
|
|
|
|
use PhpParser\Node\Expr\Variable;
|
|
|
|
use PhpParser\Node\FunctionLike;
|
|
|
|
use PhpParser\Node\Param;
|
|
|
|
use PhpParser\Node\Stmt;
|
2020-07-19 09:41:35 +00:00
|
|
|
use PhpParser\NodeTraverser;
|
2020-07-19 09:53:35 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
|
|
|
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
2020-07-13 21:13:40 +00:00
|
|
|
use Rector\Core\Rector\AbstractRector;
|
|
|
|
use Rector\Core\RectorDefinition\CodeSample;
|
|
|
|
use Rector\Core\RectorDefinition\RectorDefinition;
|
|
|
|
use Rector\Naming\Naming\ExpectedNameResolver;
|
2020-07-19 09:53:35 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2020-07-13 21:13:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @see \Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\RenameVariableToMatchGetMethodNameRectorTest
|
|
|
|
*/
|
|
|
|
final class RenameVariableToMatchGetMethodNameRector extends AbstractRector
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var ExpectedNameResolver
|
|
|
|
*/
|
|
|
|
private $expectedNameResolver;
|
|
|
|
|
|
|
|
public function __construct(ExpectedNameResolver $expectedNameResolver)
|
|
|
|
{
|
|
|
|
$this->expectedNameResolver = $expectedNameResolver;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDefinition(): RectorDefinition
|
|
|
|
{
|
|
|
|
return new RectorDefinition('Rename variable to match get method name', [
|
|
|
|
new CodeSample(
|
|
|
|
<<<'PHP'
|
|
|
|
class SomeClass {
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$a = $this->getRunner();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PHP
|
|
|
|
,
|
|
|
|
<<<'PHP'
|
|
|
|
class SomeClass {
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$runner = $this->getRunner();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PHP
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
|
|
|
{
|
|
|
|
return [Assign::class];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Assign $node
|
|
|
|
*/
|
|
|
|
public function refactor(Node $node): ?Node
|
|
|
|
{
|
|
|
|
if (
|
|
|
|
$node->expr instanceof ArrowFunction || ! $node->var instanceof Variable
|
|
|
|
) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$newName = $this->expectedNameResolver->resolveForGetCallExpr($node->expr);
|
|
|
|
if ($newName === null || $this->isName($node, $newName)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
if ($this->shouldSkipForNameConflict($node, $newName)) {
|
2020-07-13 21:13:40 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->renameVariable($node, $newName);
|
|
|
|
|
|
|
|
return $node;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:53:35 +00:00
|
|
|
private function renameVariable(Assign $assign, string $newName): void
|
2020-07-13 21:13:40 +00:00
|
|
|
{
|
2020-07-19 09:53:35 +00:00
|
|
|
$parentNodeStmts = $this->getParentNodeStmts($assign);
|
2020-07-13 21:13:40 +00:00
|
|
|
|
|
|
|
/** @var Variable $variableNode */
|
2020-07-19 09:53:35 +00:00
|
|
|
$variableNode = $assign->var;
|
2020-07-13 21:13:40 +00:00
|
|
|
|
|
|
|
/** @var string $originalName */
|
|
|
|
$originalName = $variableNode->name;
|
|
|
|
|
|
|
|
$this->traverseNodesWithCallable($parentNodeStmts, function (Node $node) use ($originalName, $newName) {
|
2020-07-19 09:53:35 +00:00
|
|
|
if (! $node instanceof Variable) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-07-13 21:13:40 +00:00
|
|
|
if (! $this->isVariableName($node, $originalName)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
$this->renameInDocComment($node, $originalName, $newName);
|
2020-07-13 21:13:40 +00:00
|
|
|
$node->name = $newName;
|
2020-07-19 09:53:35 +00:00
|
|
|
|
|
|
|
return $node;
|
2020-07-13 21:13:40 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Stmt[]
|
|
|
|
*/
|
|
|
|
private function getParentNodeStmts(Node $node): array
|
|
|
|
{
|
|
|
|
/** @var FunctionLike|null $parentNode */
|
2020-07-19 09:53:35 +00:00
|
|
|
$parentNode = $this->findFunctionLikeParent($node);
|
2020-07-13 21:13:40 +00:00
|
|
|
if ($parentNode === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parentNode->getStmts() ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Param[]
|
|
|
|
*/
|
|
|
|
private function getParentNodeParams(Node $node): array
|
|
|
|
{
|
|
|
|
/** @var FunctionLike|null $parentNode */
|
2020-07-19 09:53:35 +00:00
|
|
|
$parentNode = $this->findFunctionLikeParent($node);
|
2020-07-13 21:13:40 +00:00
|
|
|
|
|
|
|
if ($parentNode === null) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parentNode->getParams() ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return ClosureUse[]
|
|
|
|
*/
|
|
|
|
private function getParentNodeUses(Node $node): array
|
|
|
|
{
|
|
|
|
/** @var FunctionLike|null $parentNode */
|
2020-07-19 09:53:35 +00:00
|
|
|
$parentNode = $this->findFunctionLikeParent($node);
|
2020-07-13 21:13:40 +00:00
|
|
|
|
|
|
|
if ($parentNode === null || ! $parentNode instanceof Closure) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parentNode->uses ?? [];
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
private function shouldSkipForNameConflict(Assign $assign, string $newName): bool
|
2020-07-13 21:13:40 +00:00
|
|
|
{
|
2020-07-19 09:41:35 +00:00
|
|
|
if ($this->skipOnConflictParamName($assign, $newName)) {
|
2020-07-13 21:13:40 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
if ($this->skipOnConflictClosureUsesName($assign, $newName)) {
|
2020-07-13 21:13:40 +00:00
|
|
|
return true;
|
|
|
|
}
|
2020-07-19 09:41:35 +00:00
|
|
|
|
|
|
|
return $this->skipOnConflictOtherVariable($assign, $newName);
|
2020-07-13 21:13:40 +00:00
|
|
|
}
|
|
|
|
|
2020-07-19 09:53:35 +00:00
|
|
|
private function findFunctionLikeParent(Node $node): ?FunctionLike
|
2020-07-13 21:13:40 +00:00
|
|
|
{
|
2020-07-19 09:53:35 +00:00
|
|
|
return $node->getAttribute(AttributeKey::METHOD_NODE) ?? $node->getAttribute(AttributeKey::FUNCTION_NODE);
|
2020-07-13 21:13:40 +00:00
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
private function skipOnConflictParamName(Assign $assign, string $newName): bool
|
2020-07-13 21:13:40 +00:00
|
|
|
{
|
2020-07-19 09:41:35 +00:00
|
|
|
$parentNodeParams = $this->getParentNodeParams($assign);
|
2020-07-13 21:13:40 +00:00
|
|
|
if ($parentNodeParams === []) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
$originalName = $this->getName($assign->var);
|
2020-07-13 21:13:40 +00:00
|
|
|
|
|
|
|
$skip = false;
|
|
|
|
$this->traverseNodesWithCallable($parentNodeParams, function (Node $node) use (
|
|
|
|
$originalName,
|
|
|
|
$newName,
|
|
|
|
&$skip
|
|
|
|
): void {
|
|
|
|
if (
|
|
|
|
$node instanceof Param &&
|
|
|
|
($this->isVariableName($node->var, $newName) || $this->isVariableName($node->var, $originalName))
|
|
|
|
) {
|
|
|
|
$skip = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return $skip;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
private function skipOnConflictClosureUsesName(Assign $assign, string $newName): bool
|
2020-07-13 21:13:40 +00:00
|
|
|
{
|
|
|
|
$skip = false;
|
2020-07-19 09:41:35 +00:00
|
|
|
|
|
|
|
$parentNodeUses = $this->getParentNodeUses($assign);
|
|
|
|
$this->traverseNodesWithCallable($parentNodeUses, function (Node $node) use ($newName, &$skip) {
|
2020-07-13 21:13:40 +00:00
|
|
|
if ($this->isVariableName($node, $newName)) {
|
|
|
|
$skip = true;
|
2020-07-19 09:41:35 +00:00
|
|
|
return NodeTraverser::STOP_TRAVERSAL;
|
2020-07-13 21:13:40 +00:00
|
|
|
}
|
2020-07-19 09:41:35 +00:00
|
|
|
|
|
|
|
return null;
|
2020-07-13 21:13:40 +00:00
|
|
|
});
|
2020-07-19 09:41:35 +00:00
|
|
|
|
2020-07-13 21:13:40 +00:00
|
|
|
return $skip;
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:41:35 +00:00
|
|
|
private function skipOnConflictOtherVariable(Assign $assign, string $newName): bool
|
2020-07-13 21:13:40 +00:00
|
|
|
{
|
|
|
|
$skip = false;
|
2020-07-19 09:41:35 +00:00
|
|
|
$parentNodeStmts = $this->getParentNodeStmts($assign);
|
|
|
|
|
|
|
|
$this->traverseNodesWithCallable($parentNodeStmts, function (Node $node) use ($newName, &$skip): ?Node {
|
|
|
|
if (! $node instanceof Variable) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-07-13 21:13:40 +00:00
|
|
|
if ($this->isVariableName($node, $newName)) {
|
|
|
|
$skip = true;
|
|
|
|
}
|
2020-07-19 09:41:35 +00:00
|
|
|
|
|
|
|
return null;
|
2020-07-13 21:13:40 +00:00
|
|
|
});
|
2020-07-19 09:41:35 +00:00
|
|
|
|
2020-07-13 21:13:40 +00:00
|
|
|
return $skip;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function renameInDocComment(Variable $variable, string $originalName, string $newName): void
|
|
|
|
{
|
2020-07-19 09:53:35 +00:00
|
|
|
/** @var PhpDocInfo|null $phpDocInfo */
|
|
|
|
$phpDocInfo = $variable->getAttribute(AttributeKey::PHP_DOC_INFO);
|
|
|
|
if ($phpDocInfo === null) {
|
2020-07-19 09:41:35 +00:00
|
|
|
return;
|
2020-07-13 21:13:40 +00:00
|
|
|
}
|
2020-07-19 09:41:35 +00:00
|
|
|
|
2020-07-19 09:53:35 +00:00
|
|
|
$varTagValueNode = $phpDocInfo->getByType(VarTagValueNode::class);
|
|
|
|
if ($varTagValueNode === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($varTagValueNode->variableName !== '$' . $originalName) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$varTagValueNode->variableName = '$' . $newName;
|
|
|
|
|
|
|
|
// invoke doc print
|
|
|
|
$variable->setAttribute(AttributeKey::ORIGINAL_NODE, null);
|
2020-07-13 21:13:40 +00:00
|
|
|
}
|
|
|
|
}
|