diff --git a/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/Fixture/compare_same_variable.php.inc b/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/Fixture/compare_same_variable.php.inc new file mode 100644 index 00000000000..6cc81071406 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/Fixture/compare_same_variable.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/RepeatedAndNotEqualToNotInArrayRectorTest.php b/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/RepeatedAndNotEqualToNotInArrayRectorTest.php new file mode 100644 index 00000000000..88bb87c45d9 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/RepeatedAndNotEqualToNotInArrayRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/config/configured_rule.php new file mode 100644 index 00000000000..ac5b0e50f35 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([RepeatedAndNotEqualToNotInArrayRector::class]); diff --git a/rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal.php.inc b/rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal_condition.php.inc similarity index 90% rename from rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal.php.inc rename to rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal_condition.php.inc index 7ffd0e62f1a..56a37c2dfc5 100644 --- a/rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal.php.inc +++ b/rules-tests/DeadCode/Rector/If_/RemoveUnusedNonEmptyArrayBeforeForeachRector/Fixture/not_equal_condition.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\DeadCode\Rector\If_\RemoveUnusedNonEmptyArrayBeforeForeachRector\Fixture; -class NotEqual +final class NotEqualCondition { public function run() { @@ -21,7 +21,7 @@ class NotEqual namespace Rector\Tests\DeadCode\Rector\If_\RemoveUnusedNonEmptyArrayBeforeForeachRector\Fixture; -class NotEqual +final class NotEqualCondition { public function run() { diff --git a/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php b/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php new file mode 100644 index 00000000000..6078257de27 --- /dev/null +++ b/rules/CodeQuality/Rector/BooleanAnd/RepeatedAndNotEqualToNotInArrayRector.php @@ -0,0 +1,195 @@ +> + */ + public function getNodeTypes(): array + { + return [BooleanAnd::class]; + } + + /** + * @param BooleanAnd $node + */ + public function refactor(Node $node): ?BooleanNot + { + if (! $this->isNotEqualOrNotIdentical($node->right)) { + return null; + } + + // match compared variable and expr + if (! $node->left instanceof BooleanAnd && ! $this->isNotEqualOrNotIdentical($node->left)) { + return null; + } + + $comparedExprAndValueExprs = $this->matchComparedAndDesiredValues($node); + if ($comparedExprAndValueExprs === null) { + return null; + } + + if (count($comparedExprAndValueExprs) < 3) { + return null; + } + + // ensure all compared expr are the same + $valueExprs = $this->resolveValueExprs($comparedExprAndValueExprs); + + /** @var ComparedExprAndValueExpr $firstComparedExprAndValue */ + $firstComparedExprAndValue = array_pop($comparedExprAndValueExprs); + + // all compared expr must be equal + foreach ($comparedExprAndValueExprs as $comparedExprAndValueExpr) { + if (! $this->nodeComparator->areNodesEqual( + $firstComparedExprAndValue->getComparedExpr(), + $comparedExprAndValueExpr->getComparedExpr() + )) { + return null; + } + } + + $array = $this->nodeFactory->createArray($valueExprs); + + $args = $this->nodeFactory->createArgs([$firstComparedExprAndValue->getComparedExpr(), $array]); + + if ($this->isStrictComparison($node)) { + $args[] = new Arg(new ConstFetch(new Name('true'))); + } + + $inArrayFuncCall = new FuncCall(new Name('in_array'), $args); + + return new BooleanNot($inArrayFuncCall); + } + + private function isNotEqualOrNotIdentical(Expr $expr): bool + { + if ($expr instanceof NotIdentical) { + return true; + } + + return $expr instanceof NotEqual; + } + + private function matchComparedExprAndValueExpr(NotIdentical|NotEqual $expr): ComparedExprAndValueExpr + { + return new ComparedExprAndValueExpr($expr->left, $expr->right); + } + + /** + * @param ComparedExprAndValueExpr[] $comparedExprAndValueExprs + * @return Expr[] + */ + private function resolveValueExprs(array $comparedExprAndValueExprs): array + { + $valueExprs = []; + + foreach ($comparedExprAndValueExprs as $comparedExprAndValueExpr) { + $valueExprs[] = $comparedExprAndValueExpr->getValueExpr(); + } + + return $valueExprs; + } + + /** + * @return null|ComparedExprAndValueExpr[] + */ + private function matchComparedAndDesiredValues(BooleanAnd $booleanAnd): ?array + { + /** @var NotIdentical|NotEqual $rightCompare */ + $rightCompare = $booleanAnd->right; + + // match compared expr and desired value + $comparedExprAndValueExprs = [$this->matchComparedExprAndValueExpr($rightCompare)]; + + $currentBooleanAnd = $booleanAnd; + + while ($currentBooleanAnd->left instanceof BooleanAnd) { + if (! $this->isNotEqualOrNotIdentical($currentBooleanAnd->left->right)) { + return null; + } + + /** @var NotIdentical|NotEqual $leftRight */ + $leftRight = $currentBooleanAnd->left->right; + $comparedExprAndValueExprs[] = $this->matchComparedExprAndValueExpr($leftRight); + $currentBooleanAnd = $currentBooleanAnd->left; + } + + if (! $this->isNotEqualOrNotIdentical($currentBooleanAnd->left)) { + return null; + } + + /** @var NotIdentical|NotEqual $leftCompare */ + $leftCompare = $currentBooleanAnd->left; + + $comparedExprAndValueExprs[] = $this->matchComparedExprAndValueExpr($leftCompare); + + // keep original natural order, as left/right goes from bottom up + return array_reverse($comparedExprAndValueExprs); + } + + private function isStrictComparison(BooleanAnd $booleanAnd): bool + { + $notIdenticals = $this->betterNodeFinder->findInstanceOf($booleanAnd, NotIdentical::class); + $notEquals = $this->betterNodeFinder->findInstanceOf($booleanAnd, NotEqual::class); + + if ($notIdenticals !== []) { + // mix not identical and not equals, keep as is + // @see https://3v4l.org/2SoHZ + return $notEquals === []; + } + + return false; + } +} diff --git a/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php b/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php index ab12f207c6b..f6f9372a11b 100644 --- a/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php +++ b/rules/CodeQuality/Rector/BooleanOr/RepeatedOrEqualToInArrayRector.php @@ -32,7 +32,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Simplify repeated || compare of same value, to in_array() class', + 'Simplify repeated || compare of same value, to in_array() call', [ new CodeSample( <<<'CODE_SAMPLE' diff --git a/rules/Privatization/NodeManipulator/VisibilityManipulator.php b/rules/Privatization/NodeManipulator/VisibilityManipulator.php index 267391ae8e7..1166277f1c1 100644 --- a/rules/Privatization/NodeManipulator/VisibilityManipulator.php +++ b/rules/Privatization/NodeManipulator/VisibilityManipulator.php @@ -207,7 +207,7 @@ private function replaceVisibilityFlag(ClassMethod | Property | ClassConst | Par $this->makeNonStatic($node); } - if ($visibility !== Visibility::STATIC && $visibility !== Visibility::ABSTRACT && $visibility !== Visibility::FINAL) { + if (! in_array($visibility, [Visibility::STATIC, Visibility::ABSTRACT, Visibility::FINAL], true)) { $this->removeVisibility($node); } diff --git a/src/Config/Level/CodeQualityLevel.php b/src/Config/Level/CodeQualityLevel.php index 4b7a8c4cf22..3175e265825 100644 --- a/src/Config/Level/CodeQualityLevel.php +++ b/src/Config/Level/CodeQualityLevel.php @@ -6,6 +6,7 @@ use Rector\CodeQuality\Rector\Assign\CombinedAssignRector; use Rector\CodeQuality\Rector\BooleanAnd\RemoveUselessIsObjectCheckRector; +use Rector\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector; use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector; use Rector\CodeQuality\Rector\BooleanNot\ReplaceMultipleBooleanNotRector; use Rector\CodeQuality\Rector\BooleanNot\SimplifyDeMorganBinaryRector; @@ -107,6 +108,7 @@ final class CodeQualityLevel ReplaceMultipleBooleanNotRector::class, ForeachToInArrayRector::class, RepeatedOrEqualToInArrayRector::class, + RepeatedAndNotEqualToNotInArrayRector::class, SimplifyForeachToCoalescingRector::class, SimplifyFuncGetArgsCountRector::class, SimplifyInArrayValuesRector::class, diff --git a/src/ValueObject/Error/SystemError.php b/src/ValueObject/Error/SystemError.php index 213dd49e2d1..6f3d697a32d 100644 --- a/src/ValueObject/Error/SystemError.php +++ b/src/ValueObject/Error/SystemError.php @@ -83,7 +83,8 @@ public function getRectorClass(): ?string public function getRectorShortClass(): ?string { $rectorClass = $this->rectorClass; - if ($rectorClass !== null && $rectorClass !== '' && $rectorClass !== '0') { + + if (! in_array($rectorClass, [null, ''], true)) { return (string) Strings::after($rectorClass, '\\', -1); }