[CodeQuality] Add InlineArrayReturnAssignRector (#2183)

* [CodeQuality] Add InlineArrayReturnAssignRector

* enable rule in code-quality

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2022-04-28 16:46:43 +02:00 committed by GitHub
parent aa89fde52b
commit 08bb10de6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 435 additions and 2 deletions

View File

@ -1,4 +1,4 @@
# 512 Rules Overview
# 513 Rules Overview
<br>
@ -6,7 +6,7 @@
- [Arguments](#arguments) (5)
- [CodeQuality](#codequality) (70)
- [CodeQuality](#codequality) (71)
- [CodingStyle](#codingstyle) (35)
@ -852,6 +852,29 @@ Changes comparison with get_class to instanceof
<br>
### InlineArrayReturnAssignRector
Inline just in time array dim fetch assigns to direct return
- class: [`Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector`](../rules/CodeQuality/Rector/ClassMethod/InlineArrayReturnAssignRector.php)
```diff
function getPerson()
{
- $person = [];
- $person['name'] = 'Timmy';
- $person['surname'] = 'Back';
-
- return $person;
+ return [
+ 'name' => 'Timmy',
+ 'surname' => 'Back',
+ ];
}
```
<br>
### InlineConstructorDefaultToPropertyRector
Move property default from constructor to property default

View File

@ -13,6 +13,7 @@ use Rector\CodeQuality\Rector\BooleanNot\SimplifyDeMorganBinaryRector;
use Rector\CodeQuality\Rector\Catch_\ThrowWithPreviousExceptionRector;
use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector;
use Rector\CodeQuality\Rector\ClassMethod\DateTimeToDateTimeInterfaceRector;
use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector;
use Rector\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector;
use Rector\CodeQuality\Rector\Concat\JoinStringConcatRector;
use Rector\CodeQuality\Rector\Do_\DoWhileBreakFalseToIfElseRector;
@ -179,4 +180,5 @@ return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(FlipTypeControlToUseExclusiveTypeRector::class);
$rectorConfig->rule(ExplicitMethodCallOverMagicGetSetRector::class);
$rectorConfig->rule(DoWhileBreakFalseToIfElseRector::class);
$rectorConfig->rule(InlineArrayReturnAssignRector::class);
};

View File

@ -599,3 +599,4 @@ parameters:
- '#Cognitive complexity for "Rector\\TypeDeclaration\\PHPStan\\ObjectTypeSpecifier\:\:matchShortenedObjectType\(\)" is \d+, keep it under 10#'
- '#Cognitive complexity for "Rector\\TypeDeclaration\\PHPStan\\ObjectTypeSpecifier\:\:narrowToFullyQualifiedOrAliasedObjectType\(\)" is \d+, keep it under 10#'
- '#Class "Rector\\CodeQuality\\Rector\\ClassMethod\\InlineArrayReturnAssignRector" has invalid namespace category "ClassMethod"\. Pick one of\: "NodeTypeGroup"#'

View File

@ -0,0 +1,41 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector\Fixture;
final class IncludeIf
{
public function run()
{
if (mt_rand(0, 1)) {
$person = [];
$person['name'] = 'Timmy';
$person['surname'] = 'Back';
return $person;
}
return null;
}
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector\Fixture;
final class IncludeIf
{
public function run()
{
if (mt_rand(0, 1)) {
return ['name' => 'Timmy', 'surname' => 'Back'];
}
return null;
}
}
?>

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector\Fixture;
final class SkipVariable
{
public function run($person2)
{
$person = [];
$person2['name'] = 'Timmy';
$person['surname'] = 'Back';
return $person;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector\Fixture;
function getPerson()
{
$person = [];
$person['name'] = 'Timmy';
$person['surname'] = 'Back';
return $person;
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector\Fixture;
function getPerson()
{
return ['name' => 'Timmy', 'surname' => 'Back'];
}
?>

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class InlineArrayReturnAssignRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(InlineArrayReturnAssignRector::class);
};

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\NodeAnalyzer;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use Rector\CodeQuality\ValueObject\KeyAndExpr;
use Rector\Core\PhpParser\Comparing\NodeComparator;
final class VariableDimFetchAssignResolver
{
public function __construct(
private readonly NodeComparator $nodeComparator
) {
}
/**
* @param Stmt[] $stmts
* @return KeyAndExpr[]
*/
public function resolveFromStmtsAndVariable(array $stmts, Variable $variable): array
{
$keysAndExprs = [];
foreach ($stmts as $stmt) {
if (! $stmt instanceof Expression) {
return [];
}
$stmtExpr = $stmt->expr;
if (! $stmtExpr instanceof Assign) {
return [];
}
$assign = $stmtExpr;
$keyExpr = $this->matchKeyOnArrayDimFetchOfVariable($assign, $variable);
if (! $keyExpr instanceof Expr) {
return [];
}
$keysAndExprs[] = new KeyAndExpr($keyExpr, $assign->expr);
}
return $keysAndExprs;
}
public function matchKeyOnArrayDimFetchOfVariable(Assign $assign, Variable $variable): ?Expr
{
if (! $assign->var instanceof ArrayDimFetch) {
return null;
}
$arrayDimFetch = $assign->var;
if (! $this->nodeComparator->areNodesEqual($arrayDimFetch->var, $variable)) {
return null;
}
return $arrayDimFetch->dim;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\While_;
final class NodeTypeGroup
{
/**
* These nodes have a public $stmts property that can be iterated - based on https://github.com/rectorphp/php-parser-nodes-docs
* @todo create a virtual node, getStmts(): array
* @var array<class-string<Node>>
*/
public const STMTS_AWARE = [
ClassMethod::class, Function_::class, If_::class, Else_::class, ElseIf_::class, Do_::class, Foreach_::class, TryCatch::class, While_::class,
];
}

View File

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use Rector\CodeQuality\NodeAnalyzer\VariableDimFetchAssignResolver;
use Rector\CodeQuality\NodeTypeGroup;
use Rector\CodeQuality\ValueObject\KeyAndExpr;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector\InlineArrayReturnAssignRectorTest
*/
final class InlineArrayReturnAssignRector extends AbstractRector
{
public function __construct(
private readonly VariableDimFetchAssignResolver $variableDimFetchAssignResolver
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Inline just in time array dim fetch assigns to direct return', [
new CodeSample(
<<<'CODE_SAMPLE'
function getPerson()
{
$person = [];
$person['name'] = 'Timmy';
$person['surname'] = 'Back';
return $person;
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
function getPerson()
{
return [
'name' => 'Timmy',
'surname' => 'Back',
];
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return NodeTypeGroup::STMTS_AWARE;
}
/**
* @param ClassMethod|Function_ $node
*/
public function refactor(Node $node): ?Node
{
/** @var Stmt[]|null $stmts */
$stmts = $node->stmts;
if ($stmts === null) {
return null;
}
if (count($stmts) < 3) {
return null;
}
$firstStmt = array_shift($stmts);
$variable = $this->matchVariableAssignOfEmptyArray($firstStmt);
if (! $variable instanceof Variable) {
return null;
}
$lastStmt = array_pop($stmts);
if (! $lastStmt instanceof Stmt) {
return null;
}
if (! $this->isReturnOfVariable($lastStmt, $variable)) {
return null;
}
$keysAndExprs = $this->variableDimFetchAssignResolver->resolveFromStmtsAndVariable($stmts, $variable);
if ($keysAndExprs === []) {
return null;
}
$array = $this->createArray($keysAndExprs);
$node->stmts = [new Return_($array)];
return $node;
}
private function matchVariableAssignOfEmptyArray(Stmt $stmt): ?Variable
{
if (! $stmt instanceof Expression) {
return null;
}
if (! $stmt->expr instanceof Assign) {
return null;
}
$assign = $stmt->expr;
if (! $this->valueResolver->isValue($assign->expr, [])) {
return null;
}
if (! $assign->var instanceof Variable) {
return null;
}
return $assign->var;
}
private function isReturnOfVariable(Stmt $stmt, Variable $variable): bool
{
if (! $stmt instanceof Return_) {
return false;
}
if (! $stmt->expr instanceof Variable) {
return false;
}
return $this->nodeComparator->areNodesEqual($stmt->expr, $variable);
}
/**
* @param KeyAndExpr[] $keysAndExprs
*/
private function createArray(array $keysAndExprs): Array_
{
$arrayItems = [];
foreach ($keysAndExprs as $keyAndExpr) {
$arrayItems[] = new ArrayItem($keyAndExpr->getExpr(), $keyAndExpr->getKeyExpr());
}
return new Array_($arrayItems);
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\ValueObject;
use PhpParser\Node\Expr;
final class KeyAndExpr
{
public function __construct(
private readonly Expr $keyExpr,
private readonly Expr $expr
) {
}
public function getKeyExpr(): Expr
{
return $this->keyExpr;
}
public function getExpr(): Expr
{
return $this->expr;
}
}