mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-18 22:08:00 +01:00
[PHP] Add ArrayKeyFirstLastRector
This commit is contained in:
parent
bc658bee39
commit
2b2049c923
@ -1,2 +1,3 @@
|
||||
services:
|
||||
Rector\Php\Rector\BinaryOp\IsCountableRector: ~
|
||||
Rector\Php\Rector\FuncCall\ArrayKeyFirstLastRector: ~
|
||||
|
162
packages/Php/src/Rector/FuncCall/ArrayKeyFirstLastRector.php
Normal file
162
packages/Php/src/Rector/FuncCall/ArrayKeyFirstLastRector.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Rector\FuncCall;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use Rector\Printer\BetterStandardPrinter;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see https://www.tomasvotruba.cz/blog/2018/08/16/whats-new-in-php-73-in-30-seconds-in-diffs/#2-first-and-last-array-key
|
||||
*
|
||||
* This needs to removed 1 floor above, because only nodes in arrays can be removed why traversing,
|
||||
* see https://github.com/nikic/PHP-Parser/issues/389
|
||||
*/
|
||||
final class ArrayKeyFirstLastRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var BetterStandardPrinter
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $previousToNewFunctions = [
|
||||
'reset' => 'array_key_first',
|
||||
'end' => 'array_key_last',
|
||||
];
|
||||
|
||||
public function __construct(BetterStandardPrinter $betterStandardPrinter)
|
||||
{
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition(
|
||||
'Make use of array_key_first() and array_key_last()',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
reset($items);
|
||||
$firstKey = key($items);
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
$firstKey = array_key_first($items);
|
||||
CODE_SAMPLE
|
||||
),
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
end($items);
|
||||
$lastKey = key($items);
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
$lastKey = array_key_last($items);
|
||||
CODE_SAMPLE
|
||||
),
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Function_::class, ClassMethod::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Function_|ClassMethod $functionLikeNode
|
||||
*/
|
||||
public function refactor(Node $functionLikeNode): ?Node
|
||||
{
|
||||
if ($functionLikeNode->stmts === null) {
|
||||
return $functionLikeNode;
|
||||
}
|
||||
|
||||
foreach ($functionLikeNode->stmts as $key => $stmt) {
|
||||
/** @var Expression $stmt */
|
||||
if (! $this->isFuncCallMatch($stmt->expr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! isset($functionLikeNode->stmts[$key + 1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->isAssignMatch($functionLikeNode->stmts[$key + 1]->expr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$funcCallNode = $stmt->expr;
|
||||
/** @var FuncCall $funcCallNode */
|
||||
$currentFuncCallName = (string) $funcCallNode->name;
|
||||
|
||||
/** @var Assign $assignNode */
|
||||
$assignNode = $functionLikeNode->stmts[$key + 1]->expr;
|
||||
|
||||
/** @var FuncCall $assignFuncCallNode */
|
||||
$assignFuncCallNode = $assignNode->expr;
|
||||
|
||||
if (! $this->areFuncCallNodesArgsEqual($funcCallNode, $assignFuncCallNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// rename next method to new one
|
||||
$assignNode->expr->name = new Name($this->previousToNewFunctions[$currentFuncCallName]);
|
||||
|
||||
// remove unused node
|
||||
unset($functionLikeNode->stmts[$key]);
|
||||
}
|
||||
|
||||
// reindex for printer
|
||||
$functionLikeNode->stmts = array_values($functionLikeNode->stmts);
|
||||
|
||||
return $functionLikeNode;
|
||||
}
|
||||
|
||||
private function isFuncCallMatch(Node $node): bool
|
||||
{
|
||||
if (! $node instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array((string) $node->name, array_keys($this->previousToNewFunctions), true);
|
||||
}
|
||||
|
||||
private function isAssignMatch(Node $node): bool
|
||||
{
|
||||
if (! $node instanceof Assign) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $node->expr instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (string) $node->expr->name === 'key';
|
||||
}
|
||||
|
||||
private function areFuncCallNodesArgsEqual(FuncCall $firstFuncCallNode, FuncCall $secondFuncCallNode): bool
|
||||
{
|
||||
if (! isset($firstFuncCallNode->args[0]) || ! isset($secondFuncCallNode->args[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->betterStandardPrinter->prettyPrint([$firstFuncCallNode->args[0]])
|
||||
=== $this->betterStandardPrinter->prettyPrint([$secondFuncCallNode->args[0]]);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\ArrayKeyFirstLastRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Rector\Php\Rector\FuncCall\ArrayKeyFirstLastRector
|
||||
*/
|
||||
final class ArrayKeyFirstLastRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideWrongToFixedFiles()
|
||||
*/
|
||||
public function test(string $wrong, string $fixed): void
|
||||
{
|
||||
$this->doTestFileMatchesExpectedContent($wrong, $fixed);
|
||||
}
|
||||
|
||||
public function provideWrongToFixedFiles(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc'];
|
||||
yield [__DIR__ . '/Wrong/wrong2.php.inc', __DIR__ . '/Correct/correct2.php.inc'];
|
||||
}
|
||||
|
||||
protected function provideConfig(): string
|
||||
{
|
||||
return __DIR__ . '/config.yml';
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
function process() {
|
||||
$items = [1, 2, 3];
|
||||
$firstKey = array_key_first($items);
|
||||
|
||||
reset($items);
|
||||
$firstKey = key($differntItems);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
function someFunction()
|
||||
{
|
||||
$item = [1, 2, 3];
|
||||
$lastKey = array_key_last($items);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
function process() {
|
||||
$items = [1, 2, 3];
|
||||
reset($items);
|
||||
$firstKey = key($items);
|
||||
|
||||
reset($items);
|
||||
$firstKey = key($differntItems);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
function someFunction()
|
||||
{
|
||||
$item = [1, 2, 3];
|
||||
end($items);
|
||||
$lastKey = key($items);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
services:
|
||||
Rector\Php\Rector\FuncCall\ArrayKeyFirstLastRector: ~
|
@ -86,6 +86,8 @@ parameters:
|
||||
# buggy
|
||||
- '#Access to an undefined property PhpParser\\Node\\Expr::\$value#' # 2
|
||||
- '#Access to an undefined property PhpParser\\Node\\Expr::\$(name|var)#' # 2
|
||||
- '#Access to an undefined property PhpParser\\Node\\Stmt::\$expr#'
|
||||
- '#Binary operation "\+" between int\|string and 1 results in an error#'
|
||||
|
||||
# variadic false positive
|
||||
- '#In method "Rector\\Node\\NodeFactory::createArray", parameter \$items can be type-hinted to "array"#'
|
||||
|
Loading…
x
Reference in New Issue
Block a user