[DeadCode] Keep array method call in RemoveUnusedPrivateMethodRector

This commit is contained in:
Tomas Votruba 2019-07-09 19:36:42 +02:00
parent 3bee49fa90
commit bb2f77f4b0
6 changed files with 120 additions and 28 deletions

View File

@ -102,6 +102,7 @@ parameters:
Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
# tough logic
- 'src/NodeContainer/ParsedNodesByType.php'
- 'packages/PHPStan/src/Rector/Node/RemoveNonExistingVarAnnotationRector.php'
- 'packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php'
- 'src/PhpParser/Node/Commander/NodeRemovingCommander.php'

View File

@ -2,7 +2,7 @@
namespace Rector\CodeQuality\Tests\Rector\If_\ExplicitBoolCompareRector\Fixture;
final class String_
final class ExplicitString
{
public function run(string $item)
{
@ -22,7 +22,7 @@ final class String_
namespace Rector\CodeQuality\Tests\Rector\If_\ExplicitBoolCompareRector\Fixture;
final class String_
final class ExplicitString
{
public function run(string $item)
{

View File

@ -4,8 +4,8 @@ namespace Rector\DeadCode\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use Rector\NodeContainer\ParsedNodesByType;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -70,27 +70,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
/** @var ClassLike|null $classNode */
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
return null;
}
// unreliable to detect trait method usage
if ($classNode instanceof Trait_) {
return null;
}
if ($classNode instanceof Class_ && $classNode->isAnonymous()) {
return null;
}
// skips interfaces by default too
if (! $node->isPrivate()) {
return null;
}
if ($this->isName($node, '__*')) {
if ($this->shouldSkip($node)) {
return null;
}
@ -103,4 +83,30 @@ CODE_SAMPLE
return $node;
}
private function shouldSkip(ClassMethod $classMethod): bool
{
/** @var Class_|Interface_|Trait_|null $classNode */
$classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
return true;
}
// unreliable to detect trait, interface doesn't make sense
if ($classNode instanceof Trait_ || $classNode instanceof Interface_) {
return true;
}
if ($classNode->isAnonymous()) {
return true;
}
// skips interfaces by default too
if (! $classMethod->isPrivate()) {
return true;
}
// skip magic methods - @see https://www.php.net/manual/en/language.oop5.magic.php
return $this->isName($classMethod, '__*');
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveUnusedPrivateMethodRector\Fixture;
final class SkipArrayCallalbes
{
public function run()
{
$array = [3, 2, 1];
usort($array, [$this, 'sort']);
return $array;
}
private function sort($a, $b)
{
return $a <=> $b;
}
}

View File

@ -20,6 +20,7 @@ final class RemoveUnusedPrivateMethodRectorTest extends AbstractRectorTestCase
__DIR__ . '/Fixture/keep_used_method.php.inc',
__DIR__ . '/Fixture/keep_used_method_static.php.inc',
__DIR__ . '/Fixture/skip_anonymous_class.php.inc',
__DIR__ . '/Fixture/skip_array_callables.php.inc',
]);
}

View File

@ -4,6 +4,7 @@ namespace Rector\NodeContainer;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
@ -43,6 +44,8 @@ final class ParsedNodesByType
New_::class,
StaticCall::class,
MethodCall::class,
// for array callable - [$this, 'someCall']
Array_::class,
];
/**
@ -81,10 +84,16 @@ final class ParsedNodesByType
private $simpleParsedNodesByType = [];
/**
* @var mixed[]
* @var MethodCall[][][]|StaticCall[][][]
*/
private $methodsCallsByTypeAndMethod = [];
/**
* E.g. [$this, 'someLocalMethod']
* @var Array_[][][]
*/
private $arrayCallablesByTypeAndMethod = [];
public function __construct(NameResolver $nameResolver)
{
$this->nameResolver = $nameResolver;
@ -388,6 +397,22 @@ final class ParsedNodesByType
return;
}
// array callable - [$this, 'someCall']
if ($node instanceof Array_) {
$arrayCallableClassAndMethod = $this->matchArrayCallableClassAndMethod($node);
if ($arrayCallableClassAndMethod === null) {
return;
}
[$className, $methodName] = $arrayCallableClassAndMethod;
if (! method_exists($className, $methodName)) {
return;
}
$this->arrayCallablesByTypeAndMethod[$className][$methodName][] = $node;
return;
}
if ($node instanceof MethodCall || $node instanceof StaticCall) {
$this->addCall($node);
return;
@ -399,7 +424,7 @@ final class ParsedNodesByType
}
/**
* @return MethodCall[]
* @return MethodCall[]|StaticCall[]|Array_[]
*/
public function findClassMethodCalls(ClassMethod $classMethod): array
{
@ -413,11 +438,11 @@ final class ParsedNodesByType
return [];
}
return $this->methodsCallsByTypeAndMethod[$className][$methodName] ?? [];
return $this->methodsCallsByTypeAndMethod[$className][$methodName] ?? $this->arrayCallablesByTypeAndMethod[$className][$methodName] ?? [];
}
/**
* @return MethodCall[]|StaticCall[]
* @return MethodCall[][]|StaticCall[][]
*/
public function findMethodCallsOnClass(string $className): array
{
@ -575,4 +600,43 @@ final class ParsedNodesByType
$this->methodsCallsByTypeAndMethod[$className][$methodName][] = $node;
}
/**
* Matches array like: "[$this, 'methodName']" ['ClassName', 'methodName']
* @return string[]|null
*/
private function matchArrayCallableClassAndMethod(Array_ $array): ?array
{
if (count($array->items) !== 2) {
return null;
}
if ($array->items[0] === null) {
return null;
}
if (! $array->items[0]->value instanceof Variable) {
return null;
}
if (! $this->nameResolver->isName($array->items[0]->value, 'this')) {
return null;
}
if ($array->items[1] === null) {
return null;
}
if (! $array->items[1]->value instanceof Node\Scalar\String_) {
return null;
}
/** @var Node\Scalar\String_ $string */
$string = $array->items[1]->value;
$methodName = $string->value;
$className = $array->getAttribute(AttributeKey::CLASS_NAME);
return [$className, $methodName];
}
}