Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ private function removePropertyAssigns(Class_ $class, array $assignOnlyPrivatePr
$this->methodCallNamesToBeRemoved[] = $classMethodName;

$this->removeNode($node);

return;
}

Expand Down
21 changes: 15 additions & 6 deletions src/Application/ErrorAndDiffCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -82,7 +83,15 @@ public function getRemovedAndAddedFilesCount(): int

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
Expand Down
52 changes: 49 additions & 3 deletions src/Console/Output/ConsoleOutputFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand Down Expand Up @@ -110,8 +120,44 @@ private function reportRemovedFilesAndNodes(ErrorAndDiffCollector $errorAndDiffC
);
}

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>';
}
}
157 changes: 41 additions & 116 deletions src/PhpParser/Node/Commander/NodeRemovingCommander.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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 */
Expand All @@ -78,96 +51,48 @@ public function addNode(Node $node): void
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 isNodeRemoved(Node $node): bool
{
return in_array($node, $this->nodesToRemove, true);
}

public function isActive(): bool
{
return count($this->nodesToRemove) > 0;
return $this->getCount() > 0;
}

public function isNodeRemoved(Node $node): bool
public function getCount(): int
{
return in_array($node, $this->nodesToRemove, true);
return count($this->nodesToRemove);
}

private function createNodeVisitor(): NodeVisitor
/**
* @return Node[]
*/
public function getNodesToRemove(): array
{
return new class($this->nodesToRemove, $this->nodeFactory, $this->nameResolver) 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;
}
};
return $this->nodesToRemove;
}

private function ensureIsNotPartOfChainMethodCall(Node $node): void
{
if (! $node instanceof MethodCall) {
return;
}

if (! $node->var instanceof MethodCall) {
return;
}

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.'
);
}
}
Loading