diff --git a/.github/typos.toml b/.github/typos.toml index d82ac671549..89bad12e629 100644 --- a/.github/typos.toml +++ b/.github/typos.toml @@ -7,6 +7,8 @@ nd = "nd" # plurals Identicals = "Identicals" +Ands = "Ands" +Ors = "Ors" # native function names writeable = "writeable" diff --git a/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/empty_in_array.php.inc b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/empty_in_array.php.inc new file mode 100644 index 00000000000..f78bfb00341 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/empty_in_array.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/fixture.php.inc b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..5cb58498ec0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/fixture.php.inc @@ -0,0 +1,29 @@ + 20 && $b <= 50); + $d = !($a === null && $b < $c); + } +} + +?> +----- + 50; + $d = $a !== null || $b >= $c; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/skip_nested_and.php.inc b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/skip_nested_and.php.inc new file mode 100644 index 00000000000..d3187fdcd6b --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/skip_nested_and.php.inc @@ -0,0 +1,11 @@ + 20 && $b < $c && $a !== null); + } +} diff --git a/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/skip_or.php.inc b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/skip_or.php.inc new file mode 100644 index 00000000000..77882a8e51f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/Fixture/skip_or.php.inc @@ -0,0 +1,11 @@ + 20 || $b <= 50); + } +} diff --git a/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/NegatedAndsToPositiveOrsRectorTest.php b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/NegatedAndsToPositiveOrsRectorTest.php new file mode 100644 index 00000000000..592b97867dc --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/NegatedAndsToPositiveOrsRectorTest.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/BooleanNot/NegatedAndsToPositiveOrsRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/config/configured_rule.php new file mode 100644 index 00000000000..4f32b24a5ec --- /dev/null +++ b/rules-tests/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([NegatedAndsToPositiveOrsRector::class]); diff --git a/rules/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector.php b/rules/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector.php new file mode 100644 index 00000000000..78bc8c11481 --- /dev/null +++ b/rules/CodeQuality/Rector/BooleanNot/NegatedAndsToPositiveOrsRector.php @@ -0,0 +1,67 @@ + 20 && $b <= 50); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +$a = 5; +$b = 10; +$result = $a <= 20 || $b > 50; +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [BooleanNot::class]; + } + + /** + * @param BooleanNot $node + */ + public function refactor(Node $node): ?BinaryOp + { + if (! $node->expr instanceof BooleanAnd) { + return null; + } + + return $this->binaryOpManipulator->inverseBooleanAnd($node->expr); + } +} diff --git a/rules/DeadCode/Rector/TryCatch/RemoveDeadCatchRector.php b/rules/DeadCode/Rector/TryCatch/RemoveDeadCatchRector.php index d8859548602..d4fb951adde 100644 --- a/rules/DeadCode/Rector/TryCatch/RemoveDeadCatchRector.php +++ b/rules/DeadCode/Rector/TryCatch/RemoveDeadCatchRector.php @@ -110,7 +110,7 @@ private function isJustThrownSameVariable(Catch_ $catch): bool } $catchItemStmt = $catch->stmts[0]; - if (! ($catchItemStmt instanceof Expression && $catchItemStmt->expr instanceof Throw_)) { + if (!$catchItemStmt instanceof Expression || !$catchItemStmt->expr instanceof Throw_) { return false; } diff --git a/rules/DeadCode/Rector/TryCatch/RemoveDeadTryCatchRector.php b/rules/DeadCode/Rector/TryCatch/RemoveDeadTryCatchRector.php index de2d51d8883..5b28f56193c 100644 --- a/rules/DeadCode/Rector/TryCatch/RemoveDeadTryCatchRector.php +++ b/rules/DeadCode/Rector/TryCatch/RemoveDeadTryCatchRector.php @@ -87,7 +87,7 @@ public function refactor(Node $node): array|null|int } $onlyCatchStmt = $onlyCatch->stmts[0]; - if (! ($onlyCatchStmt instanceof Expression && $onlyCatchStmt->expr instanceof Throw_)) { + if (!$onlyCatchStmt instanceof Expression || !$onlyCatchStmt->expr instanceof Throw_) { return null; } diff --git a/rules/Php70/EregToPcreTransformer.php b/rules/Php70/EregToPcreTransformer.php index 4a27bbedfa1..eb41dc56ee2 100644 --- a/rules/Php70/EregToPcreTransformer.php +++ b/rules/Php70/EregToPcreTransformer.php @@ -234,7 +234,7 @@ private function processSquareBracket(string $s, int $i, int $l, string $cls, bo } else { $a = $s[$i]; ++$i; - if ($a === '-' && ! $start && ! ($i < $l && $s[$i] === ']')) { + if ($a === '-' && ! $start && ($i >= $l || $s[$i] !== ']')) { throw new InvalidEregException('"-" is invalid for the start character in the brackets'); } diff --git a/rules/Php74/Rector/ArrayDimFetch/CurlyToSquareBracketArrayStringRector.php b/rules/Php74/Rector/ArrayDimFetch/CurlyToSquareBracketArrayStringRector.php index a83d0a2c69e..83526554188 100644 --- a/rules/Php74/Rector/ArrayDimFetch/CurlyToSquareBracketArrayStringRector.php +++ b/rules/Php74/Rector/ArrayDimFetch/CurlyToSquareBracketArrayStringRector.php @@ -79,7 +79,7 @@ private function isFollowedByCurlyBracket(File $file, ArrayDimFetch $arrayDimFet if (isset($oldTokens[$endTokenPost]) && (string) $oldTokens[$endTokenPost] === '}') { $startTokenPos = $arrayDimFetch->getStartTokenPos(); - return ! (isset($oldTokens[$startTokenPos]) && (string) $oldTokens[$startTokenPos] === '${'); + return !isset($oldTokens[$startTokenPos]) || (string) $oldTokens[$startTokenPos] !== '${'; } return false; diff --git a/rules/Php80/NodeAnalyzer/MatchSwitchAnalyzer.php b/rules/Php80/NodeAnalyzer/MatchSwitchAnalyzer.php index 563ff2afe82..e59ae4980d9 100644 --- a/rules/Php80/NodeAnalyzer/MatchSwitchAnalyzer.php +++ b/rules/Php80/NodeAnalyzer/MatchSwitchAnalyzer.php @@ -71,7 +71,7 @@ public function shouldSkipSwitch(Switch_ $switch, array $condAndExprs, ?Stmt $ne return false; } - return ! ($nextStmt instanceof Expression && $nextStmt->expr instanceof Throw_); + return !$nextStmt instanceof Expression || !$nextStmt->expr instanceof Throw_; } /** diff --git a/rules/Php82/Rector/Encapsed/VariableInStringInterpolationFixerRector.php b/rules/Php82/Rector/Encapsed/VariableInStringInterpolationFixerRector.php index 3adf2a8cf47..9d2f60fdccc 100644 --- a/rules/Php82/Rector/Encapsed/VariableInStringInterpolationFixerRector.php +++ b/rules/Php82/Rector/Encapsed/VariableInStringInterpolationFixerRector.php @@ -56,7 +56,7 @@ public function refactor(Node $node): ?Node $hasChanged = false; foreach ($node->parts as $part) { - if (! $part instanceof Variable && ! ($part instanceof ArrayDimFetch && $part->var instanceof Variable)) { + if (! $part instanceof Variable && (!$part instanceof ArrayDimFetch || !$part->var instanceof Variable)) { continue; } diff --git a/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php b/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php index e22927f40e7..9434124cce5 100644 --- a/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php +++ b/rules/Removing/Rector/ClassMethod/ArgumentRemoverRector.php @@ -148,7 +148,7 @@ private function removeByName(ClassMethod | StaticCall | MethodCall $node, int $ return; } - if (! (isset($node->params[$position]) && $this->isName($node->params[$position], $name))) { + if (!isset($node->params[$position]) || !$this->isName($node->params[$position], $name)) { return; } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/StringReturnTypeFromStrictStringReturnsRector.php b/rules/TypeDeclaration/Rector/ClassMethod/StringReturnTypeFromStrictStringReturnsRector.php index 18439a43a02..b68438abb7a 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/StringReturnTypeFromStrictStringReturnsRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/StringReturnTypeFromStrictStringReturnsRector.php @@ -134,7 +134,7 @@ private function hasAlwaysStringScalarReturn(array $returns): bool { return array_all( $returns, - fn (Return_ $return): bool => ! (! $return->expr instanceof String_ && ! $return->expr instanceof InterpolatedString) + fn (Return_ $return): bool => $return->expr instanceof String_ || $return->expr instanceof InterpolatedString ); } diff --git a/src/Config/Level/CodeQualityLevel.php b/src/Config/Level/CodeQualityLevel.php index e8241448f0b..92ffa6a1970 100644 --- a/src/Config/Level/CodeQualityLevel.php +++ b/src/Config/Level/CodeQualityLevel.php @@ -10,6 +10,7 @@ use Rector\CodeQuality\Rector\BooleanAnd\RemoveUselessIsObjectCheckRector; use Rector\CodeQuality\Rector\BooleanAnd\RepeatedAndNotEqualToNotInArrayRector; use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector; +use Rector\CodeQuality\Rector\BooleanNot\NegatedAndsToPositiveOrsRector; use Rector\CodeQuality\Rector\BooleanNot\ReplaceConstantBooleanNotRector; use Rector\CodeQuality\Rector\BooleanNot\ReplaceMultipleBooleanNotRector; use Rector\CodeQuality\Rector\BooleanNot\SimplifyDeMorganBinaryRector; @@ -132,6 +133,7 @@ final class CodeQualityLevel UnnecessaryTernaryExpressionRector::class, RemoveExtraParametersRector::class, SimplifyDeMorganBinaryRector::class, + NegatedAndsToPositiveOrsRector::class, SimplifyTautologyTernaryRector::class, SingleInArrayToCompareRector::class, SimplifyIfElseToTernaryRector::class, diff --git a/src/NodeManipulator/BinaryOpManipulator.php b/src/NodeManipulator/BinaryOpManipulator.php index 3c16cb570e6..1ee6c9d2aa2 100644 --- a/src/NodeManipulator/BinaryOpManipulator.php +++ b/src/NodeManipulator/BinaryOpManipulator.php @@ -76,6 +76,23 @@ public function inverseBooleanOr(BooleanOr $booleanOr): ?BinaryOp return new $inversedNodeClass($firstInversedExpr, $secondInversedExpr); } + public function inverseBooleanAnd(BooleanAnd $booleanAnd): ?BinaryOp + { + // no nesting + if ($booleanAnd->left instanceof BooleanAnd) { + return null; + } + + if ($booleanAnd->right instanceof BooleanAnd) { + return null; + } + + $firstInversedExpr = $this->inverseNode($booleanAnd->left); + $secondInversedExpr = $this->inverseNode($booleanAnd->right); + + return new BooleanOr($firstInversedExpr, $secondInversedExpr); + } + public function invertCondition(BinaryOp $binaryOp): ?BinaryOp { // no nesting