rector/src/PhpParser/Node/BetterNodeFinder.php

365 lines
14 KiB
PHP
Raw Normal View History

2019-10-13 05:59:52 +00:00
<?php
declare (strict_types=1);
namespace Rector\Core\PhpParser\Node;
use PhpParser\Node;
2020-07-25 20:02:44 +00:00
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
2019-01-14 18:21:23 +00:00
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
2018-10-09 10:18:42 +00:00
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeFinder;
use Rector\Core\NodeAnalyzer\ClassAnalyzer;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeNestingScope\ParentScopeFinder;
use Rector\NodeTypeResolver\Node\AttributeKey;
use RectorPrefix20210822\Symplify\PackageBuilder\Php\TypeChecker;
use RectorPrefix20210822\Webmozart\Assert\Assert;
2019-09-03 09:11:45 +00:00
/**
* @see \Rector\Core\Tests\PhpParser\Node\BetterNodeFinder\BetterNodeFinderTest
2019-09-03 09:11:45 +00:00
*/
final class BetterNodeFinder
{
/**
* @var \RectorPrefix20210822\PhpParser\NodeFinder
*/
private $nodeFinder;
/**
* @var \Rector\NodeNameResolver\NodeNameResolver
*/
private $nodeNameResolver;
2020-07-25 20:02:44 +00:00
/**
* @var \RectorPrefix20210822\Symplify\PackageBuilder\Php\TypeChecker
2020-07-25 20:02:44 +00:00
*/
private $typeChecker;
/**
* @var \Rector\Core\PhpParser\Comparing\NodeComparator
*/
private $nodeComparator;
/**
* @var \Rector\Core\NodeAnalyzer\ClassAnalyzer
*/
private $classAnalyzer;
/**
* @var \Rector\NodeNestingScope\ParentScopeFinder
*/
private $parentScopeFinder;
public function __construct(\PhpParser\NodeFinder $nodeFinder, \Rector\NodeNameResolver\NodeNameResolver $nodeNameResolver, \RectorPrefix20210822\Symplify\PackageBuilder\Php\TypeChecker $typeChecker, \Rector\Core\PhpParser\Comparing\NodeComparator $nodeComparator, \Rector\Core\NodeAnalyzer\ClassAnalyzer $classAnalyzer, \Rector\NodeNestingScope\ParentScopeFinder $parentScopeFinder)
{
$this->nodeFinder = $nodeFinder;
$this->nodeNameResolver = $nodeNameResolver;
$this->typeChecker = $typeChecker;
$this->nodeComparator = $nodeComparator;
$this->classAnalyzer = $classAnalyzer;
$this->parentScopeFinder = $parentScopeFinder;
}
/**
2021-03-18 11:21:20 +00:00
* @template T of Node
2021-01-08 22:30:33 +00:00
* @param class-string<T> $type
* @return T|null
*/
public function findParentType(\PhpParser\Node $node, string $type) : ?\PhpParser\Node
2021-01-08 22:30:33 +00:00
{
\RectorPrefix20210822\Webmozart\Assert\Assert::isAOf($type, \PhpParser\Node::class);
$parent = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE);
if (!$parent instanceof \PhpParser\Node) {
2021-01-08 22:30:33 +00:00
return null;
}
do {
if (\is_a($parent, $type, \true)) {
2021-01-08 22:30:33 +00:00
return $parent;
}
if (!$parent instanceof \PhpParser\Node) {
2021-01-08 22:30:33 +00:00
return null;
}
} while ($parent = $parent->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE));
2021-01-08 22:30:33 +00:00
return null;
}
/**
* @template T of Node
* @param array<class-string<T>> $types
* @param Node|Node[]|Stmt[] $nodes
* @return T[]
*/
public function findInstancesOf($nodes, array $types) : array
{
$foundInstances = [];
foreach ($types as $type) {
$currentFoundInstances = $this->findInstanceOf($nodes, $type);
$foundInstances = \array_merge($foundInstances, $currentFoundInstances);
}
return $foundInstances;
}
/**
2021-03-18 11:21:20 +00:00
* @template T of Node
2021-01-08 22:30:33 +00:00
* @param class-string<T> $type
2019-01-14 18:21:23 +00:00
* @param Node|Node[]|Stmt[] $nodes
2021-01-08 22:30:33 +00:00
* @return T[]
*/
public function findInstanceOf($nodes, string $type) : array
{
return $this->nodeFinder->findInstanceOf($nodes, $type);
}
/**
2021-03-18 11:21:20 +00:00
* @template T of Node
2021-01-08 22:30:33 +00:00
* @param class-string<T> $type
* @param Node|Node[] $nodes
*/
public function findFirstInstanceOf($nodes, string $type) : ?\PhpParser\Node
{
\RectorPrefix20210822\Webmozart\Assert\Assert::isAOf($type, \PhpParser\Node::class);
return $this->nodeFinder->findFirstInstanceOf($nodes, $type);
}
/**
2021-03-18 11:21:20 +00:00
* @param class-string<Node> $type
* @param Node|Node[] $nodes
*/
public function hasInstanceOfName($nodes, string $type, string $name) : bool
2020-07-19 13:39:13 +00:00
{
\RectorPrefix20210822\Webmozart\Assert\Assert::isAOf($type, \PhpParser\Node::class);
2020-07-19 13:39:13 +00:00
return (bool) $this->findInstanceOfName($nodes, $type, $name);
}
/**
* @param Node|Node[] $nodes
*/
public function hasVariableOfName($nodes, string $name) : bool
2020-07-19 13:39:13 +00:00
{
2020-07-19 15:20:00 +00:00
return (bool) $this->findVariableOfName($nodes, $name);
2020-07-19 13:39:13 +00:00
}
/**
* @param Node|Node[] $nodes
2021-01-08 22:30:33 +00:00
* @return Variable|null
2020-07-19 13:39:13 +00:00
*/
public function findVariableOfName($nodes, string $name) : ?\PhpParser\Node
{
return $this->findInstanceOfName($nodes, \PhpParser\Node\Expr\Variable::class, $name);
}
/**
* @param Node|Node[] $nodes
2021-03-18 11:21:20 +00:00
* @param array<class-string<Node>> $types
*/
public function hasInstancesOf($nodes, array $types) : bool
{
\RectorPrefix20210822\Webmozart\Assert\Assert::allIsAOf($types, \PhpParser\Node::class);
foreach ($types as $type) {
2021-03-18 11:21:20 +00:00
$foundNode = $this->nodeFinder->findFirstInstanceOf($nodes, $type);
if (!$foundNode instanceof \PhpParser\Node) {
continue;
}
return \true;
}
return \false;
}
/**
2021-03-18 11:21:20 +00:00
* @template T of Node
2021-01-08 22:30:33 +00:00
* @param class-string<T> $type
* @param Node|Node[] $nodes
*/
public function findLastInstanceOf($nodes, string $type) : ?\PhpParser\Node
{
\RectorPrefix20210822\Webmozart\Assert\Assert::isAOf($type, \PhpParser\Node::class);
$foundInstances = $this->nodeFinder->findInstanceOf($nodes, $type);
2019-02-17 14:12:47 +00:00
if ($foundInstances === []) {
return null;
}
\end($foundInstances);
$lastItemKey = \key($foundInstances);
2021-01-08 22:30:33 +00:00
return $foundInstances[$lastItemKey];
}
/**
* @param Node|Node[] $nodes
* @return Node[]
*/
public function find($nodes, callable $filter) : array
{
return $this->nodeFinder->find($nodes, $filter);
}
/**
* Excludes anonymous classes!
*
* @param Node[]|Node $nodes
* @return ClassLike[]
*/
public function findClassLikes($nodes) : array
{
return $this->find($nodes, function (\PhpParser\Node $node) : bool {
if (!$node instanceof \PhpParser\Node\Stmt\ClassLike) {
return \false;
}
2019-08-18 12:15:39 +00:00
// skip anonymous classes
return !($node instanceof \PhpParser\Node\Stmt\Class_ && $this->classAnalyzer->isAnonymousClass($node));
});
}
2020-07-05 11:07:20 +00:00
/**
* @param Node[] $nodes
2021-01-08 22:30:33 +00:00
* @return ClassLike|null
2020-07-05 11:07:20 +00:00
*/
public function findFirstNonAnonymousClass(array $nodes) : ?\PhpParser\Node
2020-07-05 11:07:20 +00:00
{
return $this->findFirst($nodes, function (\PhpParser\Node $node) : bool {
if (!$node instanceof \PhpParser\Node\Stmt\ClassLike) {
return \false;
2020-07-05 11:07:20 +00:00
}
// skip anonymous classes
return !($node instanceof \PhpParser\Node\Stmt\Class_ && $this->classAnalyzer->isAnonymousClass($node));
2020-07-05 11:07:20 +00:00
});
}
2017-11-01 21:37:57 +00:00
/**
* @param Node|Node[] $nodes
*/
public function findFirst($nodes, callable $filter) : ?\PhpParser\Node
2017-11-01 21:37:57 +00:00
{
return $this->nodeFinder->findFirst($nodes, $filter);
}
2021-01-08 22:30:33 +00:00
/**
* @return Assign|null
*/
public function findPreviousAssignToExpr(\PhpParser\Node\Expr $expr) : ?\PhpParser\Node
2020-07-25 20:02:44 +00:00
{
return $this->findFirstPrevious($expr, function (\PhpParser\Node $node) use($expr) : bool {
if (!$node instanceof \PhpParser\Node\Expr\Assign) {
return \false;
2020-07-25 20:02:44 +00:00
}
return $this->nodeComparator->areNodesEqual($node->var, $expr);
2020-07-25 20:02:44 +00:00
});
}
public function findFirstPreviousOfNode(\PhpParser\Node $node, callable $filter) : ?\PhpParser\Node
{
// move to previous expression
$previousStatement = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PREVIOUS_NODE);
if ($previousStatement !== null) {
$foundNode = $this->findFirst([$previousStatement], $filter);
// we found what we need
if ($foundNode !== null) {
return $foundNode;
}
return $this->findFirstPreviousOfNode($previousStatement, $filter);
}
$parent = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE);
if ($parent instanceof \PhpParser\Node\FunctionLike) {
return null;
}
if ($parent instanceof \PhpParser\Node) {
return $this->findFirstPreviousOfNode($parent, $filter);
}
return null;
}
public function findFirstPrevious(\PhpParser\Node $node, callable $filter) : ?\PhpParser\Node
2018-10-09 10:18:42 +00:00
{
$node = $node instanceof \PhpParser\Node\Stmt\Expression ? $node : $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::CURRENT_STATEMENT);
2019-01-25 00:49:26 +00:00
if ($node === null) {
return null;
}
2018-10-09 10:18:42 +00:00
$foundNode = $this->findFirst([$node], $filter);
// we found what we need
2019-02-17 14:12:47 +00:00
if ($foundNode !== null) {
2018-10-09 10:18:42 +00:00
return $foundNode;
}
// move to previous expression
$previousStatement = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PREVIOUS_STATEMENT);
if ($previousStatement !== null) {
return $this->findFirstPrevious($previousStatement, $filter);
}
$parent = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE);
if ($parent === null) {
2018-10-09 10:18:42 +00:00
return null;
}
return $this->findFirstPrevious($parent, $filter);
2018-10-09 10:18:42 +00:00
}
2020-03-23 16:13:04 +00:00
/**
2021-03-18 11:21:20 +00:00
* @template T of Node
* @param array<class-string<T>> $types
2020-03-23 16:13:04 +00:00
*/
public function findFirstPreviousOfTypes(\PhpParser\Node $mainNode, array $types) : ?\PhpParser\Node
2020-03-23 16:13:04 +00:00
{
return $this->findFirstPrevious($mainNode, function (\PhpParser\Node $node) use($types) : bool {
return $this->typeChecker->isInstanceOf($node, $types);
2020-03-23 16:13:04 +00:00
});
}
public function findFirstNext(\PhpParser\Node $node, callable $filter) : ?\PhpParser\Node
{
$next = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::NEXT_NODE);
if ($next instanceof \PhpParser\Node) {
if ($next instanceof \PhpParser\Node\Stmt\Return_ && $next->expr === null) {
return null;
}
$found = $this->findFirst($next, $filter);
if ($found instanceof \PhpParser\Node) {
return $found;
}
return $this->findFirstNext($next, $filter);
}
$parent = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE);
if ($parent instanceof \PhpParser\Node\Stmt\Return_ || $parent instanceof \PhpParser\Node\FunctionLike) {
return null;
}
if ($parent instanceof \PhpParser\Node) {
return $this->findFirstNext($parent, $filter);
}
return null;
}
/**
* @return Expr[]
* @param \RectorPrefix20210822\PhpParser\Node\Expr|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Stmt\Property|\PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $expr
*/
public function findSameNamedExprs($expr) : array
{
// assign of empty string to something
$scopeNode = $this->parentScopeFinder->find($expr);
if ($scopeNode === null) {
return [];
}
if ($expr instanceof \PhpParser\Node\Expr\Variable) {
$exprName = $this->nodeNameResolver->getName($expr);
if ($exprName === null) {
return [];
}
$variables = $this->findInstancesOf($scopeNode, [\PhpParser\Node\Expr\Variable::class]);
return \array_filter($variables, function (\PhpParser\Node\Expr\Variable $variable) use($exprName) : bool {
return $this->nodeNameResolver->isName($variable, $exprName);
});
}
if ($expr instanceof \PhpParser\Node\Stmt\Property) {
$singleProperty = $expr->props[0];
$exprName = $this->nodeNameResolver->getName($singleProperty->name);
} elseif ($expr instanceof \PhpParser\Node\Expr\StaticPropertyFetch || $expr instanceof \PhpParser\Node\Expr\PropertyFetch) {
$exprName = $this->nodeNameResolver->getName($expr->name);
} else {
return [];
}
if ($exprName === null) {
return [];
}
$propertyFetches = $this->findInstancesOf($scopeNode, [\PhpParser\Node\Expr\PropertyFetch::class, \PhpParser\Node\Expr\StaticPropertyFetch::class]);
return \array_filter($propertyFetches, function ($propertyFetch) use($exprName) : bool {
return $this->nodeNameResolver->isName($propertyFetch->name, $exprName);
});
}
/**
2021-03-18 11:21:20 +00:00
* @template T of Node
2021-03-18 11:27:34 +00:00
* @param Node|Node[] $nodes
2021-01-08 22:30:33 +00:00
* @param class-string<T> $type
*/
private function findInstanceOfName($nodes, string $type, string $name) : ?\PhpParser\Node
{
\RectorPrefix20210822\Webmozart\Assert\Assert::isAOf($type, \PhpParser\Node::class);
$foundInstances = $this->nodeFinder->findInstanceOf($nodes, $type);
foreach ($foundInstances as $foundInstance) {
if (!$this->nodeNameResolver->isName($foundInstance, $name)) {
2021-01-08 22:30:33 +00:00
continue;
}
2021-01-08 22:30:33 +00:00
return $foundInstance;
}
return null;
}
}