2021-05-14 09:53:43 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace Rector\Php81\NodeFactory;
|
2021-05-14 09:53:43 +00:00
|
|
|
|
2024-12-01 00:43:07 +00:00
|
|
|
use RectorPrefix202412\Nette\Utils\Strings;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\BuilderFactory;
|
2024-11-20 15:58:53 +00:00
|
|
|
use PhpParser\Node\ArrayItem;
|
2024-11-20 19:59:31 +00:00
|
|
|
use PhpParser\Node\Expr\Array_;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Identifier;
|
2024-11-20 19:59:31 +00:00
|
|
|
use PhpParser\Node\Scalar\Int_;
|
2022-12-15 10:18:35 +00:00
|
|
|
use PhpParser\Node\Scalar\String_;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Stmt\Class_;
|
|
|
|
use PhpParser\Node\Stmt\ClassConst;
|
2022-12-15 14:08:31 +00:00
|
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node\Stmt\Enum_;
|
|
|
|
use PhpParser\Node\Stmt\EnumCase;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
|
|
|
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
|
|
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\PhpParser\Node\BetterNodeFinder;
|
|
|
|
use Rector\PhpParser\Node\Value\ValueResolver;
|
2021-05-14 09:53:43 +00:00
|
|
|
final class EnumFactory
|
|
|
|
{
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-14 09:53:43 +00:00
|
|
|
*/
|
2024-11-20 15:58:53 +00:00
|
|
|
private NodeNameResolver $nodeNameResolver;
|
2021-08-12 14:48:46 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-08-12 14:48:46 +00:00
|
|
|
*/
|
2024-11-20 15:58:53 +00:00
|
|
|
private PhpDocInfoFactory $phpDocInfoFactory;
|
2021-08-12 14:48:46 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-08-12 14:48:46 +00:00
|
|
|
*/
|
2024-11-20 15:58:53 +00:00
|
|
|
private BuilderFactory $builderFactory;
|
2021-12-02 07:34:16 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-12-02 07:34:16 +00:00
|
|
|
*/
|
2024-11-20 15:58:53 +00:00
|
|
|
private ValueResolver $valueResolver;
|
2022-12-15 10:18:35 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2022-12-15 10:18:35 +00:00
|
|
|
*/
|
2024-11-20 15:58:53 +00:00
|
|
|
private BetterNodeFinder $betterNodeFinder;
|
2024-01-06 13:17:15 +00:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://stackoverflow.com/a/2560017
|
|
|
|
* @see https://regex101.com/r/2xEQVj/1 for changing iso9001 to iso_9001
|
|
|
|
* @see https://regex101.com/r/Ykm6ub/1 for changing XMLParser to XML_Parser
|
|
|
|
* @see https://regex101.com/r/Zv4JhD/1 for changing needsReview to needs_Review
|
|
|
|
*/
|
|
|
|
private const PASCAL_CASE_TO_UNDERSCORE_REGEX = '/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])/';
|
2024-01-16 10:21:57 +00:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
* @see https://regex101.com/r/FneU33/1
|
|
|
|
*/
|
|
|
|
private const MULTI_UNDERSCORES_REGEX = '#_{2,}#';
|
2022-12-15 10:18:35 +00:00
|
|
|
public function __construct(NodeNameResolver $nodeNameResolver, PhpDocInfoFactory $phpDocInfoFactory, BuilderFactory $builderFactory, ValueResolver $valueResolver, BetterNodeFinder $betterNodeFinder)
|
2021-05-14 09:53:43 +00:00
|
|
|
{
|
|
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
2021-08-12 14:48:46 +00:00
|
|
|
$this->phpDocInfoFactory = $phpDocInfoFactory;
|
|
|
|
$this->builderFactory = $builderFactory;
|
2021-12-02 07:34:16 +00:00
|
|
|
$this->valueResolver = $valueResolver;
|
2022-12-15 10:18:35 +00:00
|
|
|
$this->betterNodeFinder = $betterNodeFinder;
|
2021-05-14 09:53:43 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function createFromClass(Class_ $class) : Enum_
|
2021-05-14 09:53:43 +00:00
|
|
|
{
|
|
|
|
$shortClassName = $this->nodeNameResolver->getShortName($class);
|
2022-10-17 13:37:08 +00:00
|
|
|
$enum = new Enum_($shortClassName, [], ['startLine' => $class->getStartLine(), 'endLine' => $class->getEndLine()]);
|
2022-06-21 14:59:06 +00:00
|
|
|
$enum->namespacedName = $class->namespacedName;
|
2021-12-02 07:34:16 +00:00
|
|
|
$constants = $class->getConstants();
|
2022-07-20 13:38:02 +00:00
|
|
|
$enum->stmts = $class->getTraitUses();
|
2021-12-02 07:34:16 +00:00
|
|
|
if ($constants !== []) {
|
|
|
|
$value = $this->valueResolver->getValue($constants[0]->consts[0]->value);
|
2022-06-07 08:22:29 +00:00
|
|
|
$enum->scalarType = \is_string($value) ? new Identifier('string') : new Identifier('int');
|
2021-12-02 07:34:16 +00:00
|
|
|
// constant to cases
|
|
|
|
foreach ($constants as $constant) {
|
|
|
|
$enum->stmts[] = $this->createEnumCaseFromConst($constant);
|
|
|
|
}
|
2021-05-14 09:53:43 +00:00
|
|
|
}
|
2023-09-04 07:26:59 +00:00
|
|
|
$enum->stmts = \array_merge($enum->stmts, $class->getMethods());
|
2021-05-14 09:53:43 +00:00
|
|
|
return $enum;
|
|
|
|
}
|
2024-01-06 13:17:15 +00:00
|
|
|
public function createFromSpatieClass(Class_ $class, bool $enumNameInSnakeCase = \false) : Enum_
|
2021-08-12 14:48:46 +00:00
|
|
|
{
|
|
|
|
$shortClassName = $this->nodeNameResolver->getShortName($class);
|
2022-10-17 13:37:08 +00:00
|
|
|
$enum = new Enum_($shortClassName, [], ['startLine' => $class->getStartLine(), 'endLine' => $class->getEndLine()]);
|
2022-06-21 14:59:06 +00:00
|
|
|
$enum->namespacedName = $class->namespacedName;
|
2021-08-12 14:48:46 +00:00
|
|
|
// constant to cases
|
2022-06-13 09:09:58 +00:00
|
|
|
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($class);
|
|
|
|
$docBlockMethods = $phpDocInfo->getTagsByName('@method');
|
|
|
|
if ($docBlockMethods !== []) {
|
2022-12-15 10:18:35 +00:00
|
|
|
$mapping = $this->generateMappingFromClass($class);
|
|
|
|
$identifierType = $this->getIdentifierTypeFromMappings($mapping);
|
|
|
|
$enum->scalarType = new Identifier($identifierType);
|
2021-08-12 14:48:46 +00:00
|
|
|
foreach ($docBlockMethods as $docBlockMethod) {
|
2024-01-06 13:17:15 +00:00
|
|
|
$enum->stmts[] = $this->createEnumCaseFromDocComment($docBlockMethod, $class, $mapping, $enumNameInSnakeCase);
|
2021-08-12 14:48:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $enum;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function createEnumCaseFromConst(ClassConst $classConst) : EnumCase
|
2021-05-14 09:53:43 +00:00
|
|
|
{
|
|
|
|
$constConst = $classConst->consts[0];
|
2022-12-16 12:43:56 +00:00
|
|
|
$enumCase = new EnumCase($constConst->name, $constConst->value, [], ['startLine' => $constConst->getStartLine(), 'endLine' => $constConst->getEndLine()]);
|
2021-08-12 14:48:46 +00:00
|
|
|
// mirror comments
|
2022-06-07 08:22:29 +00:00
|
|
|
$enumCase->setAttribute(AttributeKey::PHP_DOC_INFO, $classConst->getAttribute(AttributeKey::PHP_DOC_INFO));
|
|
|
|
$enumCase->setAttribute(AttributeKey::COMMENTS, $classConst->getAttribute(AttributeKey::COMMENTS));
|
2021-05-14 09:53:43 +00:00
|
|
|
return $enumCase;
|
|
|
|
}
|
2022-12-15 10:18:35 +00:00
|
|
|
/**
|
|
|
|
* @param array<int|string, mixed> $mapping
|
|
|
|
*/
|
2024-01-06 13:17:15 +00:00
|
|
|
private function createEnumCaseFromDocComment(PhpDocTagNode $phpDocTagNode, Class_ $class, array $mapping = [], bool $enumNameInSnakeCase = \false) : EnumCase
|
2021-08-12 14:48:46 +00:00
|
|
|
{
|
2021-10-27 08:50:45 +00:00
|
|
|
/** @var MethodTagValueNode $nodeValue */
|
2021-08-12 16:01:53 +00:00
|
|
|
$nodeValue = $phpDocTagNode->value;
|
2022-12-15 10:18:35 +00:00
|
|
|
$enumValue = $mapping[$nodeValue->methodName] ?? $nodeValue->methodName;
|
2024-01-06 13:17:15 +00:00
|
|
|
if ($enumNameInSnakeCase) {
|
|
|
|
$enumName = \strtoupper(Strings::replace($nodeValue->methodName, self::PASCAL_CASE_TO_UNDERSCORE_REGEX, '_$0'));
|
2024-01-16 10:21:57 +00:00
|
|
|
$enumName = Strings::replace($enumName, self::MULTI_UNDERSCORES_REGEX, '_');
|
2024-01-06 13:17:15 +00:00
|
|
|
} else {
|
|
|
|
$enumName = \strtoupper($nodeValue->methodName);
|
|
|
|
}
|
2022-12-15 10:18:35 +00:00
|
|
|
$enumExpr = $this->builderFactory->val($enumValue);
|
2022-12-16 12:43:56 +00:00
|
|
|
return new EnumCase($enumName, $enumExpr, [], ['startLine' => $class->getStartLine(), 'endLine' => $class->getEndLine()]);
|
2021-08-12 14:48:46 +00:00
|
|
|
}
|
2022-12-15 10:18:35 +00:00
|
|
|
/**
|
|
|
|
* @return array<int|string, mixed>
|
|
|
|
*/
|
|
|
|
private function generateMappingFromClass(Class_ $class) : array
|
|
|
|
{
|
|
|
|
$classMethod = $class->getMethod('values');
|
2022-12-15 14:08:31 +00:00
|
|
|
if (!$classMethod instanceof ClassMethod) {
|
2022-12-15 10:18:35 +00:00
|
|
|
return [];
|
|
|
|
}
|
2024-07-03 20:14:45 +00:00
|
|
|
$returns = $this->betterNodeFinder->findReturnsScoped($classMethod);
|
2022-12-15 14:08:31 +00:00
|
|
|
/** @var array<int|string, mixed> $mapping */
|
2022-12-15 10:18:35 +00:00
|
|
|
$mapping = [];
|
2022-12-15 14:08:31 +00:00
|
|
|
foreach ($returns as $return) {
|
|
|
|
if (!$return->expr instanceof Array_) {
|
2022-12-15 10:18:35 +00:00
|
|
|
continue;
|
|
|
|
}
|
2022-12-15 14:08:31 +00:00
|
|
|
$mapping = $this->collectMappings($return->expr->items, $mapping);
|
|
|
|
}
|
|
|
|
return $mapping;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param null[]|ArrayItem[] $items
|
|
|
|
* @param array<int|string, mixed> $mapping
|
|
|
|
* @return array<int|string, mixed>
|
|
|
|
*/
|
|
|
|
private function collectMappings(array $items, array $mapping) : array
|
|
|
|
{
|
|
|
|
foreach ($items as $item) {
|
|
|
|
if (!$item instanceof ArrayItem) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-11-20 15:58:53 +00:00
|
|
|
if (!$item->key instanceof Int_ && !$item->key instanceof String_) {
|
2022-12-15 14:08:31 +00:00
|
|
|
continue;
|
|
|
|
}
|
2024-11-20 15:58:53 +00:00
|
|
|
if (!$item->value instanceof Int_ && !$item->value instanceof String_) {
|
2022-12-15 14:08:31 +00:00
|
|
|
continue;
|
2022-12-15 10:18:35 +00:00
|
|
|
}
|
2022-12-15 14:08:31 +00:00
|
|
|
$mapping[$item->key->value] = $item->value->value;
|
2022-12-15 10:18:35 +00:00
|
|
|
}
|
|
|
|
return $mapping;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param array<int|string, mixed> $mapping
|
|
|
|
*/
|
|
|
|
private function getIdentifierTypeFromMappings(array $mapping) : string
|
|
|
|
{
|
2024-11-20 15:58:53 +00:00
|
|
|
$callableGetType = static fn($value): string => \gettype($value);
|
2022-12-15 14:08:31 +00:00
|
|
|
$valueTypes = \array_map($callableGetType, $mapping);
|
2022-12-15 10:18:35 +00:00
|
|
|
$uniqueValueTypes = \array_unique($valueTypes);
|
|
|
|
if (\count($uniqueValueTypes) === 1) {
|
|
|
|
$identifierType = \reset($uniqueValueTypes);
|
|
|
|
if ($identifierType === 'integer') {
|
|
|
|
$identifierType = 'int';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$identifierType = 'string';
|
|
|
|
}
|
|
|
|
return $identifierType;
|
|
|
|
}
|
2021-05-14 09:53:43 +00:00
|
|
|
}
|