Make RemoveUnusedPrivateClassConstantRector use of static reflection (#384)

This commit is contained in:
Tomas Votruba 2021-07-05 11:39:26 +02:00 committed by GitHub
parent 90b213c519
commit 5f43d6b712
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 180 additions and 247 deletions

View File

@ -9264,15 +9264,15 @@ Remove specific traits from code
- class: [`Rector\Removing\Rector\Class_\RemoveTraitRector`](../rules/Removing/Rector/Class_/RemoveTraitRector.php)
```php
use Rector\Removing\Rector\Class_\RemoveTraitRector;
use Rector\Removing\Rector\Class_\RemoveTraitUseRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(RemoveTraitRector::class)
$services->set(RemoveTraitUseRector::class)
->call('configure', [[
RemoveTraitRector::TRAITS_TO_REMOVE => ['TraitNameToRemove'],
RemoveTraitUseRector::TRAITS_TO_REMOVE => ['TraitNameToRemove'],
]]);
};
```

View File

@ -4,8 +4,8 @@
/**
* {@inheritdoc}
*/
- public function confirm(string $question, bool $default = true)
+ public function confirm($question, $default = true)
- public function confirm($question, $default = true)
+ public function confirm(string $question, bool $default = true)
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}

View File

@ -1,11 +0,0 @@
<?php
namespace Rector\Tests\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector\Fixture;
class SkipApi
{
/**
* @api
*/
private const USED_PUBLICLY_IN_ANOTHER_PROJECT = 'publicly';
}

View File

@ -1,11 +0,0 @@
<?php
namespace Rector\Tests\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector\Fixture;
/**
* @api
*/
class SkipOnClassApi
{
private const USED_PUBLICLY_IN_ANOTHER_PROJECT = 'publicly';
}

View File

@ -2,15 +2,11 @@
namespace Rector\Tests\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector\Fixture;
use Rector\Tests\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector\Source\TraitUsingPrivateConstant;
final class SkipUsedInTrait
{
use aTrait;
use TraitUsingPrivateConstant;
private const SOME_CONSTANT = 5;
}
trait aTrait {
public function foo(){
return self::SOME_CONSTANT;
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\DeadCode\Rector\ClassConst\RemoveUnusedPrivateClassConstantRector\Source;
trait TraitUsingPrivateConstant
{
public function foo()
{
return self::SOME_CONSTANT;
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\Fixture;
use Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\Source\TraitToBeRemoved;
class SomeClass
{
use TraitToBeRemoved;
}
?>
-----
<?php
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\Fixture;
use Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\Source\TraitToBeRemoved;
class SomeClass
{
}
?>

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\Source;
trait TraitToBeRemoved
{
}

View File

@ -0,0 +1,24 @@
<?php
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\Fixture;
use Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\Source\TraitToBeRemoved;
class SomeClass
{
use TraitToBeRemoved;
}
?>
-----
<?php
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\Fixture;
use Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\Source\TraitToBeRemoved;
class SomeClass
{
}
?>

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitRector;
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class RemoveTraitRectorTest extends AbstractRectorTestCase
final class RemoveTraitUseRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\Source;
trait TraitToBeRemoved
{
}

View File

@ -2,14 +2,14 @@
declare(strict_types=1);
use Rector\Removing\Rector\Class_\RemoveTraitRector;
use Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\Source\TraitToBeRemoved;
use Rector\Removing\Rector\Class_\RemoveTraitUseRector;
use Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\Source\TraitToBeRemoved;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(RemoveTraitRector::class)
$services->set(RemoveTraitUseRector::class)
->call('configure', [[
RemoveTraitRector::TRAITS_TO_REMOVE => [TraitToBeRemoved::class],
RemoveTraitUseRector::TRAITS_TO_REMOVE => [TraitToBeRemoved::class],
]]);
};

View File

@ -5,12 +5,10 @@ declare(strict_types=1);
namespace Rector\DeadCode\Rector\ClassConst;
use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\ObjectType;
use Rector\Core\NodeAnalyzer\EnumAnalyzer;
use Rector\Core\NodeManipulator\ClassConstManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -23,7 +21,8 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class RemoveUnusedPrivateClassConstantRector extends AbstractRector
{
public function __construct(
private ClassConstManipulator $classConstManipulator
private ClassConstManipulator $classConstManipulator,
private EnumAnalyzer $enumAnalyzer,
) {
}
@ -67,7 +66,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkip($node)) {
if ($this->shouldSkipClassConst($node)) {
return null;
}
@ -81,34 +80,16 @@ CODE_SAMPLE
return null;
}
$classLike = $node->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof ClassLike) {
if ($this->classConstManipulator->hasClassConstFetch($node, $classReflection)) {
return null;
}
$classObjectType = new ObjectType($classReflection->getName());
/** @var ClassConstFetch[] $classConstFetches */
$classConstFetches = $this->betterNodeFinder->findInstanceOf($classLike->stmts, ClassConstFetch::class);
foreach ($classConstFetches as $classConstFetch) {
if (! $this->nodeNameResolver->areNamesEqual($classConstFetch->name, $node->consts[0]->name)) {
continue;
}
$constFetchClassType = $this->nodeTypeResolver->resolve($classConstFetch->class);
// constant is used!
if ($constFetchClassType->isSuperTypeOf($classObjectType)->yes()) {
return null;
}
}
$this->removeNode($node);
return null;
}
private function shouldSkip(ClassConst $classConst): bool
private function shouldSkipClassConst(ClassConst $classConst): bool
{
if (! $classConst->isPrivate()) {
return true;
@ -118,26 +99,6 @@ CODE_SAMPLE
return true;
}
if ($this->classConstManipulator->isEnum($classConst)) {
return true;
}
if ($this->classConstManipulator->hasClassConstFetch($classConst)) {
return true;
}
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classConst);
if ($phpDocInfo->hasByName('api')) {
return true;
}
$classLike = $classConst->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike instanceof ClassLike) {
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classLike);
return $phpDocInfo->hasByName('api');
}
return false;
return $this->enumAnalyzer->isEnumClassConst($classConst);
}
}

