improve trait and interface support for RemoveUnusedAliasRector

This commit is contained in:
Tomas Votruba 2019-01-26 11:31:28 +01:00
parent 62fc1f102c
commit 624d1b528d
16 changed files with 309 additions and 61 deletions

View File

@ -105,6 +105,7 @@ parameters:
- 'packages/CodeQuality/src/Rector/Foreach_/SimplifyForeachToArrayFilterRector.php'
- 'src/PhpParser/Node/Resolver/NameResolver.php'
- 'src/Rector/MethodBody/NormalToFluentRector.php'
- 'packages/CodingStyle/src/Rector/Use_/RemoveUnusedAliasRector.php'
# copied 3rd party logic
- 'packages/Php/src/EregToPcreTransformer.php'
# dev

View File

@ -3,11 +3,17 @@
namespace Rector\CodingStyle\Rector\Use_;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\NodeVisitor\NameResolver;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
@ -18,6 +24,11 @@ use Rector\RectorDefinition\RectorDefinition;
final class RemoveUnusedAliasRector extends AbstractRector
{
/**
* @var Node[][][]
*/
private $resolvedNodeNames = [];
/**
* @var BetterNodeFinder
*/
@ -64,8 +75,9 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
$usedNameNodes = $this->resolveUsedNameNodes($node);
if ($usedNameNodes === []) {
$this->resolvedNodeNames = [];
$this->resolveUsedNameNodes($node);
if ($this->resolvedNodeNames === []) {
return null;
}
@ -78,19 +90,19 @@ CODE_SAMPLE
$aliasName = $this->getName($use->alias);
// both are used → nothing to remove
if (isset($usedNameNodes[$lastName]) && isset($usedNameNodes[$aliasName])) {
if (isset($this->resolvedNodeNames[$lastName]) && isset($this->resolvedNodeNames[$aliasName])) {
continue;
}
// only last name is used → no need for alias
if (isset($usedNameNodes[$lastName])) {
if (isset($this->resolvedNodeNames[$lastName])) {
$use->alias = null;
continue;
}
// only alias name is used → use last name directly
if (isset($usedNameNodes[$aliasName])) {
$this->renameNameNode($usedNameNodes[$aliasName], $lastName);
if (isset($this->resolvedNodeNames[$aliasName])) {
$this->renameNameNode($this->resolvedNodeNames[$aliasName], $lastName);
$use->alias = null;
}
}
@ -98,25 +110,115 @@ CODE_SAMPLE
return $node;
}
/**
* @return Node[][][]
*/
private function resolveUsedNameNodes(Use_ $node): array
private function resolveUsedNameNodes(Use_ $node): void
{
$parentNode = $node->getAttribute(Attribute::PARENT_NODE);
if ($parentNode === null) { // no namespace
$nextNode = $node->getAttribute(Attribute::NEXT_NODE);
if ($nextNode === null) {
throw new ShouldNotHappenException();
$searchNode = $this->resolveSearchNode($node);
$this->resolvedUsedNames($searchNode);
$this->resolveUsedClassNames($searchNode);
$this->resolvedTraitUseNames($searchNode);
}
/**
* @param Node[][] $usedNameNodes
*/
private function renameNameNode(array $usedNameNodes, string $lastName): void
{
/** @var Identifier|Name $usedName */
foreach ($usedNameNodes as [$usedName, $parentNode]) {
if ($parentNode instanceof TraitUse) {
foreach ($parentNode->traits as $key => $traitName) {
if (! $this->areNamesEqual($traitName, $usedName)) {
continue;
}
$parentNode->traits[$key] = new Name($lastName);
}
continue;
}
if ($parentNode instanceof Class_) {
if ($parentNode->name !== null) {
if ($this->areNamesEqual($parentNode->name, $usedName)) {
$parentNode->name = new Identifier($lastName);
}
}
if ($parentNode->extends !== null) {
if ($this->areNamesEqual($parentNode->extends, $usedName)) {
$parentNode->extends = new Name($lastName);
}
}
foreach ($parentNode->implements as $key => $implementNode) {
if ($this->areNamesEqual($implementNode, $usedName)) {
$parentNode->implements[$key] = new Name($lastName);
}
}
continue;
}
if ($parentNode instanceof Param) {
if ($parentNode->type !== null) {
if ($this->areNamesEqual($parentNode->type, $usedName)) {
$parentNode->type = new Name($lastName);
}
}
continue;
}
if ($parentNode instanceof New_) {
if ($this->areNamesEqual($parentNode->class, $usedName)) {
$parentNode->class = new Name($lastName);
}
continue;
}
if ($parentNode instanceof ClassMethod) {
if ($parentNode->returnType !== null) {
if ($this->areNamesEqual($parentNode->returnType, $usedName)) {
$parentNode->returnType = new Name($lastName);
}
}
continue;
}
if ($parentNode instanceof Interface_) {
foreach ($parentNode->extends as $key => $extendInterfaceName) {
if ($this->areNamesEqual($extendInterfaceName, $usedName)) {
$parentNode->extends[$key] = new Name($lastName);
}
}
continue;
}
}
}
$groupNode = $nextNode ?? $parentNode;
private function resolveSearchNode(Use_ $node): Node
{
$searchNode = $node->getAttribute(Attribute::PARENT_NODE);
if ($searchNode) {
return $searchNode;
}
$usedNameNodes = [];
$searchNode = $node->getAttribute(Attribute::NEXT_NODE);
if ($searchNode) {
return $searchNode;
}
throw new ShouldNotHappenException();
}
private function resolvedUsedNames(Node $searchNode): void
{
/** @var Name[] $namedNodes */
$namedNodes = $this->betterNodeFinder->findInstanceOf($groupNode, Name::class);
$namedNodes = $this->betterNodeFinder->findInstanceOf($searchNode, Name::class);
foreach ($namedNodes as $nameNode) {
/** node name before becoming FQN - attribute from @see NameResolver */
@ -127,47 +229,40 @@ CODE_SAMPLE
$parentNode = $nameNode->getAttribute(Attribute::PARENT_NODE);
if ($parentNode === null) {
throw new ShouldNotHappenException();
}
$this->resolvedNodeNames[$originalName->toString()][] = [$nameNode, $parentNode];
}
}
private function resolveUsedClassNames(Node $searchNode): void
{
/** @var ClassLike[] $classLikeNodes */
$classLikeNodes = $this->betterNodeFinder->findInstanceOf($searchNode, ClassLike::class);
foreach ($classLikeNodes as $classLikeNode) {
if ($classLikeNode->name === null) { // skip anonymous classes
continue;
}
$usedNameNodes[$originalName->toString()][] = [$nameNode, $parentNode];
}
/** @var ClassLike[] $classLikeNodes */
$classLikeNodes = $this->betterNodeFinder->findInstanceOf($parentNode, ClassLike::class);
foreach ($classLikeNodes as $classLikeNode) {
if ($classLikeNode->name) {
$name = $this->getName($classLikeNode->name);
$usedNameNodes[$name] = [$classLikeNode->name, $parentNode];
}
}
return $usedNameNodes;
}
/**
* @param Node[][] $usedNameNodes
*/
private function renameNameNode(array $usedNameNodes, string $lastName): void
{
foreach ($usedNameNodes as [$usedName, $parentNode]) {
foreach ($this->getObjectPublicPropertyNames($parentNode) as $parentNodePropertyName) {
if ($parentNode->{$parentNodePropertyName} !== $usedName) {
continue;
}
$parentNode->{$parentNodePropertyName} = new Name($lastName);
}
$name = $this->getName($classLikeNode->name);
$this->resolvedNodeNames[$name][] = [$classLikeNode->name, $classLikeNode];
}
}
/**
* @param object $node
* @return string[]
*/
private function getObjectPublicPropertyNames($node): array
private function resolvedTraitUseNames(Node $searchNode): void
{
return array_keys(get_object_vars($node));
/** @var Identifier[] $identifierNodes */
$identifierNodes = $this->betterNodeFinder->findInstanceOf($searchNode, Identifier::class);
foreach ($identifierNodes as $identifierNode) {
$parentNode = $identifierNode->getAttribute(Attribute::PARENT_NODE);
if (! $parentNode instanceof UseUse) {
continue;
}
$this->resolvedNodeNames[$identifierNode->name][] = [$identifierNode, $parentNode];
}
}
}

View File

@ -2,8 +2,8 @@
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\Kernel as BaseKernel;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\StandaloneClass as BaseKernel;
class Kernel extends BaseKernel
class StandaloneClass extends BaseKernel
{
}

View File

@ -2,11 +2,13 @@
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractInterface as BaseInterface;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractKernel as BaseKernel;
class SomeClass extends BaseKernel
class SomeClass extends BaseKernel implements BaseInterface
{
public function run(BaseKernel $baseKernel)
public function run(BaseKernel $baseKernel): BaseInterface
{
$anotherBaseKernel = new BaseKernel();
}
@ -18,11 +20,13 @@ class SomeClass extends BaseKernel
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractInterface;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractKernel;
class SomeClass extends AbstractKernel
class SomeClass extends AbstractKernel implements AbstractInterface
{
public function run(AbstractKernel $baseKernel)
public function run(AbstractKernel $baseKernel): AbstractInterface
{
$anotherBaseKernel = new AbstractKernel();
}

View File

@ -0,0 +1,23 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractInterface as NotNeededAliasInterface;
interface BasicInterface extends NotNeededAliasInterface
{
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractInterface;
interface BasicInterface extends AbstractInterface
{
}
?>

View File

@ -0,0 +1,27 @@
<?php
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\AbstractKernel as BaseKernel;
class SomeClass extends BaseKernel
{
public function run(BaseKernel $baseKernel)
{
$anotherBaseKernel = new BaseKernel();
}
}
?>
-----
<?php
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,7 @@
<?php
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\StandaloneClass as BaseKernel;
class StandaloneClass extends BaseKernel
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\StandaloneTrait as DuplicatedStandaloneTrait;
trait StandaloneTrait
{
use DuplicatedStandaloneTrait;
}

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\ClassicTrait;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\StandaloneTrait as DuplicatedStandaloneTrait;
trait UnneededStandaloneTrait
{
use ClassicTrait, DuplicatedStandaloneTrait;
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Fixture;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\ClassicTrait;
use Rector\CodingStyle\Tests\Rector\Use_\RemoveUnusedAliasRector\Source\StandaloneTrait;
trait UnneededStandaloneTrait
{
use ClassicTrait, StandaloneTrait;
}
?>

View File

@ -9,7 +9,19 @@ final class RemoveUnusedAliasRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc', __DIR__ . '/Fixture/used.php.inc']);
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/used.php.inc',
__DIR__ . '/Fixture/class_name.php.inc',
# no namespace
__DIR__ . '/Fixture/no_namespace.php.inc',
__DIR__ . '/Fixture/no_namespace_class_name.php.inc',
# traits
__DIR__ . '/Fixture/trait_name.php.inc',
__DIR__ . '/Fixture/unneeded_trait_name.php.inc',
# interfaces
__DIR__ . '/Fixture/interace_extending.php.inc',
]);
}
protected function getRectorClass(): string

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,8 @@ parameters:
ignoreErrors:
# false positive
- '#PHPDoc tag \@param for parameter \$node with type float is incompatible with native type PhpParser\\Node#'
- '#Method Rector\\CodingStyle\\Rector\\Use_\\RemoveUnusedAliasRector::resolveUsedClassNames\(\) should return array<array<array<PhpParser\\Node>>> but returns array<string, array<int, array<int, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Stmt\\ClassLike\|null>>>#'
- '#Result of && is always true#'
# missuse of interface and class
- '#Parameter \#1 (.*?) expects Symfony\\Component\\DependencyInjection\\ContainerBuilder, Symfony\\Component\\DependencyInjection\\ContainerInterface given#'