From dda66052b50dcdb6cc3dabfa96cfde8143c7e6b6 Mon Sep 17 00:00:00 2001 From: Tomas Votruba <tomas.vot@gmail.com> Date: Sat, 10 Aug 2019 09:28:27 +0200 Subject: [PATCH] decopule NodeRemovingNodeVisitor --- ...eSetterOnlyPropertyAndMethodCallRector.php | 1 + src/Application/ErrorAndDiffCollector.php | 21 ++- src/Console/Output/ConsoleOutputFormatter.php | 52 +++++- .../Node/Commander/NodeRemovingCommander.php | 151 +++++------------- .../NodeVisitor/NodeRemovingNodeVisitor.php | 88 ++++++++++ .../NodeRemovingNodeVisitorFactory.php | 35 ++++ src/Reporting/RemovedNodesCollector.php | 23 --- 7 files changed, 226 insertions(+), 145 deletions(-) create mode 100644 src/PhpParser/Node/NodeVisitor/NodeRemovingNodeVisitor.php create mode 100644 src/PhpParser/Node/NodeVisitorFactory/NodeRemovingNodeVisitorFactory.php delete mode 100644 src/Reporting/RemovedNodesCollector.php diff --git a/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php b/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php index e2f10f99865..d8b31220aa3 100644 --- a/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php +++ b/packages/DeadCode/src/Rector/Class_/RemoveSetterOnlyPropertyAndMethodCallRector.php @@ -123,6 +123,7 @@ CODE_SAMPLE $this->methodCallNamesToBeRemoved[] = $classMethodName; $this->removeNode($node); + return; } diff --git a/src/Application/ErrorAndDiffCollector.php b/src/Application/ErrorAndDiffCollector.php index da5acd355e0..525d78efab6 100644 --- a/src/Application/ErrorAndDiffCollector.php +++ b/src/Application/ErrorAndDiffCollector.php @@ -2,12 +2,13 @@ namespace Rector\Application; +use PhpParser\Node; use PHPStan\AnalysedCodeException; use Rector\Application\FileSystem\RemovedAndAddedFilesCollector; use Rector\ConsoleDiffer\DifferAndFormatter; use Rector\Error\ExceptionCorrector; +use Rector\PhpParser\Node\Commander\NodeRemovingCommander; use Rector\Reporting\FileDiff; -use Rector\Reporting\RemovedNodesCollector; use Symplify\PackageBuilder\FileSystem\SmartFileInfo; use Throwable; @@ -44,22 +45,22 @@ final class ErrorAndDiffCollector private $removedAndAddedFilesCollector; /** - * @var RemovedNodesCollector + * @var NodeRemovingCommander */ - private $removedNodesCollector; + private $nodeRemovingCommander; public function __construct( DifferAndFormatter $differAndFormatter, AppliedRectorCollector $appliedRectorCollector, ExceptionCorrector $exceptionCorrector, RemovedAndAddedFilesCollector $removedAndAddedFilesCollector, - RemovedNodesCollector $removedNodesCollector + NodeRemovingCommander $nodeRemovingCommander ) { $this->differAndFormatter = $differAndFormatter; $this->appliedRectorCollector = $appliedRectorCollector; $this->exceptionCorrector = $exceptionCorrector; $this->removedAndAddedFilesCollector = $removedAndAddedFilesCollector; - $this->removedNodesCollector = $removedNodesCollector; + $this->nodeRemovingCommander = $nodeRemovingCommander; } public function addError(Error $error): void @@ -82,7 +83,15 @@ final class ErrorAndDiffCollector public function getRemovedNodeCount(): int { - return $this->removedNodesCollector->getCount(); + return $this->nodeRemovingCommander->getCount(); + } + + /** + * @return Node[] + */ + public function getRemovedNodes(): array + { + return $this->nodeRemovingCommander->getNodesToRemove(); } public function addFileDiff(SmartFileInfo $smartFileInfo, string $newContent, string $oldContent): void diff --git a/src/Console/Output/ConsoleOutputFormatter.php b/src/Console/Output/ConsoleOutputFormatter.php index d7bc211b591..876ea356714 100644 --- a/src/Console/Output/ConsoleOutputFormatter.php +++ b/src/Console/Output/ConsoleOutputFormatter.php @@ -2,11 +2,15 @@ namespace Rector\Console\Output; +use Nette\Utils\Strings; use Rector\Application\Error; use Rector\Application\ErrorAndDiffCollector; use Rector\Contract\Console\Output\OutputFormatterInterface; +use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\PhpParser\Printer\BetterStandardPrinter; use Rector\Reporting\FileDiff; use Symfony\Component\Console\Style\SymfonyStyle; +use Symplify\PackageBuilder\FileSystem\SmartFileInfo; final class ConsoleOutputFormatter implements OutputFormatterInterface { @@ -20,9 +24,15 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface */ private $symfonyStyle; - public function __construct(SymfonyStyle $symfonyStyle) + /** + * @var BetterStandardPrinter + */ + private $betterStandardPrinter; + + public function __construct(SymfonyStyle $symfonyStyle, BetterStandardPrinter $betterStandardPrinter) { $this->symfonyStyle = $symfonyStyle; + $this->betterStandardPrinter = $betterStandardPrinter; } public function report(ErrorAndDiffCollector $errorAndDiffCollector): void @@ -110,8 +120,44 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface ); } - if ($errorAndDiffCollector->getRemovedNodeCount()) { - $this->symfonyStyle->note(sprintf('%d nodes were removed', $errorAndDiffCollector->getRemovedNodeCount())); + $this->reportRemovedNodes($errorAndDiffCollector); + } + + private function reportRemovedNodes(ErrorAndDiffCollector $errorAndDiffCollector): void + { + if ($errorAndDiffCollector->getRemovedNodeCount() === 0) { + return; + } + + $this->symfonyStyle->warning(sprintf('%d nodes were removed', $errorAndDiffCollector->getRemovedNodeCount())); + + if ($this->symfonyStyle->isVeryVerbose()) { + $i = 0; + foreach ($errorAndDiffCollector->getRemovedNodes() as $removedNode) { + /** @var SmartFileInfo $fileInfo */ + $fileInfo = $removedNode->getAttribute(AttributeKey::FILE_INFO); + + $this->symfonyStyle->writeln(sprintf( + '<options=bold>%d) %s:%d</>', + ++$i, + $fileInfo->getRelativeFilePath(), + $removedNode->getStartLine() + )); + + $printedNode = $this->betterStandardPrinter->print($removedNode); + + // color red + prefix with "-" to visually demonstrate removal + $printedNode = '-' . Strings::replace($printedNode, '#\n#', "\n-"); + $printedNode = $this->colorTextToRed($printedNode); + + $this->symfonyStyle->writeln($printedNode); + $this->symfonyStyle->newLine(1); + } } } + + private function colorTextToRed(string $text): string + { + return '<fg=red>' . $text . '</fg=red>'; + } } diff --git a/src/PhpParser/Node/Commander/NodeRemovingCommander.php b/src/PhpParser/Node/Commander/NodeRemovingCommander.php index cf871fd2a26..6ae2864237a 100644 --- a/src/PhpParser/Node/Commander/NodeRemovingCommander.php +++ b/src/PhpParser/Node/Commander/NodeRemovingCommander.php @@ -3,19 +3,14 @@ namespace Rector\PhpParser\Node\Commander; use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Expression; use PhpParser\NodeTraverser; -use PhpParser\NodeVisitor; -use PhpParser\NodeVisitorAbstract; use Rector\Contract\PhpParser\Node\CommanderInterface; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\PhpParser\Node\NodeFactory; -use Rector\PhpParser\Node\Resolver\NameResolver; -use Rector\Reporting\RemovedNodesCollector; +use Rector\PhpParser\Node\NodeVisitorFactory\NodeRemovingNodeVisitorFactory; final class NodeRemovingCommander implements CommanderInterface { @@ -25,46 +20,24 @@ final class NodeRemovingCommander implements CommanderInterface private $nodesToRemove = []; /** - * @var NodeFactory + * @var NodeRemovingNodeVisitorFactory */ - private $nodeFactory; + private $nodeRemovingNodeVisitorFactory; - /** - * @var NameResolver - */ - private $nameResolver; - - /** - * @var RemovedNodesCollector - */ - private $removedNodesCollector; - - public function __construct( - NodeFactory $nodeFactory, - NameResolver $nameResolver, - RemovedNodesCollector $removedNodesCollector - ) { - $this->nodeFactory = $nodeFactory; - $this->nameResolver = $nameResolver; - $this->removedNodesCollector = $removedNodesCollector; + public function __construct(NodeRemovingNodeVisitorFactory $nodeRemovingNodeVisitorFactory) + { + $this->nodeRemovingNodeVisitorFactory = $nodeRemovingNodeVisitorFactory; } public function addNode(Node $node): void { - $this->removedNodesCollector->collect($node); - // chain call: "->method()->another()" - if ($node instanceof MethodCall && $node->var instanceof MethodCall) { - throw new ShouldNotHappenException( - 'Chain method calls cannot be removed this way. It would remove the whole tree of calls. Remove them manually by creating new parent node with no following method.' - ); - } + $this->ensureIsNotPartOfChainMethodCall($node); - if (! $node instanceof Expression && ($node->getAttribute( - AttributeKey::PARENT_NODE - ) instanceof Expression)) { + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + if (! $node instanceof Expression && $parentNode instanceof Expression) { // only expressions can be removed - $node = $node->getAttribute(AttributeKey::PARENT_NODE); + $node = $parentNode; } /** @var Stmt $node */ @@ -78,96 +51,48 @@ final class NodeRemovingCommander implements CommanderInterface public function traverseNodes(array $nodes): array { $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->createNodeVisitor()); + + $nodeRemovingNodeVisitor = $this->nodeRemovingNodeVisitorFactory->createFromNodesToRemove($this->nodesToRemove); + $nodeTraverser->addVisitor($nodeRemovingNodeVisitor); return $nodeTraverser->traverse($nodes); } - public function isActive(): bool - { - return count($this->nodesToRemove) > 0; - } - public function isNodeRemoved(Node $node): bool { return in_array($node, $this->nodesToRemove, true); } - private function createNodeVisitor(): NodeVisitor + public function isActive(): bool { - return new class($this->nodesToRemove, $this->nodeFactory, $this->nameResolver) extends NodeVisitorAbstract { - /** - * @var Stmt[]|Expr[] - */ - private $nodesToRemove = []; + return $this->getCount() > 0; + } - /** - * @var NodeFactory - */ - private $nodeFactory; + public function getCount(): int + { + return count($this->nodesToRemove); + } - /** - * @var NameResolver - */ - private $nameResolver; + /** + * @return Node[] + */ + public function getNodesToRemove(): array + { + return $this->nodesToRemove; + } - /** - * @param Stmt[] $nodesToRemove - */ - public function __construct(array $nodesToRemove, NodeFactory $nodeFactory, NameResolver $nameResolver) - { - $this->nodesToRemove = $nodesToRemove; - $this->nodeFactory = $nodeFactory; - $this->nameResolver = $nameResolver; - } + private function ensureIsNotPartOfChainMethodCall(Node $node): void + { + if (! $node instanceof MethodCall) { + return; + } - /** - * @return int|Node|null - */ - public function enterNode(Node $node) - { - // special case for fluent methods - foreach ($this->nodesToRemove as $key => $nodeToRemove) { - if (! $nodeToRemove instanceof MethodCall) { - continue; - } + if (! $node->var instanceof MethodCall) { + return; + } - if (! $node instanceof MethodCall || ! $node->var instanceof MethodCall) { - continue; - } - - if ($nodeToRemove !== $node->var) { - continue; - } - - $methodName = $this->nameResolver->getName($node->name); - if ($methodName === null) { - continue; - } - - unset($this->nodesToRemove[$key]); - - return $this->nodeFactory->createMethodCall($node->var->var, $methodName, $node->args); - } - - return null; - } - - /** - * @return int|Node|Node[]|null - */ - public function leaveNode(Node $node) - { - foreach ($this->nodesToRemove as $key => $nodeToRemove) { - if ($node === $nodeToRemove) { - unset($this->nodesToRemove[$key]); - - return NodeTraverser::REMOVE_NODE; - } - } - - return $node; - } - }; + throw new ShouldNotHappenException( + 'Chain method calls cannot be removed this way. It would remove the whole tree of calls. Remove them manually by creating new parent node with no following method.' + ); } } diff --git a/src/PhpParser/Node/NodeVisitor/NodeRemovingNodeVisitor.php b/src/PhpParser/Node/NodeVisitor/NodeRemovingNodeVisitor.php new file mode 100644 index 00000000000..561464dee4e --- /dev/null +++ b/src/PhpParser/Node/NodeVisitor/NodeRemovingNodeVisitor.php @@ -0,0 +1,88 @@ +<?php declare(strict_types=1); + +namespace Rector\PhpParser\Node\NodeVisitor; + +use PhpParser\Node; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Stmt; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; +use Rector\PhpParser\Node\NodeFactory; +use Rector\PhpParser\Node\Resolver\NameResolver; + +final class NodeRemovingNodeVisitor extends NodeVisitorAbstract +{ + /** + * @var Stmt[]|Expr[] + */ + private $nodesToRemove = []; + + /** + * @var NodeFactory + */ + private $nodeFactory; + + /** + * @var NameResolver + */ + private $nameResolver; + + /** + * @param Stmt[] $nodesToRemove + */ + public function __construct(array $nodesToRemove, NodeFactory $nodeFactory, NameResolver $nameResolver) + { + $this->nodesToRemove = $nodesToRemove; + $this->nodeFactory = $nodeFactory; + $this->nameResolver = $nameResolver; + } + + /** + * @return int|Node|null + */ + public function enterNode(Node $node) + { + // special case for fluent methods + foreach ($this->nodesToRemove as $key => $nodeToRemove) { + if (! $nodeToRemove instanceof MethodCall) { + continue; + } + + if (! $node instanceof MethodCall || ! $node->var instanceof MethodCall) { + continue; + } + + if ($nodeToRemove !== $node->var) { + continue; + } + + $methodName = $this->nameResolver->getName($node->name); + if ($methodName === null) { + continue; + } + + unset($this->nodesToRemove[$key]); + + return $this->nodeFactory->createMethodCall($node->var->var, $methodName, $node->args); + } + + return null; + } + + /** + * @return int|Node|Node[]|null + */ + public function leaveNode(Node $node) + { + foreach ($this->nodesToRemove as $key => $nodeToRemove) { + if ($node === $nodeToRemove) { + unset($this->nodesToRemove[$key]); + + return NodeTraverser::REMOVE_NODE; + } + } + + return $node; + } +} diff --git a/src/PhpParser/Node/NodeVisitorFactory/NodeRemovingNodeVisitorFactory.php b/src/PhpParser/Node/NodeVisitorFactory/NodeRemovingNodeVisitorFactory.php new file mode 100644 index 00000000000..8114a4ce837 --- /dev/null +++ b/src/PhpParser/Node/NodeVisitorFactory/NodeRemovingNodeVisitorFactory.php @@ -0,0 +1,35 @@ +<?php declare(strict_types=1); + +namespace Rector\PhpParser\Node\NodeVisitorFactory; + +use PhpParser\Node; +use Rector\PhpParser\Node\NodeFactory; +use Rector\PhpParser\Node\NodeVisitor\NodeRemovingNodeVisitor; +use Rector\PhpParser\Node\Resolver\NameResolver; + +final class NodeRemovingNodeVisitorFactory +{ + /** + * @var NodeFactory + */ + private $nodeFactory; + + /** + * @var NameResolver + */ + private $nameResolver; + + public function __construct(NodeFactory $nodeFactory, NameResolver $nameResolver) + { + $this->nodeFactory = $nodeFactory; + $this->nameResolver = $nameResolver; + } + + /** + * @param Node[] $nodesToRemove + */ + public function createFromNodesToRemove(array $nodesToRemove): NodeRemovingNodeVisitor + { + return new NodeRemovingNodeVisitor($nodesToRemove, $this->nodeFactory, $this->nameResolver); + } +} diff --git a/src/Reporting/RemovedNodesCollector.php b/src/Reporting/RemovedNodesCollector.php deleted file mode 100644 index ad128691347..00000000000 --- a/src/Reporting/RemovedNodesCollector.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php declare(strict_types=1); - -namespace Rector\Reporting; - -use PhpParser\Node; - -final class RemovedNodesCollector -{ - /** - * @var Node[] - */ - private $removedNodes = []; - - public function collect(Node $node): void - { - $this->removedNodes[] = $node; - } - - public function getCount(): int - { - return count($this->removedNodes); - } -}