Skip to content

Commit ff54c41

Browse files
committed
[code-quality] Add RepeatedAndNotEqualToNotInArrayRector
1 parent e54360a commit ff54c41

File tree

7 files changed

+278
-3
lines changed

7 files changed

+278
-3
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector\Fixture;
4+
5+
final class CompareSameVariable
6+
{
7+
public function demo($someVariable): bool
8+
{
9+
if ($someVariable !== 'a' && $someVariable !== 'b' && $someVariable !== 'c') {
10+
return true;
11+
}
12+
13+
return false;
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector\Fixture;
22+
23+
final class CompareSameVariable
24+
{
25+
public function demo($someVariable): bool
26+
{
27+
if (!in_array($someVariable, ['a', 'b', 'c'], true)) {
28+
return true;
29+
}
30+
31+
return false;
32+
}
33+
}
34+
35+
?>
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\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class RepeatedAndNotEqualToNotInArrayRectorTest 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\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector;
6+
use Rector\Config\RectorConfig;
7+
8+
return RectorConfig::configure()
9+
->withRules([RepeatedAndNotEqualToNotInArrayRector::class]);

rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal.php.inc renamed to rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal_condition.php.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Rector\Tests\DeadCode\Rector\If_\RemoveUnusedNonEmptyArrayBeforeForeachRector\Fixture;
44

5-
class NotEqual
5+
final class NotEqualCondition
66
{
77
public function run()
88
{
@@ -21,7 +21,7 @@ class NotEqual
2121

2222
namespace Rector\Tests\DeadCode\Rector\If_\RemoveUnusedNonEmptyArrayBeforeForeachRector\Fixture;
2323

24-
class NotEqual
24+
final class NotEqualCondition
2525
{
2626
public function run()
2727
{
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodeQuality\Rector\BooleanAnd;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
11+
use PhpParser\Node\Expr\BinaryOp\Equal;
12+
use PhpParser\Node\Expr\BinaryOp\Identical;
13+
use PhpParser\Node\Expr\BinaryOp\NotEqual;
14+
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
15+
use PhpParser\Node\Expr\BooleanNot;
16+
use PhpParser\Node\Expr\ConstFetch;
17+
use PhpParser\Node\Expr\FuncCall;
18+
use PhpParser\Node\Name;
19+
use Rector\CodeQuality\ValueObject\ComparedExprAndValueExpr;
20+
use Rector\PhpParser\Node\BetterNodeFinder;
21+
use Rector\Rector\AbstractRector;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
23+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
24+
25+
/**
26+
* @see \Rector\Tests\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector\RepeatedAndNotEqualToNotInArrayRectorTest
27+
*/
28+
final class RepeatedAndNotEqualToNotInArrayRector extends AbstractRector
29+
{
30+
public function __construct(
31+
private readonly BetterNodeFinder $betterNodeFinder,
32+
) {
33+
}
34+
35+
public function getRuleDefinition(): RuleDefinition
36+
{
37+
return new RuleDefinition(
38+
'Simplify repeated && compare of same value, to ! in_array() call',
39+
[
40+
new CodeSample(
41+
<<<'CODE_SAMPLE'
42+
if ($value !== 10 && $value !== 20 && $value !== 30) {
43+
// ...
44+
}
45+
46+
CODE_SAMPLE
47+
,
48+
<<<'CODE_SAMPLE'
49+
if (! in_array($value, [10, 20, 30], true)) {
50+
// ...
51+
}
52+
CODE_SAMPLE
53+
),
54+
]
55+
);
56+
}
57+
58+
/**
59+
* @return array<class-string<Node>>
60+
*/
61+
public function getNodeTypes(): array
62+
{
63+
return [BooleanAnd::class];
64+
}
65+
66+
/**
67+
* @param BooleanAnd $node
68+
*/
69+
public function refactor(Node $node): ?BooleanNot
70+
{
71+
if (! $this->isNotEqualOrNotIdentical($node->right)) {
72+
return null;
73+
}
74+
75+
// match compared variable and expr
76+
if (! $node->left instanceof BooleanAnd && ! $this->isNotEqualOrNotIdentical($node->left)) {
77+
return null;
78+
}
79+
80+
$comparedExprAndValueExprs = $this->matchComparedAndDesiredValues($node);
81+
if ($comparedExprAndValueExprs === null) {
82+
return null;
83+
}
84+
85+
if (count($comparedExprAndValueExprs) < 3) {
86+
return null;
87+
}
88+
89+
// ensure all compared expr are the same
90+
$valueExprs = $this->resolveValueExprs($comparedExprAndValueExprs);
91+
92+
/** @var ComparedExprAndValueExpr $firstComparedExprAndValue */
93+
$firstComparedExprAndValue = array_pop($comparedExprAndValueExprs);
94+
95+
// all compared expr must be equal
96+
foreach ($comparedExprAndValueExprs as $comparedExprAndValueExpr) {
97+
if (! $this->nodeComparator->areNodesEqual(
98+
$firstComparedExprAndValue->getComparedExpr(),
99+
$comparedExprAndValueExpr->getComparedExpr()
100+
)) {
101+
return null;
102+
}
103+
}
104+
105+
$array = $this->nodeFactory->createArray($valueExprs);
106+
107+
$args = $this->nodeFactory->createArgs([$firstComparedExprAndValue->getComparedExpr(), $array]);
108+
109+
if ($this->isStrictComparison($node)) {
110+
$args[] = new Arg(new ConstFetch(new Name('true')));
111+
}
112+
113+
$inArrayFuncCall = new FuncCall(new Name('in_array'), $args);
114+
115+
return new BooleanNot($inArrayFuncCall);
116+
}
117+
118+
private function isNotEqualOrNotIdentical(Expr $expr): bool
119+
{
120+
if ($expr instanceof NotIdentical) {
121+
return true;
122+
}
123+
124+
return $expr instanceof NotEqual;
125+
}
126+
127+
private function matchComparedExprAndValueExpr(NotIdentical|NotEqual $expr): ComparedExprAndValueExpr
128+
{
129+
return new ComparedExprAndValueExpr($expr->left, $expr->right);
130+
}
131+
132+
/**
133+
* @param ComparedExprAndValueExpr[] $comparedExprAndValueExprs
134+
* @return Expr[]
135+
*/
136+
private function resolveValueExprs(array $comparedExprAndValueExprs): array
137+
{
138+
$valueExprs = [];
139+
140+
foreach ($comparedExprAndValueExprs as $comparedExprAndValueExpr) {
141+
$valueExprs[] = $comparedExprAndValueExpr->getValueExpr();
142+
}
143+
144+
return $valueExprs;
145+
}
146+
147+
/**
148+
* @return null|ComparedExprAndValueExpr[]
149+
*/
150+
private function matchComparedAndDesiredValues(BooleanAnd $booleanAnd): ?array
151+
{
152+
/** @var NotIdentical|NotEqual $rightCompare */
153+
$rightCompare = $booleanAnd->right;
154+
155+
// match compared expr and desired value
156+
$comparedExprAndValueExprs = [$this->matchComparedExprAndValueExpr($rightCompare)];
157+
158+
$currentBooleanAnd = $booleanAnd;
159+
160+
while ($currentBooleanAnd->left instanceof BooleanAnd) {
161+
if (! $this->isNotEqualOrNotIdentical($currentBooleanAnd->left->right)) {
162+
return null;
163+
}
164+
165+
/** @var NotIdentical|NotEqual $leftRight */
166+
$leftRight = $currentBooleanAnd->left->right;
167+
$comparedExprAndValueExprs[] = $this->matchComparedExprAndValueExpr($leftRight);
168+
$currentBooleanAnd = $currentBooleanAnd->left;
169+
}
170+
171+
if (! $this->isNotEqualOrNotIdentical($currentBooleanAnd->left)) {
172+
return null;
173+
}
174+
175+
/** @var NotIdentical|NotEqual $leftCompare */
176+
$leftCompare = $currentBooleanAnd->left;
177+
178+
$comparedExprAndValueExprs[] = $this->matchComparedExprAndValueExpr($leftCompare);
179+
180+
// keep original natural order, as left/right goes from bottom up
181+
return array_reverse($comparedExprAndValueExprs);
182+
}
183+
184+
private function isStrictComparison(BooleanAnd $node): bool
185+
{
186+
$notIenticals = $this->betterNodeFinder->findInstanceOf($node, NotIdentical::class);
187+
$notEquals = $this->betterNodeFinder->findInstanceOf($node, NotEqual::class);
188+
189+
if ($notIenticals !== []) {
190+
if ($notEquals !== []) {
191+
// mix identical and equals, keep as is
192+
// @see https://3v4l.org/24cFl
193+
return false;
194+
}
195+
196+
return true;
197+
}
198+
199+
return false;
200+
}
201+
}

rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function __construct(
3232
public function getRuleDefinition(): RuleDefinition
3333
{
3434
return new RuleDefinition(
35-
'Simplify repeated || compare of same value, to in_array() class',
35+
'Simplify repeated || compare of same value, to in_array() call',
3636
[
3737
new CodeSample(
3838
<<<'CODE_SAMPLE'

src/Config/Level/CodeQualityLevel.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Rector\CodeQuality\Rector\Assign\CombinedAssignRector;
88
use Rector\CodeQuality\Rector\BooleanAnd\RemoveUselessIsObjectCheckRector;
9+
use Rector\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector;
910
use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector;
1011
use Rector\CodeQuality\Rector\BooleanNot\ReplaceMultipleBooleanNotRector;
1112
use Rector\CodeQuality\Rector\BooleanNot\SimplifyDeMorganBinaryRector;
@@ -107,6 +108,7 @@ final class CodeQualityLevel
107108
ReplaceMultipleBooleanNotRector::class,
108109
ForeachToInArrayRector::class,
109110
RepeatedOrEqualToInArrayRector::class,
111+
RepeatedAndNotEqualToNotInArrayRector::class,
110112
SimplifyForeachToCoalescingRector::class,
111113
SimplifyFuncGetArgsCountRector::class,
112114
SimplifyInArrayValuesRector::class,

0 commit comments

Comments
 (0)