From 3fda3ced32fb44c3cccdb60f0c55a90163578772 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Tue, 13 Feb 2024 11:09:09 +0100 Subject: [PATCH] Add conversion of `Enum::hasValue()` to native enum --- CHANGELOG.md | 6 ++ composer.json | 4 +- src/Rector/ToNativeUsagesRector.php | 104 ++++++++++++++++++++------- tests/Rector/Usages/hasValue.php.inc | 17 +++++ 4 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 tests/Rector/Usages/hasValue.php.inc diff --git a/CHANGELOG.md b/CHANGELOG.md index 7584dade..6ef7b551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 6.9.0 + +### Added + +- Add conversion of `Enum::hasValue()` to native enum + ## 6.8.0 ### Changed diff --git a/composer.json b/composer.json index fa04ee7c..e8f13610 100644 --- a/composer.json +++ b/composer.json @@ -34,12 +34,12 @@ "require-dev": { "doctrine/dbal": "^3.4", "ergebnis/composer-normalize": "^2.28.3", + "larastan/larastan": "^2.6.3", "mll-lab/php-cs-fixer-config": "^5.4", "mockery/mockery": "^1.5", - "larastan/larastan": "^2.6.3", "orchestra/testbench": "^7.6.1 || ^8", - "phpstan/phpstan": "^1.8.2", "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^1.8.2", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-phpunit": "^1.1.1", "phpunit/phpunit": "^9.5.21 || ^10", diff --git a/src/Rector/ToNativeUsagesRector.php b/src/Rector/ToNativeUsagesRector.php index 1eec0dfb..91a709a3 100644 --- a/src/Rector/ToNativeUsagesRector.php +++ b/src/Rector/ToNativeUsagesRector.php @@ -26,6 +26,7 @@ use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\Cast\String_; use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Match_; use PhpParser\Node\Expr\MethodCall; @@ -183,6 +184,10 @@ public function refactor(Node $node): ?Node return $this->refactorGetRandomInstance($node); } + if ($this->isName($node->name, 'hasValue')) { + return $this->refactorHasValue($node); + } + return $this->refactorMaybeMagicStaticCall($node); } @@ -321,9 +326,9 @@ protected function refactorFromKey(StaticCall $call): ?Node } /** @see Enum::getInstances() */ - protected function refactorGetInstances(StaticCall $node): ?StaticCall + protected function refactorGetInstances(StaticCall $call): ?StaticCall { - $class = $node->class; + $class = $call->class; if ($class instanceof Name) { return new StaticCall($class, 'cases'); } @@ -332,11 +337,11 @@ protected function refactorGetInstances(StaticCall $node): ?StaticCall } /** @see Enum::getKeys() */ - protected function refactorGetKeys(StaticCall $node): ?Node + protected function refactorGetKeys(StaticCall $call): ?Node { - $class = $node->class; + $class = $call->class; if ($class instanceof Name) { - $args = $node->args; + $args = $call->args; if ($args === []) { $paramName = lcfirst($class->getLast()); $paramVariable = new Variable($paramName); @@ -364,11 +369,11 @@ protected function refactorGetKeys(StaticCall $node): ?Node } /** @see Enum::getValues() */ - protected function refactorGetValues(StaticCall $node): ?Node + protected function refactorGetValues(StaticCall $call): ?Node { - $class = $node->class; + $class = $call->class; if ($class instanceof Name) { - $args = $node->args; + $args = $call->args; if ($args === []) { $paramName = lcfirst($class->getLast()); $paramVariable = new Variable($paramName); @@ -395,27 +400,72 @@ protected function refactorGetValues(StaticCall $node): ?Node } /** @see Enum::getRandomInstance() */ - protected function refactorGetRandomInstance(StaticCall $staticCall): ?Node + protected function refactorGetRandomInstance(StaticCall $call): ?Node { return new MethodCall( new FuncCall(new Name('fake')), 'randomElement', - [new Arg(new StaticCall($staticCall->class, 'cases'))] + [new Arg(new StaticCall($call->class, 'cases'))] ); } + /** @see Enum::hasValue() */ + protected function refactorHasValue(StaticCall $call): ?Node + { + $class = $call->class; + if ($class instanceof Name) { + $makeTryFromNotNull = function (Arg $arg) use ($class): NotIdentical { + $tryFrom = new StaticCall( + $class, + 'tryFrom', + [$arg] + ); + $null = new ConstFetch(new Name('null')); + + return new NotIdentical($tryFrom, $null); + }; + + if ($call->isFirstClassCallable()) { + $valueVariable = new Variable('value'); + + return new ArrowFunction([ + 'static' => true, + 'params' => [new Param($valueVariable, null, 'mixed')], + 'returnType' => 'bool', + 'expr' => $makeTryFromNotNull(new Arg($valueVariable)), + ]); + } + + $args = $call->args; + $firstArg = $args[0] ?? null; + if ($firstArg instanceof Arg) { + $firstArgValue = $firstArg->value; + if ( + $firstArgValue instanceof ClassConstFetch + && $firstArgValue->class->toString() === $class->toString() + ) { + return new ConstFetch(new Name('true')); + } + + return $makeTryFromNotNull($firstArg); + } + } + + return null; + } + /** * @see Enum::__callStatic() * @see Enum::__call() */ - protected function refactorMaybeMagicStaticCall(StaticCall $node): ?Node + protected function refactorMaybeMagicStaticCall(StaticCall $call): ?Node { - $name = $node->name; + $name = $call->name; if ($name instanceof Expr) { return null; } - $class = $node->class; + $class = $call->class; if ($class instanceof Name) { if ($class->isSpecialClassName()) { $type = $this->getType($class); @@ -473,11 +523,11 @@ protected function refactorIsOrIsNot(MethodCall|NullsafeMethodCall $call, bool $ * @see Enum::in() * @see Enum::notIn() */ - protected function refactorInOrNotIn(MethodCall|NullsafeMethodCall $node, bool $in): ?Node + protected function refactorInOrNotIn(MethodCall|NullsafeMethodCall $call, bool $in): ?Node { - $args = $node->args; + $args = $call->args; if (isset($args[0]) && $args[0] instanceof Arg) { - $needle = new Arg($node->var); + $needle = new Arg($call->var); $haystack = $args[0]; $haystackValue = $haystack->value; @@ -502,17 +552,17 @@ protected function refactorInOrNotIn(MethodCall|NullsafeMethodCall $node, bool $ } /** @see Enum::__toString() */ - protected function refactorMagicToString(MethodCall|NullsafeMethodCall $node): Cast + protected function refactorMagicToString(MethodCall|NullsafeMethodCall $call): Cast { return new String_( - $this->createValueFetch($node->var, $node instanceof NullsafeMethodCall) + $this->createValueFetch($call->var, $call instanceof NullsafeMethodCall) ); } /** @see Enum::$key */ - protected function refactorKey(PropertyFetch $node): ?Node + protected function refactorKey(PropertyFetch $fetch): ?Node { - return new PropertyFetch($node->var, 'name'); + return new PropertyFetch($fetch->var, 'name'); } protected function refactorMatch(Match_ $match): ?Node @@ -611,13 +661,13 @@ protected function refactorSwitch(Switch_ $switch): ?Node return new Switch_($cond, $cases, $switch->getAttributes()); } - protected function refactorArrayItem(ArrayItem $node): ?Node + protected function refactorArrayItem(ArrayItem $arrayItem): ?Node { - $key = $node->key; + $key = $arrayItem->key; $convertedKey = $this->convertConstToValueFetch($key); - $value = $node->value; - $hasAttribute = $node->hasAttribute(self::COMPARED_AGAINST_ENUM_INSTANCE); + $value = $arrayItem->value; + $hasAttribute = $arrayItem->hasAttribute(self::COMPARED_AGAINST_ENUM_INSTANCE); $convertedValue = $hasAttribute ? null : $this->convertConstToValueFetch($value); @@ -626,9 +676,9 @@ protected function refactorArrayItem(ArrayItem $node): ?Node return new ArrayItem( $convertedValue ?? $value, $convertedKey ?? $key, - $node->byRef, - $node->getAttributes(), - $node->unpack, + $arrayItem->byRef, + $arrayItem->getAttributes(), + $arrayItem->unpack, ); } diff --git a/tests/Rector/Usages/hasValue.php.inc b/tests/Rector/Usages/hasValue.php.inc new file mode 100644 index 00000000..357a3442 --- /dev/null +++ b/tests/Rector/Usages/hasValue.php.inc @@ -0,0 +1,17 @@ + UserType::tryFrom($value) !== null;