diff --git a/config/level/php/php71.yml b/config/level/php/php71.yml new file mode 100644 index 00000000000..6bdb99a77d2 --- /dev/null +++ b/config/level/php/php71.yml @@ -0,0 +1,2 @@ +services: + Rector\Php\Rector\IsIterableRector: ~ diff --git a/config/level/php/php73.yml b/config/level/php/php73.yml new file mode 100644 index 00000000000..63f1aac9ed9 --- /dev/null +++ b/config/level/php/php73.yml @@ -0,0 +1,2 @@ +services: + Rector\Php\Rector\IsCountableRector: ~ diff --git a/packages/Php/config/config.yml b/packages/Php/config/config.yml new file mode 100644 index 00000000000..ee8975305a8 --- /dev/null +++ b/packages/Php/config/config.yml @@ -0,0 +1,2 @@ +imports: + - { resource: 'services.yml' } \ No newline at end of file diff --git a/packages/Php/config/services.yml b/packages/Php/config/services.yml new file mode 100644 index 00000000000..36b2298f8be --- /dev/null +++ b/packages/Php/config/services.yml @@ -0,0 +1,8 @@ +services: + _defaults: + public: true + autowire: true + + Rector\Php\: + resource: '../src' + exclude: '../src/{Rector/**/*Rector.php}' diff --git a/packages/Php/src/DualCheckToAble.php b/packages/Php/src/DualCheckToAble.php new file mode 100644 index 00000000000..1ba72a86758 --- /dev/null +++ b/packages/Php/src/DualCheckToAble.php @@ -0,0 +1,74 @@ +splitToInstanceOfAndFuncCall($booleanOrNode); + if ($split === null) { + return null; + } + + [$instanceOfNode, $funcCallNode] = $split; + + /** @var Instanceof_ $instanceOfNode */ + if ((string) $instanceOfNode->class !== $type) { + return null; + } + + /** @var FuncCall $funcCallNode */ + if ((string) $funcCallNode->name !== 'is_array') { + return null; + } + + // both use same var + if (! $funcCallNode->args[0]->value instanceof Variable) { + return null; + } + + /** @var Variable $firstVarNode */ + $firstVarNode = $funcCallNode->args[0]->value; + + if (! $instanceOfNode->expr instanceof Variable) { + return null; + } + + /** @var Variable $secondVarNode */ + $secondVarNode = $instanceOfNode->expr; + + // are they same variables + if ($firstVarNode->name !== $secondVarNode->name) { + return null; + } + + $funcCallNode = new FuncCall(new Name($newMethodName)); + $funcCallNode->args[0] = new Arg($firstVarNode); + + return $funcCallNode; + } + + /** + * @return Instanceof_[]|FuncCall[] + */ + private function splitToInstanceOfAndFuncCall(BooleanOr $booleanOrNode): ?array + { + if ($booleanOrNode->left instanceof Instanceof_ && $booleanOrNode->right instanceof FuncCall) { + return [$booleanOrNode->left, $booleanOrNode->right]; + } + + if ($booleanOrNode->right instanceof Instanceof_ && $booleanOrNode->left instanceof FuncCall) { + return [$booleanOrNode->right, $booleanOrNode->left]; + } + + return null; + } +} diff --git a/packages/Php/src/Rector/IsCountableRector.php b/packages/Php/src/Rector/IsCountableRector.php new file mode 100644 index 00000000000..7779cc136ad --- /dev/null +++ b/packages/Php/src/Rector/IsCountableRector.php @@ -0,0 +1,57 @@ +dualCheckToAble = $dualCheckToAble; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Changes is_array + Countable check to is_countable', + [ + new CodeSample( + <<<'CODE_SAMPLE' +is_array($foo) || $foo instanceof Countable; +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +is_countable($foo); +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [BooleanOr::class]; + } + + /** + * @param BooleanOr $booleanOrNode + */ + public function refactor(Node $booleanOrNode): ?Node + { + return $this->dualCheckToAble->processBooleanOr($booleanOrNode, 'Countable', 'is_countable') ?: $booleanOrNode; + } +} diff --git a/packages/Php/src/Rector/IsIterableRector.php b/packages/Php/src/Rector/IsIterableRector.php new file mode 100644 index 00000000000..c611fe0d577 --- /dev/null +++ b/packages/Php/src/Rector/IsIterableRector.php @@ -0,0 +1,57 @@ +dualCheckToAble = $dualCheckToAble; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Changes is_array + Traversable check to is_iterable', + [ + new CodeSample( + <<<'CODE_SAMPLE' +is_array($foo) || $foo instanceof Traversable; +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +is_iterable($foo); +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [BooleanOr::class]; + } + + /** + * @param BooleanOr $booleanOrNode + */ + public function refactor(Node $booleanOrNode): ?Node + { + return $this->dualCheckToAble->processBooleanOr($booleanOrNode, 'Traversable', 'is_iterable') ?: $booleanOrNode; + } +} diff --git a/packages/Php/tests/Rector/IsCountableRector/Correct/correct.php.inc b/packages/Php/tests/Rector/IsCountableRector/Correct/correct.php.inc new file mode 100644 index 00000000000..ca7669b5f8d --- /dev/null +++ b/packages/Php/tests/Rector/IsCountableRector/Correct/correct.php.inc @@ -0,0 +1,3 @@ +doTestFileMatchesExpectedContent($wrong, $fixed); + } + + public function provideWrongToFixedFiles(): Iterator + { + yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc']; + } + + protected function provideConfig(): string + { + return __DIR__ . '/config.yml'; + } +} diff --git a/packages/Php/tests/Rector/IsCountableRector/Wrong/wrong.php.inc b/packages/Php/tests/Rector/IsCountableRector/Wrong/wrong.php.inc new file mode 100644 index 00000000000..f6f7872cee5 --- /dev/null +++ b/packages/Php/tests/Rector/IsCountableRector/Wrong/wrong.php.inc @@ -0,0 +1,3 @@ +doTestFileMatchesExpectedContent($wrong, $fixed); + } + + public function provideWrongToFixedFiles(): Iterator + { + yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc']; + } + + protected function provideConfig(): string + { + return __DIR__ . '/config.yml'; + } +} diff --git a/packages/Php/tests/Rector/IsIterableRector/Wrong/wrong.php.inc b/packages/Php/tests/Rector/IsIterableRector/Wrong/wrong.php.inc new file mode 100644 index 00000000000..12582e54ed9 --- /dev/null +++ b/packages/Php/tests/Rector/IsIterableRector/Wrong/wrong.php.inc @@ -0,0 +1,9 @@ +