[PHP 8.0] Add return type support to ConstantListClassToEnumRector (#2420)

* add return type support

* lock to last stable phpstan

* [ci-review] Rector Rectify

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2022-06-03 09:15:40 +02:00 committed by GitHub
parent e368dabaed
commit 7722a57d80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 122 additions and 43 deletions

View File

@ -7,7 +7,7 @@
],
"require": {
"php": "^7.2|^8.0",
"phpstan/phpstan": "^1.7.6"
"phpstan/phpstan": "1.7.8"
},
"autoload": {
"files": [

View File

@ -18,7 +18,7 @@
"nikic/php-parser": "^4.14.0",
"ondram/ci-detector": "^4.1",
"phpstan/phpdoc-parser": "^1.5.1",
"phpstan/phpstan": "^1.7.6",
"phpstan/phpstan": "1.7.8",
"phpstan/phpstan-phpunit": "^1.0",
"react/child-process": "^0.6.4",
"react/event-loop": "^1.3",

View File

@ -709,3 +709,6 @@ parameters:
- '#Register "Rector\\Php80\\Rector\\Class_\\ConstantListClassToEnumRector" service to "php80\.php" config set#'
- '#Rule Rector\\Php80\\Rector\\Class_\\ConstantListClassToEnumRector must implements Rector\\VersionBonding\\Contract\\MinPhpVersionInterface#'
- '#Register "Rector\\DowngradePhp80\\Rector\\Enum_\\DowngradeEnumToConstantListClassRector" service to "downgrade\-php80\.php" config set#'
-
message: '#Method "refactor(Params|Return)\(\)" returns bool type, so the name should start with is/has/was#'
path: rules/Php80/Rector/Class_/ConstantListClassToEnumRector.php

View File

@ -0,0 +1,34 @@
<?php
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;
final class ChangeReturnType
{
/**
* @return Gear::* $gear
*/
public function changeGear(): string
{
return Gear::FIRST;
}
}
?>
-----
<?php
namespace Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Fixture;
use Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear;
final class ChangeReturnType
{
public function changeGear(): \Rector\Tests\Php80\Rector\Class_\ConstantListClassToEnumRector\Source\Gear
{
return Gear::FIRST;
}
}
?>

View File

@ -6,9 +6,10 @@ namespace Rector\Php80\NodeAnalyzer;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\Php\PhpParameterReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
@ -24,33 +25,46 @@ final class EnumParamAnalyzer
) {
}
public function matchClassName(ParameterReflection $parameterReflection, PhpDocInfo $phpDocInfo): ?string
public function matchParameterClassName(ParameterReflection $parameterReflection, PhpDocInfo $phpDocInfo): ?string
{
if (! $parameterReflection instanceof PhpParameterReflection) {
return null;
}
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($parameterReflection->getName());
if (! $paramTagValueNode instanceof ParamTagValueNode) {
return null;
}
if (! $paramTagValueNode->type instanceof ConstTypeNode) {
$className = $this->resolveClassFromConstType($paramTagValueNode->type);
if ($className === null) {
return null;
}
$constTypeNode = $paramTagValueNode->type;
if (! $constTypeNode->constExpr instanceof ConstFetchNode) {
return null;
}
$constExpr = $constTypeNode->constExpr;
$className = $constExpr->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS);
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}
return $className;
}
public function matchReturnClassName(PhpDocInfo $phpDocInfo): ?string
{
$returnTagValueNode = $phpDocInfo->getReturnTagValue();
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
return null;
}
return $this->resolveClassFromConstType($returnTagValueNode->type);
}
private function resolveClassFromConstType(TypeNode $typeNode): ?string
{
if (! $typeNode instanceof ConstTypeNode) {
return null;
}
if (! $typeNode->constExpr instanceof ConstFetchNode) {
return null;
}
$constExpr = $typeNode->constExpr;
return $constExpr->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS);
}
}

View File

@ -10,6 +10,7 @@ use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
@ -88,10 +89,6 @@ CODE_SAMPLE
private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod
{
if ($classMethod->params === []) {
return null;
}
// enum param types doc requires a docblock
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);
if (! $phpDocInfo instanceof PhpDocInfo) {
@ -103,31 +100,15 @@ CODE_SAMPLE
return null;
}
$hasNodeChanged = false;
// refactor params
$haveParamsChanged = $this->refactorParams($methodReflection, $phpDocInfo, $classMethod);
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
foreach ($parametersAcceptor->getParameters() as $parameterReflection) {
$enumLikeClass = $this->enumParamAnalyzer->matchClassName($parameterReflection, $phpDocInfo);
if ($enumLikeClass === null) {
continue;
}
$param = $this->getParamByName($classMethod, $parameterReflection->getName());
if (! $param instanceof Param) {
continue;
}
// change and remove
$param->type = new FullyQualified($enumLikeClass);
$hasNodeChanged = true;
/** @var ParamTagValueNode $paramTagValueNode */
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($parameterReflection->getName());
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $paramTagValueNode);
$hasReturnChanged = $this->refactorReturn($phpDocInfo, $classMethod);
if ($haveParamsChanged) {
return $classMethod;
}
if ($hasNodeChanged) {
if ($hasReturnChanged) {
return $classMethod;
}
@ -146,4 +127,51 @@ CODE_SAMPLE
return null;
}
private function refactorParams(
MethodReflection $methodReflection,
PhpDocInfo $phpDocInfo,
ClassMethod $classMethod
): bool {
$hasNodeChanged = false;
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
foreach ($parametersAcceptor->getParameters() as $parameterReflection) {
$enumLikeClass = $this->enumParamAnalyzer->matchParameterClassName($parameterReflection, $phpDocInfo);
if ($enumLikeClass === null) {
continue;
}
$param = $this->getParamByName($classMethod, $parameterReflection->getName());
if (! $param instanceof Param) {
continue;
}
// change and remove
$param->type = new FullyQualified($enumLikeClass);
$hasNodeChanged = true;
/** @var ParamTagValueNode $paramTagValueNode */
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($parameterReflection->getName());
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $paramTagValueNode);
}
return $hasNodeChanged;
}
private function refactorReturn(PhpDocInfo $phpDocInfo, ClassMethod $classMethod): bool
{
$returnType = $this->enumParamAnalyzer->matchReturnClassName($phpDocInfo);
if ($returnType === null) {
return false;
}
$classMethod->returnType = new FullyQualified($returnType);
/** @var ReturnTagValueNode $returnTagValueNode */
$returnTagValueNode = $phpDocInfo->getReturnTagValue();
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $returnTagValueNode);
return true;
}
}