diff --git a/config/set/downgrade-php82.php b/config/set/downgrade-php82.php index f2c4e5f0..c4b78c20 100644 --- a/config/set/downgrade-php82.php +++ b/config/set/downgrade-php82.php @@ -5,12 +5,14 @@ use Rector\Config\RectorConfig; use Rector\ValueObject\PhpVersion; use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector; +use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector; use Rector\DowngradePhp82\Rector\FunctionLike\DowngradeStandaloneNullTrueFalseReturnTypeRector; return static function (RectorConfig $rectorConfig): void { $rectorConfig->phpVersion(PhpVersion::PHP_81); $rectorConfig->rules([ DowngradeReadonlyClassRector::class, - DowngradeStandaloneNullTrueFalseReturnTypeRector::class + DowngradeStandaloneNullTrueFalseReturnTypeRector::class, + DowngradeIteratorCountToArrayRector::class, ]); }; diff --git a/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/DowngradeIteratorCountToArrayRectorTest.php b/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/DowngradeIteratorCountToArrayRectorTest.php new file mode 100644 index 00000000..c64d2a55 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/DowngradeIteratorCountToArrayRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/Fixture/possible_not_traversable.php.inc b/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/Fixture/possible_not_traversable.php.inc new file mode 100644 index 00000000..50326ac7 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/Fixture/possible_not_traversable.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/Fixture/skip_already_has_object_type.php.inc b/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/Fixture/skip_already_has_object_type.php.inc new file mode 100644 index 00000000..dadee299 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector/Fixture/skip_already_has_object_type.php.inc @@ -0,0 +1,11 @@ +rule(DowngradeIteratorCountToArrayRector::class); +}; diff --git a/rules/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector.php b/rules/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector.php new file mode 100644 index 00000000..25c45763 --- /dev/null +++ b/rules/DowngradePhp82/Rector/FuncCall/DowngradeIteratorCountToArrayRector.php @@ -0,0 +1,113 @@ +> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Ensure pass Traversable instance before use in iterator_count() and iterator_to_array()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +function test(array|Traversable $data) { + $c = iterator_count($data); + $v = iterator_to_array($data); +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +function test(array|Traversable $data) { + $c = iterator_count(is_array($data) ? new ArrayIterator($data) : $data); + $v = iterator_to_array(is_array($data) ? new ArrayIterator($data) : $data); +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isNames($node, ['iterator_count', 'iterator_to_array'])) { + return null; + } + + if ($node->isFirstClassCallable()) { + return null; + } + + $args = $node->getArgs(); + if ($this->argsAnalyzer->hasNamedArg($args)) { + return null; + } + + if (! isset($args[0])) { + return null; + } + + $type = $this->nodeTypeResolver->getType($args[0]->value); + if ($this->shouldSkip($type)) { + return null; + } + + Assert::isInstanceOf($node->args[0], Arg::class); + + $firstValue = $node->args[0]->value; + $node->args[0]->value = new Ternary( + $this->nodeFactory->createFuncCall('is_array', [new Arg($firstValue)]), + new New_(new FullyQualified('ArrayIterator'), [new Arg($firstValue)]), + $firstValue + ); + + return $node; + } + + private function shouldSkip(Type $type): bool + { + if ($type->isArray()->yes()) { + return false; + } + + return $type->isObject() + ->yes(); + } +}