Skip to content

Commit 9d0a7b2

Browse files
authored
[DowngradePhp82] Add DowngradeIteratorCountToArrayRector (#261)
* [DowngradePhp82] Add DowngradeIteratorCountToArrayRector * cs fix * Fix phpstan * Fix phpstan * fix
1 parent 183795c commit 9d0a7b2

File tree

7 files changed

+204
-1
lines changed

7 files changed

+204
-1
lines changed

config/set/downgrade-php82.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
use Rector\Config\RectorConfig;
66
use Rector\ValueObject\PhpVersion;
77
use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector;
8+
use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector;
89
use Rector\DowngradePhp82\Rector\FunctionLike\DowngradeStandaloneNullTrueFalseReturnTypeRector;
910

1011
return static function (RectorConfig $rectorConfig): void {
1112
$rectorConfig->phpVersion(PhpVersion::PHP_81);
1213
$rectorConfig->rules([
1314
DowngradeReadonlyClassRector::class,
14-
DowngradeStandaloneNullTrueFalseReturnTypeRector::class
15+
DowngradeStandaloneNullTrueFalseReturnTypeRector::class,
16+
DowngradeIteratorCountToArrayRector::class,
1517
]);
1618
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DowngradeIteratorCountToArrayRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector\Fixture;
4+
5+
final class PossibleNotTraversable
6+
{
7+
function test(array|\Traversable $data) {
8+
$c = iterator_count($data);
9+
$v = iterator_to_array($data);
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector\Fixture;
18+
19+
final class PossibleNotTraversable
20+
{
21+
function test(array|\Traversable $data) {
22+
$c = iterator_count(is_array($data) ? new \ArrayIterator($data) : $data);
23+
$v = iterator_to_array(is_array($data) ? new \ArrayIterator($data) : $data);
24+
}
25+
}
26+
27+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector\Fixture;
4+
5+
final class SkipAlreadyHasObjectType
6+
{
7+
function test(array|\Traversable $data) {
8+
$c = iterator_count(is_array($data) ? new \ArrayIterator($data) : $data);
9+
$v = iterator_to_array(is_array($data) ? new \ArrayIterator($data) : $data);
10+
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector\Fixture;
4+
5+
final class SkipAlreadyTraversable
6+
{
7+
function test(\Traversable $data)
8+
{
9+
$c = iterator_count($data);
10+
$v = iterator_to_array($data);
11+
}
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(DowngradeIteratorCountToArrayRector::class);
10+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\DowngradePhp82\Rector\FuncCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr\FuncCall;
10+
use PhpParser\Node\Expr\New_;
11+
use PhpParser\Node\Expr\Ternary;
12+
use PhpParser\Node\Name\FullyQualified;
13+
use PHPStan\Type\Type;
14+
use Rector\NodeAnalyzer\ArgsAnalyzer;
15+
use Rector\Rector\AbstractRector;
16+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
17+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
18+
use Webmozart\Assert\Assert;
19+
20+
/**
21+
* @changelog https://www.php.net/manual/en/migration82.other-changes.php#migration82.other-changes.functions.spl
22+
*
23+
* @see \Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector\DowngradeIteratorCountToArrayRectorTest
24+
* @see https://3v4l.org/TPW7a#v8.1.31
25+
*/
26+
final class DowngradeIteratorCountToArrayRector extends AbstractRector
27+
{
28+
public function __construct(
29+
private readonly ArgsAnalyzer $argsAnalyzer
30+
) {
31+
}
32+
33+
/**
34+
* @return array<class-string<Node>>
35+
*/
36+
public function getNodeTypes(): array
37+
{
38+
return [FuncCall::class];
39+
}
40+
41+
public function getRuleDefinition(): RuleDefinition
42+
{
43+
return new RuleDefinition(
44+
'Ensure pass Traversable instance before use in iterator_count() and iterator_to_array()',
45+
[
46+
new CodeSample(
47+
<<<'CODE_SAMPLE'
48+
function test(array|Traversable $data) {
49+
$c = iterator_count($data);
50+
$v = iterator_to_array($data);
51+
}
52+
CODE_SAMPLE
53+
,
54+
<<<'CODE_SAMPLE'
55+
function test(array|Traversable $data) {
56+
$c = iterator_count(is_array($data) ? new ArrayIterator($data) : $data);
57+
$v = iterator_to_array(is_array($data) ? new ArrayIterator($data) : $data);
58+
}
59+
CODE_SAMPLE
60+
),
61+
]
62+
);
63+
}
64+
65+
/**
66+
* @param FuncCall $node
67+
*/
68+
public function refactor(Node $node): ?Node
69+
{
70+
if (! $this->isNames($node, ['iterator_count', 'iterator_to_array'])) {
71+
return null;
72+
}
73+
74+
if ($node->isFirstClassCallable()) {
75+
return null;
76+
}
77+
78+
$args = $node->getArgs();
79+
if ($this->argsAnalyzer->hasNamedArg($args)) {
80+
return null;
81+
}
82+
83+
if (! isset($args[0])) {
84+
return null;
85+
}
86+
87+
$type = $this->nodeTypeResolver->getType($args[0]->value);
88+
if ($this->shouldSkip($type)) {
89+
return null;
90+
}
91+
92+
Assert::isInstanceOf($node->args[0], Arg::class);
93+
94+
$firstValue = $node->args[0]->value;
95+
$node->args[0]->value = new Ternary(
96+
$this->nodeFactory->createFuncCall('is_array', [new Arg($firstValue)]),
97+
new New_(new FullyQualified('ArrayIterator'), [new Arg($firstValue)]),
98+
$firstValue
99+
);
100+
101+
return $node;
102+
}
103+
104+
private function shouldSkip(Type $type): bool
105+
{
106+
if ($type->isArray()->yes()) {
107+
return false;
108+
}
109+
110+
return $type->isObject()
111+
->yes();
112+
}
113+
}

0 commit comments

Comments
 (0)