Fix too deep method chain calls performance on analysis

This commit is contained in:
Tomas Votruba 2018-11-07 00:05:21 +01:00
parent 9962c25e55
commit 396f076b71
3 changed files with 93 additions and 2 deletions

View File

@ -179,7 +179,7 @@ CODE_SAMPLE
return $this->createFunction('in_array', $arguments);
}
private function combineCommentsToNode(Node $originalNode, Node $newNode): void
{
$this->callableNodeTraverser->traverseNodesWithCallable([$originalNode], function (Node $node): void {

View File

@ -5,10 +5,12 @@ namespace Rector\NodeTypeResolver\PHPStan\Scope;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\NodeTraverser;
use PHPStan\Analyser\NodeScopeResolver as PHPStanNodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
/**
@ -32,14 +34,21 @@ final class NodeScopeResolver
*/
private $broker;
/**
* @var RemoveDeepChainMethodCallNodeVisitor
*/
private $removeDeepChainMethodCallNodeVisitor;
public function __construct(
ScopeFactory $scopeFactory,
PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
Broker $broker
Broker $broker,
RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor
) {
$this->scopeFactory = $scopeFactory;
$this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver;
$this->broker = $broker;
$this->removeDeepChainMethodCallNodeVisitor = $removeDeepChainMethodCallNodeVisitor;
}
/**
@ -48,8 +57,11 @@ final class NodeScopeResolver
*/
public function processNodes(array $nodes, string $filePath): array
{
$this->removeDeepChainMethodCallNodes($nodes);
$this->phpStanNodeScopeResolver->setAnalysedFiles([$filePath]);
// skip chain method calls, performance issue: https://github.com/phpstan/phpstan/issues/254
$this->phpStanNodeScopeResolver->processNodes(
$nodes,
$this->scopeFactory->createFromFile($filePath),
@ -88,4 +100,14 @@ final class NodeScopeResolver
return $scope;
}
/**
* @param Node[] $nodes
*/
private function removeDeepChainMethodCallNodes(array $nodes): void
{
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor($this->removeDeepChainMethodCallNodeVisitor);
$nodeTraverser->traverse($nodes);
}
}

View File

@ -0,0 +1,69 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\Expression;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use Rector\Utils\BetterNodeFinder;
/**
* Skips performance trap in PHPStan: https://github.com/phpstan/phpstan/issues/254
*/
final class RemoveDeepChainMethodCallNodeVisitor extends NodeVisitorAbstract
{
/**
* @var int
*/
private const NESTED_CHAIN_METHOD_CALL_LIMIT = 10;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var Expression|null
*/
private $nodeToRemove;
public function __construct(BetterNodeFinder $betterNodeFinder)
{
$this->betterNodeFinder = $betterNodeFinder;
}
/**
* @return int|Node|null
*/
public function enterNode(Node $node)
{
if (! $node instanceof Expression) {
return null;
}
if ($node->expr instanceof MethodCall && $node->expr->var instanceof MethodCall) {
$nestedChainMethodCalls = $this->betterNodeFinder->findInstanceOf([$node->expr], MethodCall::class);
if (count($nestedChainMethodCalls) > self::NESTED_CHAIN_METHOD_CALL_LIMIT) {
$this->nodeToRemove = $node;
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
}
return null;
}
/**
* @return int|Node|Node[]|null
*/
public function leaveNode(Node $node)
{
if ($node === $this->nodeToRemove) {
return NodeTraverser::REMOVE_NODE;
}
return $node;
}
}