Skip to content

Commit 72cf61a

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

File tree

9 files changed

+275
-5
lines changed

9 files changed

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

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'

rules/Privatization/NodeManipulator/VisibilityManipulator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ private function replaceVisibilityFlag(ClassMethod | Property | ClassConst | Par
207207
$this->makeNonStatic($node);
208208
}
209209

210-
if ($visibility !== Visibility::STATIC && $visibility !== Visibility::ABSTRACT && $visibility !== Visibility::FINAL) {
210+
if (!in_array($visibility, [Visibility::STATIC, Visibility::ABSTRACT, Visibility::FINAL], true)) {
211211
$this->removeVisibility($node);
212212
}
213213

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,

src/ValueObject/Error/SystemError.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ public function getRectorClass(): ?string
8383
public function getRectorShortClass(): ?string
8484
{
8585
$rectorClass = $this->rectorClass;
86-
if ($rectorClass !== null && $rectorClass !== '' && $rectorClass !== '0') {
86+
87+
if (!in_array($rectorClass, [null, ''], true)) {
8788
return (string) Strings::after($rectorClass, '\\', -1);
8889
}
8990

0 commit comments

Comments
 (0)