2020-12-08 12:03:00 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2021-04-21 22:02:38 +00:00
|
|
|
namespace Rector\DowngradePhp72\Rector\Class_;
|
2020-12-08 12:03:00 +00:00
|
|
|
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Param;
|
2021-04-21 22:02:38 +00:00
|
|
|
use PhpParser\Node\Stmt\Class_;
|
2020-12-08 12:03:00 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassLike;
|
|
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
|
|
|
use PHPStan\Analyser\Scope;
|
|
|
|
use PHPStan\Reflection\ClassReflection;
|
2021-04-23 09:03:45 +00:00
|
|
|
use PHPStan\Reflection\ReflectionProvider;
|
2021-03-17 10:12:39 +00:00
|
|
|
use PHPStan\Type\Type;
|
2021-04-22 11:29:04 +00:00
|
|
|
use Rector\ChangesReporting\ValueObject\RectorWithLineChange;
|
2020-12-08 12:03:00 +00:00
|
|
|
use Rector\Core\Rector\AbstractRector;
|
2021-04-22 11:29:04 +00:00
|
|
|
use Rector\Core\ValueObject\Application\File;
|
2021-04-21 22:02:38 +00:00
|
|
|
use Rector\DowngradePhp72\NodeAnalyzer\ClassLikeWithTraitsClassMethodResolver;
|
2021-04-23 09:03:45 +00:00
|
|
|
use Rector\DowngradePhp72\NodeAnalyzer\ParentChildClassMethodTypeResolver;
|
|
|
|
use Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator;
|
2020-12-08 12:03:00 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
|
|
|
|
|
|
|
/**
|
2021-04-10 18:47:17 +00:00
|
|
|
* @changelog https://www.php.net/manual/en/migration72.new-features.php#migration72.new-features.param-type-widening
|
2021-03-17 10:12:39 +00:00
|
|
|
* @see https://3v4l.org/fOgSE
|
2020-12-08 12:03:00 +00:00
|
|
|
*
|
2021-04-21 22:02:38 +00:00
|
|
|
* @see \Rector\Tests\DowngradePhp72\Rector\Class_\DowngradeParameterTypeWideningRector\DowngradeParameterTypeWideningRectorTest
|
2020-12-08 12:03:00 +00:00
|
|
|
*/
|
|
|
|
final class DowngradeParameterTypeWideningRector extends AbstractRector
|
|
|
|
{
|
2021-01-18 20:06:02 +00:00
|
|
|
/**
|
2021-04-23 09:03:45 +00:00
|
|
|
* @var ClassLikeWithTraitsClassMethodResolver
|
2021-01-18 20:06:02 +00:00
|
|
|
*/
|
2021-04-23 09:03:45 +00:00
|
|
|
private $classLikeWithTraitsClassMethodResolver;
|
2021-01-18 20:06:02 +00:00
|
|
|
|
2021-03-17 10:12:39 +00:00
|
|
|
/**
|
2021-04-23 09:03:45 +00:00
|
|
|
* @var ReflectionProvider
|
2021-03-17 10:12:39 +00:00
|
|
|
*/
|
2021-04-23 09:03:45 +00:00
|
|
|
private $reflectionProvider;
|
2021-03-17 10:12:39 +00:00
|
|
|
|
|
|
|
/**
|
2021-04-23 09:03:45 +00:00
|
|
|
* @var ParentChildClassMethodTypeResolver
|
2021-03-17 10:12:39 +00:00
|
|
|
*/
|
2021-04-23 09:03:45 +00:00
|
|
|
private $parentChildClassMethodTypeResolver;
|
2021-03-17 10:12:39 +00:00
|
|
|
|
2021-04-21 22:02:38 +00:00
|
|
|
/**
|
2021-04-23 09:03:45 +00:00
|
|
|
* @var NativeParamToPhpDocDecorator
|
2021-04-21 22:02:38 +00:00
|
|
|
*/
|
2021-04-23 09:03:45 +00:00
|
|
|
private $nativeParamToPhpDocDecorator;
|
2021-04-21 22:02:38 +00:00
|
|
|
|
2021-03-17 10:12:39 +00:00
|
|
|
public function __construct(
|
2021-04-23 09:03:45 +00:00
|
|
|
ClassLikeWithTraitsClassMethodResolver $classLikeWithTraitsClassMethodResolver,
|
|
|
|
ReflectionProvider $reflectionProvider,
|
|
|
|
ParentChildClassMethodTypeResolver $parentChildClassMethodTypeResolver,
|
|
|
|
NativeParamToPhpDocDecorator $nativeParamToPhpDocDecorator
|
2021-03-17 10:12:39 +00:00
|
|
|
) {
|
2021-04-21 22:02:38 +00:00
|
|
|
$this->classLikeWithTraitsClassMethodResolver = $classLikeWithTraitsClassMethodResolver;
|
2021-04-23 09:03:45 +00:00
|
|
|
$this->reflectionProvider = $reflectionProvider;
|
|
|
|
$this->parentChildClassMethodTypeResolver = $parentChildClassMethodTypeResolver;
|
|
|
|
$this->nativeParamToPhpDocDecorator = $nativeParamToPhpDocDecorator;
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getRuleDefinition(): RuleDefinition
|
|
|
|
{
|
|
|
|
return new RuleDefinition(
|
2021-03-17 10:12:39 +00:00
|
|
|
'Change param type to match the lowest type in whole family tree',
|
2020-12-08 12:03:00 +00:00
|
|
|
[
|
|
|
|
new CodeSample(
|
|
|
|
<<<'CODE_SAMPLE'
|
|
|
|
interface A
|
|
|
|
{
|
|
|
|
public function test(array $input);
|
|
|
|
}
|
|
|
|
|
|
|
|
class C implements A
|
|
|
|
{
|
2021-03-17 10:12:39 +00:00
|
|
|
public function test($input){}
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
CODE_SAMPLE
|
|
|
|
,
|
|
|
|
<<<'CODE_SAMPLE'
|
|
|
|
interface A
|
|
|
|
{
|
2021-03-17 10:12:39 +00:00
|
|
|
public function test(array $input);
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class C implements A
|
|
|
|
{
|
2021-03-17 10:12:39 +00:00
|
|
|
public function test(array $input){}
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
CODE_SAMPLE
|
2021-04-21 22:02:38 +00:00
|
|
|
),
|
2020-12-08 12:03:00 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-27 00:06:15 +00:00
|
|
|
* @return array<class-string<Node>>
|
2020-12-08 12:03:00 +00:00
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
|
|
|
{
|
2021-04-21 22:02:38 +00:00
|
|
|
return [Class_::class];
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-04-21 22:02:38 +00:00
|
|
|
* @param Class_ $node
|
2020-12-08 12:03:00 +00:00
|
|
|
*/
|
|
|
|
public function refactor(Node $node): ?Node
|
|
|
|
{
|
2021-04-21 22:02:38 +00:00
|
|
|
$scope = $node->getAttribute(AttributeKey::SCOPE);
|
2021-04-23 09:03:45 +00:00
|
|
|
if (! $scope instanceof Scope) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->isEmptyClassReflection($scope)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$classMethods = $this->classLikeWithTraitsClassMethodResolver->resolve($node);
|
2021-04-21 22:02:38 +00:00
|
|
|
foreach ($classMethods as $classMethod) {
|
|
|
|
$this->refactorClassMethod($classMethod, $scope);
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2021-04-21 22:02:38 +00:00
|
|
|
return $node;
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2021-03-17 10:12:39 +00:00
|
|
|
/**
|
|
|
|
* The topmost class is the source of truth, so we go only down to avoid up/down collission
|
|
|
|
*/
|
2021-04-23 09:03:45 +00:00
|
|
|
private function refactorParamForSelfAndSiblings(ClassMethod $classMethod, int $position, Scope $scope): void
|
2020-12-08 12:03:00 +00:00
|
|
|
{
|
2021-04-23 09:03:45 +00:00
|
|
|
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
|
|
|
|
if ($class === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$className = $this->getName($class);
|
|
|
|
if ($className === null) {
|
2020-12-08 12:03:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
if (! $this->reflectionProvider->hasClass($className)) {
|
2021-03-17 10:12:39 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-12-08 12:03:00 +00:00
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
$classReflection = $this->reflectionProvider->getClass($className);
|
|
|
|
|
2021-03-17 10:12:39 +00:00
|
|
|
/** @var string $methodName */
|
|
|
|
$methodName = $this->nodeNameResolver->getName($classMethod);
|
2020-12-08 12:03:00 +00:00
|
|
|
|
|
|
|
// Remove the types in:
|
|
|
|
// - all ancestors + their descendant classes
|
|
|
|
// - all implemented interfaces + their implementing classes
|
2021-04-23 09:03:45 +00:00
|
|
|
$parameterTypesByParentClassLikes = $this->parentChildClassMethodTypeResolver->resolve(
|
2021-03-17 10:12:39 +00:00
|
|
|
$classReflection,
|
|
|
|
$methodName,
|
2021-04-23 09:03:45 +00:00
|
|
|
$position,
|
|
|
|
$scope
|
2020-12-08 12:03:00 +00:00
|
|
|
);
|
2021-01-24 20:12:02 +00:00
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
// skip classes we cannot change
|
|
|
|
foreach (array_keys($parameterTypesByParentClassLikes) as $className) {
|
|
|
|
$classLike = $this->nodeRepository->findClassLike($className);
|
|
|
|
if (! $classLike instanceof ClassLike) {
|
|
|
|
return;
|
|
|
|
}
|
2021-01-24 20:12:02 +00:00
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
// we need at least 2 types = 2 occurances of same method
|
|
|
|
if (count($parameterTypesByParentClassLikes) <= 1) {
|
2021-03-17 10:12:39 +00:00
|
|
|
return;
|
2021-01-24 20:12:02 +00:00
|
|
|
}
|
2021-04-23 09:03:45 +00:00
|
|
|
$this->refactorParameters($parameterTypesByParentClassLikes, $methodName, $position);
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function removeParamTypeFromMethod(
|
|
|
|
ClassLike $classLike,
|
|
|
|
int $position,
|
|
|
|
ClassMethod $classMethod
|
|
|
|
): void {
|
2020-12-20 21:27:30 +00:00
|
|
|
$classMethodName = $this->getName($classMethod);
|
2021-03-17 10:12:39 +00:00
|
|
|
|
2020-12-20 21:27:30 +00:00
|
|
|
$currentClassMethod = $classLike->getMethod($classMethodName);
|
2021-01-20 11:41:35 +00:00
|
|
|
if (! $currentClassMethod instanceof ClassMethod) {
|
2020-12-08 12:03:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! isset($currentClassMethod->params[$position])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$param = $currentClassMethod->params[$position];
|
|
|
|
|
|
|
|
// It already has no type => nothing to do
|
|
|
|
if ($param->type === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the current type in the PHPDoc
|
2021-04-23 09:03:45 +00:00
|
|
|
$this->nativeParamToPhpDocDecorator->decorate($classMethod, $param);
|
2020-12-08 12:03:00 +00:00
|
|
|
|
|
|
|
// Remove the type
|
|
|
|
$param->type = null;
|
2021-04-23 09:03:45 +00:00
|
|
|
$param->setAttribute(AttributeKey::ORIGINAL_NODE, null);
|
2021-04-22 11:29:04 +00:00
|
|
|
|
|
|
|
// file from another file
|
|
|
|
$file = $param->getAttribute(AttributeKey::FILE);
|
|
|
|
if ($file instanceof File) {
|
|
|
|
$rectorWithLineChange = new RectorWithLineChange($this, $param->getLine());
|
|
|
|
$file->addRectorClassWithLine($rectorWithLineChange);
|
|
|
|
}
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
private function refactorClassMethod(ClassMethod $classMethod, Scope $classScope): void
|
|
|
|
{
|
|
|
|
if ($classMethod->isMagic()) {
|
|
|
|
return;
|
|
|
|
}
|
2021-04-04 09:01:11 +00:00
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
if ($classMethod->params === []) {
|
|
|
|
return;
|
|
|
|
}
|
2021-04-04 09:01:11 +00:00
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
foreach (array_keys($classMethod->params) as $position) {
|
|
|
|
$this->refactorParamForSelfAndSiblings($classMethod, (int) $position, $classScope);
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
private function isEmptyClassReflection(Scope $scope): bool
|
2020-12-08 12:03:00 +00:00
|
|
|
{
|
2021-04-23 09:03:45 +00:00
|
|
|
$classReflection = $scope->getClassReflection();
|
|
|
|
if (! $classReflection instanceof ClassReflection) {
|
|
|
|
return true;
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
return count($classReflection->getAncestors()) === 1;
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|
2021-03-17 10:12:39 +00:00
|
|
|
|
|
|
|
/**
|
2021-04-23 09:03:45 +00:00
|
|
|
* @param array<class-string, Type> $parameterTypesByParentClassLikes
|
2021-03-17 10:12:39 +00:00
|
|
|
*/
|
2021-04-23 09:03:45 +00:00
|
|
|
private function refactorParameters(
|
|
|
|
array $parameterTypesByParentClassLikes,
|
2021-03-17 10:12:39 +00:00
|
|
|
string $methodName,
|
2021-04-23 09:03:45 +00:00
|
|
|
int $paramPosition
|
2021-03-17 10:12:39 +00:00
|
|
|
): void {
|
2021-04-23 09:03:45 +00:00
|
|
|
foreach (array_keys($parameterTypesByParentClassLikes) as $className) {
|
|
|
|
$classLike = $this->nodeRepository->findClassLike($className);
|
2021-03-17 10:12:39 +00:00
|
|
|
if (! $classLike instanceof ClassLike) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
$classMethod = $classLike->getMethod($methodName);
|
|
|
|
if (! $classMethod instanceof ClassMethod) {
|
2021-03-17 10:12:39 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-23 09:03:45 +00:00
|
|
|
$this->removeParamTypeFromMethod($classLike, $paramPosition, $classMethod);
|
2021-04-21 22:02:38 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-08 12:03:00 +00:00
|
|
|
}
|