mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-19 06:18:07 +01:00
[PHP] Add NullCoalesceRector
This commit is contained in:
parent
116bff36f7
commit
02d2ba69e3
@ -1,2 +1,3 @@
|
|||||||
services:
|
services:
|
||||||
Rector\Php\Rector\ExceptionHandlerTypehintRector: ~
|
Rector\Php\Rector\ExceptionHandlerTypehintRector: ~
|
||||||
|
Rector\Php\Rector\TernaryToNullCoalescingRector: ~
|
||||||
|
@ -12,7 +12,6 @@ use Rector\RectorDefinition\CodeSample;
|
|||||||
use Rector\RectorDefinition\RectorDefinition;
|
use Rector\RectorDefinition\RectorDefinition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group php7
|
|
||||||
* @source https://wiki.php.net/rfc/typed_properties_v2#proposal
|
* @source https://wiki.php.net/rfc/typed_properties_v2#proposal
|
||||||
*/
|
*/
|
||||||
final class ExceptionHandlerTypehintRector extends AbstractRector
|
final class ExceptionHandlerTypehintRector extends AbstractRector
|
||||||
|
127
packages/Php/src/Rector/TernaryToNullCoalescingRector.php
Normal file
127
packages/Php/src/Rector/TernaryToNullCoalescingRector.php
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Php\Rector;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\BinaryOp\Coalesce;
|
||||||
|
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||||
|
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
|
||||||
|
use PhpParser\Node\Expr\ConstFetch;
|
||||||
|
use PhpParser\Node\Expr\Isset_;
|
||||||
|
use PhpParser\Node\Expr\Ternary;
|
||||||
|
use Rector\Printer\BetterStandardPrinter;
|
||||||
|
use Rector\Rector\AbstractRector;
|
||||||
|
use Rector\RectorDefinition\CodeSample;
|
||||||
|
use Rector\RectorDefinition\RectorDefinition;
|
||||||
|
|
||||||
|
final class TernaryToNullCoalescingRector extends AbstractRector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var BetterStandardPrinter
|
||||||
|
*/
|
||||||
|
private $betterStandardPrinter;
|
||||||
|
|
||||||
|
public function __construct(BetterStandardPrinter $betterStandardPrinter)
|
||||||
|
{
|
||||||
|
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefinition(): RectorDefinition
|
||||||
|
{
|
||||||
|
return new RectorDefinition(
|
||||||
|
'Changes unneeded null check to ?? operator',
|
||||||
|
[
|
||||||
|
new CodeSample('$value === null ? 10 : $value;', '$value ?? 10;'),
|
||||||
|
new CodeSample('isset($value) ? $value : 10;', '$value ?? 10;'),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getNodeTypes(): array
|
||||||
|
{
|
||||||
|
return [Ternary::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Ternary $ternaryNode
|
||||||
|
*/
|
||||||
|
public function refactor(Node $ternaryNode): ?Node
|
||||||
|
{
|
||||||
|
if ($ternaryNode->cond instanceof Isset_) {
|
||||||
|
$coalesceNode = $this->processTernaryWithIsset($ternaryNode);
|
||||||
|
if ($coalesceNode) {
|
||||||
|
return $coalesceNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ternaryNode->cond instanceof Identical) {
|
||||||
|
[$checkedNode, $fallbackNode] = [$ternaryNode->else, $ternaryNode->if];
|
||||||
|
} elseif ($ternaryNode->cond instanceof NotIdentical) {
|
||||||
|
[$checkedNode, $fallbackNode] = [$ternaryNode->if, $ternaryNode->else];
|
||||||
|
} else {
|
||||||
|
// not a match
|
||||||
|
return $ternaryNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Identical|NotIdentical $ternaryCompareNode */
|
||||||
|
$ternaryCompareNode = $ternaryNode->cond;
|
||||||
|
|
||||||
|
if ($this->isNullMatch($ternaryCompareNode->left, $ternaryCompareNode->right, $checkedNode)) {
|
||||||
|
return new Coalesce($checkedNode, $fallbackNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isNullMatch($ternaryCompareNode->right, $ternaryCompareNode->left, $checkedNode)) {
|
||||||
|
return new Coalesce($checkedNode, $fallbackNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ternaryNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processTernaryWithIsset(Ternary $ternaryNode): ?Coalesce
|
||||||
|
{
|
||||||
|
/** @var Isset_ $issetNode */
|
||||||
|
$issetNode = $ternaryNode->cond;
|
||||||
|
|
||||||
|
// none or multiple isset values cannot be handled here
|
||||||
|
if (! isset($issetNode->vars[0]) || count($issetNode->vars) > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ternaryNode->if === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ifContent = $this->betterStandardPrinter->prettyPrint([$ternaryNode->if]);
|
||||||
|
$varNodeContent = $this->betterStandardPrinter->prettyPrint([$issetNode->vars[0]]);
|
||||||
|
|
||||||
|
if ($ifContent === $varNodeContent) {
|
||||||
|
return new Coalesce($ternaryNode->if, $ternaryNode->else);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isNullMatch(Node $possibleNullNode, Node $firstNode, Node $secondNode): bool
|
||||||
|
{
|
||||||
|
if (! $this->isNullConstant($possibleNullNode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$firstNodeContent = $this->betterStandardPrinter->prettyPrint([$firstNode]);
|
||||||
|
$secondNodeContent = $this->betterStandardPrinter->prettyPrint([$secondNode]);
|
||||||
|
|
||||||
|
return $firstNodeContent === $secondNodeContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isNullConstant(Node $node): bool
|
||||||
|
{
|
||||||
|
if (! $node instanceof ConstFetch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $node->name->toLowerString() === 'null';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$x = $a ?? null;
|
||||||
|
|
||||||
|
$x = $a[0] ?? 1;
|
||||||
|
|
||||||
|
$x=$a ?? null;
|
||||||
|
|
||||||
|
$x = $a[ $b[ "c" ]] ?? null;
|
||||||
|
|
||||||
|
$x = $a ?? $b[func(1, true)];
|
||||||
|
|
||||||
|
$x = ($a ?? isset($b)) ? $b : "";
|
||||||
|
|
||||||
|
$x = $a ?? isset($b) ? $b : isset($c) ? $c : "";
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$x = $a ?? 1;
|
||||||
|
$y = isset($b) ? "b" : 2;
|
||||||
|
$x = $c ?? 3;
|
||||||
|
|
||||||
|
$x = $a ?? $b ?? "";
|
@ -0,0 +1,5 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$f = $g ?? 'g';
|
||||||
|
|
||||||
|
$ff = $gg ?? 'gg';
|
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$d = $e ?? 'e';
|
||||||
|
|
||||||
|
$dd = $ee ?? 'ee';
|
||||||
|
|
||||||
|
$i = $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->xxx->{" $d"} ?? 0;
|
||||||
|
|
||||||
|
$j = $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->xxx->{" $d"} ?? false;
|
||||||
|
|
||||||
|
$k = $this
|
||||||
|
->${'a'}[0]
|
||||||
|
->$$b[1][2]
|
||||||
|
::$c[3][4][5]
|
||||||
|
->{" $d"} ?? true;
|
||||||
|
|
||||||
|
$l = \Whatever\Something::$anything ?? 1;
|
||||||
|
|
||||||
|
$m = $object->anything ?? 0;
|
||||||
|
|
||||||
|
$n = ($something ?? false);
|
||||||
|
|
||||||
|
$o[$something ?? true] = true;
|
||||||
|
|
||||||
|
$p = doSomething()() ?? false;
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Php\Tests\Rector\TernaryToNullCoalescingRector;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Rector\Php\Rector\TernaryToNullCoalescingRector
|
||||||
|
*
|
||||||
|
* Some tests copied from:
|
||||||
|
* https://github.com/FriendsOfPHP/PHP-CS-Fixer/commit/0db4f91088a3888a7c8b26e5a36fba53c0d9507c#diff-02f477b178d0dc5b25ac05ab3b59e7c7
|
||||||
|
* https://github.com/slevomat/coding-standard/blob/master/tests/Sniffs/ControlStructures/data/requireNullCoalesceOperatorErrors.fixed.php
|
||||||
|
*/
|
||||||
|
final class TernaryToNullCoalescingRectorTest 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'];
|
||||||
|
yield [__DIR__ . '/Wrong/wrong4.php.inc', __DIR__ . '/Correct/correct4.php.inc'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function provideConfig(): string
|
||||||
|
{
|
||||||
|
return __DIR__ . '/config.yml';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$x = isset($a) ? $a : null;
|
||||||
|
|
||||||
|
$x = isset($a[0]) ? $a[0] : 1;
|
||||||
|
|
||||||
|
$x=isset($a)?$a:null;
|
||||||
|
|
||||||
|
$x = isset ( $a[$b["c"]]) ?$a[ $b[ "c" ]] : null;
|
||||||
|
|
||||||
|
$x = isset($a) ? $a : $b[func(1, true)];
|
||||||
|
|
||||||
|
$x = (isset($a) ? $a : isset($b)) ? $b : "";
|
||||||
|
|
||||||
|
$x = isset($a) ? $a : isset($b) ? $b : isset($c) ? $c : "";
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$x = isset($a) ? $a : 1;
|
||||||
|
$y = isset($b) ? "b" : 2;
|
||||||
|
$x = isset($c) ? $c : 3;
|
||||||
|
|
||||||
|
$x = isset($a) ? $a : (isset($b) ? $b : "");
|
@ -0,0 +1,5 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$f = $g === null ? 'g' : $g;
|
||||||
|
|
||||||
|
$ff = null === $gg ? 'gg' : $gg;
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
$d = $e !== null ? $e : 'e';
|
||||||
|
|
||||||
|
$dd = null !== $ee ? $ee : 'ee';
|
||||||
|
|
||||||
|
$i = $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->xxx->{" $d"} !== null ? $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->xxx->{" $d"} : 0;
|
||||||
|
|
||||||
|
$j = $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->xxx->{" $d"} === null ? false : $this->${'a'}[0]->$$b[1][2]::$c[3][4][5]->xxx->{" $d"};
|
||||||
|
|
||||||
|
$k = $this
|
||||||
|
->${'a'}[0]->$$b[1][2]
|
||||||
|
::$c[3][4][5]->{" $d"} !== null
|
||||||
|
? $this
|
||||||
|
->${'a'}[0]
|
||||||
|
->$$b[1][2]
|
||||||
|
::$c[3][4][5]
|
||||||
|
->{" $d"}
|
||||||
|
: true;
|
||||||
|
|
||||||
|
$l = \Whatever\Something::$anything !== null ? \Whatever\Something::$anything : 1;
|
||||||
|
|
||||||
|
$m = $object->anything === null ? 0 : $object->anything;
|
||||||
|
|
||||||
|
$n = ($something === null ? false : $something);
|
||||||
|
|
||||||
|
$o[$something === null ? true : $something] = true;
|
||||||
|
|
||||||
|
$p = doSomething()() === null ? false : doSomething()();
|
@ -0,0 +1,2 @@
|
|||||||
|
services:
|
||||||
|
Rector\Php\Rector\TernaryToNullCoalescingRector: ~
|
Loading…
x
Reference in New Issue
Block a user