add mapToDocString()

This commit is contained in:
TomasVotruba 2020-01-15 03:16:22 +01:00
parent a4e90efb7c
commit 71cdc8fe40
21 changed files with 271 additions and 117 deletions

View File

@ -23,5 +23,5 @@ interface TypeMapperInterface
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node;
public function mapToDocString(Type $phpStanType, ?Type $parentType = null): string;
public function mapToDocString(Type $type, ?Type $parentType = null): string;
}

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper;
use Closure;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
@ -15,44 +14,17 @@ use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\CallableType;
use PHPStan\Type\ClosureType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\ResourceType;
use PHPStan\Type\StaticType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use PHPStan\Type\VoidType;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
use Rector\Exception\NotImplementedException;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStan\Type\AliasedObjectType;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;
final class PHPStanStaticTypeMapper
{
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;
/**
* @var TypeMapperInterface[]
*/
@ -61,14 +33,14 @@ final class PHPStanStaticTypeMapper
/**
* @param TypeMapperInterface[] $typeMappers
*/
public function __construct(PhpVersionProvider $phpVersionProvider, array $typeMappers)
public function __construct(array $typeMappers)
{
$this->phpVersionProvider = $phpVersionProvider;
$this->typeMappers = $typeMappers;
}
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
// @todo move to ArrayTypeMapper
if ($type instanceof ArrayType) {
$itemTypeNode = $this->mapToPHPStanPhpDocTypeNode($type->getItemType());
if ($itemTypeNode instanceof UnionTypeNode) {
@ -94,10 +66,6 @@ final class PHPStanStaticTypeMapper
*/
public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?Node
{
if ($phpStanType instanceof SelfObjectType) {
return new Identifier('self');
}
foreach ($this->typeMappers as $typeMapper) {
// it cannot be is_a for SelfObjectType, because type classes inherit from each other
if (! is_a($phpStanType, $typeMapper->getNodeClass(), true)) {
@ -141,54 +109,13 @@ final class PHPStanStaticTypeMapper
public function mapToDocString(Type $phpStanType, ?Type $parentType = null): string
{
if ($phpStanType instanceof UnionType || $phpStanType instanceof IntersectionType) {
$stringTypes = [];
foreach ($phpStanType->getTypes() as $unionedType) {
$stringTypes[] = $this->mapToDocString($unionedType);
foreach ($this->typeMappers as $typeMapper) {
// it cannot be is_a for SelfObjectType, because type classes inherit from each other
if (! is_a($phpStanType, $typeMapper->getNodeClass(), true)) {
continue;
}
// remove empty values, e.g. void/iterable
$stringTypes = array_unique($stringTypes);
$stringTypes = array_filter($stringTypes);
$joinCharacter = $phpStanType instanceof IntersectionType ? '&' : '|';
return implode($joinCharacter, $stringTypes);
}
if ($phpStanType instanceof AliasedObjectType) {
// no preslash for alias
return $phpStanType->getClassName();
}
if ($phpStanType instanceof ShortenedObjectType) {
return '\\' . $phpStanType->getFullyQualifiedName();
}
if ($phpStanType instanceof FullyQualifiedObjectType) {
// always prefixed with \\
return '\\' . $phpStanType->getClassName();
}
if ($phpStanType instanceof ObjectType) {
if (ClassExistenceStaticHelper::doesClassLikeExist($phpStanType->getClassName())) {
return '\\' . $phpStanType->getClassName();
}
return $phpStanType->getClassName();
}
if ($phpStanType instanceof ObjectWithoutClassType) {
return 'object';
}
if ($phpStanType instanceof ClosureType) {
return '\\' . Closure::class;
}
if ($phpStanType instanceof StringType || $phpStanType instanceof NullType || $phpStanType instanceof IntegerType || $phpStanType instanceof MixedType || $phpStanType instanceof FloatType || $phpStanType instanceof CallableType || $phpStanType instanceof ResourceType) {
return $phpStanType->describe(VerbosityLevel::typeOnly());
return $typeMapper->mapToDocString($phpStanType, $parentType);
}
if ($phpStanType instanceof ArrayType) {
@ -217,33 +144,6 @@ final class PHPStanStaticTypeMapper
return implode('|', $docStringTypes);
}
if ($phpStanType instanceof VoidType) {
if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) {
// the void type is better done in PHP code
return '';
}
// fallback for PHP 7.0 and older, where void type was only in docs
return 'void';
}
if ($phpStanType instanceof IterableType) {
if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) {
// the void type is better done in PHP code
return '';
}
return 'iterable';
}
if ($phpStanType instanceof BooleanType) {
return 'bool';
}
if ($phpStanType instanceof NeverType) {
return 'mixed';
}
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
}

View File

@ -50,4 +50,12 @@ final class BooleanTypeMapper implements TypeMapperInterface
return new Identifier('bool');
}
/**
* @param BooleanType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return 'bool';
}
}

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\CallableType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\Exception\NotImplementedException;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
@ -38,4 +39,9 @@ final class CallableTypeMapper implements TypeMapperInterface
return new Identifier('callable');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -9,6 +9,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
final class ClassStringTypeMapper implements TypeMapperInterface
@ -33,4 +34,9 @@ final class ClassStringTypeMapper implements TypeMapperInterface
{
return null;
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use Closure;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
@ -38,4 +39,9 @@ final class ClosureTypeMapper implements TypeMapperInterface
return new Identifier('callable');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return '\\' . Closure::class;
}
}

View File

@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\FloatType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;
@ -50,4 +51,9 @@ final class FloatTypeMapper implements TypeMapperInterface
return new Identifier('float');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\IntegerType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;
@ -50,4 +51,9 @@ final class IntegerTypeMapper implements TypeMapperInterface
return new Identifier('int');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIntersectionTypeNode;
use Rector\Exception\NotImplementedException;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
final class IntersectionTypeMapper implements TypeMapperInterface
{
/**
* @var PHPStanStaticTypeMapper
*/
private $phpStanStaticTypeMapper;
/**
* @required
*/
public function autowireIntersectionTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
public function getNodeClass(): string
{
return IntersectionType::class;
}
/**
* @param IntersectionType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
$intersectionTypesNodes = [];
foreach ($type->getTypes() as $unionedType) {
$intersectionTypesNodes[] = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($unionedType);
}
$intersectionTypesNodes = array_unique($intersectionTypesNodes);
return new AttributeAwareIntersectionTypeNode($intersectionTypesNodes);
}
/**
* @param IntersectionType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
throw new NotImplementedException();
}
/**
* @param IntersectionType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
$stringTypes = [];
foreach ($type->getTypes() as $unionedType) {
$stringTypes[] = $this->phpStanStaticTypeMapper->mapToDocString($unionedType);
}
// remove empty values, e.g. void/iterable
$stringTypes = array_unique($stringTypes);
$stringTypes = array_filter($stringTypes);
return implode('&', $stringTypes);
}
}

View File

@ -11,9 +11,12 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\IterableType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
use Rector\ValueObject\PhpVersionFeature;
final class IterableTypeMapper implements TypeMapperInterface
{
@ -22,10 +25,20 @@ final class IterableTypeMapper implements TypeMapperInterface
*/
private $phpStanStaticTypeMapper;
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;
public function __construct(PhpVersionProvider $phpVersionProvider)
{
$this->phpVersionProvider = $phpVersionProvider;
}
/**
* @required
*/
public function autowire(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
public function autowireIterableTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
@ -56,6 +69,19 @@ final class IterableTypeMapper implements TypeMapperInterface
return new Identifier('iterable');
}
/**
* @param IterableType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) {
// iterable type is better done in PHP code, than in doc
return '';
}
return $type->describe(VerbosityLevel::typeOnly());
}
private function convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes(
UnionTypeNode $unionTypeNode
): AttributeAwareUnionTypeNode {

View File

@ -9,6 +9,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
final class MixedTypeMapper implements TypeMapperInterface
@ -33,4 +34,9 @@ final class MixedTypeMapper implements TypeMapperInterface
{
return null;
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -33,4 +33,9 @@ final class NeverTypeMapper implements TypeMapperInterface
{
return null;
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return 'mixed';
}
}

View File

@ -9,6 +9,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
final class NullTypeMapper implements TypeMapperInterface
@ -33,4 +34,9 @@ final class NullTypeMapper implements TypeMapperInterface
{
return null;
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -13,8 +13,11 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\PHPStan\Type\AliasedObjectType;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
@ -38,6 +41,10 @@ final class ObjectTypeMapper implements TypeMapperInterface
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if ($type instanceof SelfObjectType) {
return new Identifier('self');
}
if ($type instanceof ShortenedObjectType) {
return new FullyQualified($type->getFullyQualifiedName());
}
@ -59,4 +66,31 @@ final class ObjectTypeMapper implements TypeMapperInterface
// fallback
return new FullyQualified($type->getClassName());
}
/**
* @param ObjectType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
if ($type instanceof AliasedObjectType) {
// no preslash for alias
return $type->getClassName();
}
if ($type instanceof ShortenedObjectType) {
return '\\' . $type->getFullyQualifiedName();
}
if ($type instanceof FullyQualifiedObjectType) {
// always prefixed with \\
return '\\' . $type->getClassName();
}
if (ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) {
// FQN by default
return '\\' . $type->describe(VerbosityLevel::typeOnly());
}
return $type->getClassName();
}
}

View File

@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;
@ -50,4 +51,9 @@ final class ObjectWithoutClassTypeMapper implements TypeMapperInterface
return new Identifier('object');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStan\Type\ParentStaticType;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
@ -34,4 +35,9 @@ final class ParentStaticTypeMapper implements TypeMapperInterface
{
return new Identifier('parent');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -9,6 +9,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ResourceType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
final class ResourceTypeMapper implements TypeMapperInterface
@ -33,4 +34,9 @@ final class ResourceTypeMapper implements TypeMapperInterface
{
return null;
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;
@ -47,4 +48,9 @@ final class StringTypeMapper implements TypeMapperInterface
return new Identifier('string');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
final class ThisTypeMapper implements TypeMapperInterface
@ -34,4 +35,12 @@ final class ThisTypeMapper implements TypeMapperInterface
{
return new Identifier('self');
}
/**
* @param ThisType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}

View File

@ -46,6 +46,14 @@ final class UnionTypeMapper implements TypeMapperInterface
$this->unionTypeAnalyzer = $unionTypeAnalyzer;
}
/**
* @required
*/
public function autowireUnionTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
public function getNodeClass(): string
{
return UnionType::class;
@ -67,14 +75,6 @@ final class UnionTypeMapper implements TypeMapperInterface
return new AttributeAwareUnionTypeNode($unionTypesNodes);
}
/**
* @required
*/
public function autowire(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
/**
* @param UnionType $type
*/
@ -109,6 +109,24 @@ final class UnionTypeMapper implements TypeMapperInterface
return new NullableType($nullabledTypeNode);
}
/**
* @param UnionType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
$docStrings = [];
foreach ($type->getTypes() as $unionedType) {
$docStrings[] = $this->phpStanStaticTypeMapper->mapToDocString($unionedType);
}
// remove empty values, e.g. void/iterable
$docStrings = array_unique($docStrings);
$docStrings = array_filter($docStrings);
return implode('|', $docStrings);
}
private function matchArrayTypes(UnionType $unionType): ?Identifier
{
$unionTypeAnalysis = $this->unionTypeAnalyzer->analyseForNullableAndIterable($unionType);

View File

@ -54,4 +54,15 @@ final class VoidTypeMapper implements TypeMapperInterface
return new Identifier('void');
}
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) {
// the void type is better done in PHP code
return '';
}
// fallback for PHP 7.0 and older, where void type was only in docs
return 'void';
}
}