From e368dabaed1f3246962720aaa5236653823bba2b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 2 Jun 2022 15:08:06 +0200 Subject: [PATCH] [Downgrade] Add class method param to DowngradeEnumToConstantListClassRector (#2417) * add param enum downgrade * [ci-review] Rector Rectify Co-authored-by: GitHub Action --- .../docs/rector_rules_overview.md | 75 +++++++++++++- .../Fixture/no_type_param_method.php.inc | 32 ++++++ .../Fixture/param_method.php.inc | 32 ++++++ .../Source/AnythingYouWant.php | 12 +++ .../Source/GearValue.php | 11 +++ .../NodeAnalyzer/EnumAnalyzer.php | 97 +++++++++++++++++++ ...DowngradeEnumToConstantListClassRector.php | 76 ++++++++++++++- .../Class_/DowngradeReadonlyClassRector.php | 2 +- src/NodeAnalyzer/EnumAnalyzer.php | 3 +- src/PhpParser/ClassLikeAstResolver.php | 8 +- 10 files changed, 332 insertions(+), 16 deletions(-) create mode 100644 rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/no_type_param_method.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/param_method.php.inc create mode 100644 rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Source/AnythingYouWant.php create mode 100644 rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Source/GearValue.php create mode 100644 rules/DowngradePhp80/NodeAnalyzer/EnumAnalyzer.php diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md index ae21ee1add6..93ff14c6288 100644 --- a/build/target-repository/docs/rector_rules_overview.md +++ b/build/target-repository/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 513 Rules Overview +# 516 Rules Overview
@@ -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.
+### 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'; + } +``` + +
+ ### DowngradeMatchToSwitchRector Downgrade `match()` to `switch()` @@ -6050,6 +6071,30 @@ Remove "readonly" property type, add a "@readonly" tag instead
+## 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'; + } + } +``` + +
+ ## EarlyReturn ### ChangeAndIfToEarlyReturnRector @@ -8178,6 +8223,26 @@ Change simple property init and assign to constructor promotion
+### 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; + } +``` + +
+ ### DoctrineAnnotationClassToAttributeRector Refactor Doctrine `@annotation` annotated class to a PHP 8.0 attribute class diff --git a/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/no_type_param_method.php.inc b/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/no_type_param_method.php.inc new file mode 100644 index 00000000000..dfda570a5f4 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/no_type_param_method.php.inc @@ -0,0 +1,32 @@ + +----- + diff --git a/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/param_method.php.inc b/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/param_method.php.inc new file mode 100644 index 00000000000..b58cf027563 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Fixture/param_method.php.inc @@ -0,0 +1,32 @@ + +----- + diff --git a/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Source/AnythingYouWant.php b/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Source/AnythingYouWant.php new file mode 100644 index 00000000000..211b57efab0 --- /dev/null +++ b/rules-tests/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector/Source/AnythingYouWant.php @@ -0,0 +1,12 @@ +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(); + } +} diff --git a/rules/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector.php b/rules/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector.php index 7d162e27e63..22a8db656d8 100644 --- a/rules/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector.php +++ b/rules/DowngradePhp80/Rector/Enum_/DowngradeEnumToConstantListClassRector.php @@ -5,9 +5,20 @@ 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 @@ 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 @@ class Direction */ 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); } } diff --git a/rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php b/rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php index 7a26056c2bf..b9dc6116fe9 100644 --- a/rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php +++ b/rules/DowngradePhp82/Rector/Class_/DowngradeReadonlyClassRector.php @@ -51,7 +51,7 @@ public function __construct() CODE_SAMPLE , <<<'CODE_SAMPLE' -final readonly class SomeClass +final class SomeClass { public readonly string $foo; diff --git a/src/NodeAnalyzer/EnumAnalyzer.php b/src/NodeAnalyzer/EnumAnalyzer.php index c5a512a453f..43d857cbd7f 100644 --- a/src/NodeAnalyzer/EnumAnalyzer.php +++ b/src/NodeAnalyzer/EnumAnalyzer.php @@ -4,6 +4,7 @@ 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 @@ public function isEnumClassConst(ClassConst $classConst): bool return false; } - if ($class->extends === null) { + if (! $class->extends instanceof Name) { return false; } diff --git a/src/PhpParser/ClassLikeAstResolver.php b/src/PhpParser/ClassLikeAstResolver.php index 63fda2002c1..0ce9c75335a 100644 --- a/src/PhpParser/ClassLikeAstResolver.php +++ b/src/PhpParser/ClassLikeAstResolver.php @@ -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 + * @var array */ private array $classLikesByName = []; @@ -32,7 +32,7 @@ public function __construct( public function resolveClassFromClassReflection( ClassReflection $classReflection, - string $className + string $desiredClassName ): Trait_ | Class_ | Interface_ | Enum_ | null { if ($classReflection->isBuiltin()) { return null; @@ -58,12 +58,12 @@ public function resolveClassFromClassReflection( return null; } - /** @var array $classLikes */ + /** @var array $classLikes */ $classLikes = $this->betterNodeFinder->findInstanceOf($stmts, ClassLike::class); $reflectionClassName = $classReflection->getName(); foreach ($classLikes as $classLike) { - if ($reflectionClassName !== $className) { + if ($reflectionClassName !== $desiredClassName) { continue; }