[CodingStyle] Add RemoveUnusedAliasRector

This commit is contained in:
Tomas Votruba 2019-01-02 18:45:13 +01:00
parent 392d444862
commit 3414bd925e
8 changed files with 249 additions and 2 deletions

View File

@ -6,3 +6,4 @@ services:
Rector\CodingStyle\Rector\Switch_\BinarySwitchToIfElseRector: ~
Rector\CodingStyle\Rector\FuncCall\ConsistentImplodeRector: ~
Rector\CodingStyle\Rector\FuncCall\SetTypeToCastRector: ~
Rector\CodingStyle\Rector\Use_\RemoveUnusedAliasRector: ~

View File

@ -0,0 +1,159 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Rector\Use_;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\NodeVisitor\NameResolver;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class RemoveUnusedAliasRector extends AbstractRector
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(BetterNodeFinder $betterNodeFinder)
{
$this->betterNodeFinder = $betterNodeFinder;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Removes unused use aliases', [
new CodeSample(
<<<'CODE_SAMPLE'
use Symfony\Kernel as BaseKernel;
class SomeClass extends BaseKernel
{
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Symfony\Kernel;
class SomeClass extends Kernel
{
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Use_::class];
}
/**
* @param Use_ $node
*/
public function refactor(Node $node): ?Node
{
$parentNode = $node->getAttribute(Attribute::PARENT_NODE);
$usedNameNodes = $this->resolveUsedNameNodes($parentNode);
if ($usedNameNodes === []) {
return null;
}
foreach ($node->uses as $use) {
if ($use->alias === null) {
continue;
}
$lastName = $use->name->getLast();
$aliasName = $this->getName($use->alias);
// both are used → nothing to remove
if (isset($usedNameNodes[$lastName]) && isset($usedNameNodes[$aliasName])) {
continue;
}
// only last name is used → no need for alias
if (isset($usedNameNodes[$lastName])) {
$use->alias = null;
continue;
}
// only alis name is used → use last name directly
if (isset($usedNameNodes[$aliasName])) {
$this->renameNameNode($usedNameNodes, $aliasName, $lastName);
$use->alias = null;
}
}
return $node;
}
/**
* @return Name[][]|Node[][]
*/
private function resolveUsedNameNodes(Node $parentNode): array
{
$usedNameNodes = [];
// assumption for namespaced content
if ($parentNode instanceof Namespace_) {
/** @var Name[] $namedNodes */
$namedNodes = $this->betterNodeFinder->find($parentNode, function (Node $node) {
if ($node instanceof Name) {
return true;
}
return false;
});
foreach ($namedNodes as $nameNode) {
/** node name before becoming FQN - attribute from @see NameResolver */
$originalName = $nameNode->getAttribute('originalName');
if (! $originalName instanceof Name) {
continue;
}
$usedNameNodes[$originalName->toString()][] = [
$nameNode,
$nameNode->getAttribute(Attribute::PARENT_NODE),
];
}
}
return $usedNameNodes;
}
/**
* @param Name[][]|Node[][] $usedNameNodes
*/
private function renameNameNode(array $usedNameNodes, string $aliasName, string $lastName): void
{
foreach ($usedNameNodes[$aliasName] as [$usedName, $parentNode]) {
foreach ($this->getObjectPublicPropertyNames($parentNode) as $parentNodePropertyName) {
if ($parentNode->{$parentNodePropertyName} !== $usedName) {
continue;
}
$parentNode->{$parentNodePropertyName} = new Name($lastName);
}
}
}
/**
* @param object $node
* @return string[]
*/
private function getObjectPublicPropertyNames($node): array
{
return array_keys(get_object_vars($node));
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractKernel as BaseKernel;
class SomeClass extends BaseKernel
{
public function run(BaseKernel $baseKernel)
{
$anotherBaseKernel = new BaseKernel();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractKernel;
class SomeClass extends AbstractKernel
{
public function run(AbstractKernel $baseKernel)
{
$anotherBaseKernel = new AbstractKernel();
}
}
?>

View File

@ -0,0 +1,15 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractKernel as BaseKernel;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\Another\AbstractKernel;
class Used extends BaseKernel
{
public function run(BaseKernel $baseKernel, AbstractKernel $abstractKernel)
{
$anotherBaseKernel = new BaseKernel();
$anotherAbstractKernel = new AbstractKernel();
}
}

View File

@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector;
use Rector\CodingStyle\Rector\Use_\RemoveUnusedAliasRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class RemoveUnusedAliasRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/used.php.inc',
]);
}
protected function getRectorClass(): string
{
return RemoveUnusedAliasRector::class;
}
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source;
class AbstractKernel
{
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\Another;
class AbstractKernel
{
}

View File

@ -83,14 +83,17 @@ final class NodeScopeAndMetadataDecorator
public function decorateNodesFromFile(array $nodes, string $filePath): array
{
$nodeTraverser = new NodeTraverser();
// specially rewrite nodes for PHPStan
$nodeTraverser->addVisitor(new NameResolver());
$nodeTraverser->addVisitor(new NameResolver(null, [
'preserveOriginalNames' => true,
'replaceNodes' => true, // required by PHPStan
]));
$nodes = $nodeTraverser->traverse($nodes);
$nodes = $this->nodeScopeResolver->processNodes($nodes, $filePath);
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor(new NameResolver(null, [
'preserveOriginalNames' => true,
// this option would override old non-fqn-namespaced nodes otherwise, so it needs to be disabled
'replaceNodes' => false,
]));