Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix conversion of in() and notIn() to native enums when called with non-arrays #359

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 29 additions & 7 deletions src/Rector/ToNativeUsagesRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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],
);

Expand Down
4 changes: 4 additions & 0 deletions tests/EnumAnnotateCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -19,6 +21,7 @@ public function testAnnotateClass(string $class): void
}

/** @dataProvider classes */
#[DataProvider('classes')]
public function testAnnotateClassAlreadyAnnotated(string $class): void
{
$filesystem = $this->filesystem();
Expand Down Expand Up @@ -48,6 +51,7 @@ public static function sources(): iterable
}

/** @dataProvider sources */
#[DataProvider('sources')]
public function testAnnotateFolder(string $source): void
{
$filesystem = $this->filesystem();
Expand Down
5 changes: 5 additions & 0 deletions tests/PHPStan/UniqueValuesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ public function testRule(): void
],
);
}

protected function shouldFailOnPhpErrors(): bool
{
return false;
}
}
2 changes: 2 additions & 0 deletions tests/Rector/ToNativeImplementationRectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions tests/Rector/ToNativeUsagesRectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions tests/Rector/Usages/in.php.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserType> $iterable */
$userType->in($iterable);
/** @var \Illuminate\Support\Collection $collection */
$userType->in($collection);
-----
<?php

Expand All @@ -13,3 +17,7 @@ use BenSampo\Enum\Tests\Enums\UserType;
/** @var UserType $userType */
in_array($userType, [UserType::Administrator, UserType::Subscriber, null]);
in_array($userType, [UserType::Administrator, $userType, null]);
/** @var iterable<UserType> $iterable */
in_array($userType, iterator_to_array($iterable));
/** @var \Illuminate\Support\Collection $collection */
$collection->contains($userType);
8 changes: 8 additions & 0 deletions tests/Rector/Usages/notIn.php.inc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use BenSampo\Enum\Tests\Enums\UserType;
/** @var UserType $userType */
$userType->notIn([UserType::Administrator]);
$userType?->notIn([UserType::Administrator, $userType]);
/** @var iterable<UserType> $userTypes */
$userType->notIn($userTypes);
/** @var \Illuminate\Support\Collection $collection */
$userType->notIn($collection);
-----
<?php

Expand All @@ -13,3 +17,7 @@ use BenSampo\Enum\Tests\Enums\UserType;
/** @var UserType $userType */
!in_array($userType, [UserType::Administrator]);
!in_array($userType, [UserType::Administrator, $userType]);
/** @var iterable<UserType> $userTypes */
!in_array($userType, iterator_to_array($userTypes));
/** @var \Illuminate\Support\Collection $collection */
$collection->doesntContain($userType);
Loading