[Downgrade] Add class method param to DowngradeEnumToConstantListClassRector (#2417)

* add param enum downgrade

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2022-06-02 15:08:06 +02:00 committed by GitHub
parent 340f5b999c
commit e368dabaed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 332 additions and 16 deletions

View File

@ -1,4 +1,4 @@
# 513 Rules Overview
# 516 Rules Overview
<br>
@ -38,10 +38,12 @@
- [DowngradePhp74](#downgradephp74) (12)
- [DowngradePhp80](#downgradephp80) (28)
- [DowngradePhp80](#downgradephp80) (29)
- [DowngradePhp81](#downgradephp81) (9)
- [DowngradePhp82](#downgradephp82) (1)
- [EarlyReturn](#earlyreturn) (11)
- [MysqlToMysqli](#mysqltomysqli) (4)
@ -70,7 +72,7 @@
- [Php74](#php74) (14)
- [Php80](#php80) (17)
- [Php80](#php80) (18)
- [Php81](#php81) (9)
@ -2192,13 +2194,12 @@ Changes `$this->...` and static:: to self:: or vise versa for given types
```php
use PHPUnit\Framework\TestCase;
use Rector\CodingStyle\Enum\PreferenceSelfThis;
use Rector\CodingStyle\Rector\MethodCall\PreferThisOrSelfMethodCallRector;
use Rector\Config\RectorConfig;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->ruleWithConfiguration(PreferThisOrSelfMethodCallRector::class, [
TestCase::class => PreferenceSelfThis::PREFER_SELF(),
TestCase::class => 'prefer_self',
]);
};
```
@ -5413,6 +5414,26 @@ Add parentheses around non-dereferenceable expressions.
<br>
### DowngradeEnumToConstantListClassRector
Downgrade enum to constant list class
- class: [`Rector\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector`](../rules/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector.php)
```diff
-enum Direction
+class Direction
{
- case LEFT;
+ public const LEFT = 'left';
- case RIGHT;
+ public const RIGHT = 'right';
}
```
<br>
### DowngradeMatchToSwitchRector
Downgrade `match()` to `switch()`
@ -6050,6 +6071,30 @@ Remove "readonly" property type, add a "@readonly" tag instead
<br>
## DowngradePhp82
### DowngradeReadonlyClassRector
Remove "readonly" class type, decorate all properties to "readonly"
- class: [`Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector`](../rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php)
```diff
-final readonly class SomeClass
+final class SomeClass
{
- public string $foo;
+ public readonly string $foo;
public function __construct()
{
$this->foo = 'foo';
}
}
```
<br>
## EarlyReturn
### ChangeAndIfToEarlyReturnRector
@ -8178,6 +8223,26 @@ Change simple property init and assign to constructor promotion
<br>
### ConstantListClassToEnumRector
Upgrade constant list classes to full blown enum
- class: [`Rector\Php80\Rector\Class_\ConstantListClassToEnumRector`](../rules/Php80/Rector/Class_/ConstantListClassToEnumRector.php)
```diff
-class Direction
+enum Direction
{
- public const LEFT = 'left';
+ case LEFT;
- public const RIGHT = 'right';
+ case RIGHT;
}
```
<br>
### DoctrineAnnotationClassToAttributeRector
Refactor Doctrine `@annotation` annotated class to a PHP 8.0 attribute class

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Fixture;
use Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source\AnythingYouWant;
final class NoTypeParamMethod
{
public function with(AnythingYouWant $anythingYouWant)
{
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Fixture;
use Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source\AnythingYouWant;
final class NoTypeParamMethod
{
/**
* @param \Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source\AnythingYouWant::* $anythingYouWant
*/
public function with($anythingYouWant)
{
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Fixture;
use Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source\GearValue;
final class ParamMethod
{
public function changeGear(GearValue $gearValue)
{
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Fixture;
use Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source\GearValue;
final class ParamMethod
{
/**
* @param \Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source\GearValue::* $gearValue
*/
public function changeGear(string $gearValue)
{
}
}
?>

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source;
enum AnythingYouWant
{
const LEFT = 'left';
const TWO = 5;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\DowngradePhp80\Rector\Enum_\DowngradeEnumToConstantListClassRector\Source;
enum GearValue
{
case FIRST;
case SECOND;
}

View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp80\NodeAnalyzer;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\EnumCase;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\AstResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class EnumAnalyzer
{
public function __construct(
private readonly AstResolver $astResolver,
private readonly NodeTypeResolver $nodeTypeResolver
) {
}
public function resolveType(ClassReflection $classReflection): ?Identifier
{
$class = $this->astResolver->resolveClassFromClassReflection($classReflection, $classReflection->getName());
if (! $class instanceof Enum_) {
throw new ShouldNotHappenException();
}
$scalarType = $class->scalarType;
if ($scalarType instanceof Identifier) {
// can be only int or string
return $scalarType;
}
$enumExprTypes = $this->resolveEnumExprTypes($class);
$enumExprTypeClasses = [];
foreach ($enumExprTypes as $enumExprType) {
$enumExprTypeClasses[] = $enumExprType::class;
}
$uniqueEnumExprTypeClasses = array_unique($enumExprTypeClasses);
if (count($uniqueEnumExprTypeClasses) === 1) {
$uniqueEnumExprTypeClass = $uniqueEnumExprTypeClasses[0];
if (is_a($uniqueEnumExprTypeClass, StringType::class, true)) {
return new Identifier('string');
}
if (is_a($uniqueEnumExprTypeClass, IntegerType::class, true)) {
return new Identifier('int');
}
if (is_a($uniqueEnumExprTypeClass, FloatType::class, true)) {
return new Identifier('float');
}
}
// unknown or multiple types
return null;
}
/**
* @return Type[]
*/
private function resolveEnumExprTypes(Enum_ $enum): array
{
$enumExprTypes = [];
foreach ($enum->stmts as $classStmt) {
if (! $classStmt instanceof EnumCase) {
continue;
}
$enumExprTypes[] = $this->resolveEnumCaseType($classStmt);
}
return $enumExprTypes;
}
private function resolveEnumCaseType(EnumCase $enumCase): Type
{
$classExpr = $enumCase->expr;
if ($classExpr instanceof Expr) {
return $this->nodeTypeResolver->getType($classExpr);
}
// in case of no value, fallback to string type
return new StringType();
}
}

View File

@ -5,9 +5,20 @@ declare(strict_types=1);
namespace Rector\DowngradePhp80\Rector\Enum_;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Rector\AbstractRector;
use Rector\DowngradePhp80\NodeAnalyzer\EnumAnalyzer;
use Rector\Php81\NodeFactory\ClassFromEnumFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@ -18,7 +29,9 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class DowngradeEnumToConstantListClassRector extends AbstractRector
{
public function __construct(
private readonly ClassFromEnumFactory $classFromEnumFactory
private readonly ClassFromEnumFactory $classFromEnumFactory,
private readonly ReflectionProvider $reflectionProvider,
private readonly EnumAnalyzer $enumAnalyzer,
) {
}
@ -53,14 +66,67 @@ CODE_SAMPLE
*/
public function getNodeTypes(): array
{
return [Enum_::class];
return [Enum_::class, ClassMethod::class];
}
/**
* @param Enum_ $node
* @param Enum_|ClassMethod $node
*/
public function refactor(Node $node): Class_
public function refactor(Node $node): Class_|ClassMethod|null
{
return $this->classFromEnumFactory->createFromEnum($node);
if ($node instanceof Enum_) {
return $this->classFromEnumFactory->createFromEnum($node);
}
$hasChanged = false;
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
foreach ($node->params as $param) {
if (! $param->type instanceof Name) {
continue;
}
// is enum type?
$typeName = $this->getName($param->type);
if (! $this->reflectionProvider->hasClass($typeName)) {
continue;
}
$classLikeReflection = $this->reflectionProvider->getClass($typeName);
if (! $classLikeReflection->isEnum()) {
continue;
}
$param->type = $this->resolveParamType($classLikeReflection);
$hasChanged = true;
$this->decorateParamDocType($classLikeReflection, $param, $phpDocInfo);
}
if ($hasChanged) {
return $node;
}
return null;
}
public function resolveParamType(ClassReflection $classReflection): ?Identifier
{
return $this->enumAnalyzer->resolveType($classReflection);
}
private function decorateParamDocType(
ClassReflection $classReflection,
Param $param,
PhpDocInfo $phpDocInfo
): void {
$constFetchNode = new ConstFetchNode('\\' . $classReflection->getName(), '*');
$constTypeNode = new ConstTypeNode($constFetchNode);
$paramName = '$' . $this->getName($param);
$paramTagValueNode = new ParamTagValueNode($constTypeNode, false, $paramName, '');
$phpDocInfo->addTagValueNode($paramTagValueNode);
}
}

View File

@ -51,7 +51,7 @@ final readonly class SomeClass
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final readonly class SomeClass
final class SomeClass
{
public readonly string $foo;

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\Core\NodeAnalyzer;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
@ -27,7 +28,7 @@ final class EnumAnalyzer
return false;
}
if ($class->extends === null) {
if (! $class->extends instanceof Name) {
return false;
}

View File

@ -20,7 +20,7 @@ final class ClassLikeAstResolver
* Parsing files is very heavy performance, so this will help to leverage it
* The value can be also null, as the method might not exist in the class.
*
* @var array<class-string, Class_|Trait_|Interface_|null>
* @var array<class-string, Class_|Trait_|Interface_|Enum_|null>
*/
private array $classLikesByName = [];
@ -32,7 +32,7 @@ final class ClassLikeAstResolver
public function resolveClassFromClassReflection(
ClassReflection $classReflection,
string $className
string $desiredClassName
): Trait_ | Class_ | Interface_ | Enum_ | null {
if ($classReflection->isBuiltin()) {
return null;
@ -58,12 +58,12 @@ final class ClassLikeAstResolver
return null;
}
/** @var array<Class_|Trait_|Interface_> $classLikes */
/** @var array<Class_|Trait_|Interface_|Enum_> $classLikes */
$classLikes = $this->betterNodeFinder->findInstanceOf($stmts, ClassLike::class);
$reflectionClassName = $classReflection->getName();
foreach ($classLikes as $classLike) {
if ($reflectionClassName !== $className) {
if ($reflectionClassName !== $desiredClassName) {
continue;
}