From facd826e65fe44b3c0a4f9c726c2433cf8a4a9ea Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 21 Aug 2024 11:44:14 +0200 Subject: [PATCH] Fix conversion of `in()` and `notIn()` to native enums when called with non-arrays --- CHANGELOG.md | 6 ++++ phpstan.neon | 1 + src/Rector/ToNativeUsagesRector.php | 36 +++++++++++++++---- tests/EnumAnnotateCommandTest.php | 4 +++ tests/PHPStan/UniqueValuesRuleTest.php | 5 +++ .../ToNativeImplementationRectorTest.php | 2 ++ tests/Rector/ToNativeUsagesRectorTest.php | 2 ++ tests/Rector/Usages/in.php.inc | 8 +++++ tests/Rector/Usages/notIn.php.inc | 8 +++++ 9 files changed, 65 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d9206e..a5555a5e 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.11.1 + +### Fixed + +- Fix conversion of `in()` and `notIn()` to native enums when called with non-arrays + ## 6.11.0 ### Added diff --git a/phpstan.neon b/phpstan.neon index 4c2fedfb..8dc29ac3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,6 +14,7 @@ parameters: - '#unknown class Illuminate\\Support\\Facades\\Process#' - '#unknown class Illuminate\\Process#' - '#invalid type Illuminate\\Process#' + - '#^Attribute class PHPUnit\\Framework\\Attributes\\DataProvider does not exist\.$#' # Only available with newer PHPUnit versions excludePaths: - tests/Enums/ToNativeFixtures # Fails with PHP < 8.1 - tests/PHPStan/Fixtures diff --git a/src/Rector/ToNativeUsagesRector.php b/src/Rector/ToNativeUsagesRector.php index 42245085..58e2d122 100644 --- a/src/Rector/ToNativeUsagesRector.php +++ b/src/Rector/ToNativeUsagesRector.php @@ -5,6 +5,7 @@ use BenSampo\Enum\Enum; use BenSampo\Enum\Tests\Enums\UserType; use Illuminate\Support\Arr; +use Illuminate\Support\Enumerable; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr; @@ -565,7 +566,9 @@ protected function refactorMaybeMagicStaticCall(StaticCall $call): ?Node */ protected function refactorIsOrIsNot(MethodCall|NullsafeMethodCall $call, bool $is): ?Node { - $comparison = $is ? Identical::class : NotIdentical::class; + $comparison = $is + ? Identical::class + : NotIdentical::class; if ($call->isFirstClassCallable()) { $param = new Variable('value'); @@ -601,19 +604,38 @@ protected function refactorInOrNotIn(MethodCall|NullsafeMethodCall $call, bool $ { $args = $call->args; if (isset($args[0]) && $args[0] instanceof Arg) { - $needle = new Arg($call->var); - $haystack = $args[0]; + $enumArg = new Arg($call->var); + $valuesArg = $args[0]; - $haystackValue = $haystack->value; - if ($haystackValue instanceof Array_) { - foreach ($haystackValue->items as $item) { + $valuesValue = $valuesArg->value; + if ($valuesValue instanceof Array_) { + foreach ($valuesValue->items as $item) { $item->setAttribute(self::COMPARED_AGAINST_ENUM_INSTANCE, true); } } + if ($this->isObjectType($valuesValue, new ObjectType(Enumerable::class))) { + return new MethodCall( + $valuesValue, + new Identifier($in + ? 'contains' + : 'doesntContain'), + [$enumArg], + ); + } + + $haystackArg = $this->getType($valuesValue)->isArray()->yes() + ? $valuesArg + : new Arg( + new FuncCall( + new Name('iterator_to_array'), + [$valuesArg], + ), + ); + $inArray = new FuncCall( new Name('in_array'), - [$needle, $haystack], + [$enumArg, $haystackArg], [self::COMPARED_AGAINST_ENUM_INSTANCE => true], ); diff --git a/tests/EnumAnnotateCommandTest.php b/tests/EnumAnnotateCommandTest.php index 643b2b4e..fb89e04d 100644 --- a/tests/EnumAnnotateCommandTest.php +++ b/tests/EnumAnnotateCommandTest.php @@ -3,10 +3,12 @@ namespace BenSampo\Enum\Tests; use Illuminate\Filesystem\Filesystem; +use PHPUnit\Framework\Attributes\DataProvider; final class EnumAnnotateCommandTest extends ApplicationTestCase { /** @dataProvider classes */ + #[DataProvider('classes')] public function testAnnotateClass(string $class): void { $filesystem = $this->filesystem(); @@ -19,6 +21,7 @@ public function testAnnotateClass(string $class): void } /** @dataProvider classes */ + #[DataProvider('classes')] public function testAnnotateClassAlreadyAnnotated(string $class): void { $filesystem = $this->filesystem(); @@ -48,6 +51,7 @@ public static function sources(): iterable } /** @dataProvider sources */ + #[DataProvider('sources')] public function testAnnotateFolder(string $source): void { $filesystem = $this->filesystem(); diff --git a/tests/PHPStan/UniqueValuesRuleTest.php b/tests/PHPStan/UniqueValuesRuleTest.php index 228e865b..b7499e87 100644 --- a/tests/PHPStan/UniqueValuesRuleTest.php +++ b/tests/PHPStan/UniqueValuesRuleTest.php @@ -28,4 +28,9 @@ public function testRule(): void ], ); } + + protected function shouldFailOnPhpErrors(): bool + { + return false; + } } diff --git a/tests/Rector/ToNativeImplementationRectorTest.php b/tests/Rector/ToNativeImplementationRectorTest.php index 45982086..942bd6ad 100644 --- a/tests/Rector/ToNativeImplementationRectorTest.php +++ b/tests/Rector/ToNativeImplementationRectorTest.php @@ -2,12 +2,14 @@ namespace BenSampo\Enum\Tests\Rector; +use PHPUnit\Framework\Attributes\DataProvider; use Rector\Testing\PHPUnit\AbstractRectorTestCase; /** @see \BenSampo\Enum\Rector\ToNativeImplementationRector */ final class ToNativeImplementationRectorTest extends AbstractRectorTestCase { /** @dataProvider provideData */ + #[DataProvider('provideData')] public function test(string $filePath): void { $this->doTestFile($filePath); diff --git a/tests/Rector/ToNativeUsagesRectorTest.php b/tests/Rector/ToNativeUsagesRectorTest.php index 325f4635..fdfda94f 100644 --- a/tests/Rector/ToNativeUsagesRectorTest.php +++ b/tests/Rector/ToNativeUsagesRectorTest.php @@ -2,12 +2,14 @@ namespace BenSampo\Enum\Tests\Rector; +use PHPUnit\Framework\Attributes\DataProvider; use Rector\Testing\PHPUnit\AbstractRectorTestCase; /** @see \BenSampo\Enum\Rector\ToNativeUsagesRector */ final class ToNativeUsagesRectorTest extends AbstractRectorTestCase { /** @dataProvider provideData */ + #[DataProvider('provideData')] public function test(string $filePath): void { $this->doTestFile($filePath); diff --git a/tests/Rector/Usages/in.php.inc b/tests/Rector/Usages/in.php.inc index e3313ac5..dd19999a 100644 --- a/tests/Rector/Usages/in.php.inc +++ b/tests/Rector/Usages/in.php.inc @@ -5,6 +5,10 @@ use BenSampo\Enum\Tests\Enums\UserType; /** @var UserType $userType */ $userType->in([UserType::Administrator, UserType::Subscriber(), null]); $userType?->in([UserType::Administrator, $userType, null]); +/** @var iterable $iterable */ +$userType->in($iterable); +/** @var \Illuminate\Support\Collection $collection */ +$userType->in($collection); ----- $iterable */ +in_array($userType, iterator_to_array($iterable)); +/** @var \Illuminate\Support\Collection $collection */ +$collection->contains($userType); diff --git a/tests/Rector/Usages/notIn.php.inc b/tests/Rector/Usages/notIn.php.inc index 29196a09..8672e340 100644 --- a/tests/Rector/Usages/notIn.php.inc +++ b/tests/Rector/Usages/notIn.php.inc @@ -5,6 +5,10 @@ use BenSampo\Enum\Tests\Enums\UserType; /** @var UserType $userType */ $userType->notIn([UserType::Administrator]); $userType?->notIn([UserType::Administrator, $userType]); +/** @var iterable $userTypes */ +$userType->notIn($userTypes); +/** @var \Illuminate\Support\Collection $collection */ +$userType->notIn($collection); ----- $userTypes */ +!in_array($userType, iterator_to_array($userTypes)); +/** @var \Illuminate\Support\Collection $collection */ +$collection->doesntContain($userType);