diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_basic_usage.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_basic_usage.php.inc new file mode 100644 index 00000000000..fcc73afcb43 --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_basic_usage.php.inc @@ -0,0 +1,62 @@ + 10)) { + return false; + } + } + return true; + } + + public function checkAllItemsEqualTarget(array $items) + { + foreach ($items as $key => $value) { + if (!($value === 'target')) { + return false; + } + } + return true; + } +} + +?> +----- + str_starts_with($animal, 'c')); + } + + public function checkAllNumbersGreaterThanTen(array $numbers) + { + return array_all($numbers, fn($number) => $number > 10); + } + + public function checkAllItemsEqualTarget(array $items) + { + return array_all($items, fn($value) => $value === 'target'); + } +} + +?> diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_missing_return_true.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_missing_return_true.php.inc new file mode 100644 index 00000000000..b1c4cedff81 --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_missing_return_true.php.inc @@ -0,0 +1,17 @@ + diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_multiple_statements_after_foreach.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_multiple_statements_after_foreach.php.inc new file mode 100644 index 00000000000..812362336a3 --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_multiple_statements_after_foreach.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_multiple_statements_within_if_statement.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_multiple_statements_within_if_statement.php.inc new file mode 100644 index 00000000000..98bdf075a4b --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_multiple_statements_within_if_statement.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_nested_if_statements.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_nested_if_statements.php.inc new file mode 100644 index 00000000000..89a0b47f702 --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_nested_if_statements.php.inc @@ -0,0 +1,20 @@ + diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_non_array.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_non_array.php.inc new file mode 100644 index 00000000000..0e99c69ffad --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_non_array.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_unexpected_return_type.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_unexpected_return_type.php.inc new file mode 100644 index 00000000000..d97e9a2f02e --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_skip_unexpected_return_type.php.inc @@ -0,0 +1,17 @@ + diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_with_key.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_with_key.php.inc new file mode 100644 index 00000000000..225ada3af3f --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/early_return_with_key.php.inc @@ -0,0 +1,32 @@ + $v) { + if (!isset($params[$k]) || (string) $params[$k] !== (string) $v) { + return false; + } + } + return true; + } +} + +?> +----- + !(!isset($params[$k]) || (string) $params[$k] !== (string) $v)); + } +} + +?> diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/with_key.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/with_key.php.inc index f529df69767..8e0a9fac68b 100644 --- a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/with_key.php.inc +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/with_key.php.inc @@ -32,4 +32,4 @@ class WithKey } } -?> \ No newline at end of file +?> diff --git a/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php b/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php index 96629535526..de0c9230f88 100644 --- a/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php +++ b/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php @@ -5,6 +5,7 @@ namespace Rector\Php84\Rector\Foreach_; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\BooleanNot; @@ -14,6 +15,7 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Foreach_; use PhpParser\Node\Stmt\If_; +use PhpParser\Node\Stmt\Return_; use Rector\Contract\PhpParser\Node\StmtsAwareInterface; use Rector\NodeManipulator\StmtsManipulator; use Rector\Php84\NodeAnalyzer\ForeachKeyUsedInConditionalAnalyzer; @@ -39,7 +41,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Replace foreach with boolean assignment and break with array_all', + 'Replace foreach with boolean assignment and break OR foreach with early return with array_all', [ new CodeSample( <<<'CODE_SAMPLE' @@ -54,6 +56,20 @@ public function getRuleDefinition(): RuleDefinition , <<<'CODE_SAMPLE' $found = array_all($animals, fn($animal) => str_starts_with($animal, 'c')); +CODE_SAMPLE + ), + new CodeSample( + <<<'CODE_SAMPLE' +foreach ($animals as $animal) { + if (!str_starts_with($animal, 'c')) { + return false; + } +} +return true; +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +return array_all($animals, fn($animal) => str_starts_with($animal, 'c')); CODE_SAMPLE ), ] @@ -77,6 +93,17 @@ public function refactor(Node $node): ?Node return null; } + return $this->refactorBooleanAssignmentPattern($node) ?? + $this->refactorEarlyReturnPattern($node); + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::ARRAY_ALL; + } + + private function refactorBooleanAssignmentPattern(StmtsAwareInterface $node): ?Node + { foreach ($node->stmts as $key => $stmt) { if (! $stmt instanceof Foreach_) { continue; @@ -104,7 +131,7 @@ public function refactor(Node $node): ?Node $assignedVariable = $prevAssign->var; - if (! $this->isValidForeachStructure($foreach, $assignedVariable)) { + if (! $this->isValidBooleanAssignmentForeachStructure($foreach, $assignedVariable)) { continue; } @@ -158,12 +185,104 @@ public function refactor(Node $node): ?Node return null; } - public function provideMinPhpVersion(): int + private function refactorEarlyReturnPattern(StmtsAwareInterface $node): ?Node { - return PhpVersionFeature::ARRAY_ALL; + foreach ($node->stmts as $key => $stmt) { + if (! $stmt instanceof Foreach_) { + continue; + } + + $foreach = $stmt; + $nextStmt = $node->stmts[$key + 1] ?? null; + + if (! $nextStmt instanceof Return_) { + continue; + } + + if (! $nextStmt->expr instanceof Expr) { + continue; + } + + if (! $this->valueResolver->isTrue($nextStmt->expr)) { + continue; + } + + if (! $this->isValidEarlyReturnForeachStructure($foreach)) { + continue; + } + + /** @var If_ $firstNodeInsideForeach */ + $firstNodeInsideForeach = $foreach->stmts[0]; + $condition = $firstNodeInsideForeach->cond; + + $params = []; + if ($foreach->valueVar instanceof Variable) { + $params[] = new Param($foreach->valueVar); + } + + if ( + $foreach->keyVar instanceof Variable && + $this->foreachKeyUsedInConditionalAnalyzer->isUsed($foreach->keyVar, $condition) + ) { + $params[] = new Param(new Variable((string) $this->getName($foreach->keyVar))); + } + + $negatedCondition = $condition instanceof BooleanNot ? $condition->expr : new BooleanNot($condition); + + $arrowFunction = new ArrowFunction([ + 'params' => $params, + 'expr' => $negatedCondition, + ]); + + $funcCall = $this->nodeFactory->createFuncCall('array_all', [$foreach->expr, $arrowFunction]); + + $node->stmts[$key] = new Return_($funcCall); + unset($node->stmts[$key + 1]); + $node->stmts = array_values($node->stmts); + + return $node; + } + + return null; + } + + private function isValidEarlyReturnForeachStructure(Foreach_ $foreach): bool + { + if (count($foreach->stmts) !== 1) { + return false; + } + + if (! $foreach->stmts[0] instanceof If_) { + return false; + } + + $ifStmt = $foreach->stmts[0]; + + if (count($ifStmt->stmts) !== 1) { + return false; + } + + if (! $ifStmt->stmts[0] instanceof Return_) { + return false; + } + + $returnStmt = $ifStmt->stmts[0]; + + if (! $returnStmt->expr instanceof Expr) { + return false; + } + + if (! $this->valueResolver->isFalse($returnStmt->expr)) { + return false; + } + + $type = $this->nodeTypeResolver->getNativeType($foreach->expr); + + return $type->isArray() + ->yes(); } - private function isValidForeachStructure(Foreach_ $foreach, Variable $assignedVariable): bool + private function isValidBooleanAssignmentForeachStructure(Foreach_ $foreach, Variable $assignedVariable): bool { if (count($foreach->stmts) !== 1) { return false;