From 396f076b7115d876847d0811e41b2af712ee5d4d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 7 Nov 2018 00:05:21 +0100 Subject: [PATCH] Fix too deep method chain calls performance on analysis --- .../Foreach_/ForeachToInArrayRector.php | 2 +- .../src/PHPStan/Scope/NodeScopeResolver.php | 24 ++++++- .../RemoveDeepChainMethodCallNodeVisitor.php | 69 +++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 packages/NodeTypeResolver/src/PHPStan/Scope/NodeVisitor/RemoveDeepChainMethodCallNodeVisitor.php diff --git a/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php b/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php index ea2efcaa32f..20efe5e0458 100644 --- a/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php +++ b/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php @@ -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 { diff --git a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php index ac20d8a1bab..99d1f848618 100644 --- a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php +++ b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php @@ -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); + } } diff --git a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeVisitor/RemoveDeepChainMethodCallNodeVisitor.php b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeVisitor/RemoveDeepChainMethodCallNodeVisitor.php new file mode 100644 index 00000000000..bf78c5eff4b --- /dev/null +++ b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeVisitor/RemoveDeepChainMethodCallNodeVisitor.php @@ -0,0 +1,69 @@ +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; + } +}