mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 13:28:18 +01:00
[DeadCode] Improve parent and current type comparison on RemoveDelegatingParentCallRector (#4409)
* decouple CurrentAndParentClassMethodComparator * fix param reflections * [rector] fix param reflections * [cs] fix param reflections * composer: bump assert * [rector] composer: bump assert * [cs] composer: bump assert * static fixes Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
parent
15600516af
commit
e085f52f86
@ -12,7 +12,7 @@ Rector helps you with 2 areas - major code changes and in daily work.
|
||||
|
||||
It's a tool that [we develop](https://getrector.org/) and share for free, so you anyone can automate their refactoring.
|
||||
|
||||
Would you like to skip learning Rector, AST and nodes, educate your team about Rectors benefits and setup Rector in your CI that will speedup your development by 300 %, [hire us](https://getrector.org/contact)?
|
||||
[Hire us](https://getrector.org/contact) to skip learning Rector, AST and nodes, to educate your team about Rectors benefits and to setup Rector in your project - so you can enjoy the 300 % development speed :+1:
|
||||
|
||||
[![Coverage Status](https://img.shields.io/coveralls/rectorphp/rector/master.svg?style=flat-square)](https://coveralls.io/github/rectorphp/rector?branch=master)
|
||||
[![Downloads](https://img.shields.io/packagist/dt/rector/rector.svg?style=flat-square)](https://packagist.org/packages/rector/rector)
|
||||
|
@ -39,7 +39,7 @@
|
||||
"symplify/package-builder": "^8.3.35",
|
||||
"symplify/set-config-resolver": "^8.3.35",
|
||||
"symplify/smart-file-system": "^8.3.35",
|
||||
"webmozart/assert": "^1.8"
|
||||
"webmozart/assert": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
|
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeCollector\Reflection;
|
||||
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\Native\NativeMethodReflection;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeUtils;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
|
||||
final class MethodReflectionProvider
|
||||
{
|
||||
/**
|
||||
* @var NodeTypeResolver
|
||||
*/
|
||||
private $nodeTypeResolver;
|
||||
|
||||
/**
|
||||
* @var ReflectionProvider
|
||||
*/
|
||||
private $reflectionProvider;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(
|
||||
NodeTypeResolver $nodeTypeResolver,
|
||||
NodeNameResolver $nodeNameResolver,
|
||||
ReflectionProvider $reflectionProvider
|
||||
) {
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
public function provideParameterTypesFromMethodReflection(MethodReflection $methodReflection): array
|
||||
{
|
||||
if ($methodReflection instanceof NativeMethodReflection) {
|
||||
// method "getParameters()" does not exist there
|
||||
return [];
|
||||
}
|
||||
|
||||
$parameterTypes = [];
|
||||
|
||||
$parameterReflections = $this->getParameterReflectionsFromMethodReflection($methodReflection);
|
||||
foreach ($parameterReflections as $phpParameterReflection) {
|
||||
$parameterTypes[] = $phpParameterReflection->getType();
|
||||
}
|
||||
|
||||
return $parameterTypes;
|
||||
}
|
||||
|
||||
public function provideByClassAndMethodName(string $class, string $method, Scope $scope): ?MethodReflection
|
||||
{
|
||||
$classReflection = $this->reflectionProvider->getClass($class);
|
||||
if (! $classReflection->hasMethod($method)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $classReflection->getMethod($method, $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
public function provideParameterTypesByStaticCall(StaticCall $staticCall): array
|
||||
{
|
||||
$methodReflection = $this->provideByStaticCall($staticCall);
|
||||
if ($methodReflection === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->provideParameterTypesFromMethodReflection($methodReflection);
|
||||
}
|
||||
|
||||
public function provideByStaticCall(StaticCall $staticCall): ?MethodReflection
|
||||
{
|
||||
$objectType = $this->nodeTypeResolver->resolve($staticCall->class);
|
||||
$classes = TypeUtils::getDirectClassNames($objectType);
|
||||
|
||||
$methodName = $this->nodeNameResolver->getName($staticCall->name);
|
||||
if ($methodName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Scope|null $scope */
|
||||
$scope = $staticCall->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$methodReflection = $this->provideByClassAndMethodName($class, $methodName, $scope);
|
||||
if ($methodReflection instanceof MethodReflection) {
|
||||
return $methodReflection;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
public function provideParameterTypesByClassMethod(ClassMethod $classMethod): array
|
||||
{
|
||||
$methodReflection = $this->provideByClassMethod($classMethod);
|
||||
if ($methodReflection === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->provideParameterTypesFromMethodReflection($methodReflection);
|
||||
}
|
||||
|
||||
public function provideByClassMethod(ClassMethod $classMethod): ?MethodReflection
|
||||
{
|
||||
$class = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if (! is_string($class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$method = $this->nodeNameResolver->getName($classMethod->name);
|
||||
if (! is_string($method)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->provideByClassAndMethodName($class, $method, $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParameterReflection[]
|
||||
*/
|
||||
public function getParameterReflectionsFromMethodReflection(MethodReflection $methodReflection): array
|
||||
{
|
||||
$methodReflectionVariant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
|
||||
return $methodReflectionVariant->getParameters();
|
||||
}
|
||||
}
|
@ -7,11 +7,11 @@
|
||||
"symfony/dependency-injection": "^4.4.8|^5.1",
|
||||
"symfony/config": "^4.4.8|^5.1",
|
||||
"symfony/http-kernel": "^4.4.8|^5.1",
|
||||
"symplify/package-builder": "^8.3.33"
|
||||
"symplify/package-builder": "^8.3.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5|^9.2",
|
||||
"symplify/easy-testing": "^8.3.33"
|
||||
"symplify/easy-testing": "^8.3.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -6,7 +6,7 @@
|
||||
"php": "^7.2.4|^8.0",
|
||||
"symfony/dependency-injection": "^4.4.8|^5.1",
|
||||
"symfony/http-kernel": "^4.4.8|^5.1",
|
||||
"symplify/package-builder": "^8.3.33"
|
||||
"symplify/package-builder": "^8.3.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5|^9.2"
|
||||
|
19
phpstan.neon
19
phpstan.neon
@ -898,17 +898,6 @@ parameters:
|
||||
paths:
|
||||
- tests/debug_functions.php
|
||||
|
||||
-
|
||||
message: '#Do not use @method tag in class docblock#'
|
||||
paths:
|
||||
- src/PhpParser/Builder/ClassBuilder.php # 15
|
||||
- src/PhpParser/Builder/MethodBuilder.php # 15
|
||||
- src/PhpParser/Builder/NamespaceBuilder.php # 15
|
||||
- src/PhpParser/Builder/ParamBuilder.php # 15
|
||||
- src/PhpParser/Builder/PropertyBuilder.php # 15
|
||||
- src/PhpParser/Builder/TraitUseBuilder.php # 15
|
||||
- src/PhpParser/Builder/UseBuilder.php # 15
|
||||
|
||||
-
|
||||
message: '#Property with protected modifier is not allowed\. Use interface instead#'
|
||||
paths:
|
||||
@ -993,3 +982,11 @@ parameters:
|
||||
- src/Testing/PHPUnit/AbstractNodeVisitorTestCase.php # 30
|
||||
- src/Testing/PHPUnit/AbstractRectorTestCase.php # 24
|
||||
- utils/doctrine-annotation-parser-syncer/src/FileSyncer/AbstractClassSyncer.php # 19
|
||||
|
||||
- '#Method Rector\\Core\\PhpParser\\Builder\\NamespaceBuilder\:\:getNode\(\) should return PhpParser\\Node\\Stmt\\Namespace_ but returns PhpParser\\Node#'
|
||||
- '#Method Rector\\Core\\PhpParser\\Builder\\TraitUseBuilder\:\:getNode\(\) should return PhpParser\\Node\\Stmt\\TraitUse but returns PhpParser\\Node#'
|
||||
- '#Method Rector\\Core\\PhpParser\\Builder\\UseBuilder\:\:getNode\(\) should return PhpParser\\Node\\Stmt\\Use_ but returns PhpParser\\Node#'
|
||||
|
||||
- '#Method Rector\\Utils\\PHPStanAttributeTypeSyncer\\NodeFactory\\AttributeAwareClassFactoryFactory\:\:createFromPhpDocParserNodeClass\(\) should return PhpParser\\Node\\Stmt\\Namespace_ but returns PhpParser\\Node#'
|
||||
- '#Method Rector\\Utils\\PHPStanAttributeTypeSyncer\\NodeFactory\\AttributeAwareClassFactory\:\:createFromPhpDocParserNodeClass\(\) should return PhpParser\\Node\\Stmt\\Namespace_ but returns PhpParser\\Node#'
|
||||
- '#Method Rector\\NetteKdyby\\NodeFactory\\EventValueObjectClassFactory\:\:wrapClassToNamespace\(\) should return PhpParser\\Node\\Stmt\\Namespace_ but returns PhpParser\\Node#'
|
||||
|
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Comparator;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
|
||||
use Rector\DeadCode\Comparator\Parameter\ParameterDefaultsComparator;
|
||||
use Rector\DeadCode\Comparator\Parameter\ParameterTypeComparator;
|
||||
use Rector\NodeCollector\NodeCollector\NodeRepository;
|
||||
use Rector\NodeCollector\Reflection\MethodReflectionProvider;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class CurrentAndParentClassMethodComparator
|
||||
{
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
/**
|
||||
* @var BetterStandardPrinter
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @var NodeRepository
|
||||
*/
|
||||
private $nodeRepository;
|
||||
|
||||
/**
|
||||
* @var MethodReflectionProvider
|
||||
*/
|
||||
private $methodReflectionProvider;
|
||||
|
||||
/**
|
||||
* @var ParameterDefaultsComparator
|
||||
*/
|
||||
private $parameterDefaultsComparator;
|
||||
|
||||
/**
|
||||
* @var ParameterTypeComparator
|
||||
*/
|
||||
private $parameterTypeComparator;
|
||||
|
||||
public function __construct(
|
||||
NodeNameResolver $nodeNameResolver,
|
||||
BetterStandardPrinter $betterStandardPrinter,
|
||||
NodeRepository $nodeRepository,
|
||||
MethodReflectionProvider $methodReflectionProvider,
|
||||
ParameterDefaultsComparator $parameterDefaultsComparator,
|
||||
ParameterTypeComparator $parameterTypeComparator
|
||||
) {
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
$this->nodeRepository = $nodeRepository;
|
||||
$this->methodReflectionProvider = $methodReflectionProvider;
|
||||
$this->parameterDefaultsComparator = $parameterDefaultsComparator;
|
||||
$this->parameterTypeComparator = $parameterTypeComparator;
|
||||
}
|
||||
|
||||
public function isParentCallMatching(ClassMethod $classMethod, StaticCall $staticCall): bool
|
||||
{
|
||||
if (! $this->isSameMethodParentCall($classMethod, $staticCall)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->areArgsAndParamsEqual($staticCall->args, $classMethod->params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->parameterTypeComparator->compareCurrentClassMethodAndParentStaticCall($classMethod, $staticCall)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->isParentClassMethodVisibilityOrDefaultOverride($classMethod, $staticCall);
|
||||
}
|
||||
|
||||
private function isSameMethodParentCall(ClassMethod $classMethod, StaticCall $staticCall): bool
|
||||
{
|
||||
if (! $this->nodeNameResolver->areNamesEqual($staticCall->name, $classMethod->name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->nodeNameResolver->isName($staticCall->class, 'parent');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Arg[] $parentStaticCallArgs
|
||||
* @param Param[] $currentClassMethodParams
|
||||
*/
|
||||
private function areArgsAndParamsEqual(array $parentStaticCallArgs, array $currentClassMethodParams): bool
|
||||
{
|
||||
if (count($parentStaticCallArgs) !== count($currentClassMethodParams)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($parentStaticCallArgs) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($parentStaticCallArgs as $key => $arg) {
|
||||
if (! isset($currentClassMethodParams[$key])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this only compares variable name, but those can be differnt, so its kinda useless
|
||||
$param = $currentClassMethodParams[$key];
|
||||
if (! $this->betterStandardPrinter->areNodesEqual($param->var, $arg->value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isParentClassMethodVisibilityOrDefaultOverride(
|
||||
ClassMethod $classMethod,
|
||||
StaticCall $staticCall
|
||||
): bool {
|
||||
/** @var string $className */
|
||||
$className = $staticCall->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
$parentClassName = get_parent_class($className);
|
||||
if (! $parentClassName) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
/** @var string $methodName */
|
||||
$methodName = $this->nodeNameResolver->getName($staticCall->name);
|
||||
|
||||
$parentClassMethod = $this->nodeRepository->findClassMethod($parentClassName, $methodName);
|
||||
if ($parentClassMethod !== null && $parentClassMethod->isProtected() && $classMethod->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName);
|
||||
}
|
||||
|
||||
private function checkOverrideUsingReflection(
|
||||
ClassMethod $classMethod,
|
||||
string $parentClassName,
|
||||
string $methodName
|
||||
): bool {
|
||||
// @todo use phpstan reflecoitn
|
||||
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$parentMethodReflection = $this->methodReflectionProvider->provideByClassAndMethodName(
|
||||
$parentClassName,
|
||||
$methodName,
|
||||
$scope
|
||||
);
|
||||
|
||||
// 3rd party code
|
||||
if ($parentMethodReflection !== null) {
|
||||
if (! $parentMethodReflection->isPrivate() && ! $parentMethodReflection->isPublic() && $classMethod->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($parentMethodReflection->isInternal()->yes()) {
|
||||
// we can't know for certain so we assume its a override with purpose
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->areParameterDefaultsDifferent($classMethod, $parentMethodReflection)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function areParameterDefaultsDifferent(
|
||||
ClassMethod $classMethod,
|
||||
MethodReflection $methodReflection
|
||||
): bool {
|
||||
$parameterReflections = $this->methodReflectionProvider->getParameterReflectionsFromMethodReflection(
|
||||
$methodReflection
|
||||
);
|
||||
|
||||
foreach ($parameterReflections as $key => $parameterReflection) {
|
||||
if (! isset($classMethod->params[$key])) {
|
||||
if ($parameterReflection->getDefaultValue() !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$methodParam = $classMethod->params[$key];
|
||||
|
||||
if ($this->parameterDefaultsComparator->areDefaultValuesDifferent(
|
||||
$parameterReflection,
|
||||
$methodParam
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Comparator\Parameter;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Param;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\Constant\ConstantBooleanType;
|
||||
use PHPStan\Type\Constant\ConstantFloatType;
|
||||
use PHPStan\Type\Constant\ConstantIntegerType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\ConstantType;
|
||||
use PHPStan\Type\NullType;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\Node\Value\ValueResolver;
|
||||
|
||||
final class ParameterDefaultsComparator
|
||||
{
|
||||
/**
|
||||
* @var ValueResolver
|
||||
*/
|
||||
private $valueResolver;
|
||||
|
||||
public function __construct(ValueResolver $valueResolver)
|
||||
{
|
||||
$this->valueResolver = $valueResolver;
|
||||
}
|
||||
|
||||
public function areDefaultValuesDifferent(ParameterReflection $parameterReflection, Param $param): bool
|
||||
{
|
||||
if ($parameterReflection->getDefaultValue() === null && $param->default === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isMutuallyExclusiveNull($parameterReflection, $param)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var Expr $paramDefault */
|
||||
$paramDefault = $param->default;
|
||||
|
||||
$firstParameterValue = $this->resolveParameterReflectionDefaultValue($parameterReflection);
|
||||
$secondParameterValue = $this->valueResolver->getValue($paramDefault);
|
||||
|
||||
return $firstParameterValue !== $secondParameterValue;
|
||||
}
|
||||
|
||||
private function isMutuallyExclusiveNull(ParameterReflection $parameterReflection, Param $param): bool
|
||||
{
|
||||
if ($parameterReflection->getDefaultValue() === null && $param->default !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $parameterReflection->getDefaultValue() !== null && $param->default === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|float|int|string|mixed[]|null
|
||||
*/
|
||||
private function resolveParameterReflectionDefaultValue(ParameterReflection $parameterReflection)
|
||||
{
|
||||
$defaultValue = $parameterReflection->getDefaultValue();
|
||||
if (! $defaultValue instanceof ConstantType) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
if ($defaultValue instanceof ConstantArrayType) {
|
||||
return $defaultValue->getAllArrays();
|
||||
}
|
||||
|
||||
/** @var ConstantStringType|ConstantIntegerType|ConstantFloatType|ConstantBooleanType|NullType $defaultValue */
|
||||
return $defaultValue->getValue();
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Comparator\Parameter;
|
||||
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\NodeCollector\Reflection\MethodReflectionProvider;
|
||||
|
||||
final class ParameterTypeComparator
|
||||
{
|
||||
/**
|
||||
* @var MethodReflectionProvider
|
||||
*/
|
||||
private $methodReflectionProvider;
|
||||
|
||||
public function __construct(MethodReflectionProvider $methodReflectionProvider)
|
||||
{
|
||||
$this->methodReflectionProvider = $methodReflectionProvider;
|
||||
}
|
||||
|
||||
public function compareCurrentClassMethodAndParentStaticCall(
|
||||
ClassMethod $classMethod,
|
||||
StaticCall $staticCall
|
||||
): bool {
|
||||
$currentParameterTypes = $this->methodReflectionProvider->provideParameterTypesByClassMethod($classMethod);
|
||||
$parentParameterTypes = $this->methodReflectionProvider->provideParameterTypesByStaticCall($staticCall);
|
||||
|
||||
foreach ($currentParameterTypes as $key => $currentParameterType) {
|
||||
if (! isset($parentParameterTypes[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentParameterType = $parentParameterTypes[$key];
|
||||
if (! $currentParameterType->equals($parentParameterType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Rector\DeadCode\Rector\ClassMethod;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
@ -14,21 +13,27 @@ use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
use Rector\Core\ValueObject\MethodName;
|
||||
use Rector\DeadCode\Comparator\CurrentAndParentClassMethodComparator;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
|
||||
/**
|
||||
* @see \Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\RemoveDelegatingParentCallRectorTest
|
||||
*/
|
||||
final class RemoveDelegatingParentCallRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var CurrentAndParentClassMethodComparator
|
||||
*/
|
||||
private $currentAndParentClassMethodComparator;
|
||||
|
||||
public function __construct(CurrentAndParentClassMethodComparator $currentAndParentClassMethodComparator)
|
||||
{
|
||||
$this->currentAndParentClassMethodComparator = $currentAndParentClassMethodComparator;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Removed dead parent call, that does not change anything', [
|
||||
@ -83,7 +88,11 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
$staticCall = $this->matchStaticCall($onlyStmt);
|
||||
if (! $this->isParentCallMatching($node, $staticCall)) {
|
||||
if ($staticCall === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->currentAndParentClassMethodComparator->isParentCallMatching($node, $staticCall)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -144,26 +153,6 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isParentCallMatching(ClassMethod $classMethod, ?StaticCall $staticCall): bool
|
||||
{
|
||||
if ($staticCall === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->areNamesEqual($staticCall->name, $classMethod->name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->isName($staticCall->class, 'parent')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->areArgsAndParamsEqual($staticCall->args, $classMethod->params)) {
|
||||
return false;
|
||||
}
|
||||
return ! $this->isParentClassMethodVisibilityOrDefaultOverride($classMethod, $staticCall);
|
||||
}
|
||||
|
||||
private function hasRequiredAnnotation(Node $node): bool
|
||||
{
|
||||
/** @var PhpDocInfo|null $phpDocInfo */
|
||||
@ -172,120 +161,6 @@ CODE_SAMPLE
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $phpDocInfo->hasByName('required');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Arg[] $args
|
||||
* @param Param[] $params
|
||||
*/
|
||||
private function areArgsAndParamsEqual(array $args, array $params): bool
|
||||
{
|
||||
if (count($args) !== count($params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($args as $key => $arg) {
|
||||
if (! isset($params[$key])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$param = $params[$key];
|
||||
|
||||
if (! $this->areNodesEqual($param->var, $arg->value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isParentClassMethodVisibilityOrDefaultOverride(
|
||||
ClassMethod $classMethod,
|
||||
StaticCall $staticCall
|
||||
): bool {
|
||||
/** @var string $className */
|
||||
$className = $staticCall->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
$parentClassName = get_parent_class($className);
|
||||
if (! $parentClassName) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
/** @var string $methodName */
|
||||
$methodName = $this->getName($staticCall->name);
|
||||
|
||||
$parentClassMethod = $this->nodeRepository->findClassMethod($parentClassName, $methodName);
|
||||
if ($parentClassMethod !== null && $parentClassMethod->isProtected() && $classMethod->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName);
|
||||
}
|
||||
|
||||
private function checkOverrideUsingReflection(
|
||||
ClassMethod $classMethod,
|
||||
string $parentClassName,
|
||||
string $methodName
|
||||
): bool {
|
||||
$parentMethodReflection = $this->getReflectionMethod($parentClassName, $methodName);
|
||||
// 3rd party code
|
||||
if ($parentMethodReflection !== null) {
|
||||
if ($parentMethodReflection->isProtected() && $classMethod->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
if ($parentMethodReflection->isInternal()) {
|
||||
//we can't know for certain so we assume its an override
|
||||
return true;
|
||||
}
|
||||
if ($this->areParameterDefaultsDifferent($classMethod, $parentMethodReflection)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getReflectionMethod(string $className, string $methodName): ?ReflectionMethod
|
||||
{
|
||||
if (! method_exists($className, $methodName)) {
|
||||
//internal classes don't have __construct method
|
||||
if ($methodName === MethodName::CONSTRUCT && class_exists($className)) {
|
||||
return (new ReflectionClass($className))->getConstructor();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return new ReflectionMethod($className, $methodName);
|
||||
}
|
||||
|
||||
private function areParameterDefaultsDifferent(
|
||||
ClassMethod $classMethod,
|
||||
ReflectionMethod $reflectionMethod
|
||||
): bool {
|
||||
foreach ($reflectionMethod->getParameters() as $key => $reflectionParameter) {
|
||||
if (! isset($classMethod->params[$key])) {
|
||||
if ($reflectionParameter->isDefaultValueAvailable()) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$methodParam = $classMethod->params[$key];
|
||||
|
||||
if ($this->areDefaultValuesDifferent($reflectionParameter, $methodParam)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function areDefaultValuesDifferent(ReflectionParameter $reflectionParameter, Param $methodParam): bool
|
||||
{
|
||||
if ($reflectionParameter->isDefaultValueAvailable() !== isset($methodParam->default)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $reflectionParameter->isDefaultValueAvailable() && $methodParam->default !== null &&
|
||||
! $this->isValue($methodParam->default, $reflectionParameter->getDefaultValue());
|
||||
return $phpDocInfo->hasByName('required');
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\DifferentDefaults;
|
||||
|
||||
Class Base {
|
||||
public function __construct($message = "")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultOverride extends Base
|
||||
{
|
||||
public function __construct($message = "My special message")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
class NoOverride extends Base
|
||||
{
|
||||
public function __construct($message = "")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\DifferentDefaults;
|
||||
|
||||
Class Base {
|
||||
public function __construct($message = "")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultOverride extends Base
|
||||
{
|
||||
public function __construct($message = "My special message")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
class NoOverride extends Base
|
||||
{
|
||||
}
|
||||
?>
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Fixture;
|
||||
|
||||
use Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source\ClassWithStringDefaultParameter;
|
||||
|
||||
final class IdenticalDefaults extends ClassWithStringDefaultParameter
|
||||
{
|
||||
public function __construct($message = '')
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Fixture;
|
||||
|
||||
use Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source\ClassWithStringDefaultParameter;
|
||||
|
||||
final class IdenticalDefaults extends ClassWithStringDefaultParameter
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Fixture;
|
||||
|
||||
use Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source\ClassWithStringDefaultParameter;
|
||||
|
||||
final class SkipDifferentDefaults extends ClassWithStringDefaultParameter
|
||||
{
|
||||
public function __construct($message = 'My special message')
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Fixture;
|
||||
|
||||
use Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source\ChildOfToBeImplementedInterface;
|
||||
use Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source\ParentClassWithInterfaceType;
|
||||
|
||||
class SkipParentDifferentType extends ParentClassWithInterfaceType
|
||||
{
|
||||
public function __construct(ChildOfToBeImplementedInterface $toBeImplemented)
|
||||
{
|
||||
parent::__construct($toBeImplemented);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source;
|
||||
|
||||
final class ChildOfToBeImplementedInterface implements ToBeImplementedInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source;
|
||||
|
||||
class ClassWithStringDefaultParameter
|
||||
{
|
||||
public function __construct($message = '')
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source;
|
||||
|
||||
class ParentClassWithInterfaceType
|
||||
{
|
||||
public function __construct(ToBeImplementedInterface $toBeImplemented)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDelegatingParentCallRector\Source;
|
||||
|
||||
interface ToBeImplementedInterface
|
||||
{
|
||||
|
||||
}
|
@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace Rector\Core\PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder\Class_;
|
||||
use PhpParser\Node\Stmt\Class_ as ClassStmt;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method ClassStmt getNode()
|
||||
*/
|
||||
final class ClassBuilder extends Class_
|
||||
{
|
||||
|
@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace Rector\Core\PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder\Method;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method ClassMethod getNode()
|
||||
*/
|
||||
final class MethodBuilder extends Method
|
||||
{
|
||||
|
@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace Rector\Core\PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder\Namespace_;
|
||||
use PhpParser\Node\Stmt\Namespace_ as NamespaceStmt;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method NamespaceStmt getNode()
|
||||
*/
|
||||
final class NamespaceBuilder extends Namespace_
|
||||
{
|
||||
|
@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace Rector\Core\PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder\Param;
|
||||
use PhpParser\Node\Param as ParamNode;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method ParamNode getNode()
|
||||
*/
|
||||
final class ParamBuilder extends Param
|
||||
{
|
||||
|
@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace Rector\Core\PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder\Property;
|
||||
use PhpParser\Node\Stmt\Property as PropertyNode;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method PropertyNode getNode()
|
||||
*/
|
||||
final class PropertyBuilder extends Property
|
||||
{
|
||||
|
@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace Rector\Core\PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder\TraitUse;
|
||||
use PhpParser\Node\Stmt\TraitUse as TraitUseStmt;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method TraitUseStmt getNode()
|
||||
*/
|
||||
final class TraitUseBuilder extends TraitUse
|
||||
{
|
||||
|
@ -9,8 +9,6 @@ use PhpParser\Node\Stmt\Use_ as UseStmt;
|
||||
|
||||
/**
|
||||
* Fixed duplicated naming in php-parser and prevents confusion
|
||||
*
|
||||
* @method UseStmt getNode()
|
||||
*/
|
||||
final class UseBuilder extends Use_
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user