mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 20:00:50 +00:00
[PHP 8.0] Add method param to ConstantListClassToEnumRector (#2415)
* add skipped fixtures * skip non-scalar types * prepare parameter update * add ConstExprClassNameDecorator, add PhpDocNodeDecoratorInterface
This commit is contained in:
parent
ba0869a9a5
commit
a73dafd30a
|
@ -3,6 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use PHPStan\PhpDocParser\Parser\TypeParser;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
|
||||
use Rector\CodingStyle\Contract\ClassNameImport\ClassNameImportSkipVoterInterface;
|
||||
use Rector\Core\Contract\Console\OutputStyleInterface;
|
||||
use Rector\Core\Contract\PhpParser\Node\StmtsAwareInterface;
|
||||
|
@ -40,6 +41,7 @@ use Symplify\EasyCI\ValueObject\Option;
|
|||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$parameters = $containerConfigurator->parameters();
|
||||
$parameters->set(Option::TYPES_TO_SKIP, [
|
||||
PhpDocNodeDecoratorInterface::class,
|
||||
Command::class,
|
||||
Application::class,
|
||||
RectorInterface::class,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\Contract\PhpDocParser;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
||||
|
||||
interface PhpDocNodeDecoratorInterface
|
||||
{
|
||||
public function decorate(PhpDocNode $phpDocNode, Node $phpNode): void;
|
||||
}
|
|
@ -35,7 +35,7 @@ final class PhpDocInfoFactory
|
|||
private readonly StaticTypeMapper $staticTypeMapper,
|
||||
private readonly AnnotationNaming $annotationNaming,
|
||||
private readonly RectorChangeCollector $rectorChangeCollector,
|
||||
private readonly PhpDocNodeByTypeFinder $phpDocNodeByTypeFinder
|
||||
private readonly PhpDocNodeByTypeFinder $phpDocNodeByTypeFinder,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\BetterPhpDocParser\PhpDocParser;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
|
@ -14,10 +15,12 @@ use PHPStan\PhpDocParser\Parser\ConstExprParser;
|
|||
use PHPStan\PhpDocParser\Parser\PhpDocParser;
|
||||
use PHPStan\PhpDocParser\Parser\TokenIterator;
|
||||
use PHPStan\PhpDocParser\Parser\TypeParser;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory;
|
||||
use Rector\BetterPhpDocParser\ValueObject\Parser\BetterTokenIterator;
|
||||
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
|
||||
use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
|
||||
use Rector\Core\Configuration\CurrentNodeProvider;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
|
||||
|
||||
|
@ -26,17 +29,18 @@ use Symplify\PackageBuilder\Reflection\PrivatesCaller;
|
|||
*/
|
||||
final class BetterPhpDocParser extends PhpDocParser
|
||||
{
|
||||
private readonly PrivatesCaller $privatesCaller;
|
||||
|
||||
/**
|
||||
* @param PhpDocNodeDecoratorInterface[] $phpDocNodeDecorators
|
||||
*/
|
||||
public function __construct(
|
||||
TypeParser $typeParser,
|
||||
ConstExprParser $constExprParser,
|
||||
private readonly CurrentNodeProvider $currentNodeProvider,
|
||||
private readonly TokenIteratorFactory $tokenIteratorFactory,
|
||||
private readonly DoctrineAnnotationDecorator $doctrineAnnotationDecorator
|
||||
private readonly array $phpDocNodeDecorators,
|
||||
private readonly PrivatesCaller $privatesCaller = new PrivatesCaller(),
|
||||
) {
|
||||
parent::__construct($typeParser, $constExprParser);
|
||||
|
||||
$this->privatesCaller = new PrivatesCaller();
|
||||
}
|
||||
|
||||
public function parse(TokenIterator $tokenIterator): PhpDocNode
|
||||
|
@ -59,14 +63,23 @@ final class BetterPhpDocParser extends PhpDocParser
|
|||
$tokenIterator->tryConsumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC);
|
||||
|
||||
$phpDocNode = new PhpDocNode($children);
|
||||
// replace generic nodes with DoctrineAnnotations
|
||||
$this->doctrineAnnotationDecorator->decorate($phpDocNode);
|
||||
|
||||
// decorate FQN classes etc.
|
||||
$node = $this->currentNodeProvider->getNode();
|
||||
if (! $node instanceof Node) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
foreach ($this->phpDocNodeDecorators as $phpDocNodeDecorator) {
|
||||
$phpDocNodeDecorator->decorate($phpDocNode, $node);
|
||||
}
|
||||
|
||||
return $phpDocNode;
|
||||
}
|
||||
|
||||
public function parseTag(TokenIterator $tokenIterator): PhpDocTagNode
|
||||
{
|
||||
// replace generic nodes with DoctrineAnnotations
|
||||
if (! $tokenIterator instanceof BetterTokenIterator) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\PhpDocParser;
|
||||
|
||||
use PhpParser\Node as PhpNode;
|
||||
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
|
||||
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
|
||||
use PHPStan\PhpDocParser\Ast\Node;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
|
||||
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
|
||||
use Rector\StaticTypeMapper\Naming\NameScopeFactory;
|
||||
use Symplify\Astral\PhpDocParser\PhpDocNodeTraverser;
|
||||
|
||||
/**
|
||||
* Decorate node with fully qualified class name for const epxr,
|
||||
* e.g. Direction::*
|
||||
*/
|
||||
final class ConstExprClassNameDecorator implements PhpDocNodeDecoratorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NameScopeFactory $nameScopeFactory,
|
||||
private readonly PhpDocNodeTraverser $phpDocNodeTraverser
|
||||
) {
|
||||
}
|
||||
|
||||
public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void
|
||||
{
|
||||
$this->phpDocNodeTraverser->traverseWithCallable($phpDocNode, '', function (Node $node) use (
|
||||
$phpNode
|
||||
): Node|null {
|
||||
if (! $node instanceof ConstExprNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$className = $this->resolveFullyQualifiedClass($node, $phpNode);
|
||||
if ($className === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className);
|
||||
return $node;
|
||||
});
|
||||
}
|
||||
|
||||
private function resolveFullyQualifiedClass(ConstExprNode $constExprNode, PhpNode $phpNode): ?string
|
||||
{
|
||||
if (! $constExprNode instanceof ConstFetchNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($phpNode);
|
||||
return $nameScope->resolveStringName($constExprNode->className);
|
||||
}
|
||||
}
|
|
@ -14,17 +14,16 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
|
|||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use Rector\BetterPhpDocParser\Attributes\AttributeMirrorer;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
|
||||
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
|
||||
use Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\TokenIteratorFactory;
|
||||
use Rector\BetterPhpDocParser\ValueObject\DoctrineAnnotation\SilentKeyMap;
|
||||
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
|
||||
use Rector\BetterPhpDocParser\ValueObject\StartAndEnd;
|
||||
use Rector\Core\Configuration\CurrentNodeProvider;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\Util\StringUtils;
|
||||
|
||||
final class DoctrineAnnotationDecorator
|
||||
final class DoctrineAnnotationDecorator implements PhpDocNodeDecoratorInterface
|
||||
{
|
||||
/**
|
||||
* Special short annotations, that are resolved as FQN by Doctrine annotation parser
|
||||
|
@ -45,7 +44,6 @@ final class DoctrineAnnotationDecorator
|
|||
private const NESTED_ANNOTATION_END_REGEX = '#(\s+)?\}\)(\s+)?#';
|
||||
|
||||
public function __construct(
|
||||
private readonly CurrentNodeProvider $currentNodeProvider,
|
||||
private readonly ClassAnnotationMatcher $classAnnotationMatcher,
|
||||
private readonly StaticDoctrineAnnotationParser $staticDoctrineAnnotationParser,
|
||||
private readonly TokenIteratorFactory $tokenIteratorFactory,
|
||||
|
@ -53,16 +51,11 @@ final class DoctrineAnnotationDecorator
|
|||
) {
|
||||
}
|
||||
|
||||
public function decorate(PhpDocNode $phpDocNode): void
|
||||
public function decorate(PhpDocNode $phpDocNode, Node $phpNode): void
|
||||
{
|
||||
$currentPhpNode = $this->currentNodeProvider->getNode();
|
||||
if (! $currentPhpNode instanceof Node) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
// merge split doctrine nested tags
|
||||
$this->mergeNestedDoctrineAnnotations($phpDocNode);
|
||||
$this->transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes($phpDocNode, $currentPhpNode);
|
||||
$this->transformGenericTagValueNodesToDoctrineAnnotationTagValueNodes($phpDocNode, $phpNode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;
|
||||
|
||||
final class ChangeParamType
|
||||
{
|
||||
/**
|
||||
* @param Gear::* $gear
|
||||
*/
|
||||
public function changeGear(string $gear)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;
|
||||
|
||||
final class ChangeParamType
|
||||
{
|
||||
public function changeGear(\Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear $gear)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;
|
||||
|
||||
final class MultipleParamsChange
|
||||
{
|
||||
/**
|
||||
* @param string $carType
|
||||
* @param Gear::* $gear
|
||||
* @param int $speed
|
||||
*/
|
||||
public function changeGear($carType, string $gear, $speed)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;
|
||||
|
||||
final class MultipleParamsChange
|
||||
{
|
||||
/**
|
||||
* @param string $carType
|
||||
* @param int $speed
|
||||
*/
|
||||
public function changeGear($carType, \Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear $gear, $speed)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
final class SkipAlsoOtherElements
|
||||
{
|
||||
public const LEFT = 'left';
|
||||
|
||||
public const RIGHT = 'right';
|
||||
|
||||
protected $value;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
final class SkipDifferentType
|
||||
{
|
||||
public const LEFT = 'left';
|
||||
|
||||
public const RIGHT = 5;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
final class SkipNonPublicConst
|
||||
{
|
||||
public const LEFT = 'left';
|
||||
|
||||
protected const RIGHT = 5;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
final class SkipNonScalarTypes
|
||||
{
|
||||
public const LEFT = self::class;
|
||||
|
||||
public const RIGHT = self::class;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
|
||||
|
||||
final class SkipUnknownClass
|
||||
{
|
||||
/**
|
||||
* @param AnythingNonExisting::* $gear
|
||||
*/
|
||||
public function changeGear(string $gear)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source;
|
||||
|
||||
final class Gear
|
||||
{
|
||||
public const FIRST = 'first';
|
||||
|
||||
public const SECOND = 'second';
|
||||
}
|
|
@ -4,8 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Php80\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
|
||||
final class EnumConstListClassDetector
|
||||
|
@ -30,10 +32,8 @@ final class EnumConstListClassDetector
|
|||
}
|
||||
|
||||
// all constant must be public
|
||||
foreach ($classConstants as $classConstant) {
|
||||
if (! $classConstant->isPublic()) {
|
||||
return false;
|
||||
}
|
||||
if (! $this->hasExclusivelyPublicClassConsts($classConstants)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// all constants must have exactly 1 value
|
||||
|
@ -43,26 +43,49 @@ final class EnumConstListClassDetector
|
|||
}
|
||||
}
|
||||
|
||||
$constantUniqueTypeCount = $this->resolveConstantUniqueTypeCount($classConstants);
|
||||
// only scalar values are allowed
|
||||
foreach ($classConstants as $classConstant) {
|
||||
$onlyConstConst = $classConstant->consts[0];
|
||||
if (! $onlyConstConst->value instanceof Scalar) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$uniqueTypeClasses = $this->resolveClassConstTypes($classConstants);
|
||||
|
||||
// must be exactly 1 type
|
||||
return $constantUniqueTypeCount === 1;
|
||||
return count($uniqueTypeClasses) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassConst[] $classConsts
|
||||
* @return array<class-string<Type>>
|
||||
*/
|
||||
private function resolveConstantUniqueTypeCount(array $classConsts): int
|
||||
private function resolveClassConstTypes(array $classConsts): array
|
||||
{
|
||||
$typeClasses = [];
|
||||
|
||||
// all constants must have same type
|
||||
foreach ($classConsts as $classConst) {
|
||||
$const = $classConst->consts[0];
|
||||
$constantType = $this->nodeTypeResolver->getType($const->value);
|
||||
$typeClasses[] = $constantType::class;
|
||||
$type = $this->nodeTypeResolver->getType($const->value);
|
||||
$typeClasses[] = $type::class;
|
||||
}
|
||||
|
||||
$uniqueTypeClasses = array_unique($typeClasses);
|
||||
return count($uniqueTypeClasses);
|
||||
return array_unique($typeClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassConst[] $classConsts
|
||||
*/
|
||||
private function hasExclusivelyPublicClassConsts(array $classConsts): bool
|
||||
{
|
||||
foreach ($classConsts as $classConst) {
|
||||
if (! $classConst->isPublic()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
56
rules/Php80/NodeAnalyzer/EnumParamAnalyzer.php
Normal file
56
rules/Php80/NodeAnalyzer/EnumParamAnalyzer.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\NodeAnalyzer;
|
||||
|
||||
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\Php\PhpParameterReflection;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
|
||||
|
||||
/**
|
||||
* Detects enum-like params, e.g.
|
||||
* Direction::*
|
||||
*/
|
||||
final class EnumParamAnalyzer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReflectionProvider $reflectionProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function matchClassName(ParameterReflection $parameterReflection, PhpDocInfo $phpDocInfo): ?string
|
||||
{
|
||||
if (! $parameterReflection instanceof PhpParameterReflection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($parameterReflection->getName());
|
||||
if (! $paramTagValueNode instanceof ParamTagValueNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $paramTagValueNode->type instanceof ConstTypeNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$constTypeNode = $paramTagValueNode->type;
|
||||
if (! $constTypeNode->constExpr instanceof ConstFetchNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$constExpr = $constTypeNode->constExpr;
|
||||
$className = $constExpr->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS);
|
||||
|
||||
if (! $this->reflectionProvider->hasClass($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
}
|
|
@ -5,9 +5,19 @@ declare(strict_types=1);
|
|||
namespace Rector\Php80\Rector\Class_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\Reflection\ReflectionResolver;
|
||||
use Rector\Php80\NodeAnalyzer\EnumConstListClassDetector;
|
||||
use Rector\Php80\NodeAnalyzer\EnumParamAnalyzer;
|
||||
use Rector\Php81\NodeFactory\EnumFactory;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
@ -19,7 +29,10 @@ final class ConstantListClassToEnumRector extends AbstractRector
|
|||
{
|
||||
public function __construct(
|
||||
private readonly EnumConstListClassDetector $enumConstListClassDetector,
|
||||
private readonly EnumFactory $enumFactory
|
||||
private readonly EnumFactory $enumFactory,
|
||||
private readonly EnumParamAnalyzer $enumParamAnalyzer,
|
||||
private readonly ReflectionResolver $reflectionResolver,
|
||||
private readonly PhpDocTagRemover $phpDocTagRemover
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -54,18 +67,83 @@ CODE_SAMPLE
|
|||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Class_::class];
|
||||
return [Class_::class, ClassMethod::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $node
|
||||
* @param Class_|ClassMethod $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if (! $this->enumConstListClassDetector->detect($node)) {
|
||||
if ($node instanceof Class_) {
|
||||
if (! $this->enumConstListClassDetector->detect($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->enumFactory->createFromClass($node);
|
||||
}
|
||||
|
||||
return $this->refactorClassMethod($node);
|
||||
}
|
||||
|
||||
private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod
|
||||
{
|
||||
if ($classMethod->params === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->enumFactory->createFromClass($node);
|
||||
// enum param types doc requires a docblock
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);
|
||||
if (! $phpDocInfo instanceof PhpDocInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$methodReflection = $this->reflectionResolver->resolveMethodReflectionFromClassMethod($classMethod);
|
||||
if (! $methodReflection instanceof MethodReflection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hasNodeChanged = false;
|
||||
|
||||
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
|
||||
|
||||
foreach ($parametersAcceptor->getParameters() as $parameterReflection) {
|
||||
$enumLikeClass = $this->enumParamAnalyzer->matchClassName($parameterReflection, $phpDocInfo);
|
||||
if ($enumLikeClass === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$param = $this->getParamByName($classMethod, $parameterReflection->getName());
|
||||
if (! $param instanceof Param) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// change and remove
|
||||
$param->type = new FullyQualified($enumLikeClass);
|
||||
$hasNodeChanged = true;
|
||||
|
||||
/** @var ParamTagValueNode $paramTagValueNode */
|
||||
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($parameterReflection->getName());
|
||||
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $paramTagValueNode);
|
||||
}
|
||||
|
||||
if ($hasNodeChanged) {
|
||||
return $classMethod;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getParamByName(ClassMethod $classMethod, string $desiredParamName): ?Param
|
||||
{
|
||||
foreach ($classMethod->params as $param) {
|
||||
if (! $this->nodeNameResolver->isName($param, $desiredParamName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $param;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user