mirror of
https://github.com/rectorphp/rector.git
synced 2024-07-30 21:30:23 +00:00
Add TernaryToSpaceshipRector
This commit is contained in:
parent
e7e01659ef
commit
8b43393e6a
@ -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: ~
|
||||
|
125
packages/Php/src/Rector/Ternary/TernaryToSpaceshipRector.php
Normal file
125
packages/Php/src/Rector/Ternary/TernaryToSpaceshipRector.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
?>
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
services:
|
||||
Rector\Php\Rector\Ternary\TernaryToSpaceshipRector: ~
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user