mirror of
https://github.com/rectorphp/rector.git
synced 2025-03-20 07:19:47 +01:00
[CodeQuality] Add SimplifyForeachToCoalescingRector
This commit is contained in:
parent
f76048c961
commit
e21ebbbaf2
packages/CodeQuality
src/Rector/Foreach_
tests/Rector/Foreach_/SimplifyForeachToCoalescingRector
@ -0,0 +1,190 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodeQuality\Rector\Foreach_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\BinaryOp\Coalesce;
|
||||
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Foreach_;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use Rector\NodeTypeResolver\Node\Attribute;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
final class SimplifyForeachToCoalescingRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var Return_|null
|
||||
*/
|
||||
private $returnToBeRemoved;
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Changes foreach that returns set value to ??', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
|
||||
if ($currentFunction === $oldFunction) {
|
||||
return $newFunction;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
return $this->oldToNewFunctions[$currentFunction] ?? null;
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Foreach_::class, Return_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Foreach_|Return_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node instanceof Return_) {
|
||||
if ($node === $this->returnToBeRemoved) {
|
||||
$this->returnToBeRemoved = null;
|
||||
$this->removeNode = true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->shouldSkip($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->processForeachNode($node);
|
||||
}
|
||||
|
||||
private function shouldSkip(Foreach_ $foreachNode): bool
|
||||
{
|
||||
if (! $foreachNode->keyVar) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($foreachNode->stmts) !== 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$insideForeachStmt = $foreachNode->stmts[0];
|
||||
if (! $insideForeachStmt instanceof If_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $insideForeachStmt->cond instanceof Identical) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($insideForeachStmt->stmts) !== 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function processForeachNode(Foreach_ $node): ?Node
|
||||
{
|
||||
/** @var If_ $insideForeachStmt */
|
||||
$insideForeachStmt = $node->stmts[0];
|
||||
|
||||
$returnOrAssignNode = $insideForeachStmt->stmts[0];
|
||||
|
||||
/** @var Return_|Assign|null $insideReturnOrAssignNode */
|
||||
$insideReturnOrAssignNode = $returnOrAssignNode instanceof Expression ? $returnOrAssignNode->expr : $returnOrAssignNode;
|
||||
if ($insideReturnOrAssignNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// return $newValue;
|
||||
// we don't return the node value
|
||||
if (! $this->areNodesEqual($node->valueVar, $insideReturnOrAssignNode->expr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($insideReturnOrAssignNode instanceof Return_) {
|
||||
return $this->processForeachNodeWithReturnInside($node, $insideReturnOrAssignNode);
|
||||
}
|
||||
|
||||
if ($insideReturnOrAssignNode instanceof Assign) {
|
||||
return $this->processForeachNodeWithAssignInside($node, $insideReturnOrAssignNode);
|
||||
}
|
||||
}
|
||||
|
||||
private function processForeachNodeWithReturnInside(Foreach_ $foreachNode, Return_ $returnNode): ?Node
|
||||
{
|
||||
if (! $this->areNodesEqual($foreachNode->valueVar, $returnNode->expr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var If_ $ifNode */
|
||||
$ifNode = $foreachNode->stmts[0];
|
||||
|
||||
/** @var Identical $identicalNode */
|
||||
$identicalNode = $ifNode->cond;
|
||||
|
||||
if ($this->areNodesEqual($identicalNode->left, $foreachNode->keyVar)) {
|
||||
$checkedNode = $identicalNode->right;
|
||||
} elseif ($this->areNodesEqual($identicalNode->right, $foreachNode->keyVar)) {
|
||||
$checkedNode = $identicalNode->left;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// is next node Return?
|
||||
if ($foreachNode->getAttribute(Attribute::NEXT_NODE) instanceof Return_) {
|
||||
$this->returnToBeRemoved = $foreachNode->getAttribute(Attribute::NEXT_NODE);
|
||||
}
|
||||
|
||||
$coalesceNode = new Coalesce(new ArrayDimFetch(
|
||||
$foreachNode->expr,
|
||||
$checkedNode
|
||||
), $this->returnToBeRemoved ? $this->returnToBeRemoved->expr : $checkedNode);
|
||||
|
||||
if ($this->returnToBeRemoved) {
|
||||
return new Return_($coalesceNode);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function processForeachNodeWithAssignInside(Foreach_ $foreachNode, Assign $assignNode): ?Node
|
||||
{
|
||||
/** @var If_ $ifNode */
|
||||
$ifNode = $foreachNode->stmts[0];
|
||||
|
||||
/** @var Identical $identicalNode */
|
||||
$identicalNode = $ifNode->cond;
|
||||
|
||||
if ($this->areNodesEqual($identicalNode->left, $foreachNode->keyVar)) {
|
||||
$checkedNode = $assignNode->var;
|
||||
} elseif ($this->areNodesEqual($identicalNode->right, $foreachNode->keyVar)) {
|
||||
$checkedNode = $assignNode->var;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assignNode = new Assign($checkedNode, new Coalesce(new ArrayDimFetch(
|
||||
$foreachNode->expr,
|
||||
$foreachNode->valueVar
|
||||
), $checkedNode));
|
||||
|
||||
return new Expression($assignNode);
|
||||
}
|
||||
}
|
9
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Correct/correct.php.inc
Normal file
9
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Correct/correct.php.inc
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return $this->oldToNewFunctions[$currentFunction] ?? 45;
|
||||
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
|
||||
if ($currentFunction === $oldFunction) {
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
return 0;
|
3
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Correct/correct2.php.inc
Normal file
3
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Correct/correct2.php.inc
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
return $this->oldToNewFunctions[$currentFunction] ?? 45;
|
6
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Correct/correct3.php.inc
Normal file
6
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Correct/correct3.php.inc
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
$newValue = null;
|
||||
$values = [];
|
||||
|
||||
$newValue = $values[$value] ?? $newValue;
|
32
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/SimplifyForeachToCoalescingRectorTest.php
Normal file
32
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/SimplifyForeachToCoalescingRectorTest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Foreach_\SimplifyForeachToCoalescingRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Rector\CodeQuality\Rector\Foreach_\SimplifyForeachToCoalescingRector
|
||||
*/
|
||||
final class SimplifyForeachToCoalescingRectorTest 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'];
|
||||
yield [__DIR__ . '/Wrong/wrong3.php.inc', __DIR__ . '/Correct/correct3.php.inc'];
|
||||
}
|
||||
|
||||
protected function provideConfig(): string
|
||||
{
|
||||
return __DIR__ . '/config.yml';
|
||||
}
|
||||
}
|
16
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Wrong/wrong.php.inc
Normal file
16
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Wrong/wrong.php.inc
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
|
||||
if ($currentFunction === $oldFunction) {
|
||||
return $newFunction;
|
||||
}
|
||||
}
|
||||
|
||||
return 45;
|
||||
|
||||
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
|
||||
if ($currentFunction === $oldFunction) {
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
return 0;
|
9
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Wrong/wrong2.php.inc
Normal file
9
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Wrong/wrong2.php.inc
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
|
||||
if ($oldFunction === $currentFunction) {
|
||||
return $newFunction;
|
||||
}
|
||||
}
|
||||
|
||||
return 45;
|
10
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Wrong/wrong3.php.inc
Normal file
10
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/Wrong/wrong3.php.inc
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
$newValue = null;
|
||||
$values = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if ($key === $input) {
|
||||
$newValue = $value;
|
||||
}
|
||||
}
|
2
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/config.yml
Normal file
2
packages/CodeQuality/tests/Rector/Foreach_/SimplifyForeachToCoalescingRector/config.yml
Normal file
@ -0,0 +1,2 @@
|
||||
services:
|
||||
Rector\CodeQuality\Rector\Foreach_\SimplifyForeachToCoalescingRector: ~
|
Loading…
x
Reference in New Issue
Block a user