mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 11:50:51 +00:00
[Generics] Skip existing tag and add nullable type support (#5367)
This commit is contained in:
parent
4590b10b43
commit
a4a490d69a
|
@ -48,16 +48,16 @@
|
|||
"symfony/finder": "^4.4.8|^5.1",
|
||||
"symfony/http-kernel": "^4.4.8|^5.1",
|
||||
"symfony/process": "^4.4.8|^5.1",
|
||||
"symplify/astral": "^9.0.34",
|
||||
"symplify/autowire-array-parameter": "^9.0.34",
|
||||
"symplify/console-color-diff": "^9.0.34",
|
||||
"symplify/package-builder": "^9.0.34",
|
||||
"symplify/rule-doc-generator": "^9.0.34",
|
||||
"symplify/set-config-resolver": "^9.0.34",
|
||||
"symplify/simple-php-doc-parser": "^9.0",
|
||||
"symplify/skipper": "^9.0.34",
|
||||
"symplify/smart-file-system": "^9.0.34",
|
||||
"symplify/symfony-php-config": "^9.0.34",
|
||||
"symplify/astral": "^9.0.48",
|
||||
"symplify/autowire-array-parameter": "^9.0.48",
|
||||
"symplify/console-color-diff": "^9.0.48",
|
||||
"symplify/package-builder": "^9.0.48",
|
||||
"symplify/rule-doc-generator": "^9.0.48",
|
||||
"symplify/set-config-resolver": "^9.0.48",
|
||||
"symplify/simple-php-doc-parser": "^9.0.48",
|
||||
"symplify/skipper": "^9.0.48",
|
||||
"symplify/smart-file-system": "^9.0.48",
|
||||
"symplify/symfony-php-config": "^9.0.48",
|
||||
"webmozart/assert": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -72,12 +72,12 @@
|
|||
"sebastian/diff": "^4.0.4",
|
||||
"symfony/security-core": "^5.2",
|
||||
"symfony/security-http": "^5.2",
|
||||
"symplify/changelog-linker": "^9.0.34",
|
||||
"symplify/coding-standard": "^9.0.34",
|
||||
"symplify/easy-coding-standard": "^9.0.34",
|
||||
"symplify/easy-testing": "^9.0.34",
|
||||
"symplify/phpstan-extensions": "^9.0.34",
|
||||
"symplify/phpstan-rules": "^9.0.34",
|
||||
"symplify/changelog-linker": "^9.0.48",
|
||||
"symplify/coding-standard": "^9.0.48",
|
||||
"symplify/easy-coding-standard": "^9.0.48",
|
||||
"symplify/easy-testing": "^9.0.48",
|
||||
"symplify/phpstan-extensions": "^9.0.48",
|
||||
"symplify/phpstan-rules": "^9.0.48",
|
||||
"tracy/tracy": "^2.7"
|
||||
},
|
||||
"replace": {
|
||||
|
|
|
@ -464,6 +464,19 @@ final class PhpDocInfo
|
|||
return $this->hasChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMethodTagNames(): array
|
||||
{
|
||||
$methodTagNames = [];
|
||||
foreach ($this->phpDocNode->getMethodTagValues() as $methodTagValueNode) {
|
||||
$methodTagNames[] = $methodTagValueNode->methodName;
|
||||
}
|
||||
|
||||
return $methodTagNames;
|
||||
}
|
||||
|
||||
private function getTypeOrMixed(?PhpDocTagValueNode $phpDocTagValueNode): Type
|
||||
{
|
||||
if ($phpDocTagValueNode === null) {
|
||||
|
|
|
@ -6,14 +6,13 @@ namespace Rector\Generics\Rector\Class_;
|
|||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Generics\NodeType\GenericTypeSpecifier;
|
||||
use Rector\Generics\Reflection\ClassGenericMethodResolver;
|
||||
use Rector\Generics\Reflection\GenericClassReflectionAnalyzer;
|
||||
use Rector\Generics\ValueObject\GenericChildParentClassReflections;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
|
@ -119,11 +118,7 @@ CODE_SAMPLE
|
|||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node->extends === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$genericChildParentClassReflections = $this->resolveGenericChildParentClassReflections($node);
|
||||
$genericChildParentClassReflections = $this->genericClassReflectionAnalyzer->resolveChildParent($node);
|
||||
if (! $genericChildParentClassReflections instanceof GenericChildParentClassReflections) {
|
||||
return null;
|
||||
}
|
||||
|
@ -133,13 +128,16 @@ CODE_SAMPLE
|
|||
$genericChildParentClassReflections->getParentClassReflection()
|
||||
);
|
||||
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
|
||||
|
||||
$methodTagValueNodes = $this->filterOutExistingMethodTagValuesNodes($methodTagValueNodes, $phpDocInfo);
|
||||
|
||||
$this->genericTypeSpecifier->replaceGenericTypesWithSpecificTypes(
|
||||
$methodTagValueNodes,
|
||||
$node,
|
||||
$genericChildParentClassReflections->getChildClassReflection()
|
||||
);
|
||||
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
|
||||
foreach ($methodTagValueNodes as $methodTagValueNode) {
|
||||
$phpDocInfo->addTagValueNode($methodTagValueNode);
|
||||
}
|
||||
|
@ -147,31 +145,28 @@ CODE_SAMPLE
|
|||
return $node;
|
||||
}
|
||||
|
||||
private function resolveGenericChildParentClassReflections(Class_ $class): ?GenericChildParentClassReflections
|
||||
{
|
||||
$scope = $class->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
return null;
|
||||
/**
|
||||
* @param MethodTagValueNode[] $methodTagValueNodes
|
||||
* @return MethodTagValueNode[]
|
||||
*/
|
||||
private function filterOutExistingMethodTagValuesNodes(
|
||||
array $methodTagValueNodes,
|
||||
PhpDocInfo $phpDocInfo
|
||||
): array {
|
||||
$methodTagNames = $phpDocInfo->getMethodTagNames();
|
||||
if ($methodTagNames === []) {
|
||||
return $methodTagValueNodes;
|
||||
}
|
||||
|
||||
$classReflection = $scope->getClassReflection();
|
||||
if (! $classReflection instanceof ClassReflection) {
|
||||
return null;
|
||||
$filteredMethodTagValueNodes = [];
|
||||
foreach ($methodTagValueNodes as $methodTagValueNode) {
|
||||
if (in_array($methodTagValueNode->methodName, $methodTagNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filteredMethodTagValueNodes[] = $methodTagValueNode;
|
||||
}
|
||||
|
||||
if (! $this->genericClassReflectionAnalyzer->isGeneric($classReflection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parentClassReflection = $classReflection->getParentClass();
|
||||
if (! $parentClassReflection instanceof ClassReflection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->genericClassReflectionAnalyzer->isGeneric($parentClassReflection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GenericChildParentClassReflections($classReflection, $parentClassReflection);
|
||||
return $filteredMethodTagValueNodes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,16 +4,52 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Generics\Reflection;
|
||||
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use Rector\Generics\ValueObject\GenericChildParentClassReflections;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class GenericClassReflectionAnalyzer
|
||||
{
|
||||
public function resolveChildParent(Class_ $class): ?GenericChildParentClassReflections
|
||||
{
|
||||
if ($class->extends === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$scope = $class->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classReflection = $scope->getClassReflection();
|
||||
if (! $classReflection instanceof ClassReflection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isGeneric($classReflection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parentClassReflection = $classReflection->getParentClass();
|
||||
if (! $parentClassReflection instanceof ClassReflection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isGeneric($parentClassReflection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GenericChildParentClassReflections($classReflection, $parentClassReflection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve isGeneric() ignores extends and similar tags,
|
||||
* so it has to be extended with "@extends" and "@implements"
|
||||
*/
|
||||
public function isGeneric(ClassReflection $classReflection): bool
|
||||
private function isGeneric(ClassReflection $classReflection): bool
|
||||
{
|
||||
if ($classReflection->isGeneric()) {
|
||||
return true;
|
||||
|
|
|
@ -7,10 +7,15 @@ namespace Rector\Generics\TagValueNodeFactory;
|
|||
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\StaticTypeMapper\StaticTypeMapper;
|
||||
|
||||
final class MethodTagValueNodeFactory
|
||||
|
@ -45,17 +50,7 @@ final class MethodTagValueNodeFactory
|
|||
$classReflection = $methodReflection->getDeclaringClass();
|
||||
$templateTypeMap = $classReflection->getTemplateTypeMap();
|
||||
|
||||
$returnTagTypeNode = $returnTagValueNode->type;
|
||||
|
||||
if ($returnTagValueNode->type instanceof IdentifierTypeNode) {
|
||||
$typeName = $returnTagValueNode->type->name;
|
||||
$genericType = $templateTypeMap->getType($typeName);
|
||||
|
||||
if ($genericType instanceof Type) {
|
||||
$returnTagType = $genericType;
|
||||
$returnTagTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($returnTagType);
|
||||
}
|
||||
}
|
||||
$returnTagTypeNode = $this->resolveReturnTagTypeNode($returnTagValueNode, $templateTypeMap);
|
||||
|
||||
return new MethodTagValueNode(
|
||||
false,
|
||||
|
@ -82,4 +77,68 @@ final class MethodTagValueNodeFactory
|
|||
|
||||
return $stringParameters;
|
||||
}
|
||||
|
||||
private function resolveReturnTagTypeNode(
|
||||
ReturnTagValueNode $returnTagValueNode,
|
||||
TemplateTypeMap $templateTypeMap
|
||||
): TypeNode {
|
||||
$returnTagTypeNode = $returnTagValueNode->type;
|
||||
if ($returnTagValueNode->type instanceof UnionTypeNode) {
|
||||
return $this->resolveUnionTypeNode($returnTagValueNode->type, $templateTypeMap);
|
||||
}
|
||||
|
||||
if ($returnTagValueNode->type instanceof IdentifierTypeNode) {
|
||||
return $this->resolveIdentifierTypeNode(
|
||||
$returnTagValueNode->type,
|
||||
$templateTypeMap,
|
||||
$returnTagTypeNode
|
||||
);
|
||||
}
|
||||
|
||||
return $returnTagTypeNode;
|
||||
}
|
||||
|
||||
private function resolveUnionTypeNode(UnionTypeNode $unionTypeNode, TemplateTypeMap $templateTypeMap): UnionTypeNode
|
||||
{
|
||||
$resolvedTypes = [];
|
||||
foreach ($unionTypeNode->types as $unionedTypeNode) {
|
||||
if ($unionedTypeNode instanceof ArrayTypeNode) {
|
||||
if (! $unionedTypeNode->type instanceof IdentifierTypeNode) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$resolvedType = $this->resolveIdentifierTypeNode(
|
||||
$unionedTypeNode->type,
|
||||
$templateTypeMap,
|
||||
$unionedTypeNode
|
||||
);
|
||||
|
||||
$resolvedTypes[] = new ArrayTypeNode($resolvedType);
|
||||
} elseif ($unionedTypeNode instanceof IdentifierTypeNode) {
|
||||
$resolvedTypes[] = $this->resolveIdentifierTypeNode(
|
||||
$unionedTypeNode,
|
||||
$templateTypeMap,
|
||||
$unionedTypeNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new UnionTypeNode($resolvedTypes);
|
||||
}
|
||||
|
||||
private function resolveIdentifierTypeNode(
|
||||
IdentifierTypeNode $identifierTypeNode,
|
||||
TemplateTypeMap $templateTypeMap,
|
||||
TypeNode $fallbackTypeNode
|
||||
): TypeNode {
|
||||
$typeName = $identifierTypeNode->name;
|
||||
$genericType = $templateTypeMap->getType($typeName);
|
||||
|
||||
if ($genericType instanceof Type) {
|
||||
$returnTagType = $genericType;
|
||||
return $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($returnTagType);
|
||||
}
|
||||
|
||||
return $fallbackTypeNode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractGenericArrayRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
*/
|
||||
final class ArrayLike extends AbstractGenericArrayRepository
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractGenericArrayRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
* @method \Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject[] findAll()
|
||||
*/
|
||||
final class ArrayLike extends AbstractGenericArrayRepository
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractGenericMaybeArrayRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
*/
|
||||
final class ArrayLikeNullable extends AbstractGenericMaybeArrayRepository
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractGenericMaybeArrayRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
* @method \Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject[]|null findAllMaybe(string $some, int $type, $unknown)
|
||||
*/
|
||||
final class ArrayLikeNullable extends AbstractGenericMaybeArrayRepository
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractMaybeGenericRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
*/
|
||||
final class NullableGet extends AbstractMaybeGenericRepository
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractMaybeGenericRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
* @method \Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject|null find($id)
|
||||
*/
|
||||
final class NullableGet extends AbstractMaybeGenericRepository
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
|
||||
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\AbstractGenericRepository;
|
||||
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
|
||||
|
||||
/**
|
||||
* @template TEntity as RealObject
|
||||
* @extends AbstractRepository<TEntity>
|
||||
* @method \Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject find($id)
|
||||
*/
|
||||
final class SkipAlreadyMethod extends AbstractGenericRepository
|
||||
{
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source;
|
||||
|
||||
/**
|
||||
* @template TEntity as object
|
||||
*/
|
||||
abstract class AbstractGenericArrayRepository
|
||||
{
|
||||
/**
|
||||
* @return TEntity[]
|
||||
*/
|
||||
public function findAll()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source;
|
||||
|
||||
/**
|
||||
* @template TEntity as object
|
||||
*/
|
||||
abstract class AbstractGenericMaybeArrayRepository
|
||||
{
|
||||
/**
|
||||
* @return TEntity[]|null
|
||||
*/
|
||||
public function findAllMaybe(string $some, int $type, $unknown)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source;
|
||||
|
||||
/**
|
||||
* @template TEntity as object
|
||||
*/
|
||||
abstract class AbstractMaybeGenericRepository
|
||||
{
|
||||
/**
|
||||
* @return TEntity|null
|
||||
*/
|
||||
public function find($id)
|
||||
{
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user