mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-16 16:12:22 +00:00
[TypeDeclaratoin] Add class method param type resolving by property
This commit is contained in:
parent
d0ca8a63f0
commit
442e2abac5
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\VendorLocker;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\VendorLocker\NodeVendorLocker\ClassMethodParamVendorLockResolver;
|
||||
|
@ -45,6 +46,15 @@ final class VendorLockResolver
|
|||
$this->classMethodVendorLockResolver = $classMethodVendorLockResolver;
|
||||
}
|
||||
|
||||
public function isClassMethodParamLockedIn(Node $node, int $paramPosition): bool
|
||||
{
|
||||
if (! $node instanceof ClassMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->isParamChangeVendorLockedIn($node, $paramPosition);
|
||||
}
|
||||
|
||||
public function isParamChangeVendorLockedIn(ClassMethod $classMethod, int $paramPosition): bool
|
||||
{
|
||||
return $this->classMethodParamVendorLockResolver->isVendorLocked($classMethod, $paramPosition);
|
||||
|
|
|
@ -6,28 +6,35 @@ namespace Rector\TypeDeclaration\Rector\FunctionLike;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
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 PhpParser\Node\UnionType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
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\TypeInferer\ParamTypeInferer;
|
||||
|
||||
/**
|
||||
* @see \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ParamTypeDeclarationRector\ParamTypeDeclarationRectorTest
|
||||
*/
|
||||
final class ParamTypeDeclarationRector extends AbstractTypeDeclarationRector
|
||||
{
|
||||
/**
|
||||
* @var ParamTypeInferer
|
||||
*/
|
||||
private $paramTypeInferer;
|
||||
|
||||
public function __construct(ParamTypeInferer $paramTypeInferer)
|
||||
{
|
||||
$this->paramTypeInferer = $paramTypeInferer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Change @param types to type declarations if not a BC-break', [
|
||||
|
@ -110,35 +117,52 @@ PHP
|
|||
return null;
|
||||
}
|
||||
|
||||
/** @var PhpDocInfo|null $phpDocInfo */
|
||||
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
|
||||
if ($phpDocInfo === null) {
|
||||
return null;
|
||||
foreach ($node->params as $position => $param) {
|
||||
$this->refactorParam($param, $node, (int) $position);
|
||||
}
|
||||
|
||||
$paramWithTypes = $phpDocInfo->getParamTypesByName();
|
||||
// no tags, nothing to complete here
|
||||
if ($paramWithTypes === []) {
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Function_ $functionLike
|
||||
*/
|
||||
private function refactorParam(Param $param, FunctionLike $functionLike, int $position): void
|
||||
{
|
||||
if ($this->shouldSkipParam($param, $functionLike, $position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->refactorParams($node, $paramWithTypes);
|
||||
$inferedType = $this->paramTypeInferer->inferParam($param);
|
||||
if ($inferedType instanceof MixedType) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $node;
|
||||
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
|
||||
$inferedType,
|
||||
PHPStanStaticTypeMapper::KIND_PARAM
|
||||
);
|
||||
|
||||
if ($paramTypeNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$param->type = $paramTypeNode;
|
||||
$this->populateChildren($functionLike, $position, $inferedType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add typehint to all children
|
||||
* @param ClassMethod|Function_ $node
|
||||
* @param ClassMethod|Function_ $functionLike
|
||||
*/
|
||||
private function populateChildren(Node $node, int $position, Type $paramType): void
|
||||
private function populateChildren(FunctionLike $functionLike, int $position, Type $paramType): void
|
||||
{
|
||||
if (! $node instanceof ClassMethod) {
|
||||
if (! $functionLike instanceof ClassMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var string $className */
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
/** @var string|null $className */
|
||||
$className = $functionLike->getAttribute(AttributeKey::CLASS_NAME);
|
||||
// anonymous class
|
||||
if ($className === null) {
|
||||
return;
|
||||
|
@ -152,11 +176,11 @@ PHP
|
|||
$usedTraits = $this->classLikeParsedNodesFinder->findUsedTraitsInClass($childClassLike);
|
||||
|
||||
foreach ($usedTraits as $trait) {
|
||||
$this->addParamTypeToMethod($trait, $position, $node, $paramType);
|
||||
$this->addParamTypeToMethod($trait, $position, $functionLike, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addParamTypeToMethod($childClassLike, $position, $node, $paramType);
|
||||
$this->addParamTypeToMethod($childClassLike, $position, $functionLike, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,110 +220,22 @@ PHP
|
|||
$this->notifyNodeFileInfo($paramNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Function_ $functionLike
|
||||
* @param Type[] $paramWithTypes
|
||||
*/
|
||||
private function refactorParams(FunctionLike $functionLike, array $paramWithTypes): void
|
||||
private function shouldSkipParam(Param $param, FunctionLike $functionLike, int $position): bool
|
||||
{
|
||||
foreach ($functionLike->params as $position => $param) {
|
||||
// to be sure
|
||||
$position = (int) $position;
|
||||
|
||||
if ($this->shouldSkipParam($param)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasNewType = false;
|
||||
|
||||
$docParamType = $this->matchParamNodeFromDoc($paramWithTypes, $param);
|
||||
if ($docParamType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
|
||||
$docParamType,
|
||||
PHPStanStaticTypeMapper::KIND_PARAM
|
||||
);
|
||||
|
||||
if ($paramTypeNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($functionLike instanceof ClassMethod && $this->vendorLockResolver->isParamChangeVendorLockedIn(
|
||||
$functionLike,
|
||||
$position
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->changeParamNodeType($hasNewType, $paramTypeNode, $param);
|
||||
|
||||
$this->populateChildren($functionLike, $position, $docParamType);
|
||||
}
|
||||
}
|
||||
|
||||
private function isResourceType(Node $node): bool
|
||||
{
|
||||
if ($this->isName($node, 'resource')) {
|
||||
if ($this->vendorLockResolver->isClassMethodParamLockedIn($functionLike, $position)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($node instanceof NullableType) {
|
||||
return $this->isResourceType($node->type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Identifier|Name|NullableType|UnionType $paramTypeNode
|
||||
*/
|
||||
private function changeParamNodeType(bool $hasNewType, Node $paramTypeNode, Param $param): void
|
||||
{
|
||||
if ($hasNewType) {
|
||||
// should override - is it subtype?
|
||||
if ($param->type === null) {
|
||||
$param->type = $paramTypeNode;
|
||||
} elseif ($this->phpParserTypeAnalyzer->isSubtypeOf($paramTypeNode, $param->type)) {
|
||||
// allow override
|
||||
$param->type = $paramTypeNode;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isResourceType($paramTypeNode)) {
|
||||
// "resource" is valid phpdoc type, but it's not implemented in PHP
|
||||
$param->type = null;
|
||||
} else {
|
||||
$param->type = $paramTypeNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $paramWithTypes
|
||||
*/
|
||||
private function matchParamNodeFromDoc(array $paramWithTypes, Param $param): ?Type
|
||||
{
|
||||
$paramNodeName = '$' . $this->getName($param->var);
|
||||
|
||||
return $paramWithTypes[$paramNodeName] ?? null;
|
||||
}
|
||||
|
||||
private function shouldSkipParam(Param $param): bool
|
||||
{
|
||||
if ($param->variadic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// already set → skip
|
||||
// no type → check it
|
||||
if ($param->type === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasNewType = $param->type->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
|
||||
|
||||
return ! $hasNewType;
|
||||
// already set → skip
|
||||
return ! $param->type->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
|
||||
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Param;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\TypeDeclaration\Contract\TypeInferer\ParamTypeInfererInterface;
|
||||
use Rector\TypeDeclaration\TypeInferer\AbstractTypeInferer;
|
||||
|
||||
final class FunctionLikeDocParamTypeInferer extends AbstractTypeInferer implements ParamTypeInfererInterface
|
||||
{
|
||||
public function __construct(NodeNameResolver $nodeNameResolver)
|
||||
{
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
public function inferParam(Param $param): Type
|
||||
{
|
||||
$functionLike = $this->resolveScopeNode($param);
|
||||
if ($functionLike === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
/** @var PhpDocInfo|null $phpDocInfo */
|
||||
$phpDocInfo = $functionLike->getAttribute(AttributeKey::PHP_DOC_INFO);
|
||||
if ($phpDocInfo === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
$paramWithTypes = $phpDocInfo->getParamTypesByName();
|
||||
if ($paramWithTypes === []) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return $this->matchParamNodeFromDoc($paramWithTypes, $param);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $paramWithTypes
|
||||
*/
|
||||
private function matchParamNodeFromDoc(array $paramWithTypes, Param $param): Type
|
||||
{
|
||||
$paramNodeName = '$' . $this->nodeNameResolver->getName($param->var);
|
||||
return $paramWithTypes[$paramNodeName] ?? new MixedType();
|
||||
}
|
||||
|
||||
private function resolveScopeNode(Param $param): ?FunctionLike
|
||||
{
|
||||
return $param->getAttribute(AttributeKey::METHOD_NODE) ?? $param->getAttribute(AttributeKey::FUNCTION_NODE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture;
|
||||
|
||||
class InferFromPropertyType
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture;
|
||||
|
||||
class InferFromPropertyType
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function setId(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
Loading…
Reference in New Issue
Block a user