[Naming] Name variable after get method

This commit is contained in:
dobryy 2020-07-13 23:13:40 +02:00
parent 959d8c7404
commit 3692798de6
18 changed files with 643 additions and 6 deletions

View File

@ -0,0 +1,2 @@
services:
Rector\Naming\Rector\Assign\RenameVariableToMatchGetMethodNameRector: null

View File

@ -1,4 +1,4 @@
# All 525 Rectors Overview
# All 526 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -29,7 +29,7 @@
- [MockeryToProphecy](#mockerytoprophecy) (2)
- [MockistaToMockery](#mockistatomockery) (2)
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (2)
- [Naming](#naming) (3)
- [Nette](#nette) (12)
- [NetteCodeQuality](#nettecodequality) (1)
- [NetteKdyby](#nettekdyby) (4)
@ -2226,11 +2226,11 @@ Prefer quote that are not inside the string
Assign outcome of ternary condition to variable, where applicable
```diff
function ternary($value)
{
function ternary($value)
{
- $value ? $a = 1 : $a = 0;
+ $a = $value ? 1 : 0;
}
}
```
<br><br>
@ -4979,6 +4979,25 @@ Rename property and method param to match its type
<br><br>
### `RenameVariableToMatchGetMethodNameRector`
- class: [`Rector\Naming\Rector\Assign\RenameVariableToMatchGetMethodNameRector`](/../master/rules/naming/src/Rector/Assign/RenameVariableToMatchGetMethodNameRector.php)
- [test fixtures](/../master/rules/naming/tests/Rector/Assign/RenameVariableToMatchGetMethodNameRector/Fixture)
Rename variable to match get method name
```diff
class SomeClass {
public function run()
{
- $a = $this->getRunner();
+ $runner = $this->getRunner();
}
}
```
<br><br>
### `RenameVariableToMatchNewTypeRector`
- class: [`Rector\Naming\Rector\ClassMethod\RenameVariableToMatchNewTypeRector`](/../master/rules/naming/src/Rector/ClassMethod/RenameVariableToMatchNewTypeRector.php)

View File

@ -5,13 +5,19 @@ declare(strict_types=1);
namespace Rector\Naming\Naming;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Property;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
@ -34,14 +40,21 @@ final class ExpectedNameResolver
*/
private $staticTypeMapper;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(
NodeNameResolver $nodeNameResolver,
PropertyNaming $propertyNaming,
StaticTypeMapper $staticTypeMapper
StaticTypeMapper $staticTypeMapper,
BetterNodeFinder $betterNodeFinder
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->propertyNaming = $propertyNaming;
$this->staticTypeMapper = $staticTypeMapper;
$this->betterNodeFinder = $betterNodeFinder;
}
public function resolveForPropertyIfNotYet(Property $property): ?string
@ -148,6 +161,32 @@ final class ExpectedNameResolver
return $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
}
public function resolveForGetCallExpr(Expr $expr): ?string
{
/** @var MethodCall|StaticCall|FuncCall|null $callNode */
$callNode = $this->betterNodeFinder->findFirst($expr, function (Node $node) {
return $node instanceof MethodCall || $node instanceof StaticCall || $node instanceof FuncCall;
});
if ($callNode === null) {
return null;
}
$name = $this->nodeNameResolver->getName($callNode->name);
if ($name === null) {
return null;
}
// @see https://regex101.com/r/hnU5pm/2/
$matches = Strings::match($name, '#^get(([A-Z]).+)#');
if ($matches === null) {
return null;
}
return lcfirst($matches[1]);
}
/**
* Ends with ucname
* Starts with adjective, e.g. (Post $firstPost, Post $secondPost)

View File

@ -0,0 +1,239 @@
<?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;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Naming\Naming\ExpectedNameResolver;
/**
* @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;
}
$skip = $this->skipOnConflict($node, $newName);
if ($skip) {
return null;
}
$this->renameVariable($node, $newName);
return $node;
}
private function renameVariable(Node $node, string $newName): void
{
$parentNodeStmts = $this->getParentNodeStmts($node);
/** @var Variable $variableNode */
$variableNode = $node->var;
/** @var string $originalName */
$originalName = $variableNode->name;
$this->traverseNodesWithCallable($parentNodeStmts, function (Node $node) use ($originalName, $newName) {
/** @var Variable $node */
if (! $this->isVariableName($node, $originalName)) {
return null;
}
$this->renameInDocComment($node, $originalName, $newName);
$node->name = $newName;
});
}
/**
* @return Stmt[]
*/
private function getParentNodeStmts(Node $node): array
{
/** @var FunctionLike|null $parentNode */
$parentNode = $this->findFirstFunctionLikeParent($node);
if ($parentNode === null) {
return [];
}
return $parentNode->getStmts() ?? [];
}
/**
* @return Param[]
*/
private function getParentNodeParams(Node $node): array
{
/** @var FunctionLike|null $parentNode */
$parentNode = $this->findFirstFunctionLikeParent($node);
if ($parentNode === null) {
return [];
}
return $parentNode->getParams() ?? [];
}
/**
* @return ClosureUse[]
*/
private function getParentNodeUses(Node $node): array
{
/** @var FunctionLike|null $parentNode */
$parentNode = $this->findFirstFunctionLikeParent($node);
if ($parentNode === null || ! $parentNode instanceof Closure) {
return [];
}
return $parentNode->uses ?? [];
}
private function skipOnConflict(Node $node, string $newName): bool
{
if ($this->skipOnConflictParamName($node, $newName)) {
return true;
}
if ($this->skipOnConflictClosureUsesName($node, $newName)) {
return true;
}
return $this->skipOnConflictOtherVariable($node, $newName);
}
private function findFirstFunctionLikeParent(Node $node): ?FunctionLike
{
return $this->betterNodeFinder->findFirstParentInstanceOf($node, FunctionLike::class);
}
private function skipOnConflictParamName(Node $node, string $newName): bool
{
$parentNodeParams = $this->getParentNodeParams($node);
if ($parentNodeParams === []) {
return false;
}
$originalName = $this->getName($node->var);
$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;
}
private function skipOnConflictClosureUsesName(Node $node, string $newName): bool
{
$skip = false;
$parentNodeUses = $this->getParentNodeUses($node);
$this->traverseNodesWithCallable($parentNodeUses, function (Node $node) use ($newName, &$skip): void {
if ($this->isVariableName($node, $newName)) {
$skip = true;
}
});
return $skip;
}
private function skipOnConflictOtherVariable(Node $node, string $newName): bool
{
$skip = false;
$parentNodeStmts = $this->getParentNodeStmts($node);
$this->traverseNodesWithCallable($parentNodeStmts, function (Node $node) use ($newName, &$skip): void {
/** @var Variable $node */
if ($this->isVariableName($node, $newName)) {
$skip = true;
}
});
return $skip;
}
private function renameInDocComment(Variable $variable, string $originalName, string $newName): void
{
/** @var Doc $docComment */
$docComment = $variable->getDocComment();
if ($docComment !== null) {
$newText = str_replace('$' . $originalName, '$' . $newName, $docComment->getText());
$newDocComment = new Doc($newText);
$variable->setDocComment($newDocComment);
}
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class ClosureClass
{
public function run()
{
$datetime = new DateTime();
array_map(function ($element) use ($datetime) {
$a = $datetime->getTimestamp() + $element;
return $a;
}, [1, 2, 3]);
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class ClosureClass
{
public function run()
{
$datetime = new DateTime();
array_map(function ($element) use ($datetime) {
$timestamp = $datetime->getTimestamp() + $element;
return $timestamp;
}, [1, 2, 3]);
}
}
?>

View File

@ -0,0 +1,29 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SomeClass
{
public function run()
{
$a = $this->getFastRunner();
$a->exit();
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SomeClass
{
public function run()
{
$fastRunner = $this->getFastRunner();
$fastRunner->exit();
}
}
?>

View File

@ -0,0 +1,23 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
function run()
{
$a = getFastRunner();
$a->exit();
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
function run()
{
$fastRunner = getFastRunner();
$fastRunner->exit();
}
?>

View File

@ -0,0 +1,29 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class LastInChainClass
{
public function run()
{
$a = $this->getRunners()->getFast();
$a->exit();
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class LastInChainClass
{
public function run()
{
$fast = $this->getRunners()->getFast();
$fast->exit();
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class RanameInDocblockClass
{
public function run()
{
/** @var FastRunner $a */
$a = $this->getFastRunner();
$a->exit();
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class RanameInDocblockClass
{
public function run()
{
/** @var FastRunner $fastRunner */
$fastRunner = $this->getFastRunner();
$fastRunner->exit();
}
}
?>

View File

@ -0,0 +1,15 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class ArrayDimFetchClass
{
public function run()
{
$a[] = 1;
$a[] = $this->getFastRunner();
$a[1] = 'string';
}
}
?>

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SkipNoGetClass
{
public function run()
{
$a = $this->fastRunner();
}
}
?>

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SkipOnConflictClass
{
public function run()
{
$a = $this->getFastRunner();
$a->exit();
$fastRunner = 2;
}
}
?>

View File

@ -0,0 +1,18 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SkipOnConflictWithClosureUses
{
public function run()
{
$timestamp = time();
array_map(function ($element) use ($timestamp) {
$now = new DateTime();
$time = $now->getTimestamp();
return $time;
}, [1, 2, 3]);
}
}
?>

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SkipOnConflictWithParamNameClass
{
public function run($fastRunner)
{
if ($a instanceof SlowRunner) {
$a = $this->getFastRunner();
$a->exit();
}
}
}
?>

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class SkipVariablesFromParams
{
public function run($runner)
{
if ($runner instanceof SlowRunner) {
$runner = $this->getFastRunner();
$runner->exit();
}
}
}
?>

View File

@ -0,0 +1,29 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class StaticCallClass
{
public function run()
{
$a = static::getFastRunner();
$a->exit();
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
class StaticCallClass
{
public function run()
{
$fastRunner = static::getFastRunner();
$fastRunner->exit();
}
}
?>

View File

@ -0,0 +1,23 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
function testArrowFunction() {
$datetime = new DateTime();
$fn1 = fn($x) => $x + $stamp = $datetime->getTimestamp();
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
function testArrowFunction() {
$datetime = new DateTime();
$fn1 = fn($x) => $x + $timestamp = $datetime->getTimestamp();
}
?>

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Naming\Rector\Assign\RenameVariableToMatchGetMethodNameRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class RenameVariableToMatchGetMethodNameRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @requires PHP >= 7.4
* @dataProvider provideDataPhp74()
*/
public function testPhp74(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideDataPhp74(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp74');
}
protected function getRectorClass(): string
{
return RenameVariableToMatchGetMethodNameRector::class;
}
}