improve ParamTypeDeclarationRector complexity

This commit is contained in:
TomasVotruba 2020-02-11 14:11:47 +01:00
parent 3e5dd77bb6
commit 4aac338d20
6 changed files with 139 additions and 65 deletions

View File

@ -16,6 +16,16 @@ use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
final class PHPStanStaticTypeMapper
{
/**
* @var string
*/
public const KIND_PARAM = 'param';
/**
* @var string
*/
public const KIND_PROPERTY = 'property';
/**
* @var TypeMapperInterface[]
*/

View File

@ -35,6 +35,9 @@ final class ClassStringTypeMapper implements TypeMapperInterface
return null;
}
/**
* @param ClassStringType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());

View File

@ -16,7 +16,9 @@ parameters:
- compiler/src
paths:
- bin
- src
- rules
- packages
- tests
- compiler/src
@ -232,3 +234,7 @@ parameters:
- '#Method Rector\\SOLID\\Reflection\\ParentConstantReflectionResolver\:\:(.*?)\(\) should return ReflectionClassConstant\|null but returns ReflectionClassConstant\|false#'
- '#Parameter \#1 \$firstStmt of method Rector\\Core\\Rector\\MethodBody\\NormalToFluentRector\:\:isBothMethodCallMatch\(\) expects PhpParser\\Node\\Stmt\\Expression, PhpParser\\Node\\Stmt given#'
- '#Method Rector\\Core\\Rector\\AbstractRector\:\:wrapToArg\(\) should return array<PhpParser\\Node\\Arg\> but returns array<PhpParser\\Node\\Arg\|PhpParser\\Node\\Expr\>#'
- '#Property PhpParser\\Node\\Stmt\\ClassMethod\:\:\$returnType \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#'
- '#Parameter \#1 \$possibleSubtype of method Rector\\TypeDeclaration\\PhpParserTypeAnalyzer\:\:isSubtypeOf\(\) expects PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType, PhpParser\\Node given#'
- '#Parameter \#2 \$inferredReturnNode of method Rector\\TypeDeclaration\\Rector\\FunctionLike\\ReturnTypeDeclarationRector\:\:addReturnType\(\) expects PhpParser\\Node, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null given#'

View File

@ -10,6 +10,7 @@
</php>
<testsuites>
<testsuite name="main">
<directory>rules/*/tests</directory>
<directory>packages/*/tests</directory>
<directory>tests</directory>
</testsuite>
@ -17,6 +18,7 @@
<filter>
<whitelist addUncoveredFilesFromWhitelist="false">
<directory suffix=".php">rules/*/src</directory>
<directory suffix=".php">packages/*/src</directory>
<directory>src</directory>
</whitelist>

View File

@ -15,6 +15,7 @@ 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\PropertyTypeInferer;
use Rector\VendorLocker\VendorLockResolver;
@ -95,7 +96,10 @@ PHP
return null;
}
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($varType, 'property');
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$varType,
PHPStanStaticTypeMapper::KIND_PROPERTY
);
if ($propertyTypeNode === null) {
return null;
}

View File

@ -5,18 +5,23 @@ declare(strict_types=1);
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\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;
/**
* @see \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ParamTypeDeclarationRector\ParamTypeDeclarationRectorTest
@ -114,70 +119,7 @@ PHP
return null;
}
foreach ($node->params as $position => $paramNode) {
// skip variadics
if ($paramNode->variadic) {
continue;
}
// already set → skip
$hasNewType = false;
if ($paramNode->type !== null) {
$hasNewType = $paramNode->type->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
if (! $hasNewType) {
continue;
}
}
$paramNodeName = '$' . $this->getName($paramNode->var);
// no info about it
if (! isset($paramWithTypes[$paramNodeName])) {
continue;
}
$paramType = $paramWithTypes[$paramNodeName];
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType, 'param');
if ($paramTypeNode === null) {
continue;
}
$position = (int) $position;
if ($node instanceof ClassMethod && $this->vendorLockResolver->isParamChangeVendorLockedIn(
$node,
$position
)) {
continue;
}
if ($hasNewType) {
// should override - is it subtype?
$possibleOverrideNewReturnType = $paramTypeNode;
if ($possibleOverrideNewReturnType !== null) {
if ($paramNode->type === null) {
$paramNode->type = $paramTypeNode;
} elseif ($this->phpParserTypeAnalyzer->isSubtypeOf(
$possibleOverrideNewReturnType,
$paramNode->type
)) {
// allow override
$paramNode->type = $paramTypeNode;
}
}
} else {
$paramNode->type = $paramTypeNode;
$paramNodeType = $paramNode->type instanceof NullableType ? $paramNode->type->type : $paramNode->type;
// "resource" is valid phpdoc type, but it's not implemented in PHP
if ($paramNodeType instanceof Name && reset($paramNodeType->parts) === 'resource') {
$paramNode->type = null;
continue;
}
}
$this->populateChildren($node, $position, $paramType);
}
$this->refactorParams($node, $paramWithTypes);
return $node;
}
@ -250,4 +192,111 @@ PHP
$this->notifyNodeChangeFileInfo($paramNode);
}
/**
* @param ClassMethod|Function_ $functionLike
* @param Type[] $paramWithTypes
*/
private function refactorParams(FunctionLike $functionLike, array $paramWithTypes): void
{
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')) {
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
if ($param->type === null) {
return false;
}
$hasNewType = $param->type->getAttribute(self::HAS_NEW_INHERITED_TYPE, false);
return ! $hasNewType;
}
}