init IsIterableRector [ref #638]

This commit is contained in:
Tomas Votruba 2018-10-03 22:11:30 +08:00
parent 6b87d9e545
commit 9e9f5e796f
15 changed files with 290 additions and 0 deletions

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\IsIterableRector: ~

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\IsCountableRector: ~

View File

@ -0,0 +1,2 @@
imports:
- { resource: 'services.yml' }

View File

@ -0,0 +1,8 @@
services:
_defaults:
public: true
autowire: true
Rector\Php\:
resource: '../src'
exclude: '../src/{Rector/**/*Rector.php}'

View File

@ -0,0 +1,74 @@
<?php declare(strict_types=1);
namespace Rector\Php;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
final class DualCheckToAble
{
public function processBooleanOr(BooleanOr $booleanOrNode, string $type, string $newMethodName): ?FuncCall
{
$split = $this->splitToInstanceOfAndFuncCall($booleanOrNode);
if ($split === null) {
return null;
}
[$instanceOfNode, $funcCallNode] = $split;
/** @var Instanceof_ $instanceOfNode */
if ((string) $instanceOfNode->class !== $type) {
return null;
}
/** @var FuncCall $funcCallNode */
if ((string) $funcCallNode->name !== 'is_array') {
return null;
}
// both use same var
if (! $funcCallNode->args[0]->value instanceof Variable) {
return null;
}
/** @var Variable $firstVarNode */
$firstVarNode = $funcCallNode->args[0]->value;
if (! $instanceOfNode->expr instanceof Variable) {
return null;
}
/** @var Variable $secondVarNode */
$secondVarNode = $instanceOfNode->expr;
// are they same variables
if ($firstVarNode->name !== $secondVarNode->name) {
return null;
}
$funcCallNode = new FuncCall(new Name($newMethodName));
$funcCallNode->args[0] = new Arg($firstVarNode);
return $funcCallNode;
}
/**
* @return Instanceof_[]|FuncCall[]
*/
private function splitToInstanceOfAndFuncCall(BooleanOr $booleanOrNode): ?array
{
if ($booleanOrNode->left instanceof Instanceof_ && $booleanOrNode->right instanceof FuncCall) {
return [$booleanOrNode->left, $booleanOrNode->right];
}
if ($booleanOrNode->right instanceof Instanceof_ && $booleanOrNode->left instanceof FuncCall) {
return [$booleanOrNode->right, $booleanOrNode->left];
}
return null;
}
}

View File

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use Rector\Php\DualCheckToAble;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class IsCountableRector extends AbstractRector
{
/**
* @var DualCheckToAble
*/
private $dualCheckToAble;
public function __construct(DualCheckToAble $dualCheckToAble)
{
$this->dualCheckToAble = $dualCheckToAble;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Changes is_array + Countable check to is_countable',
[
new CodeSample(
<<<'CODE_SAMPLE'
is_array($foo) || $foo instanceof Countable;
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
is_countable($foo);
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [BooleanOr::class];
}
/**
* @param BooleanOr $booleanOrNode
*/
public function refactor(Node $booleanOrNode): ?Node
{
return $this->dualCheckToAble->processBooleanOr($booleanOrNode, 'Countable', 'is_countable') ?: $booleanOrNode;
}
}

View File

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use Rector\Php\DualCheckToAble;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class IsIterableRector extends AbstractRector
{
/**
* @var DualCheckToAble
*/
private $dualCheckToAble;
public function __construct(DualCheckToAble $dualCheckToAble)
{
$this->dualCheckToAble = $dualCheckToAble;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Changes is_array + Traversable check to is_iterable',
[
new CodeSample(
<<<'CODE_SAMPLE'
is_array($foo) || $foo instanceof Traversable;
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
is_iterable($foo);
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [BooleanOr::class];
}
/**
* @param BooleanOr $booleanOrNode
*/
public function refactor(Node $booleanOrNode): ?Node
{
return $this->dualCheckToAble->processBooleanOr($booleanOrNode, 'Traversable', 'is_iterable') ?: $booleanOrNode;
}
}

View File

@ -0,0 +1,3 @@
<?php declare(strict_types=1);
is_countable($foo);

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\IsCountableRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Php\Rector\IsCountableRector
*/
final class IsCountableRectorTest 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,3 @@
<?php declare(strict_types=1);
is_array($foo) || $foo instanceof Countable;

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\IsCountableRector: ~

View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
is_iterable($foo);
is_string($foo) || $foo instanceof Traversable;
is_array($foo2) || $foo instanceof Traversable;
$foo2 || $foo instanceof Traversable;

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\IsIterableRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Php\Rector\IsIterableRector
*/
final class IsIterableRectorTest 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,9 @@
<?php declare(strict_types=1);
is_array($foo) || $foo instanceof Traversable;
is_string($foo) || $foo instanceof Traversable;
is_array($foo2) || $foo instanceof Traversable;
$foo2 || $foo instanceof Traversable;

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\IsIterableRector: ~