Skip to content

Commit b2626a4

Browse files
committed
[DowngradePhp82] Add DowngradeIteratorCountToArrayRector
1 parent 183795c commit b2626a4

File tree

6 files changed

+206
-0
lines changed

6 files changed

+206
-0
lines changed
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: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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;
10+
use PhpParser\Node\Expr\FuncCall;
11+
use PhpParser\Node\Expr\New_;
12+
use PhpParser\Node\Expr\Ternary;
13+
use PhpParser\Node\Name\FullyQualified;
14+
use PHPStan\Type\Type;
15+
use PHPStan\Type\UnionType;
16+
use Rector\NodeAnalyzer\ArgsAnalyzer;
17+
use Rector\Rector\AbstractRector;
18+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20+
21+
/**
22+
* @changelog https://www.php.net/manual/en/migration82.other-changes.php#migration82.other-changes.functions.spl
23+
*
24+
* @see \Rector\Tests\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector\DowngradeIteratorCountToArrayRectorTest
25+
* @see https://3v4l.org/TPW7a#v8.1.31
26+
*/
27+
final class DowngradeIteratorCountToArrayRector extends AbstractRector
28+
{
29+
public function __construct(
30+
private readonly ArgsAnalyzer $argsAnalyzer
31+
) {
32+
}
33+
34+
/**
35+
* @return array<class-string<Node>>
36+
*/
37+
public function getNodeTypes(): array
38+
{
39+
return [FuncCall::class];
40+
}
41+
42+
public function getRuleDefinition(): RuleDefinition
43+
{
44+
return new RuleDefinition(
45+
'Ensure pass Traversable instance before use in iterator_count() and iterator_to_array()',
46+
[
47+
new CodeSample(
48+
<<<'CODE_SAMPLE'
49+
function test(array|Traversable $data) {
50+
$c = iterator_count($data);
51+
$v = iterator_to_array($data);
52+
}
53+
CODE_SAMPLE
54+
,
55+
<<<'CODE_SAMPLE'
56+
function test(array|Traversable $data) {
57+
$c = iterator_count(is_array($data) ? new ArrayIterator($data) : $data);
58+
$v = iterator_to_array(is_array($data) ? new ArrayIterator($data) : $data);
59+
}
60+
CODE_SAMPLE
61+
),
62+
]
63+
);
64+
}
65+
66+
/**
67+
* @param FuncCall $node
68+
*/
69+
public function refactor(Node $node): ?Node
70+
{
71+
if (! $this->isNames($node, ['iterator_count', 'iterator_to_array'])) {
72+
return null;
73+
}
74+
75+
if ($node->isFirstClassCallable()) {
76+
return null;
77+
}
78+
79+
$args = $node->getArgs();
80+
if ($this->argsAnalyzer->hasNamedArg($args)) {
81+
return null;
82+
}
83+
84+
if (! isset($args[0])) {
85+
return null;
86+
}
87+
88+
$type = $this->nodeTypeResolver->getType($args[0]->value);
89+
if ($this->shouldSkip($type, $args[0]->value)) {
90+
return null;
91+
}
92+
93+
$firstValue = $node->args[0]->value;
94+
$node->args[0]->value = new Ternary(
95+
$this->nodeFactory->createFuncCall('is_array', [new Arg($firstValue)]),
96+
new New_(new FullyQualified('ArrayIterator'), [new Arg($firstValue)]),
97+
$firstValue
98+
);
99+
100+
return $node;
101+
}
102+
103+
private function shouldSkip(Type $type, Expr $expr): bool
104+
{
105+
// only array type
106+
if ($type->isArray()->yes()) {
107+
return false;
108+
}
109+
110+
if ($type instanceof UnionType) {
111+
// possibly already transformed
112+
return $expr instanceof Ternary;
113+
}
114+
115+
// already has object type check
116+
return $type->isObject()->yes();
117+
}
118+
}

0 commit comments

Comments
 (0)