Skip to content

Commit 3c8adb6

Browse files
[coding-style] Add NestedTernaryToMatchRector (#7572)
* [coding-style] Add NestedTernaryToMatchTrueRector * fixes * add identical support * rename to NestedTernaryToMatchRector * add fixture with different identical varible * [ci-review] Rector Rectify --------- Co-authored-by: GitHub Action <actions@github.com>
1 parent 392fec1 commit 3c8adb6

File tree

9 files changed

+401
-0
lines changed

9 files changed

+401
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
4+
5+
final class NegatedIdentical
6+
{
7+
public function run($value)
8+
{
9+
$result = $value !== 10 ? 'equal to 10' : ($value !== 5 ? 'equal to 5' : 'other value');
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
18+
19+
final class NegatedIdentical
20+
{
21+
public function run($value)
22+
{
23+
$result = match (true) {
24+
$value !== 10 => 'equal to 10',
25+
$value !== 5 => 'equal to 5',
26+
default => 'other value',
27+
};
28+
}
29+
}
30+
31+
?>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
4+
5+
final class NestedTernaryToMatchRector
6+
{
7+
public function run($value)
8+
{
9+
$result = $value > 10 ? 'greater than 10' : ($value === 10 ? 'equal to 10' : 'less than 10');
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
18+
19+
final class NestedTernaryToMatchRector
20+
{
21+
public function run($value)
22+
{
23+
$result = match (true) {
24+
$value > 10 => 'greater than 10',
25+
$value === 10 => 'equal to 10',
26+
default => 'less than 10',
27+
};
28+
}
29+
}
30+
31+
?>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
4+
5+
final class NestedTernaryWithIdentical
6+
{
7+
public function run($value)
8+
{
9+
$result = $value === 10 ? 'equal to 10' : ($value === 5 ? 'equal to 5' : 'other value');
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
18+
19+
final class NestedTernaryWithIdentical
20+
{
21+
public function run($value)
22+
{
23+
$result = match ($value) {
24+
10 => 'equal to 10',
25+
5 => 'equal to 5',
26+
default => 'other value',
27+
};
28+
}
29+
}
30+
31+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
4+
5+
final class NestedTernaryWithIdenticalDifferentValues
6+
{
7+
public function run($value, $nextValue)
8+
{
9+
$result = $value === 10 ? 'equal to 10' : ($nextValue === 5 ? 'equal to 5' : 'other value');
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
18+
19+
final class NestedTernaryWithIdenticalDifferentValues
20+
{
21+
public function run($value, $nextValue)
22+
{
23+
$result = match (true) {
24+
$value === 10 => 'equal to 10',
25+
$nextValue === 5 => 'equal to 5',
26+
default => 'other value',
27+
};
28+
}
29+
}
30+
31+
?>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\Fixture;
6+
7+
final class SkipSilentConditions
8+
{
9+
public function run($value)
10+
{
11+
$value = 5;
12+
13+
$result = $value > 10 ? 'greater than 10' : ($value === 10 ?: 'less than 10');
14+
}
15+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class NestedTernaryToMatchRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\CodingStyle\Rector\Assign\NestedTernaryToMatchRector;
6+
use Rector\Config\RectorConfig;
7+
8+
return RectorConfig::configure()
9+
->withRules([NestedTernaryToMatchRector::class]);
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodingStyle\Rector\Assign;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\Assign;
10+
use PhpParser\Node\Expr\Match_;
11+
use PhpParser\Node\Expr\Ternary;
12+
use PhpParser\Node\Expr\Variable;
13+
use PhpParser\Node\MatchArm;
14+
use Rector\CodingStyle\ValueObject\ConditionAndResult;
15+
use Rector\Rector\AbstractRector;
16+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
17+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
18+
19+
/**
20+
* @see \Rector\Tests\CodingStyle\Rector\Assign\NestedTernaryToMatchRector\NestedTernaryToMatchRectorTest
21+
*/
22+
final class NestedTernaryToMatchRector extends AbstractRector
23+
{
24+
public function getRuleDefinition(): RuleDefinition
25+
{
26+
return new RuleDefinition('Convert nested ternary expressions to match(true) statements', [
27+
new CodeSample(
28+
<<<'CODE_SAMPLE'
29+
class SomeClass
30+
{
31+
public function getValue($input)
32+
{
33+
return $input > 100 ? 'more than 100' : ($input > 5 ? 'more than 5' : 'less');
34+
}
35+
}
36+
CODE_SAMPLE
37+
,
38+
<<<'CODE_SAMPLE'
39+
class SomeClass
40+
{
41+
public function getValue($input)
42+
{
43+
return match (true) {
44+
$input > 100 => 'more than 100',
45+
$input > 5 => 'more than 5',
46+
default => 'less',
47+
};
48+
}
49+
}
50+
CODE_SAMPLE
51+
),
52+
53+
]);
54+
}
55+
56+
/**
57+
* @return array<class-string<Node>>
58+
*/
59+
public function getNodeTypes(): array
60+
{
61+
return [Assign::class];
62+
}
63+
64+
/**
65+
* @param Assign $node
66+
*/
67+
public function refactor(Node $node): ?Assign
68+
{
69+
if (! $node->expr instanceof Ternary) {
70+
return null;
71+
}
72+
73+
$ternary = $node->expr;
74+
75+
// traverse nested ternaries to collect them all
76+
$currentTernary = $ternary;
77+
78+
/** @var ConditionAndResult[] $conditionsAndResults */
79+
$conditionsAndResults = [];
80+
$defaultExpr = null;
81+
82+
while ($currentTernary instanceof Ternary) {
83+
if (!$currentTernary->if instanceof Expr) {
84+
// short ternary, skip
85+
return null;
86+
}
87+
88+
$conditionsAndResults[] = new ConditionAndResult($currentTernary->cond, $currentTernary->if);
89+
90+
$currentTernary = $currentTernary->else;
91+
92+
if (! $currentTernary instanceof Ternary) {
93+
$defaultExpr = $currentTernary;
94+
}
95+
}
96+
97+
// nothing long enough
98+
if (count($conditionsAndResults) < 2 || ! $defaultExpr instanceof Expr) {
99+
return null;
100+
}
101+
102+
$match = $this->createMatch($conditionsAndResults, $defaultExpr);
103+
$node->expr = $match;
104+
105+
return $node;
106+
}
107+
108+
/**
109+
* @param ConditionAndResult[] $conditionsAndResults
110+
*/
111+
private function createMatch(array $conditionsAndResults, Expr $defaultExpr): Match_
112+
{
113+
$singleVariableName = $this->matchAlwaysIdenticalVariableName($conditionsAndResults);
114+
if (is_string($singleVariableName)) {
115+
$isVariableIdentical = true;
116+
$match = new Match_(new Variable($singleVariableName));
117+
} else {
118+
$isVariableIdentical = false;
119+
$match = new Match_($this->nodeFactory->createTrue());
120+
}
121+
122+
foreach ($conditionsAndResults as $conditionAndResult) {
123+
$match->arms[] = new MatchArm([
124+
$isVariableIdentical ? $conditionAndResult->getIdenticalExpr() : $conditionAndResult->getConditionExpr(),
125+
], $conditionAndResult->getResultExpr());
126+
}
127+
128+
$match->arms[] = new MatchArm(null, $defaultExpr);
129+
130+
return $match;
131+
}
132+
133+
/**
134+
* @param ConditionAndResult[] $conditionsAndResults
135+
*/
136+
private function matchAlwaysIdenticalVariableName(array $conditionsAndResults): mixed
137+
{
138+
$identicalVariableNames = [];
139+
foreach ($conditionsAndResults as $conditionAndResult) {
140+
if (! $conditionAndResult->isIdenticalCompare()) {
141+
return null;
142+
}
143+
144+
$variableName = $conditionAndResult->getIdenticalVariableName();
145+
if (! is_string($variableName)) {
146+
return null;
147+
}
148+
149+
$identicalVariableNames[] = $variableName;
150+
}
151+
152+
$uniqueIdenticalVariableNames = array_unique($identicalVariableNames);
153+
$uniqueIdenticalVariableNames = array_values($uniqueIdenticalVariableNames);
154+
155+
if (count($uniqueIdenticalVariableNames) === 1) {
156+
return $uniqueIdenticalVariableNames[0];
157+
}
158+
159+
return null;
160+
}
161+
}

0 commit comments

Comments
 (0)