Skip to content

Commit

Permalink
[PHP 8.0] Add ConstantListClassToEnumRector (#2404)
Browse files Browse the repository at this point in the history
* [PHP 8.0] Add ConstantListClassToEnumRector

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
TomasVotruba and actions-user authored Jun 1, 2022
1 parent 8eac546 commit e6ebae3
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 0 deletions.
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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#'
Original file line number Diff line number Diff line change
@@ -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';
}
}
Original file line number Diff line number Diff line change
@@ -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';
}

?>
Original file line number Diff line number Diff line change
@@ -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);
};
68 changes: 68 additions & 0 deletions rules/Php80/NodeAnalyzer/EnumConstListClassDetector.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
71 changes: 71 additions & 0 deletions rules/Php80/Rector/Class_/ConstantListClassToEnumRector.php
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit e6ebae3

Please sign in to comment.