[CodeQuality] Remove MoveVariableDeclarationNearReferenceRector for too wide domain, not capable on most of real projects (#587)

This commit is contained in:
Tomas Votruba 2021-08-03 19:55:29 +02:00 committed by GitHub
parent aff0c5d90c
commit 0a51a73ad6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1 additions and 1150 deletions

View File

@ -3,7 +3,6 @@
declare(strict_types=1);
use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
use Rector\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector;
use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector;
use Rector\CodingStyle\Rector\MethodCall\UseMessageVariableForSprintfInSymfonyStyleRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
@ -11,7 +10,6 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(CountArrayToEmptyArrayComparisonRector::class);
$services->set(MoveVariableDeclarationNearReferenceRector::class);
$services->set(UseMessageVariableForSprintfInSymfonyStyleRector::class);
$services->set(FlipTypeControlToUseExclusiveTypeRector::class);
};

View File

@ -25,6 +25,7 @@ final class SetList implements SetListInterface
public const CODE_QUALITY = __DIR__ . '/../../../config/set/code-quality.php';
/**
* @deprecated Use only/directly CODE_QUALITY instead
* @var string
*/
public const CODE_QUALITY_STRICT = __DIR__ . '/../../../config/set/code-quality-strict.php';

View File

@ -1,33 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class Fixture
{
function myMethod()
{
$var = 1;
if (mktime() === false) {
return $var;
}
}
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class Fixture
{
function myMethod()
{
if (mktime() === false) {
$var = 1;
return $var;
}
}
}
?>

View File

@ -1,35 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class FixtureUsedOnlyAfterIf
{
function myMethod()
{
$var = 1;
if (mktime() === false) {
null;
}
return $var;
}
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class FixtureUsedOnlyAfterIf
{
function myMethod()
{
if (mktime() === false) {
null;
}
$var = 1;
return $var;
}
}
?>

View File

@ -1,31 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function variableInClosure()
{
$closure = function () {
$var = 1;
if (mktime() === false) {
return $var;
}
};
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function variableInClosure()
{
$closure = function () {
if (mktime() === false) {
$var = 1;
return $var;
}
};
}
?>

View File

@ -1,31 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function variableInClosureWithReturn()
{
return function () {
$var = 1;
if (mktime() === false) {
return $var;
}
};
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function variableInClosureWithReturn()
{
return function () {
if (mktime() === false) {
$var = 1;
return $var;
}
};
}
?>

View File

@ -1,29 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function variableWithMultipleUsages()
{
$var = 1;
if (mktime() === false) {
echo $var;
return $var;
}
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function variableWithMultipleUsages()
{
if (mktime() === false) {
$var = 1;
echo $var;
return $var;
}
}
?>

View File

@ -1,28 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipArrayDimFetchNext
{
private function createArgsFromItems(array $items, ?string $silentKey = null): array
{
$args = [];
if ($silentKey !== null && isset($items[$silentKey])) {
$args[] = 1;
}
if (true) {
foreach ($items as $key => $value) {
$args[] = 2;
}
} else {
foreach ($items as $value) {
$args[] = 3;
}
}
return $args;
}
}
?>

View File

@ -1,19 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipArrayDimFetchNextAsKey
{
private function foo($data, $aliasName)
{
$loweredAliasName = strtolower($aliasName);
if (isset($data[$loweredAliasName])) {
return true;
}
echo $loweredAliasName;
}
}
?>

View File

@ -1,13 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
final class SkipAssignExprArrayDimFetch
{
public function run()
{
$value = $values[$i];
++$i;
return $value;
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipDeclaredInExtraParentPrevious
{
function myMethod()
{
$absoluteDirectories = [];
foreach ($directories as $directory) {
if (Strings::contains($directory, '*')) {
$absoluteDirectories = array_merge($absoluteDirectories, glob($directory, GLOB_ONLYDIR));
} else {
$this->ensureDirectoryExists($directory);
$absoluteDirectories[] = $directory;
}
}
}
}
?>

View File

@ -1,15 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipExprReused
{
function myMethod($x)
{
$var = $x;
$x = 1;
echo $var;
}
}
?>

View File

@ -1,19 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipExprVarUsed
{
function myMethod()
{
$var = run($a, $b);
$a = 1;
$b = 2;
if (mktime() === false) {
return $var;
}
}
}
?>

View File

@ -1,17 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipInsideCondition
{
function myMethod($node)
{
if (mktime() === false) {
$node = 1;
} else {
echo $node;
}
}
}
?>

View File

@ -1,17 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipInsideLoop
{
function myMethod()
{
$var = rand(0, 1);
for ($i = 0; $i <= 10; $i++) {
echo $var + $i;
}
}
}
?>

View File

@ -1,14 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
function test() {
$title = 'abc';
?>
<h1>
<?php echo $title; ?>
</h1>
<?php
}
?>

View File

@ -1,19 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
/**
* @see https://github.com/codeigniter4/CodeIgniter4/blob/2c3f9897762fba3c18f95237686c1297822cdff4/tests/system/Database/Live/FabricatorLiveTest.php#L68-L72
*/
class SkipNonNativeFunction
{
function myMethod()
{
$count = Fabricator::getCount('user');
fake(UserModel::class, ['country' => 'Italy']);
$count !== Fabricator::getCount('user');
}
}
?>

View File

@ -1,17 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipNotUsed
{
function myMethod()
{
$var = 1;
if (mktime() === false) {
}
}
}
?>

View File

@ -1,16 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
final class SkipObGetContents
{
public function run()
{
ob_start();
$git->status();
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
}

View File

@ -1,13 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
final class SkipPassBcdiv
{
public function run($data, $options)
{
$a = bcscale(3);
echo bcdiv('105', '6.55957');
echo $a;
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
final class SkipPassJsonLastError
{
public function run($data, $options)
{
$result = json_encode($data, $options, 512);
if (json_last_error()) {
throw new \Exception();
}
echo $result;
}
}

View File

@ -1,20 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipPropertyInExpr
{
function myMethod()
{
$var = $this->run($this->a->b, $this->c->d);
if (true) {
$this->a = new \stdClass;
$this->b = new \stdClass;
echo $var;
}
}
}
?>

View File

@ -1,16 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipReassign
{
function myMethod()
{
$var = 1;
if (mktime() === false) {
$var = 2;
}
}
}
?>

View File

@ -1,14 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsageInMethodCall
{
function myMethod()
{
$crawler = $kernelBrowser->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertStringContainsString('Hello World', $crawler->filter('h1, h2, h3')->text());
}
}
?>

View File

@ -1,22 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsageInMultipleLevels
{
function myMethod()
{
$varused = 1;
if (mktime() === false) {
if (true) {
if (false) {
return $varused;
}
}
}
echo $varused;
}
}
?>

View File

@ -1,24 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsageInMultipleLevelsHasStmtBetween
{
function myMethod()
{
$varused = 1;
if (mktime() === false) {
if (true) {
if (false) {
return $varused;
}
}
}
echo 'STATEMENT HERE';
echo $varused;
}
}
?>

View File

@ -1,14 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsageInStaticCall
{
function myMethod()
{
$crawler = $kernelBrowser->request('GET', '/');
self::assertResponseIsSuccessful();
self::assertStringContainsString('Hello World', $crawler->filter('h1, h2, h3')->text());
}
}
?>

View File

@ -1,22 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsedInCatch
{
function myMethod()
{
$position = getPosition();
try {
execute();
echo $position;
} catch (\Exception $e) {
echo $position + 1;
} finally {
echo $position + 2;
}
}
}
?>

View File

@ -1,20 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsedInNextCaseOfSwitch
{
function myMethod()
{
$do_sfe = $do_trt->get_sfe_do();
switch($do_trt->get_mar_do()->get_cod_s()) {
case 'MAR1':
return class1::get_price_all($do_sfe);
case 'MAR2':
return class1::get_price_all2($do_sfe);
}
}
}
?>

View File

@ -1,21 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsedInNextElse
{
function myMethod()
{
$position = getPosition();
if (rand(0, 1) === 1) {
echo $position;
} elseif (rand(0, 1) === 0) {
echo $position + 1;
} else {
echo $position + 2;
}
}
}
?>

View File

@ -1,17 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsedInNextTernary
{
function myMethod()
{
$position = getPosition();
rand(0, 1) === 1
? $position
: $position + 1;
}
}
?>

View File

@ -1,20 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
class SkipUsedMultipleTimes
{
function myMethod()
{
$var = 1;
if (mktime() === false) {
return $var;
}
if (mktime() === true) {
return $var + 1;
}
}
}
?>

View File

@ -1,39 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
use Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Source\MyException;
class StaticCallNextException
{
function myMethod()
{
$var = do_something();
if (mktime() === false) {
throw MyException::notFound();
}
echo $var;
}
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Fixture;
use Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Source\MyException;
class StaticCallNextException
{
function myMethod()
{
if (mktime() === false) {
throw MyException::notFound();
}
$var = do_something();
echo $var;
}
}
?>

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class MoveVariableDeclarationNearReferenceRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\Source;
class MyException extends \Exception
{
public static function notFound()
{
return new self('Page not found');
}
}

View File

@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(MoveVariableDeclarationNearReferenceRector::class);
};

View File

@ -1,244 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Rector\Variable;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\For_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\While_;
use Rector\CodeQuality\UsageFinder\UsageInNextStmtFinder;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeNestingScope\NodeFinder\ScopeAwareNodeFinder;
use Rector\NodeNestingScope\ParentFinder;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\CodeQuality\Rector\Variable\MoveVariableDeclarationNearReferenceRector\MoveVariableDeclarationNearReferenceRectorTest
*/
final class MoveVariableDeclarationNearReferenceRector extends AbstractRector
{
public function __construct(
private ScopeAwareNodeFinder $scopeAwareNodeFinder,
private ParentFinder $parentFinder,
private UsageInNextStmtFinder $usageInNextStmtFinder
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Move variable declaration near its reference',
[
new CodeSample(
<<<'CODE_SAMPLE'
$var = 1;
if ($condition === null) {
return $var;
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
if ($condition === null) {
$var = 1;
return $var;
}
CODE_SAMPLE
),
]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Variable::class];
}
/**
* @param Variable $node
*/
public function refactor(Node $node): ?Node
{
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if (! ($parent instanceof Assign && $parent->var === $node)) {
return null;
}
if ($parent->expr instanceof ArrayDimFetch) {
return null;
}
$expression = $parent->getAttribute(AttributeKey::PARENT_NODE);
if (! $expression instanceof Expression) {
return null;
}
if ($this->isUsedAsArraykeyOrInsideIfCondition($expression, $node)) {
return null;
}
if ($this->hasPropertyInExpr($parent->expr)) {
return null;
}
if ($this->shouldSkipReAssign($expression, $parent)) {
return null;
}
$usageStmt = $this->findUsageStmt($expression, $node);
if (! $usageStmt instanceof Node) {
return null;
}
if ($this->isInsideLoopStmts($usageStmt)) {
return null;
}
$this->addNodeBeforeNode($expression, $usageStmt);
$this->removeNode($expression);
return $node;
}
private function isUsedAsArraykeyOrInsideIfCondition(Expression $expression, Variable $variable): bool
{
$parentExpression = $expression->getAttribute(AttributeKey::PARENT_NODE);
if ($this->isUsedAsArrayKey($parentExpression, $variable)) {
return true;
}
return $this->isInsideCondition($expression);
}
private function hasPropertyInExpr(Expr $expr): bool
{
return (bool) $this->betterNodeFinder->findFirst(
$expr,
fn (Node $node): bool => $node instanceof PropertyFetch || $node instanceof StaticPropertyFetch
);
}
private function shouldSkipReAssign(Expression $expression, Assign $assign): bool
{
if ($this->hasReAssign($expression, $assign->var)) {
return true;
}
return $this->hasReAssign($expression, $assign->expr);
}
private function isInsideLoopStmts(Node $node): bool
{
$loopNode = $this->parentFinder->findByTypes(
$node,
[For_::class, While_::class, Foreach_::class, Do_::class]
);
return (bool) $loopNode;
}
private function isUsedAsArrayKey(?Node $node, Variable $variable): bool
{
if (! $node instanceof Node) {
return false;
}
/** @var ArrayDimFetch[] $arrayDimFetches */
$arrayDimFetches = $this->betterNodeFinder->findInstanceOf($node, ArrayDimFetch::class);
foreach ($arrayDimFetches as $arrayDimFetch) {
/** @var Node|null $dim */
$dim = $arrayDimFetch->dim;
if (! $dim instanceof Node) {
continue;
}
$isFoundInKey = (bool) $this->betterNodeFinder->findFirst(
$dim,
fn (Node $node): bool => $this->nodeComparator->areNodesEqual($node, $variable)
);
if ($isFoundInKey) {
return true;
}
}
return false;
}
private function isInsideCondition(Expression $expression): bool
{
return (bool) $this->scopeAwareNodeFinder->findParentType(
$expression,
[If_::class, Else_::class, ElseIf_::class]
);
}
private function hasReAssign(Expression $expression, Expr $expr): bool
{
$next = $expression->getAttribute(AttributeKey::NEXT_NODE);
$exprValues = $this->betterNodeFinder->find($expr, fn (Node $node): bool => $node instanceof Variable);
if ($exprValues === []) {
return false;
}
while ($next) {
foreach ($exprValues as $exprValue) {
$isReAssign = (bool) $this->betterNodeFinder->findFirst($next, function (Node $node): bool {
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if (! $parent instanceof Assign) {
return false;
}
$node = $this->mayBeArrayDimFetch($node);
return (string) $this->getName($node) === (string) $this->getName($parent->var);
});
if (! $isReAssign) {
continue;
}
return true;
}
$next = $next->getAttribute(AttributeKey::NEXT_NODE);
}
return false;
}
private function mayBeArrayDimFetch(Node $node): Node
{
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parent instanceof ArrayDimFetch) {
$node = $parent->var;
}
return $node;
}
private function findUsageStmt(Expression $expression, Variable $variable): Node | null
{
$nextVariable = $this->usageInNextStmtFinder->getUsageInNextStmts($expression, $variable);
if (! $nextVariable instanceof Variable) {
return null;
}
return $nextVariable->getAttribute(AttributeKey::CURRENT_STATEMENT);
}
}

View File

@ -1,195 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\UsageFinder;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\InlineHTML;
use PhpParser\Node\Stmt\Switch_;
use PhpParser\Node\Stmt\TryCatch;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\DeadCode\SideEffect\SideEffectNodeDetector;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class UsageInNextStmtFinder
{
public function __construct(
private SideEffectNodeDetector $sideEffectNodeDetector,
private BetterNodeFinder $betterNodeFinder,
private NodeNameResolver $nodeNameResolver
) {
}
public function getUsageInNextStmts(Expression $expression, Variable $variable): ?Variable
{
/** @var Node|null $next */
$next = $expression->getAttribute(AttributeKey::NEXT_NODE);
if (! $next instanceof Node) {
return null;
}
if ($next instanceof InlineHTML) {
return null;
}
if ($this->hasCall($next)) {
return null;
}
$countFound = $this->getCountFound($next, $variable);
if ($countFound === 0) {
return null;
}
if ($countFound >= 2) {
return null;
}
$nextVariable = $this->getSameVarName([$next], $variable);
if ($nextVariable instanceof Variable) {
return $nextVariable;
}
return $this->getSameVarNameInNexts($next, $variable);
}
private function hasCall(Node $node): bool
{
return (bool) $this->betterNodeFinder->findFirst(
$node,
fn (Node $n): bool => $this->sideEffectNodeDetector->detectCallExpr($n)
);
}
private function getCountFound(Node $node, Variable $variable): int
{
$countFound = 0;
while ($node) {
$isFound = (bool) $this->getSameVarName([$node], $variable);
if ($isFound) {
++$countFound;
}
$countFound = $this->countWithElseIf($node, $variable, $countFound);
$countFound = $this->countWithTryCatch($node, $variable, $countFound);
$countFound = $this->countWithSwitchCase($node, $variable, $countFound);
/** @var Node|null $node */
$node = $node->getAttribute(AttributeKey::NEXT_NODE);
}
return $countFound;
}
private function getSameVarNameInNexts(Node $node, Variable $variable): ?Variable
{
while ($node) {
$found = $this->getSameVarName([$node], $variable);
if ($found instanceof Variable) {
return $found;
}
/** @var Node|null $node */
$node = $node->getAttribute(AttributeKey::NEXT_NODE);
}
return null;
}
private function countWithElseIf(Node $node, Variable $variable, int $countFound): int
{
if (! $node instanceof If_) {
return $countFound;
}
$isFoundElseIf = (bool) $this->getSameVarName($node->elseifs, $variable);
$isFoundElse = (bool) $this->getSameVarName([$node->else], $variable);
if ($isFoundElseIf || $isFoundElse) {
++$countFound;
}
return $countFound;
}
private function countWithTryCatch(Node $node, Variable $variable, int $countFound): int
{
if (! $node instanceof TryCatch) {
return $countFound;
}
$isFoundInCatch = (bool) $this->getSameVarName($node->catches, $variable);
$isFoundInFinally = (bool) $this->getSameVarName([$node->finally], $variable);
if ($isFoundInCatch || $isFoundInFinally) {
++$countFound;
}
return $countFound;
}
private function countWithSwitchCase(Node $node, Variable $variable, int $countFound): int
{
if (! $node instanceof Switch_) {
return $countFound;
}
$isFoundInCases = (bool) $this->getSameVarName($node->cases, $variable);
if ($isFoundInCases) {
++$countFound;
}
return $countFound;
}
/**
* @param array<int, Node|null> $multiNodes
*/
private function getSameVarName(array $multiNodes, Variable $variable): ?Variable
{
foreach ($multiNodes as $multiNode) {
if ($multiNode === null) {
continue;
}
/** @var Variable|null $found */
$found = $this->betterNodeFinder->findFirst($multiNode, function (Node $currentNode) use ($variable): bool {
$currentNode = $this->unwrapArrayDimFetch($currentNode);
if (! $currentNode instanceof Variable) {
return false;
}
return $this->nodeNameResolver->isName(
$currentNode,
(string) $this->nodeNameResolver->getName($variable)
);
});
if ($found !== null) {
return $found;
}
}
return null;
}
private function unwrapArrayDimFetch(Node $node): Node
{
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
while ($parent instanceof ArrayDimFetch) {
$node = $parent->var;
$parent = $parent->getAttribute(AttributeKey::PARENT_NODE);
}
return $node;
}
}