Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnBasicUsage
{
public function checkAllAnimalsStartWithC(array $animals)
{
foreach ($animals as $animal) {
if (!str_starts_with($animal, 'c')) {
return false;
}
}
return true;
}

public function checkAllNumbersGreaterThanTen(array $numbers)
{
foreach ($numbers as $number) {
if (!($number > 10)) {
return false;
}
}
return true;
}

public function checkAllItemsEqualTarget(array $items)
{
foreach ($items as $key => $value) {
if (!($value === 'target')) {
return false;
}
}
return true;
}
}

?>
-----
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnBasicUsage
{
public function checkAllAnimalsStartWithC(array $animals)
{
return array_all($animals, fn($animal) => 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');
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnSkipMissingReturn
{
public function run(array $animals)
{
foreach ($animals as $animal) {
if (! str_starts_with($animal, 'c')) {
return false;
}
}
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnSkipMultipleStatements
{
public function run(array $animals)
{
foreach ($animals as $animal) {
if (! str_starts_with($animal, 'c')) {
return false;
}
}
echo 'ok';
return true;
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnSkipMultipleStatementsWithinIf
{
public function run(array $animals)
{
foreach ($animals as $animal) {
if (! str_starts_with($animal, 'c')) {
echo 'ok';
return false;
}
}
return true;
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnSkipNestedIfStatements
{
public function run(array $animals)
{
foreach ($animals as $animal) {
if(str_starts_with($animal, 'foo')){
if (! str_starts_with($animal, 'c')) {
echo 'ok';
return false;
}
}
}
return true;
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnSkipNonArray
{
public function run(\ArrayIterator $animals)
{
foreach ($animals as $animal) {
if (! str_starts_with($animal, 'c')) {
echo 'ok';
return false;
}
}
return true;
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnSkipUnexpectedReturnType
{
public function run(array $animals)
{
foreach ($animals as $animal) {
if (! str_starts_with($animal, 'c')) {
return 'ok';
}
}
return true;
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnWithKey
{
public function runWithKey(array $animals, array $params)
{
foreach ($animals as $k => $v) {
if (!isset($params[$k]) || (string) $params[$k] !== (string) $v) {
return false;
}
}
return true;
}
}

?>
-----
<?php

namespace Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\Fixture;

class EarlyReturnWithKey
{
public function runWithKey(array $animals, array $params)
{
return array_all($animals, fn($v, $k) => !(!isset($params[$k]) || (string) $params[$k] !== (string) $v));
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ class WithKey
}
}

?>
?>
129 changes: 124 additions & 5 deletions rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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'
Expand All @@ -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
),
]
Expand All @@ -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;
Expand Down Expand Up @@ -104,7 +131,7 @@ public function refactor(Node $node): ?Node

$assignedVariable = $prevAssign->var;

if (! $this->isValidForeachStructure($foreach, $assignedVariable)) {
if (! $this->isValidBooleanAssignmentForeachStructure($foreach, $assignedVariable)) {
continue;
}

Expand Down Expand Up @@ -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;
Expand Down
Loading