[PHP 8.0] Add ConstantListClassToEnumRector (#2404)

* [PHP 8.0] Add ConstantListClassToEnumRector

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2022-06-01 15:16:02 +02:00 committed by GitHub
parent 8eac546e11
commit e6ebae365b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 0 deletions

View File

@ -704,3 +704,7 @@ parameters:
-
message: '#Class has a static method must so must contains "Static" in its name#'
path: rules/CodingStyle/Enum/PreferenceSelfThis.php
# optional rule for PHP 8.0
- '#Register "Rector\\Php80\\Rector\\Class_\\ConstantListClassToEnumRector" service to "php80\.php" config set#'
- '#Rule Rector\\Php80\\Rector\\Class_\\ConstantListClassToEnumRector must implements Rector\\VersionBonding\\Contract\\MinPhpVersionInterface#'

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ConstantListClassToEnumRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
class Direction
{
public const LEFT = 'left';
public const RIGHT = 'right';
}
?>
-----
<?php
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
enum Direction : string
{
case LEFT = 'left';
case RIGHT = 'right';
}
?>

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\ConstantListClassToEnumRector;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(ConstantListClassToEnumRector::class);
};

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\NodeAnalyzer;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class EnumConstListClassDetector
{
public function __construct(
private readonly NodeTypeResolver $nodeTypeResolver
) {
}
public function detect(Class_ $class): bool
{
$classConstants = $class->getConstants();
// must have at least 2 constants, otherwise probably not enum
if (count($classConstants) < 2) {
return false;
}
// only constants are allowed, nothing else
if (count($class->stmts) !== count($classConstants)) {
return false;
}
// all constant must be public
foreach ($classConstants as $classConstant) {
if (! $classConstant->isPublic()) {
return false;
}
}
// all constants must have exactly 1 value
foreach ($classConstants as $classConstant) {
if (count($classConstant->consts) !== 1) {
return false;
}
}
$constantUniqueTypeCount = $this->resolveConstantUniqueTypeCount($classConstants);
// must be exactly 1 type
return $constantUniqueTypeCount === 1;
}
/**
* @param ClassConst[] $classConsts
*/
private function resolveConstantUniqueTypeCount(array $classConsts): int
{
$typeClasses = [];
// all constants must have same type
foreach ($classConsts as $classConst) {
$const = $classConst->consts[0];
$constantType = $this->nodeTypeResolver->getType($const->value);
$typeClasses[] = $constantType::class;
}
$uniqueTypeClasses = array_unique($typeClasses);
return count($uniqueTypeClasses);
}
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use Rector\Core\Rector\AbstractRector;
use Rector\Php80\NodeAnalyzer\EnumConstListClassDetector;
use Rector\Php81\NodeFactory\EnumFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\ConstantListClassToEnumRectorTest
*/
final class ConstantListClassToEnumRector extends AbstractRector
{
public function __construct(
private readonly EnumConstListClassDetector $enumConstListClassDetector,
private readonly EnumFactory $enumFactory
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Upgrade constant list classes to full blown enum', [
new CodeSample(
<<<'CODE_SAMPLE'
class Direction
{
public const LEFT = 'left';
public const RIGHT = 'right';
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
enum Direction
{
case LEFT;
case RIGHT;
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->enumConstListClassDetector->detect($node)) {
return null;
}
return $this->enumFactory->createFromClass($node);
}
}