[CodeQuality] Add SimplifyArrayCallableRector

This commit is contained in:
Tomas Votruba 2018-10-15 23:07:46 +08:00
parent 68b3372cc5
commit 3c0873e3b3
8 changed files with 177 additions and 4 deletions

View File

@ -0,0 +1,101 @@
<?php declare(strict_types=1);
namespace Rector\CodeQuality\Rector\FuncCall;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Return_;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class SimplifyArrayCallableRector extends AbstractRector
{
/**
* @var string|null
*/
private $activeFuncCallName;
/**
* @var int[]
*/
private $functionsWithCallableArgumentPosition = [
'array_filter' => 1,
'array_map' => 0,
];
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Changes redundant anonymous bool functions to simple calls', [
new CodeSample(
<<<'CODE_SAMPLE'
$paths = array_filter($paths, function ($path): bool {
return is_dir($path);
});
CODE_SAMPLE
,
'array_filter($paths, "is_dir");'
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [FuncCall::class];
}
/**
* @param FuncCall $node
*/
public function refactor(Node $node): ?Node
{
foreach ($this->functionsWithCallableArgumentPosition as $function => $callablePosition) {
if (! $this->isName($node, $function)) {
continue;
}
if (! $node->args[$callablePosition]->value instanceof Closure) {
continue;
}
/** @var Closure $closureNode */
$closureNode = $node->args[$callablePosition]->value;
if ($this->isUsefulClosure($closureNode)) {
return null;
}
$node->args[$callablePosition] = new Arg(new String_($this->activeFuncCallName));
return $node;
}
return $node;
}
private function isUsefulClosure(Closure $closureNode): bool
{
// too complicated
if (! $closureNode->stmts[0] instanceof Return_) {
return true;
}
/** @var Return_ $returnNode */
$returnNode = $closureNode->stmts[0];
if (! $returnNode->expr instanceof FuncCall) {
return true;
}
/** @var FuncCall $funcCallNode */
$funcCallNode = $returnNode->expr;
$this->activeFuncCallName = $this->getName($funcCallNode);
return ! $this->areNodesEqual($closureNode->params, $returnNode->expr->args);
}
}

View File

@ -0,0 +1,11 @@
<?php
$paths = array_filter($paths, 'is_dir');
$paths = array_filter($paths, 'strlen');
$paths = array_filter($paths, function ($path): bool {
return ! is_dir($path);
});
$pathLength = array_map('strlen', $paths);

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\CodeQuality\Tests\Rector\FuncCall\SimplifyArrayCallableRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\CodeQuality\Rector\FuncCall\SimplifyArrayCallableRector
*/
final class SimplifyArrayCallableRectorTest 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'];
}
protected function provideConfig(): string
{
return __DIR__ . '/config.yml';
}
}

View File

@ -0,0 +1,17 @@
<?php
$paths = array_filter($paths, function ($path): bool {
return is_dir($path);
});
$paths = array_filter($paths, function ($path): bool {
return strlen($path);
});
$paths = array_filter($paths, function ($path): bool {
return ! is_dir($path);
});
$pathLength = array_map(function ($path) {
return strlen($path);
}, $paths);

View File

@ -0,0 +1,2 @@
services:
Rector\CodeQuality\Rector\FuncCall\SimplifyArrayCallableRector: ~

View File

@ -116,7 +116,9 @@ final class CreateRectorCommand extends Command
'_Description_' => $configuration->getDescription(),
'_Name_' => $configuration->getName(),
'_CodeBefore_' => $configuration->getCodeBefore(),
'_CodeBeforeExample_' => $this->prepareCodeForDefinition($configuration->getCodeBefore()),
'_CodeAfter_' => $configuration->getCodeAfter(),
'_CodeAfterExample_' => $this->prepareCodeForDefinition($configuration->getCodeAfter()),
];
$arrayNodes = [];
@ -137,4 +139,15 @@ final class CreateRectorCommand extends Command
{
return str_replace(array_keys($data), array_values($data), $content);
}
private function prepareCodeForDefinition(string $code): string
{
if (Strings::contains($code, PHP_EOL)) {
// multi lines
return sprintf("<<<'CODE_SAMPLE'%s%sCODE_SAMPLE%s", PHP_EOL, $code, PHP_EOL);
}
// single line
return "'" . str_replace("'", '"', $code) . "'";
}
}

View File

@ -13,8 +13,8 @@ final class _Name_ extends AbstractRector
{
return new RectorDefinition('_Description_', [
new CodeSample(
'_CodeBefore_',
'_CodeAfter_'
_CodeBeforeExample_,
_CodeAfterExample_
)
]);
}

View File

@ -60,8 +60,7 @@ final class ReplaceCreateMethodWithoutReviewerRector extends AbstractRector
return null;
}
if (
$this->methodArgumentAnalyzer->hasMethodNthArgument($node, 2)
if ($this->methodArgumentAnalyzer->hasMethodNthArgument($node, 2)
&& ! $this->methodArgumentAnalyzer->isMethodNthArgumentNull($node, 2)
) {
return null;