View File

@ -13,9 +13,10 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ThisType;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\PhpParser\AstResolver;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -81,16 +82,16 @@ CODE_SAMPLE
}
$type = $scope->getType($node->var);
if ($type instanceof ThisType) {
$type = $type->getStaticObjectType();
}
if (! $type instanceof ObjectType) {
if (! $type instanceof TypeWithClassName) {
return null;
}
$class = $this->reflectionAstResolver->resolveClassFromObjectType($type);
if ($this->shouldSkipClassMethod($class, $node)) {
$classLike = $this->reflectionAstResolver->resolveClassFromObjectType($type);
if ($classLike === null) {
return null;
}
if ($this->shouldSkipClassMethod($classLike, $node)) {
return null;
}
@ -113,9 +114,9 @@ CODE_SAMPLE
return $node;
}
private function shouldSkipClassMethod(?Class_ $class, MethodCall $methodCall): bool
private function shouldSkipClassMethod(Class_ | Trait_ | Interface_ $classLike, MethodCall $methodCall): bool
{
if (! $class instanceof Class_) {
if (! $classLike instanceof Class_) {
return true;
}
@ -124,7 +125,7 @@ CODE_SAMPLE
return true;
}
$classMethod = $class->getMethod($methodName);
$classMethod = $classLike->getMethod($methodName);
if (! $classMethod instanceof ClassMethod) {
return true;
}

View File

@ -5,20 +5,18 @@ declare(strict_types=1);
namespace Rector\Removing\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Trait_;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\NodeManipulator\ClassManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\Removing\Rector\Class_\RemoveTraitRector\RemoveTraitRectorTest
* @see \Rector\Tests\Removing\Rector\Class_\RemoveTraitUseRector\RemoveTraitUseRectorTest
*/
final class RemoveTraitRector extends AbstractRector implements ConfigurableRectorInterface
final class RemoveTraitUseRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var string
@ -32,11 +30,6 @@ final class RemoveTraitRector extends AbstractRector implements ConfigurableRect
*/
private array $traitsToRemove = [];
public function __construct(
private ClassManipulator $classManipulator
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Remove specific traits from code', [
@ -74,13 +67,18 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
$usedTraits = $this->classManipulator->getUsedTraits($node);
if ($usedTraits === []) {
return null;
}
$this->classHasChanged = false;
$this->removeTraits($usedTraits);
foreach ($node->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $trait) {
if (! $this->isNames($trait, $this->traitsToRemove)) {
continue;
}
$this->removeNode($traitUse);
$this->classHasChanged = true;
}
}
// invoke re-print
if ($this->classHasChanged) {
@ -98,22 +96,4 @@ CODE_SAMPLE
{
$this->traitsToRemove = $configuration[self::TRAITS_TO_REMOVE] ?? [];
}
/**
* @param Name[] $usedTraits
*/
private function removeTraits(array $usedTraits): void
{
foreach ($usedTraits as $usedTrait) {
foreach ($this->traitsToRemove as $traitToRemove) {
if (! $this->isName($usedTrait, $traitToRemove)) {
continue;
}
$this->removeNode($usedTrait);
$this->classHasChanged = true;
continue 2;
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Rector\Core\NodeAnalyzer;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class EnumAnalyzer
{
public function __construct(
private NodeNameResolver $nodeNameResolver
) {
}
/**
* @see https://github.com/myclabs/php-enum#declaration
*/
public function isEnumClassConst(ClassConst $classConst): bool
{
$classLike = $classConst->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return false;
}
if ($classLike->extends === null) {
return false;
}
return $this->nodeNameResolver->isName($classLike->extends, '*Enum');
}
}

View File

@ -8,10 +8,10 @@ use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\Trait_;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Reflection\ClassReflection;
use Rector\Core\PhpParser\AstResolver;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -19,62 +19,46 @@ final class ClassConstManipulator
{
public function __construct(
private BetterNodeFinder $betterNodeFinder,
private ClassManipulator $classManipulator,
private NodeNameResolver $nodeNameResolver,
private NodeRepository $nodeRepository,
private NodeComparator $nodeComparator
private AstResolver $astResolver
) {
}
public function hasClassConstFetch(ClassConst $classConst): bool
public function hasClassConstFetch(ClassConst $classConst, ClassReflection $classReflection): bool
{
$classLike = $classConst->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return false;
}
$searchInNodes = [$classLike];
foreach ($classReflection->getAncestors() as $ancestorClassReflection) {
$ancestorClass = $this->astResolver->resolveClassFromClassReflection(
$ancestorClassReflection,
$ancestorClassReflection->getName()
);
$usedTraitNames = $this->classManipulator->getUsedTraits($classLike);
foreach ($usedTraitNames as $usedTraitName) {
$usedTraitName = $this->nodeRepository->findTrait((string) $usedTraitName);
if (! $usedTraitName instanceof Trait_) {
if (! $ancestorClass instanceof ClassLike) {
continue;
}
$searchInNodes[] = $usedTraitName;
}
// has in class?
$isClassConstFetchFound = (bool) $this->betterNodeFinder->find($ancestorClass, function (Node $node) use (
$classConst
): bool {
// property + static fetch
if (! $node instanceof ClassConstFetch) {
return false;
}
return (bool) $this->betterNodeFinder->find($searchInNodes, function (Node $node) use ($classConst): bool {
// itself
if ($this->nodeComparator->areNodesEqual($node, $classConst)) {
return false;
return $this->isNameMatch($node, $classConst);
});
if ($isClassConstFetchFound) {
return true;
}
// property + static fetch
if (! $node instanceof ClassConstFetch) {
return false;
}
return $this->isNameMatch($node, $classConst);
});
}
/**
* @see https://github.com/myclabs/php-enum#declaration
*/
public function isEnum(ClassConst $classConst): bool
{
$classLike = $classConst->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return false;
}
if ($classLike->extends === null) {
return false;
}
return $this->nodeNameResolver->isName($classLike->extends, '*Enum');
return false;
}
private function isNameMatch(ClassConstFetch $classConstFetch, ClassConst $classConst): bool

View File

@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Rector\Core\NodeManipulator;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\NodeNameResolver\NodeNameResolver;
@ -24,24 +22,6 @@ final class ClassManipulator
) {
}
/**
* @deprecated
* @return array<string, Name>
*/
public function getUsedTraits(Class_ | Trait_ $classLike): array
{
$usedTraits = [];
foreach ($classLike->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $trait) {
/** @var string $traitName */
$traitName = $this->nodeNameResolver->getName($trait);
$usedTraits[$traitName] = $trait;
}
}
return $usedTraits;
}
public function hasParentMethodOrInterface(ObjectType $objectType, string $methodName): bool
{
if (! $this->reflectionProvider->hasClass($objectType->getClassName())) {

View File

@ -8,15 +8,17 @@ use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeFinder;
use PhpParser\Parser;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Php\PhpFunctionReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\Reflection\ReflectionResolver;
@ -53,9 +55,9 @@ final class AstResolver
* Parsing files is very heavy performance, so this will help to leverage it
* The value can be also null, as the method might not exist in the class.
*
* @var array<class-string, Class_|null>
* @var array<class-string, Class_|Trait_|Interface_|null>
*/
private array $classesByName = [];
private array $classLikesByName = [];
public function __construct(
private Parser $parser,
@ -70,14 +72,15 @@ final class AstResolver
) {
}
public function resolveClassFromObjectType(ObjectType $objectType): ?Class_
{
if (! $this->reflectionProvider->hasClass($objectType->getClassName())) {
public function resolveClassFromObjectType(
TypeWithClassName $typeWithClassName
): Class_ | Trait_ | Interface_ | null {
if (! $this->reflectionProvider->hasClass($typeWithClassName->getClassName())) {
return null;
}
$classReflection = $this->reflectionProvider->getClass($objectType->getClassName());
return $this->resolveClassFromClassReflection($classReflection, $objectType->getClassName());
$classReflection = $this->reflectionProvider->getClass($typeWithClassName->getClassName());
return $this->resolveClassFromClassReflection($classReflection, $typeWithClassName->getClassName());
}
public function resolveClassMethodFromMethodReflection(MethodReflection $methodReflection): ?ClassMethod
@ -204,14 +207,16 @@ final class AstResolver
return $this->resolveClassMethod($callerStaticType->getClassName(), $methodName);
}
public function resolveClassFromClassReflection(ClassReflection $classReflection, string $className): ?Class_
{
public function resolveClassFromClassReflection(
ClassReflection $classReflection,
string $className
): Trait_ | Class_ | Interface_ | null {
if ($classReflection->isBuiltin()) {
return null;
}
if (isset($this->classesByName[$classReflection->getName()])) {
return $this->classesByName[$classReflection->getName()];
if (isset($this->classLikesByName[$classReflection->getName()])) {
return $this->classLikesByName[$classReflection->getName()];
}
$fileName = $classReflection->getFileName();
@ -219,7 +224,7 @@ final class AstResolver
// probably internal class
if ($fileName === false) {
// avoid parsing falsy-file again
$this->classesByName[$classReflection->getName()] = null;
$this->classLikesByName[$classReflection->getName()] = null;
return null;
}
@ -228,24 +233,24 @@ final class AstResolver
$nodes = $this->parser->parse($fileContent);
if ($nodes === null) {
// avoid parsing falsy-file again
$this->classesByName[$classReflection->getName()] = null;
$this->classLikesByName[$classReflection->getName()] = null;
return null;
}
/** @var Class_[] $classes */
$classes = $this->betterNodeFinder->findInstanceOf($nodes, Class_::class);
/** @var array<Class_|Trait_|Interface_> $classLikes */
$classLikes = $this->betterNodeFinder->findInstanceOf($nodes, ClassLike::class);
$reflectionClassName = $classReflection->getName();
foreach ($classes as $class) {
foreach ($classLikes as $classLike) {
if ($reflectionClassName !== $className) {
continue;
}
$this->classesByName[$classReflection->getName()] = $class;
return $class;
$this->classLikesByName[$classReflection->getName()] = $classLike;
return $classLike;
}
$this->classesByName[$classReflection->getName()] = null;
$this->classLikesByName[$classReflection->getName()] = null;
return null;
}
}