Add TernaryToSpaceshipRector

This commit is contained in:
Tomas Votruba 2018-12-15 13:20:07 +01:00
parent e7e01659ef
commit 8b43393e6a
6 changed files with 199 additions and 1 deletions

View File

@ -16,3 +16,4 @@ services:
Rector\Php\Rector\FuncCall\CallUserMethodRector : ~
Rector\Php\Rector\FuncCall\EregToPregMatchRector: ~
Rector\Php\Rector\Switch_\ReduceMultipleDefaultSwitchRector: ~
Rector\Php\Rector\Ternary\TernaryToSpaceshipRector: ~

View File

@ -0,0 +1,125 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector\Ternary;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp\Greater;
use PhpParser\Node\Expr\BinaryOp\Smaller;
use PhpParser\Node\Expr\BinaryOp\Spaceship;
use PhpParser\Node\Expr\Ternary;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see https://wiki.php.net/rfc/combined-comparison-operator
*/
final class TernaryToSpaceshipRector extends AbstractRector
{
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Use <=> spaceship instead of ternary with same effect', [
new CodeSample(
<<<'CODE_SAMPLE'
function order_func($a, $b) {
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
function order_func($a, $b) {
return $a <=> $b;
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Ternary::class];
}
/**
* @param Ternary $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkip($node)) {
return null;
}
/** @var Ternary $nestedTernary */
$nestedTernary = $node->else;
$spaceshipNode = $this->processSmallerThanTernary($node, $nestedTernary);
if ($spaceshipNode !== null) {
return $spaceshipNode;
}
return $this->processGreaterThanTernary($node, $nestedTernary);
}
private function shouldSkip(Ternary $ternaryNode): bool
{
if (! $ternaryNode->cond instanceof BinaryOp) {
return true;
}
if (! $ternaryNode->else instanceof Ternary) {
return true;
}
$nestedTernary = $ternaryNode->else;
// $a X $b ? . : ($a X $b ? . : .)
if (! $this->areNodesEqual($ternaryNode->cond->left, $nestedTernary->cond->left)) {
return true;
}
// $a X $b ? . : ($a X $b ? . : .)
if (! $this->areNodesEqual($ternaryNode->cond->right, $nestedTernary->cond->right)) {
return true;
}
return false;
}
/**
* Matches "$a < $b ? -1 : ($a > $b ? 1 : 0)"
*/
private function processSmallerThanTernary(Ternary $node, Ternary $nestedTernary): ?Spaceship
{
if ($node->cond instanceof Smaller && $nestedTernary->cond instanceof Greater) {
if ($this->isValue($node->if, -1) && $this->isValue($nestedTernary->if, 1) && $this->isValue(
$nestedTernary->else,
0
)) {
return new Spaceship($node->cond->left, $node->cond->right);
}
}
return null;
}
/**
* Matches "$a > $b ? -1 : ($a < $b ? 1 : 0)"
*/
private function processGreaterThanTernary(Ternary $node, Ternary $nestedTernary): ?Spaceship
{
if ($node->cond instanceof Greater && $nestedTernary->cond instanceof Smaller) {
if ($this->isValue($node->if, -1) && $this->isValue($nestedTernary->if, 1) && $this->isValue(
$nestedTernary->else,
0
)) {
return new Spaceship($node->cond->right, $node->cond->left);
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
<?php
function order_func($a, $b) {
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
return ($a > $b) ? -1 : (($a < $b) ? 1 : 0);
}
?>
-----
<?php
function order_func($a, $b) {
return $a <=> $b;
return $b <=> $a;
}
?>

View File

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\Ternary\TernaryToSpaceshipRector;
use Rector\Php\Rector\Ternary\TernaryToSpaceshipRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class TernaryToSpaceshipRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']);
}
protected function getRectorClass(): string
{
return TernaryToSpaceshipRector::class;
}
}

View File

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

View File

@ -2,6 +2,7 @@
namespace Rector\Rector;
use PhpParser\ConstExprEvaluator;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
@ -34,15 +35,22 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
*/
private $symfonyStyle;
/**
* @var ConstExprEvaluator
*/
private $constExprEvaluator;
/**
* @required
*/
public function setAbstractRectorDependencies(
AppliedRectorCollector $appliedRectorCollector,
SymfonyStyle $symfonyStyle
SymfonyStyle $symfonyStyle,
ConstExprEvaluator $constExprEvaluator
): void {
$this->appliedRectorCollector = $appliedRectorCollector;
$this->symfonyStyle = $symfonyStyle;
$this->constExprEvaluator = $constExprEvaluator;
}
/**
@ -101,6 +109,30 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
return $nodes;
}
/**
* @return mixed
*/
protected function getValue(Node $node)
{
if (! $node instanceof Expr) {
return null;
}
return $this->constExprEvaluator->evaluateSilently($node);
}
/**
* @param mixed $expectedValue
*/
protected function isValue(Node $node, $expectedValue): bool
{
$nodeValue = $this->getValue($node);
if ($nodeValue === null) {
return false;
}
return $nodeValue === $expectedValue;
}
protected function notifyNodeChangeFileInfo(Node $node): void
{
/** @var SmartFileInfo|null $fileInfo */