2020-03-25 18:00:26 +00:00
|
|
|
<?php
|
|
|
|
|
2021-05-09 20:15:43 +00:00
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace Rector\DeadCode\Rector\If_;
|
2020-03-25 18:00:26 +00:00
|
|
|
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node;
|
2023-04-08 14:11:59 +00:00
|
|
|
use PhpParser\Node\Expr;
|
2023-10-18 08:05:23 +00:00
|
|
|
use PhpParser\Node\Expr\ArrayDimFetch;
|
2023-04-08 14:11:59 +00:00
|
|
|
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
|
2023-12-21 15:22:15 +00:00
|
|
|
use PhpParser\Node\Expr\Empty_;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Expr\Variable;
|
2022-08-05 09:27:15 +00:00
|
|
|
use PhpParser\Node\Stmt;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Stmt\Foreach_;
|
|
|
|
use PhpParser\Node\Stmt\If_;
|
2022-09-18 11:37:35 +00:00
|
|
|
use PHPStan\Analyser\Scope;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\DeadCode\NodeManipulator\CountManipulator;
|
|
|
|
use Rector\DeadCode\UselessIfCondBeforeForeachDetector;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\NodeAnalyzer\PropertyFetchAnalyzer;
|
|
|
|
use Rector\NodeManipulator\IfManipulator;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\Php\ReservedKeywordAnalyzer;
|
|
|
|
use Rector\Rector\AbstractScopeAwareRector;
|
2022-06-07 09:18:30 +00:00
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
2020-03-25 18:00:26 +00:00
|
|
|
/**
|
2021-03-12 22:20:25 +00:00
|
|
|
* @see \Rector\Tests\DeadCode\Rector\If_\RemoveUnusedNonEmptyArrayBeforeForeachRector\RemoveUnusedNonEmptyArrayBeforeForeachRectorTest
|
2020-03-25 18:00:26 +00:00
|
|
|
*/
|
2022-09-18 11:37:35 +00:00
|
|
|
final class RemoveUnusedNonEmptyArrayBeforeForeachRector extends AbstractScopeAwareRector
|
2020-03-25 18:00:26 +00:00
|
|
|
{
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\DeadCode\NodeManipulator\CountManipulator
|
2020-03-25 18:00:26 +00:00
|
|
|
*/
|
2021-05-10 23:39:21 +00:00
|
|
|
private $countManipulator;
|
2020-03-26 20:23:52 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2024-01-02 02:40:38 +00:00
|
|
|
* @var \Rector\NodeManipulator\IfManipulator
|
2020-03-26 20:23:52 +00:00
|
|
|
*/
|
2021-05-10 23:39:21 +00:00
|
|
|
private $ifManipulator;
|
2020-03-26 20:50:07 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\DeadCode\UselessIfCondBeforeForeachDetector
|
2020-03-26 20:50:07 +00:00
|
|
|
*/
|
2021-05-10 23:39:21 +00:00
|
|
|
private $uselessIfCondBeforeForeachDetector;
|
2021-08-24 05:31:50 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2024-01-02 02:40:38 +00:00
|
|
|
* @var \Rector\Php\ReservedKeywordAnalyzer
|
2021-08-24 05:31:50 +00:00
|
|
|
*/
|
|
|
|
private $reservedKeywordAnalyzer;
|
2022-11-10 07:49:28 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2024-01-02 02:40:38 +00:00
|
|
|
* @var \Rector\NodeAnalyzer\PropertyFetchAnalyzer
|
2022-11-10 07:49:28 +00:00
|
|
|
*/
|
|
|
|
private $propertyFetchAnalyzer;
|
|
|
|
public function __construct(CountManipulator $countManipulator, IfManipulator $ifManipulator, UselessIfCondBeforeForeachDetector $uselessIfCondBeforeForeachDetector, ReservedKeywordAnalyzer $reservedKeywordAnalyzer, PropertyFetchAnalyzer $propertyFetchAnalyzer)
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2021-05-10 23:39:21 +00:00
|
|
|
$this->countManipulator = $countManipulator;
|
2020-03-25 18:00:26 +00:00
|
|
|
$this->ifManipulator = $ifManipulator;
|
2020-03-26 20:23:52 +00:00
|
|
|
$this->uselessIfCondBeforeForeachDetector = $uselessIfCondBeforeForeachDetector;
|
2021-08-24 05:31:50 +00:00
|
|
|
$this->reservedKeywordAnalyzer = $reservedKeywordAnalyzer;
|
2022-11-10 07:49:28 +00:00
|
|
|
$this->propertyFetchAnalyzer = $propertyFetchAnalyzer;
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function getRuleDefinition() : RuleDefinition
|
2020-03-25 18:00:26 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
return new RuleDefinition('Remove unused if check to non-empty array before foreach of the array', [new CodeSample(<<<'CODE_SAMPLE'
|
2020-03-25 18:00:26 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$values = [];
|
|
|
|
if ($values !== []) {
|
|
|
|
foreach ($values as $value) {
|
|
|
|
echo $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2021-05-09 20:15:43 +00:00
|
|
|
, <<<'CODE_SAMPLE'
|
2020-03-25 18:00:26 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$values = [];
|
|
|
|
foreach ($values as $value) {
|
|
|
|
echo $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 08:23:13 +00:00
|
|
|
CODE_SAMPLE
|
2021-05-09 20:15:43 +00:00
|
|
|
)]);
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
|
|
|
/**
|
2021-02-27 00:06:15 +00:00
|
|
|
* @return array<class-string<Node>>
|
2020-03-25 18:00:26 +00:00
|
|
|
*/
|
2021-05-09 20:15:43 +00:00
|
|
|
public function getNodeTypes() : array
|
2020-03-25 18:00:26 +00:00
|
|
|
{
|
2023-04-14 08:11:59 +00:00
|
|
|
return [If_::class, StmtsAwareInterface::class];
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
|
|
|
/**
|
2023-04-14 08:11:59 +00:00
|
|
|
* @param If_|StmtsAwareInterface $node
|
|
|
|
* @return Stmt[]|Foreach_|StmtsAwareInterface|null
|
2020-03-25 18:00:26 +00:00
|
|
|
*/
|
2022-09-18 11:37:35 +00:00
|
|
|
public function refactorWithScope(Node $node, Scope $scope)
|
2020-03-25 18:00:26 +00:00
|
|
|
{
|
2023-04-14 08:11:59 +00:00
|
|
|
if ($node instanceof If_) {
|
2023-05-30 12:38:31 +00:00
|
|
|
return $this->refactorIf($node, $scope);
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
2023-04-14 08:11:59 +00:00
|
|
|
return $this->refactorStmtsAware($node);
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
2022-09-18 11:37:35 +00:00
|
|
|
private function isUselessBeforeForeachCheck(If_ $if, Scope $scope) : bool
|
2020-03-25 18:00:26 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$this->ifManipulator->isIfWithOnly($if, Foreach_::class)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
2020-03-26 20:23:52 +00:00
|
|
|
/** @var Foreach_ $foreach */
|
|
|
|
$foreach = $if->stmts[0];
|
|
|
|
$foreachExpr = $foreach->expr;
|
2023-10-14 13:25:46 +00:00
|
|
|
if ($this->shouldSkipForeachExpr($foreachExpr, $scope)) {
|
|
|
|
return \false;
|
2021-08-24 05:31:50 +00:00
|
|
|
}
|
2023-04-08 14:11:59 +00:00
|
|
|
$ifCond = $if->cond;
|
|
|
|
if ($ifCond instanceof BooleanAnd) {
|
|
|
|
return $this->isUselessBooleanAnd($ifCond, $foreachExpr);
|
|
|
|
}
|
|
|
|
if (($ifCond instanceof Variable || $this->propertyFetchAnalyzer->isPropertyFetch($ifCond)) && $this->nodeComparator->areNodesEqual($ifCond, $foreachExpr)) {
|
2023-10-14 12:51:53 +00:00
|
|
|
$ifType = $scope->getNativeType($ifCond);
|
2023-04-08 14:11:59 +00:00
|
|
|
return $ifType->isArray()->yes();
|
2022-11-10 07:49:28 +00:00
|
|
|
}
|
2020-03-26 20:23:52 +00:00
|
|
|
if ($this->uselessIfCondBeforeForeachDetector->isMatchingNotIdenticalEmptyArray($if, $foreachExpr)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
2022-09-18 11:37:35 +00:00
|
|
|
if ($this->uselessIfCondBeforeForeachDetector->isMatchingNotEmpty($if, $foreachExpr, $scope)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-03-26 20:50:07 +00:00
|
|
|
}
|
|
|
|
return $this->countManipulator->isCounterHigherThanOne($if->cond, $foreachExpr);
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|
2023-04-08 14:11:59 +00:00
|
|
|
private function isUselessBooleanAnd(BooleanAnd $booleanAnd, Expr $foreachExpr) : bool
|
|
|
|
{
|
|
|
|
if (!$booleanAnd->left instanceof Variable) {
|
|
|
|
return \false;
|
|
|
|
}
|
|
|
|
if (!$this->nodeComparator->areNodesEqual($booleanAnd->left, $foreachExpr)) {
|
|
|
|
return \false;
|
|
|
|
}
|
|
|
|
return $this->countManipulator->isCounterHigherThanOne($booleanAnd->right, $foreachExpr);
|
|
|
|
}
|
2023-04-14 08:11:59 +00:00
|
|
|
private function refactorStmtsAware(StmtsAwareInterface $stmtsAware) : ?StmtsAwareInterface
|
|
|
|
{
|
|
|
|
if ($stmtsAware->stmts === null) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-05-30 12:38:31 +00:00
|
|
|
foreach ($stmtsAware->stmts as $key => $stmt) {
|
|
|
|
if (!$stmt instanceof Foreach_) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$previousStmt = $stmtsAware->stmts[$key - 1] ?? null;
|
|
|
|
if (!$previousStmt instanceof If_) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// not followed by any stmts
|
|
|
|
$nextStmt = $stmtsAware->stmts[$key + 1] ?? null;
|
|
|
|
if ($nextStmt instanceof Stmt) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!$this->uselessIfCondBeforeForeachDetector->isMatchingEmptyAndForeachedExpr($previousStmt, $stmt->expr)) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-12-21 15:22:15 +00:00
|
|
|
/** @var Empty_ $empty */
|
|
|
|
$empty = $previousStmt->cond;
|
|
|
|
// scope need to be pulled from Empty_ node to ensure it get correct type
|
|
|
|
$scope = $empty->getAttribute(AttributeKey::SCOPE);
|
|
|
|
if (!$scope instanceof Scope) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$ifType = $scope->getNativeType($empty->expr);
|
|
|
|
if (!$ifType->isArray()->yes()) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-30 12:38:31 +00:00
|
|
|
unset($stmtsAware->stmts[$key - 1]);
|
|
|
|
return $stmtsAware;
|
2023-04-14 08:11:59 +00:00
|
|
|
}
|
2023-05-30 12:38:31 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
private function refactorIf(If_ $if, Scope $scope) : ?Foreach_
|
|
|
|
{
|
|
|
|
if (!$this->isUselessBeforeForeachCheck($if, $scope)) {
|
2023-04-14 08:11:59 +00:00
|
|
|
return null;
|
|
|
|
}
|
2023-05-30 12:38:31 +00:00
|
|
|
/** @var Foreach_ $stmt */
|
|
|
|
$stmt = $if->stmts[0];
|
|
|
|
$ifComments = $if->getAttribute(AttributeKey::COMMENTS) ?? [];
|
|
|
|
$stmtComments = $stmt->getAttribute(AttributeKey::COMMENTS) ?? [];
|
|
|
|
$comments = \array_merge($ifComments, $stmtComments);
|
|
|
|
$stmt->setAttribute(AttributeKey::COMMENTS, $comments);
|
|
|
|
return $stmt;
|
2023-04-14 08:11:59 +00:00
|
|
|
}
|
2023-10-14 13:25:46 +00:00
|
|
|
private function shouldSkipForeachExpr(Expr $foreachExpr, Scope $scope) : bool
|
|
|
|
{
|
2023-10-15 02:06:32 +00:00
|
|
|
if ($foreachExpr instanceof ArrayDimFetch && $foreachExpr->dim !== null) {
|
2023-10-14 13:25:46 +00:00
|
|
|
$exprType = $this->nodeTypeResolver->getNativeType($foreachExpr->var);
|
|
|
|
$dimType = $this->nodeTypeResolver->getNativeType($foreachExpr->dim);
|
|
|
|
if (!$exprType->hasOffsetValueType($dimType)->yes()) {
|
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($foreachExpr instanceof Variable) {
|
|
|
|
$variableName = $this->nodeNameResolver->getName($foreachExpr);
|
|
|
|
if (\is_string($variableName) && $this->reservedKeywordAnalyzer->isNativeVariable($variableName)) {
|
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
$ifType = $scope->getNativeType($foreachExpr);
|
|
|
|
if (!$ifType->isArray()->yes()) {
|
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return \false;
|
|
|
|
}
|
2020-03-25 18:00:26 +00:00
|
|
|
}
|