diff --git a/config/set/downgrade-php82.php b/config/set/downgrade-php82.php index c4b78c20..e7e81dbe 100644 --- a/config/set/downgrade-php82.php +++ b/config/set/downgrade-php82.php @@ -5,6 +5,7 @@ use Rector\Config\RectorConfig; use Rector\ValueObject\PhpVersion; use Rector\DowngradePhp82\Rector\Class_\DowngradeReadonlyClassRector; +use Rector\DowngradePhp82\Rector\Class_\DowngradeUnionIntersectionRector; use Rector\DowngradePhp82\Rector\FuncCall\DowngradeIteratorCountToArrayRector; use Rector\DowngradePhp82\Rector\FunctionLike\DowngradeStandaloneNullTrueFalseReturnTypeRector; @@ -14,5 +15,6 @@ DowngradeReadonlyClassRector::class, DowngradeStandaloneNullTrueFalseReturnTypeRector::class, DowngradeIteratorCountToArrayRector::class, + DowngradeUnionIntersectionRector::class, ]); }; diff --git a/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/DowngradeUnionIntersectionRectorTest.php b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/DowngradeUnionIntersectionRectorTest.php new file mode 100644 index 00000000..9770ae04 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/DowngradeUnionIntersectionRectorTest.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/Class_/DowngradeUnionIntersectionRector/Fixture/on_param_function.php.inc b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_param_function.php.inc new file mode 100644 index 00000000..9536410b --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_param_function.php.inc @@ -0,0 +1,22 @@ + +----- + diff --git a/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_property.php.inc b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_property.php.inc new file mode 100644 index 00000000..eb619cd1 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_property.php.inc @@ -0,0 +1,24 @@ + +----- + diff --git a/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_return_function.php.inc b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_return_function.php.inc new file mode 100644 index 00000000..9d1b9085 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/on_return_function.php.inc @@ -0,0 +1,22 @@ + +----- + diff --git a/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/skip_other_union.php.inc b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/skip_other_union.php.inc new file mode 100644 index 00000000..3e91fd22 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/Fixture/skip_other_union.php.inc @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/config/configured_rule.php b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/config/configured_rule.php new file mode 100644 index 00000000..40c4b785 --- /dev/null +++ b/rules-tests/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(DowngradeUnionIntersectionRector::class); +}; diff --git a/rules/DowngradePhp80/Rector/Expression/DowngradeThrowExprRector.php b/rules/DowngradePhp80/Rector/Expression/DowngradeThrowExprRector.php index 3b48b98e..429d24f1 100644 --- a/rules/DowngradePhp80/Rector/Expression/DowngradeThrowExprRector.php +++ b/rules/DowngradePhp80/Rector/Expression/DowngradeThrowExprRector.php @@ -108,7 +108,9 @@ public function refactor(Node $node): Node|array|null foreach ($args as $arg) { if ($arg->value instanceof Ternary && $arg->value->else instanceof Throw_) { $refactorTernary = $this->refactorTernary($arg->value, null, true); - if (is_array($refactorTernary) && $refactorTernary[0] instanceof Expression && $refactorTernary[0]->expr instanceof Assign) { + if (is_array( + $refactorTernary + ) && $refactorTernary[0] instanceof Expression && $refactorTernary[0]->expr instanceof Assign) { $arg->value = $refactorTernary[0]->expr->var; return [...$refactorTernary, $node]; diff --git a/rules/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector.php b/rules/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector.php new file mode 100644 index 00000000..9f7ee694 --- /dev/null +++ b/rules/DowngradePhp82/Rector/Class_/DowngradeUnionIntersectionRector.php @@ -0,0 +1,146 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassLike::class, ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; + } + + /** + * @param ClassLike|ClassMethod|Function_|Closure|ArrowFunction $node + */ + public function refactor(Node $node): ?Node + { + if (! $node instanceof ClassLike) { + return $this->processFunctionLike($node); + } + + return $this->processClassLike($node); + } + + private function processFunctionLike( + ClassMethod|Function_|Closure|ArrowFunction $functionLike + ): null|ClassMethod|Function_|Closure|ArrowFunction { + $paramDecorated = false; + foreach ($functionLike->getParams() as $param) { + if (! $this->isUnionIntersection($param->type)) { + continue; + } + + Assert::isInstanceOf($param->type, UnionType::class); + $this->phpDocFromTypeDeclarationDecorator->decorateParam( + $param, + $functionLike, + [\PHPStan\Type\UnionType::class] + ); + $paramDecorated = true; + } + + if (! $this->isUnionIntersection($functionLike->returnType)) { + if ($paramDecorated) { + return $functionLike; + } + + return null; + } + + Assert::isInstanceOf($functionLike->returnType, UnionType::class); + $this->phpDocFromTypeDeclarationDecorator->decorateReturn($functionLike); + + return $functionLike; + } + + private function processClassLike(ClassLike $classLike): ?ClassLike + { + $hasChanged = false; + foreach ($classLike->getProperties() as $property) { + if (! $this->isUnionIntersection($property->type)) { + continue; + } + + Assert::isInstanceOf($property->type, UnionType::class); + $this->propertyDecorator->decorateWithDocBlock($property, $property->type); + + $property->type = null; + $hasChanged = true; + } + + return $hasChanged ? $classLike : null; + } + + private function isUnionIntersection(?Node $node): bool + { + if (! $node instanceof UnionType) { + return false; + } + + foreach ($node->types as $type) { + if ($type instanceof IntersectionType) { + return true; + } + } + + return false; + } +}