mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-22 02:52:23 +00:00
decouple ChildParamPopulator
This commit is contained in:
parent
442e2abac5
commit
5df24b4087
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\ChildPopulator;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser\Node\UnionType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\StaticType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\ChangesReporting\Collector\RectorChangeCollector;
|
||||
use Rector\NodeCollector\NodeFinder\ClassLikeParsedNodesFinder;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PHPStan\Type\SelfObjectType;
|
||||
use Rector\StaticTypeMapper\StaticTypeMapper;
|
||||
use Rector\TypeDeclaration\ValueObject\NewType;
|
||||
|
||||
final class ChildParamPopulator
|
||||
{
|
||||
/**
|
||||
* @var ClassLikeParsedNodesFinder
|
||||
*/
|
||||
private $classLikeParsedNodesFinder;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
/**
|
||||
* @var RectorChangeCollector
|
||||
*/
|
||||
private $rectorChangeCollector;
|
||||
|
||||
public function __construct(
|
||||
ClassLikeParsedNodesFinder $classLikeParsedNodesFinder,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
NodeNameResolver $nodeNameResolver,
|
||||
RectorChangeCollector $rectorChangeCollector
|
||||
) {
|
||||
$this->classLikeParsedNodesFinder = $classLikeParsedNodesFinder;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
$this->rectorChangeCollector = $rectorChangeCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add typehint to all children
|
||||
* @param ClassMethod|Function_ $functionLike
|
||||
*/
|
||||
public function populateChildClassMethod(FunctionLike $functionLike, int $position, Type $paramType): void
|
||||
{
|
||||
if (! $functionLike instanceof ClassMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var string|null $className */
|
||||
$className = $functionLike->getAttribute(AttributeKey::CLASS_NAME);
|
||||
// anonymous class
|
||||
if ($className === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$childrenClassLikes = $this->classLikeParsedNodesFinder->findClassesAndInterfacesByType($className);
|
||||
|
||||
// update their methods as well
|
||||
foreach ($childrenClassLikes as $childClassLike) {
|
||||
if ($childClassLike instanceof Class_) {
|
||||
$usedTraits = $this->classLikeParsedNodesFinder->findUsedTraitsInClass($childClassLike);
|
||||
|
||||
foreach ($usedTraits as $trait) {
|
||||
$this->addParamTypeToMethod($trait, $position, $functionLike, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addParamTypeToMethod($childClassLike, $position, $functionLike, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
private function addParamTypeToMethod(
|
||||
ClassLike $classLike,
|
||||
int $position,
|
||||
ClassMethod $classMethod,
|
||||
Type $paramType
|
||||
): void {
|
||||
$methodName = $this->nodeNameResolver->getName($classMethod);
|
||||
if ($methodName === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentClassMethod = $classLike->getMethod($methodName);
|
||||
if ($currentClassMethod === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($currentClassMethod->params[$position])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paramNode = $currentClassMethod->params[$position];
|
||||
|
||||
// already has a type
|
||||
if ($paramNode->type !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$resolvedChildType = $this->resolveChildTypeNode($paramType);
|
||||
if ($resolvedChildType === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let the method know it was changed now
|
||||
$paramNode->type = $resolvedChildType;
|
||||
$paramNode->type->setAttribute(NewType::HAS_NEW_INHERITED_TYPE, true);
|
||||
|
||||
$this->rectorChangeCollector->notifyNodeFileInfo($paramNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|Identifier|UnionType|null
|
||||
*/
|
||||
private function resolveChildTypeNode(Type $type): ?Node
|
||||
{
|
||||
if ($type instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($type instanceof SelfObjectType || $type instanceof StaticType) {
|
||||
$type = new ObjectType($type->getClassName());
|
||||
}
|
||||
|
||||
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type);
|
||||
}
|
||||
}
|
|
@ -30,11 +30,6 @@ use Rector\VendorLocker\VendorLockResolver;
|
|||
*/
|
||||
abstract class AbstractTypeDeclarationRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected const HAS_NEW_INHERITED_TYPE = 'has_new_inherited_return_type';
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
|
|
|
@ -7,8 +7,6 @@ namespace Rector\TypeDeclaration\Rector\FunctionLike;
|
|||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PHPStan\Type\MixedType;
|
||||
|
@ -16,9 +14,10 @@ use PHPStan\Type\Type;
|
|||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
|
||||
use Rector\TypeDeclaration\ChildPopulator\ChildParamPopulator;
|
||||
use Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
|
||||
use Rector\TypeDeclaration\ValueObject\NewType;
|
||||
|
||||
/**
|
||||
* @see \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ParamTypeDeclarationRector\ParamTypeDeclarationRectorTest
|
||||
|
@ -30,9 +29,15 @@ final class ParamTypeDeclarationRector extends AbstractTypeDeclarationRector
|
|||
*/
|
||||
private $paramTypeInferer;
|
||||
|
||||
public function __construct(ParamTypeInferer $paramTypeInferer)
|
||||
/**
|
||||
* @var ChildParamPopulator
|
||||
*/
|
||||
private $childParamPopulator;
|
||||
|
||||
public function __construct(ParamTypeInferer $paramTypeInferer, ChildParamPopulator $childParamPopulator)
|
||||
{
|
||||
$this->paramTypeInferer = $paramTypeInferer;
|
||||
$this->childParamPopulator = $childParamPopulator;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
|
@ -148,76 +153,7 @@ PHP
|
|||
}
|
||||
|
||||
$param->type = $paramTypeNode;
|
||||
$this->populateChildren($functionLike, $position, $inferedType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add typehint to all children
|
||||
* @param ClassMethod|Function_ $functionLike
|
||||
*/
|
||||
private function populateChildren(FunctionLike $functionLike, int $position, Type $paramType): void
|
||||
{
|
||||
if (! $functionLike instanceof ClassMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var string|null $className */
|
||||
$className = $functionLike->getAttribute(AttributeKey::CLASS_NAME);
|
||||
// anonymous class
|
||||
if ($className === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$childrenClassLikes = $this->classLikeParsedNodesFinder->findClassesAndInterfacesByType($className);
|
||||
|
||||
// update their methods as well
|
||||
foreach ($childrenClassLikes as $childClassLike) {
|
||||
if ($childClassLike instanceof Class_) {
|
||||
$usedTraits = $this->classLikeParsedNodesFinder->findUsedTraitsInClass($childClassLike);
|
||||
|
||||
foreach ($usedTraits as $trait) {
|
||||
$this->addParamTypeToMethod($trait, $position, $functionLike, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addParamTypeToMethod($childClassLike, $position, $functionLike, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
private function addParamTypeToMethod(
|
||||
ClassLike $classLike,
|
||||
int $position,
|
||||
ClassMethod $classMethod,
|
||||
Type $paramType
|
||||
): void {
|
||||
$methodName = $this->getName($classMethod);
|
||||
|
||||
$currentClassMethod = $classLike->getMethod($methodName);
|
||||
if ($currentClassMethod === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($currentClassMethod->params[$position])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paramNode = $currentClassMethod->params[$position];
|
||||
|
||||
// already has a type
|
||||
if ($paramNode->type !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$resolvedChildType = $this->resolveChildTypeNode($paramType);
|
||||
if ($resolvedChildType === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let the method know it was changed now
|
||||
$paramNode->type = $resolvedChildType;
|
||||
$paramNode->type->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
|
||||
|
||||
$this->notifyNodeFileInfo($paramNode);
|
||||
$this->childParamPopulator->populateChildClassMethod($functionLike, $position, $inferedType);
|
||||
}
|
||||
|
||||
private function shouldSkipParam(Param $param, FunctionLike $functionLike, int $position): bool
|
||||
|
@ -236,6 +172,6 @@ PHP
|
|||
}
|
||||
|
||||
// already set → skip
|
||||
return ! $param->type->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
|
||||
return ! $param->type->getAttribute(NewType::HAS_NEW_INHERITED_TYPE, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Rector\TypeDeclaration\TypeAlreadyAddedChecker;
|
|||
|
||||
use Iterator;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
|
@ -58,11 +59,11 @@ final class ReturnTypeAlreadyAddedChecker
|
|||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Function_ $node
|
||||
* @param ClassMethod|Function_ $functionLike
|
||||
*/
|
||||
public function isReturnTypeAlreadyAdded(Node $node, Type $returnType): bool
|
||||
public function isReturnTypeAlreadyAdded(FunctionLike $functionLike, Type $returnType): bool
|
||||
{
|
||||
$nodeReturnType = $node->returnType;
|
||||
$nodeReturnType = $functionLike->returnType;
|
||||
|
||||
/** @param Identifier|Name|NullableType|PhpParserUnionType|null $returnTypeNode */
|
||||
if ($nodeReturnType === null) {
|
||||
|
@ -70,7 +71,7 @@ final class ReturnTypeAlreadyAddedChecker
|
|||
}
|
||||
|
||||
$returnNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
|
||||
if ($this->betterStandardPrinter->areNodesEqual($nodeReturnType, $returnNode)) {
|
||||
if ($this->betterStandardPrinter->areNodesWithoutCommentsEqual($nodeReturnType, $returnNode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -89,14 +90,16 @@ final class ReturnTypeAlreadyAddedChecker
|
|||
}
|
||||
|
||||
// prevent overriding self with itself
|
||||
if ($this->betterStandardPrinter->printWithoutComments($node->returnType) === 'self') {
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if (ltrim($this->betterStandardPrinter->printWithoutComments($returnNode), '\\') === $className) {
|
||||
return true;
|
||||
}
|
||||
if (! $functionLike->returnType instanceof Name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if ($functionLike->returnType->toLowerString() !== 'self') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$className = $functionLike->getAttribute(AttributeKey::CLASS_NAME);
|
||||
return ltrim($this->betterStandardPrinter->printWithoutComments($returnNode), '\\') === $className;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,11 +26,13 @@ final class ParamTypeInferer
|
|||
|
||||
public function inferParam(Param $param): Type
|
||||
{
|
||||
foreach ($this->paramTypeInferers as $paramTypeInferers) {
|
||||
$type = $paramTypeInferers->inferParam($param);
|
||||
if (! $type instanceof MixedType) {
|
||||
return $type;
|
||||
foreach ($this->paramTypeInferers as $paramTypeInferer) {
|
||||
$type = $paramTypeInferer->inferParam($param);
|
||||
if ($type instanceof MixedType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
return new MixedType();
|
||||
|
|
13
rules/type-declaration/src/ValueObject/NewType.php
Normal file
13
rules/type-declaration/src/ValueObject/NewType.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\ValueObject;
|
||||
|
||||
final class NewType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const HAS_NEW_INHERITED_TYPE = 'has_new_inherited_type';
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture;
|
||||
|
||||
class InferFromPropertyPhpdocType
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture;
|
||||
|
||||
class InferFromPropertyPhpdocType
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function setId(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -4,10 +4,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
|
|||
|
||||
class InferFromPropertyType
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
private int $id;
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
|
@ -23,10 +20,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
|
|||
|
||||
class InferFromPropertyType
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
private int $id;
|
||||
|
||||
public function setId(int $id)
|
||||
{
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ParamTypeDeclarationRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector;
|
||||
|
||||
final class PropertyTypeParamTypeDeclarationRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @requires PHP >= 7.4
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(string $file): void
|
||||
{
|
||||
$this->doTestFile($file);
|
||||
}
|
||||
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePropertyType');
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return ParamTypeDeclarationRector::class;
|
||||
}
|
||||
|
||||
protected function getPhpVersion(): string
|
||||
{
|
||||
return PhpVersionFeature::TYPED_PROPERTIES;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user