Skip to content

Commit 9a68b2a

Browse files
calebdwGeniJaho
andauthored
feat: add ConvertEnumerableToArrayToAllRector (#363)
* tests: add debug functions from upstream * feat: add ConvertEnumerableToArrayToAllRector The `toArray()` method on an Enumerable maps over all the items and recursively calls the `toArray()` method on each item if it is an Arrayable object. For collections that do not contain any Arrayable items, this is an unnecessary operation and the method can be replaced with `all()` which simply returns the underlying array. --------- Co-authored-by: Geni Jaho <[email protected]>
1 parent 47de732 commit 9a68b2a

File tree

12 files changed

+244
-2
lines changed

12 files changed

+244
-2
lines changed

composer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"phpunit/phpunit": "^10.5",
2020
"symplify/rule-doc-generator": "^12.2",
2121
"tightenco/duster": "^3.1",
22-
"nette/utils": "^4.0"
22+
"nette/utils": "^4.0",
23+
"tracy/tracy": "^2.10"
2324
},
2425
"autoload": {
2526
"psr-4": {
@@ -31,7 +32,8 @@
3132
"RectorLaravel\\Tests\\": "tests",
3233
"RectorLaravel\\Commands\\": "commands"
3334
},
34-
"classmap": ["stubs"]
35+
"classmap": ["stubs"],
36+
"files": ["tests/debug_functions.php"]
3537
},
3638
"scripts": {
3739
"phpstan": "vendor/bin/phpstan analyse --ansi",

config/sets/laravel-collection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
use Rector\Config\RectorConfig;
66
use RectorLaravel\Rector\BooleanNot\AvoidNegatedCollectionContainsOrDoesntContainRector;
77
use RectorLaravel\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector;
8+
use RectorLaravel\Rector\MethodCall\ConvertEnumerableToArrayToAllRector;
89
use RectorLaravel\Rector\MethodCall\UnaliasCollectionMethodsRector;
910

1011
return static function (RectorConfig $rectorConfig): void {
1112
$rectorConfig->import(__DIR__ . '/../config.php');
1213
$rectorConfig->rule(AvoidNegatedCollectionContainsOrDoesntContainRector::class);
1314
$rectorConfig->rule(AvoidNegatedCollectionFilterOrRejectRector::class);
15+
$rectorConfig->rule(ConvertEnumerableToArrayToAllRector::class);
1416
$rectorConfig->rule(UnaliasCollectionMethodsRector::class);
1517
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Rector\MethodCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Expr\NullsafeMethodCall;
10+
use PhpParser\Node\Identifier;
11+
use PHPStan\Type\ObjectType;
12+
use PHPStan\Type\TypeCombinator;
13+
use RectorLaravel\AbstractRector;
14+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
15+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
16+
17+
/**
18+
* @see \RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector\ConvertEnumerableToArrayToAllRectorTest
19+
*/
20+
final class ConvertEnumerableToArrayToAllRector extends AbstractRector
21+
{
22+
public function getRuleDefinition(): RuleDefinition
23+
{
24+
return new RuleDefinition(
25+
'Convert `toArray()` to `all()` when the collection does not contain any Arrayable objects.',
26+
[
27+
new CodeSample(
28+
<<<'CODE_SAMPLE'
29+
use Illuminate\Support\Collection;
30+
31+
new Collection([0, 1, -1])->toArray();
32+
CODE_SAMPLE
33+
,
34+
<<<'CODE_SAMPLE'
35+
use Illuminate\Support\Collection;
36+
37+
new Collection([0, 1, -1])->all();
38+
CODE_SAMPLE
39+
),
40+
]
41+
);
42+
}
43+
44+
/**
45+
* @return array<class-string<Node>>
46+
*/
47+
public function getNodeTypes(): array
48+
{
49+
return [MethodCall::class, NullsafeMethodCall::class];
50+
}
51+
52+
/**
53+
* @param MethodCall|NullsafeMethodCall $node
54+
*/
55+
public function refactor(Node $node): ?Node
56+
{
57+
if (! $this->isName($node->name, 'toArray')) {
58+
return null;
59+
}
60+
61+
if (! $this->isObjectType($node->var, new ObjectType('Illuminate\Support\Enumerable'))) {
62+
return null;
63+
}
64+
65+
$type = TypeCombinator::removeNull($this->getType($node->var));
66+
$valueType = $type->getTemplateType('Illuminate\Support\Enumerable', 'TValue');
67+
68+
if (! (new ObjectType('Illuminate\Contracts\Support\Arrayable'))->isSuperTypeOf($valueType)->no()) {
69+
return null;
70+
}
71+
72+
$node->name = new Identifier('all');
73+
74+
return $node;
75+
}
76+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Illuminate\Contracts\Support;
6+
7+
if (interface_exists('Illuminate\Contracts\Support\Arrayable')) {
8+
return;
9+
}
10+
11+
/**
12+
* @template TKey of array-key
13+
* @template TValue
14+
*/
15+
interface Arrayable
16+
{
17+
/**
18+
* Get the instance as an array.
19+
*
20+
* @return array<TKey, TValue>
21+
*/
22+
public function toArray();
23+
}

stubs/Illuminate/Support/Enumerable.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,18 @@ public function unlessEmpty();
101101
* @return $this|TUnlessNotEmptyReturnType
102102
*/
103103
public function unlessNotEmpty();
104+
105+
/**
106+
* Get the collection of items as a plain array.
107+
*
108+
* @return array<TKey, mixed>
109+
*/
110+
public function toArray();
111+
112+
/**
113+
* Get all items in the enumerable.
114+
*
115+
* @return array<TKey, TValue>
116+
*/
117+
public function all();
104118
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class ConvertEnumerableToArrayToAllRectorTest extends AbstractRectorTestCase
12+
{
13+
public static function provideData(): Iterator
14+
{
15+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
16+
}
17+
18+
/**
19+
* @test
20+
*/
21+
#[DataProvider('provideData')]
22+
public function test(string $filePath): void
23+
{
24+
$this->doTestFile($filePath);
25+
}
26+
27+
public function provideConfigFilePath(): string
28+
{
29+
return __DIR__ . '/config/configured_rule.php';
30+
}
31+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector\Fixture;
4+
5+
use Illuminate\Support\Collection;
6+
7+
/** @var Collection<int, int> $collection */
8+
$collection->toArray();
9+
/** @var Collection<int, int>|null $nullableCollection */
10+
$nullableCollection->toArray();
11+
$nullableCollection?->toArray();
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector\Fixture;
18+
19+
use Illuminate\Support\Collection;
20+
21+
/** @var Collection<int, int> $collection */
22+
$collection->all();
23+
/** @var Collection<int, int>|null $nullableCollection */
24+
$nullableCollection->all();
25+
$nullableCollection?->all();
26+
27+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector\Fixture;
4+
5+
use Illuminate\Contracts\Support\Arrayable;
6+
use Illuminate\Support\Collection;
7+
8+
/** @var Collection<int, Arrayable> $collection */
9+
$collection->toArray();
10+
11+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector\Fixture;
4+
5+
use Illuminate\Contracts\Support\Arrayable;
6+
use Illuminate\Support\Collection;
7+
8+
/** @var Collection<int, Arrayable|int> $collection */
9+
$collection->toArray();
10+
11+
?>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\ConvertEnumerableToArrayToAllRector\Fixture;
4+
5+
use Illuminate\Support\Collection;
6+
7+
/** @var Collection<int, mixed> $collection */
8+
$collection->toArray();
9+
10+
?>

0 commit comments

Comments
 (0)