mirror of
https://github.com/rectorphp/rector.git
synced 2024-05-28 23:10:51 +00:00
Add failing test fixture for RemoveUnusedPrivateMethodRector (#604)
* Add failing test fixture for ReturnTypeFromReturnNewRector # Failing Test for ReturnTypeFromReturnNewRector Based on https://getrector.org/demo/1ebef269-093c-6afe-9c29-afdc8959befb refs https://github.com/rectorphp/rector/issues/6595 * Add failing test fixture for RemoveUnusedPrivateMethodRector # Failing Test for RemoveUnusedPrivateMethodRector Based on https://getrector.org/demo/1ebf50d7-992f-63ba-9a8a-bd39c81b9232 refs: https://github.com/rectorphp/rector/issues/6613 * fix test case skip_abstract_private_method_in_trait.php.inc * remove file from previous PR * fix fixture by moving namespace to top * import TraitUse * use AstResolver * added test case to check if non-abstract private methods are considered * refac: moved "is used" checks into it's own class to reduce class complexity * rename constructor arg to represent class name
This commit is contained in:
parent
e740a93886
commit
7398a6637c
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodRector\Fixture;
|
||||
|
||||
trait TraitWithNonAbstractPrivate
|
||||
{
|
||||
private function privateMethod() {}
|
||||
|
||||
public function publicMethod() {
|
||||
$this->privateMethod();
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveIfMethodNonAbstractPrivateInTrait
|
||||
{
|
||||
use TraitWithNonAbstractPrivate;
|
||||
|
||||
private function privateMethod()
|
||||
{
|
||||
//code
|
||||
}
|
||||
}
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodRector\Fixture;
|
||||
|
||||
trait TraitWithNonAbstractPrivate
|
||||
{
|
||||
private function privateMethod() {}
|
||||
|
||||
public function publicMethod() {
|
||||
$this->privateMethod();
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveIfMethodNonAbstractPrivateInTrait
|
||||
{
|
||||
use TraitWithNonAbstractPrivate;
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodRector\Fixture;
|
||||
|
||||
trait TraitWithAbstractPrivate
|
||||
{
|
||||
abstract private function privateMethod();
|
||||
|
||||
public function publicMethod() {
|
||||
$this->privateMethod();
|
||||
}
|
||||
}
|
||||
|
||||
class SkipAbstractPrivateMethodInTrait
|
||||
{
|
||||
use TraitWithAbstractPrivate;
|
||||
|
||||
private function privateMethod()
|
||||
{
|
||||
//code
|
||||
}
|
||||
}
|
||||
?>
|
174
rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php
Normal file
174
rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php
Normal file
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace Rector\DeadCode\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use Rector\Core\PhpParser\AstResolver;
|
||||
use Rector\Core\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\Core\PhpParser\Node\Value\ValueResolver;
|
||||
use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodMatcher;
|
||||
use Rector\NodeCollector\ValueObject\ArrayCallable;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class IsClassMethodUsedAnalyzer
|
||||
{
|
||||
public function __construct(
|
||||
private NodeNameResolver $nodeNameResolver,
|
||||
private AstResolver $astResolver,
|
||||
private BetterNodeFinder $betterNodeFinder,
|
||||
private ValueResolver $valueResolver,
|
||||
private ArrayCallableMethodMatcher $arrayCallableMethodMatcher,
|
||||
private CallCollectionAnalyzer $callCollectionAnalyzer
|
||||
) {
|
||||
}
|
||||
|
||||
public function isClassMethodUsed(ClassMethod $classMethod): bool
|
||||
{
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if (! $class instanceof Class_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$classMethodName = $this->nodeNameResolver->getName($classMethod);
|
||||
|
||||
// 1. direct normal calls
|
||||
if ($this->isClassMethodCalledInLocalMethodCall($class, $classMethodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. direct static calls
|
||||
if ($this->isClassMethodUsedInLocalStaticCall($class, $classMethodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. magic array calls!
|
||||
if ($this->isClassMethodCalledInLocalArrayCall($class, $classMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. abstract private method from trait (PHP 8.0+)
|
||||
return $this->isPrivateAbstractMethodInTrait($classMethod, $classMethodName);
|
||||
}
|
||||
|
||||
private function isClassMethodUsedInLocalStaticCall(Class_ $class, string $classMethodName): bool
|
||||
{
|
||||
$className = $this->nodeNameResolver->getName($class);
|
||||
|
||||
/** @var StaticCall[] $staticCalls */
|
||||
$staticCalls = $this->betterNodeFinder->findInstanceOf($class, StaticCall::class);
|
||||
return $this->callCollectionAnalyzer->isExists($staticCalls, $classMethodName, $className);
|
||||
}
|
||||
|
||||
private function isClassMethodCalledInLocalMethodCall(Class_ $class, string $classMethodName): bool
|
||||
{
|
||||
$className = $this->nodeNameResolver->getName($class);
|
||||
|
||||
/** @var MethodCall[] $methodCalls */
|
||||
$methodCalls = $this->betterNodeFinder->findInstanceOf($class, MethodCall::class);
|
||||
return $this->callCollectionAnalyzer->isExists($methodCalls, $classMethodName, $className);
|
||||
}
|
||||
|
||||
private function isInArrayMap(Class_ $class, Array_ $array): bool
|
||||
{
|
||||
$parentFuncCall = $this->betterNodeFinder->findParentType($array, FuncCall::class);
|
||||
if (! $parentFuncCall instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($parentFuncCall->name, 'array_map')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($array->items) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $array->items[1] instanceof ArrayItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $this->valueResolver->getValue($array->items[1]->value);
|
||||
|
||||
if (! is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $class->getMethod($value) instanceof ClassMethod;
|
||||
}
|
||||
|
||||
private function isClassMethodCalledInLocalArrayCall(Class_ $class, ClassMethod $classMethod): bool
|
||||
{
|
||||
/** @var Array_[] $arrays */
|
||||
$arrays = $this->betterNodeFinder->findInstanceOf($class, Array_::class);
|
||||
|
||||
foreach ($arrays as $array) {
|
||||
if ($this->isInArrayMap($class, $array)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$arrayCallable = $this->arrayCallableMethodMatcher->match($array);
|
||||
if ($this->shouldSkipArrayCallable($class, $arrayCallable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// the method is used
|
||||
/** @var ArrayCallable $arrayCallable */
|
||||
if ($this->nodeNameResolver->isName($classMethod->name, $arrayCallable->getMethod())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function shouldSkipArrayCallable(Class_ $class, ?ArrayCallable $arrayCallable): bool
|
||||
{
|
||||
if (! $arrayCallable instanceof ArrayCallable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// is current class method?
|
||||
return ! $this->nodeNameResolver->isName($class, $arrayCallable->getClass());
|
||||
}
|
||||
|
||||
private function isPrivateAbstractMethodInTrait(ClassMethod $classMethod, ?string $classMethodName): bool
|
||||
{
|
||||
if (null === $classMethodName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classReflection = $scope->getClassReflection();
|
||||
if (null === $classReflection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$traits = $this->astResolver->parseClassReflectionTraits($classReflection);
|
||||
foreach ($traits as $trait) {
|
||||
$method = $trait->getMethod($classMethodName);
|
||||
if (! $method instanceof ClassMethod) {
|
||||
continue;
|
||||
}
|
||||
if ($method->isAbstract()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -5,20 +5,12 @@ declare(strict_types=1);
|
|||
namespace Rector\DeadCode\Rector\ClassMethod;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\ValueObject\MethodName;
|
||||
use Rector\DeadCode\NodeAnalyzer\CallCollectionAnalyzer;
|
||||
use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodMatcher;
|
||||
use Rector\NodeCollector\ValueObject\ArrayCallable;
|
||||
use Rector\DeadCode\NodeAnalyzer\IsClassMethodUsedAnalyzer;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
@ -29,8 +21,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
|||
final class RemoveUnusedPrivateMethodRector extends AbstractRector
|
||||
{
|
||||
public function __construct(
|
||||
private ArrayCallableMethodMatcher $arrayCallableMethodMatcher,
|
||||
private CallCollectionAnalyzer $callCollectionAnalyzer
|
||||
private IsClassMethodUsedAnalyzer $isClassMethodUsedAnalyzer
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -83,25 +74,7 @@ CODE_SAMPLE
|
|||
return null;
|
||||
}
|
||||
|
||||
$class = $node->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if (! $class instanceof Class_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classMethodName = $this->nodeNameResolver->getName($node);
|
||||
|
||||
// 1. direct normal calls
|
||||
if ($this->isClassMethodCalledInLocalMethodCall($class, $classMethodName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. direct static calls
|
||||
if ($this->isClassMethodUsedInLocalStaticCall($class, $classMethodName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. magic array calls!
|
||||
if ($this->isClassMethodCalledInLocalArrayCall($class, $node)) {
|
||||
if ($this->isClassMethodUsedAnalyzer->isClassMethodUsed($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -147,85 +120,4 @@ CODE_SAMPLE
|
|||
|
||||
return $classReflection->hasMethod(MethodName::CALL);
|
||||
}
|
||||
|
||||
private function isClassMethodUsedInLocalStaticCall(Class_ $class, string $classMethodName): bool
|
||||
{
|
||||
$className = $this->getName($class);
|
||||
|
||||
/** @var StaticCall[] $staticCalls */
|
||||
$staticCalls = $this->betterNodeFinder->findInstanceOf($class, StaticCall::class);
|
||||
return $this->callCollectionAnalyzer->isExists($staticCalls, $classMethodName, $className);
|
||||
}
|
||||
|
||||
private function isClassMethodCalledInLocalMethodCall(Class_ $class, string $classMethodName): bool
|
||||
{
|
||||
$className = $this->getName($class);
|
||||
|
||||
/** @var MethodCall[] $methodCalls */
|
||||
$methodCalls = $this->betterNodeFinder->findInstanceOf($class, MethodCall::class);
|
||||
return $this->callCollectionAnalyzer->isExists($methodCalls, $classMethodName, $className);
|
||||
}
|
||||
|
||||
private function isInArrayMap(Class_ $class, Array_ $array): bool
|
||||
{
|
||||
$parentFuncCall = $this->betterNodeFinder->findParentType($array, FuncCall::class);
|
||||
if (! $parentFuncCall instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($parentFuncCall->name, 'array_map')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($array->items) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $array->items[1] instanceof ArrayItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $this->valueResolver->getValue($array->items[1]->value);
|
||||
|
||||
if (! is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $class->getMethod($value) instanceof ClassMethod;
|
||||
}
|
||||
|
||||
private function isClassMethodCalledInLocalArrayCall(Class_ $class, ClassMethod $classMethod): bool
|
||||
{
|
||||
/** @var Array_[] $arrays */
|
||||
$arrays = $this->betterNodeFinder->findInstanceOf($class, Array_::class);
|
||||
|
||||
foreach ($arrays as $array) {
|
||||
if ($this->isInArrayMap($class, $array)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$arrayCallable = $this->arrayCallableMethodMatcher->match($array);
|
||||
if ($this->shouldSkipArrayCallable($class, $arrayCallable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// the method is used
|
||||
/** @var ArrayCallable $arrayCallable */
|
||||
if ($this->nodeNameResolver->isName($classMethod->name, $arrayCallable->getMethod())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function shouldSkipArrayCallable(Class_ $class, ?ArrayCallable $arrayCallable): bool
|
||||
{
|
||||
if (! $arrayCallable instanceof ArrayCallable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// is current class method?
|
||||
return ! $this->isName($class, $arrayCallable->getClass());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user