decouple ChildParamPopulator

This commit is contained in:
TomasVotruba 2020-03-28 03:38:20 +01:00
parent 442e2abac5
commit 5df24b4087
9 changed files with 269 additions and 102 deletions

View File

@ -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);
}
}

View File

@ -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
*/

View File

@ -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);
}
}

View File

@ -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;
}
/**

View File

@ -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();

View 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';
}

View File

@ -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;
}
}
?>

View File

@ -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)
{

View File

@ -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;
}
}