mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-22 19:05:10 +00:00
7e7a2f067a
476cfb00cb
disable fallback for now
265 lines
9.0 KiB
PHP
265 lines
9.0 KiB
PHP
<?php
|
|
|
|
declare (strict_types=1);
|
|
namespace Rector\Php81\Rector\MethodCall;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Arg;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Expr\BinaryOp\Identical;
|
|
use PhpParser\Node\Expr\ClassConstFetch;
|
|
use PhpParser\Node\Expr\MethodCall;
|
|
use PhpParser\Node\Expr\PropertyFetch;
|
|
use PhpParser\Node\Expr\StaticCall;
|
|
use PhpParser\Node\Expr\Variable;
|
|
use PHPStan\Reflection\ReflectionProvider;
|
|
use PHPStan\Type\ObjectType;
|
|
use Rector\Rector\AbstractRector;
|
|
use Rector\ValueObject\PhpVersionFeature;
|
|
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
|
/**
|
|
* @changelog https://wiki.php.net/rfc/enumerations
|
|
* @changelog https://github.com/myclabs/php-enum
|
|
*
|
|
* @see \Rector\Tests\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector\MyCLabsMethodCallToEnumConstRectorTest
|
|
*/
|
|
final class MyCLabsMethodCallToEnumConstRector extends AbstractRector implements MinPhpVersionInterface
|
|
{
|
|
/**
|
|
* @readonly
|
|
* @var \PHPStan\Reflection\ReflectionProvider
|
|
*/
|
|
private $reflectionProvider;
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
private const ENUM_METHODS = ['from', 'values', 'keys', 'isValid', 'search', 'toArray', 'assertValidValue'];
|
|
public function __construct(ReflectionProvider $reflectionProvider)
|
|
{
|
|
$this->reflectionProvider = $reflectionProvider;
|
|
}
|
|
public function getRuleDefinition() : RuleDefinition
|
|
{
|
|
return new RuleDefinition('Refactor MyCLabs enum fetch to Enum const', [new CodeSample(<<<'CODE_SAMPLE'
|
|
$name = SomeEnum::VALUE()->getKey();
|
|
CODE_SAMPLE
|
|
, <<<'CODE_SAMPLE'
|
|
$name = SomeEnum::VALUE;
|
|
CODE_SAMPLE
|
|
)]);
|
|
}
|
|
/**
|
|
* @return array<class-string<Node>>
|
|
*/
|
|
public function getNodeTypes() : array
|
|
{
|
|
return [MethodCall::class, StaticCall::class];
|
|
}
|
|
/**
|
|
* @param MethodCall|StaticCall $node
|
|
*/
|
|
public function refactor(Node $node) : ?Node
|
|
{
|
|
if ($node->name instanceof Expr) {
|
|
return null;
|
|
}
|
|
$enumCaseName = $this->getName($node->name);
|
|
if ($enumCaseName === null) {
|
|
return null;
|
|
}
|
|
if ($this->shouldOmitEnumCase($enumCaseName)) {
|
|
return null;
|
|
}
|
|
if ($node instanceof MethodCall) {
|
|
return $this->refactorMethodCall($node, $enumCaseName);
|
|
}
|
|
if (!$this->isObjectType($node->class, new ObjectType('MyCLabs\\Enum\\Enum'))) {
|
|
return null;
|
|
}
|
|
$className = $this->getName($node->class);
|
|
if (!\is_string($className)) {
|
|
return null;
|
|
}
|
|
if (!$this->isEnumConstant($className, $enumCaseName)) {
|
|
return null;
|
|
}
|
|
return $this->nodeFactory->createClassConstFetch($className, $enumCaseName);
|
|
}
|
|
public function provideMinPhpVersion() : int
|
|
{
|
|
return PhpVersionFeature::ENUM;
|
|
}
|
|
private function isEnumConstant(string $className, string $constant) : bool
|
|
{
|
|
$classReflection = $this->reflectionProvider->getClass($className);
|
|
return $classReflection->hasConstant($constant);
|
|
}
|
|
private function refactorGetKeyMethodCall(MethodCall $methodCall) : ?ClassConstFetch
|
|
{
|
|
if (!$methodCall->var instanceof StaticCall) {
|
|
return null;
|
|
}
|
|
$staticCall = $methodCall->var;
|
|
$className = $this->getName($staticCall->class);
|
|
if ($className === null) {
|
|
return null;
|
|
}
|
|
$enumCaseName = $this->getName($staticCall->name);
|
|
if ($enumCaseName === null) {
|
|
return null;
|
|
}
|
|
if ($this->shouldOmitEnumCase($enumCaseName)) {
|
|
return null;
|
|
}
|
|
return $this->nodeFactory->createClassConstFetch($className, $enumCaseName);
|
|
}
|
|
private function refactorGetValueMethodCall(MethodCall $methodCall) : ?PropertyFetch
|
|
{
|
|
if (!$methodCall->var instanceof StaticCall) {
|
|
return null;
|
|
}
|
|
$staticCall = $methodCall->var;
|
|
$className = $this->getName($staticCall->class);
|
|
if ($className === null) {
|
|
return null;
|
|
}
|
|
$enumCaseName = $this->getName($staticCall->name);
|
|
if ($enumCaseName === null) {
|
|
return null;
|
|
}
|
|
if ($this->shouldOmitEnumCase($enumCaseName)) {
|
|
return null;
|
|
}
|
|
$enumConstFetch = $this->nodeFactory->createClassConstFetch($className, $enumCaseName);
|
|
return new PropertyFetch($enumConstFetch, 'value');
|
|
}
|
|
private function refactorEqualsMethodCall(MethodCall $methodCall) : ?Identical
|
|
{
|
|
$expr = $this->getNonEnumReturnTypeExpr($methodCall->var);
|
|
if (!$expr instanceof Expr) {
|
|
$expr = $this->getValidEnumExpr($methodCall->var);
|
|
if (!$expr instanceof Expr) {
|
|
return null;
|
|
}
|
|
}
|
|
$arg = $methodCall->getArgs()[0] ?? null;
|
|
if (!$arg instanceof Arg) {
|
|
return null;
|
|
}
|
|
$right = $this->getNonEnumReturnTypeExpr($arg->value);
|
|
if (!$right instanceof Expr) {
|
|
$right = $this->getValidEnumExpr($arg->value);
|
|
if (!$right instanceof Expr) {
|
|
return null;
|
|
}
|
|
}
|
|
return new Identical($expr, $right);
|
|
}
|
|
/**
|
|
* @param \PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\MethodCall $node
|
|
*/
|
|
private function isCallerClassEnum($node) : bool
|
|
{
|
|
if ($node instanceof StaticCall) {
|
|
return $this->isObjectType($node->class, new ObjectType('MyCLabs\\Enum\\Enum'));
|
|
}
|
|
return $this->isObjectType($node->var, new ObjectType('MyCLabs\\Enum\\Enum'));
|
|
}
|
|
/**
|
|
* @return null|\PhpParser\Node\Expr\ClassConstFetch|\PhpParser\Node\Expr
|
|
*/
|
|
private function getNonEnumReturnTypeExpr(Node $node)
|
|
{
|
|
if (!$node instanceof StaticCall && !$node instanceof MethodCall) {
|
|
return null;
|
|
}
|
|
if ($this->isCallerClassEnum($node)) {
|
|
$methodName = $this->getName($node->name);
|
|
if ($methodName === null) {
|
|
return null;
|
|
}
|
|
if ($node instanceof StaticCall) {
|
|
$className = $this->getName($node->class);
|
|
}
|
|
if ($node instanceof MethodCall) {
|
|
$className = $this->getName($node->var);
|
|
}
|
|
if ($className === null) {
|
|
return null;
|
|
}
|
|
$classReflection = $this->reflectionProvider->getClass($className);
|
|
// method self::getValidEnumExpr process enum static methods from constants
|
|
if ($classReflection->hasConstant($methodName)) {
|
|
return null;
|
|
}
|
|
}
|
|
return $node;
|
|
}
|
|
/**
|
|
* @return null|\PhpParser\Node\Expr\ClassConstFetch|\PhpParser\Node\Expr
|
|
*/
|
|
private function getValidEnumExpr(Node $node)
|
|
{
|
|
switch (\get_class($node)) {
|
|
case Variable::class:
|
|
case PropertyFetch::class:
|
|
return $this->getPropertyFetchOrVariable($node);
|
|
case StaticCall::class:
|
|
return $this->getEnumConstFetch($node);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\Variable $expr
|
|
* @return null|\PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\Variable
|
|
*/
|
|
private function getPropertyFetchOrVariable($expr)
|
|
{
|
|
if (!$this->isObjectType($expr, new ObjectType('MyCLabs\\Enum\\Enum'))) {
|
|
return null;
|
|
}
|
|
return $expr;
|
|
}
|
|
private function getEnumConstFetch(StaticCall $staticCall) : ?\PhpParser\Node\Expr\ClassConstFetch
|
|
{
|
|
$className = $this->getName($staticCall->class);
|
|
if ($className === null) {
|
|
return null;
|
|
}
|
|
$enumCaseName = $this->getName($staticCall->name);
|
|
if ($enumCaseName === null) {
|
|
return null;
|
|
}
|
|
if ($this->shouldOmitEnumCase($enumCaseName)) {
|
|
return null;
|
|
}
|
|
return $this->nodeFactory->createClassConstFetch($className, $enumCaseName);
|
|
}
|
|
/**
|
|
* @return null|\PhpParser\Node\Expr\ClassConstFetch|\PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\BinaryOp\Identical
|
|
*/
|
|
private function refactorMethodCall(MethodCall $methodCall, string $methodName)
|
|
{
|
|
if (!$this->isObjectType($methodCall->var, new ObjectType('MyCLabs\\Enum\\Enum'))) {
|
|
return null;
|
|
}
|
|
if ($methodName === 'getKey') {
|
|
return $this->refactorGetKeyMethodCall($methodCall);
|
|
}
|
|
if ($methodName === 'getValue') {
|
|
return $this->refactorGetValueMethodCall($methodCall);
|
|
}
|
|
if ($methodName === 'equals') {
|
|
return $this->refactorEqualsMethodCall($methodCall);
|
|
}
|
|
return null;
|
|
}
|
|
private function shouldOmitEnumCase(string $enumCaseName) : bool
|
|
{
|
|
return \in_array($enumCaseName, self::ENUM_METHODS, \true);
|
|
}
|
|
}
|