1
0
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:
Tomas Votruba 2018-10-16 14:38:03 +08:00
parent f76048c961
commit e21ebbbaf2
9 changed files with 277 additions and 0 deletions

@ -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);
}
}

@ -0,0 +1,9 @@
<?php
return $this->oldToNewFunctions[$currentFunction] ?? 45;
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
if ($currentFunction === $oldFunction) {
return 15;
}
}
return 0;

@ -0,0 +1,3 @@
<?php
return $this->oldToNewFunctions[$currentFunction] ?? 45;

@ -0,0 +1,6 @@
<?php
$newValue = null;
$values = [];
$newValue = $values[$value] ?? $newValue;

@ -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';
}
}

@ -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;

@ -0,0 +1,9 @@
<?php
foreach ($this->oldToNewFunctions as $oldFunction => $newFunction) {
if ($oldFunction === $currentFunction) {
return $newFunction;
}
}
return 45;

@ -0,0 +1,10 @@
<?php
$newValue = null;
$values = [];
foreach ($values as $key => $value) {
if ($key === $input) {
$newValue = $value;
}
}

@ -0,0 +1,2 @@
services:
Rector\CodeQuality\Rector\Foreach_\SimplifyForeachToCoalescingRector: ~