|
<?php |
|
|
|
namespace App\Rector; |
|
|
|
use PhpParser\Node; |
|
use PhpParser\Node\Expr\ClassConstFetch; |
|
use PhpParser\Node\Identifier; |
|
use PhpParser\Node\Stmt\EnumCase; |
|
use Rector\Rector\AbstractRector; |
|
use ReflectionClass; |
|
use ReflectionException; |
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; |
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; |
|
use PHPStan\Reflection\ReflectionProvider; |
|
|
|
final class RenameEnumCasesToPascalCaseRector extends AbstractRector |
|
{ |
|
public function __construct( |
|
private ReflectionProvider $reflectionProvider |
|
) { |
|
} |
|
|
|
|
|
public function getRuleDefinition(): RuleDefinition |
|
{ |
|
return new RuleDefinition( |
|
'Convert snake_case enum cases to PascalCase', |
|
[ |
|
new CodeSample( |
|
<<<'CODE_SAMPLE' |
|
enum SomeEnum |
|
{ |
|
case SNAKE_CASE_CONSTANT; |
|
} |
|
function example() { |
|
$value = SomeEnum::SNAKE_CASE_CONSTANT; |
|
} |
|
CODE_SAMPLE |
|
, |
|
<<<'CODE_SAMPLE' |
|
enum SomeEnum |
|
{ |
|
case SnakeCaseConstant; |
|
} |
|
function example() { |
|
$value = SomeEnum::SnakeCaseConstant; |
|
} |
|
CODE_SAMPLE |
|
), |
|
] |
|
); |
|
} |
|
|
|
|
|
public function getNodeTypes(): array |
|
{ |
|
return [EnumCase::class, ClassConstFetch::class]; |
|
} |
|
|
|
public function refactor(Node $node): ?Node |
|
{ |
|
if ($node instanceof EnumCase) { |
|
return $this->refactorEnumCase($node); |
|
} |
|
|
|
if ($node instanceof ClassConstFetch) { |
|
return $this->refactorEnumCaseFetch($node); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
private function refactorEnumCase(EnumCase $node): ?Node |
|
{ |
|
$oldName = $this->getName($node->name); |
|
$newName = $this->convertSnakeToPascalCase($oldName); |
|
|
|
if ($oldName !== $newName) { |
|
$node->name = new Identifier($newName); |
|
|
|
return $node; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
private function refactorEnumCaseFetch(ClassConstFetch $node): ?Node |
|
{ |
|
if ($this->isEnumCaseFetch($node)) { |
|
$oldName = $this->getName($node->name); |
|
if ($oldName === 'class' || $oldName === null) { |
|
return null; |
|
} |
|
$newName = $this->convertSnakeToPascalCase($oldName); |
|
if ($oldName !== $newName) { |
|
$node->name = new Identifier($newName); |
|
} |
|
} |
|
|
|
return $node; |
|
} |
|
|
|
private function isEnumCaseFetch(ClassConstFetch $node): bool |
|
{ |
|
// the static reflection should be used instead, to avoid calling the analysed code |
|
// @see https://phpstan.org/blog/zero-config-analysis-with-static-reflection |
|
$objectType = $this->getType($node->class); |
|
|
|
return $objectType->isEnum()->yes(); |
|
} |
|
|
|
private function convertSnakeToPascalCase(string $name): string |
|
{ |
|
if (!$this->isSnakeCase($name)) { |
|
return $name; |
|
} |
|
return str_replace(' ', '', ucwords(str_replace('_', ' ', mb_strtolower($name)))); |
|
} |
|
|
|
private function isSnakeCase(string $name): bool |
|
{ |
|
return str_contains($name, '_') || ctype_lower($name) === true || ctype_upper($name) === true; |
|
} |
|
} |
Just a small note, but if you want to make Rector a bit more efficient, your rules should only return a node if a change has been made, otherwise you should return null.