diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md index 9ead1067224..8a8e689ad53 100644 --- a/build/target-repository/docs/rector_rules_overview.md +++ b/build/target-repository/docs/rector_rules_overview.md @@ -558,6 +558,10 @@ Add explicit return null to method/function that returns a value, but missed mai ```diff class SomeClass { + /** +- * @return string|void ++ * @return string|null + */ public function run(int $number) { if ($number > 50) { diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector/Fixture/with_union_void_doc.php.inc b/rules-tests/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector/Fixture/with_union_void_doc.php.inc new file mode 100644 index 00000000000..77b475c104e --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector/Fixture/with_union_void_doc.php.inc @@ -0,0 +1,38 @@ + 50) { + return 'yes'; + } + } +} + +?> +----- + 50) { + return 'yes'; + } + return null; + } +} + +?> diff --git a/rules/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector.php b/rules/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector.php index d5ab2f9b6e1..971b75e9ce3 100644 --- a/rules/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector.php +++ b/rules/CodeQuality/Rector/ClassMethod/ExplicitReturnNullRector.php @@ -12,6 +12,12 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; use PhpParser\Node\Stmt\Throw_; +use PHPStan\Type\NullType; +use PHPStan\Type\UnionType; +use PHPStan\Type\VoidType; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; +use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver; @@ -25,7 +31,10 @@ final class ExplicitReturnNullRector extends AbstractRector { public function __construct( private readonly BetterNodeFinder $betterNodeFinder, - private readonly SilentVoidResolver $silentVoidResolver + private readonly SilentVoidResolver $silentVoidResolver, + private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly TypeFactory $typeFactory, + private readonly PhpDocTypeChanger $phpDocTypeChanger ) { } @@ -38,6 +47,9 @@ public function getRuleDefinition(): RuleDefinition <<<'CODE_SAMPLE' class SomeClass { + /** + * @return string|void + */ public function run(int $number) { if ($number > 50) { @@ -51,6 +63,9 @@ public function run(int $number) <<<'CODE_SAMPLE' class SomeClass { + /** + * @return string|null + */ public function run(int $number) { if ($number > 50) { @@ -100,9 +115,37 @@ public function refactor(Node $node): ?Node $node->stmts[] = new Return_(new ConstFetch(new Name('null'))); + $this->transformDocUnionVoidToUnionNull($node); + return $node; } + private function transformDocUnionVoidToUnionNull(ClassMethod $classMethod): void + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); + + $returnType = $phpDocInfo->getReturnType(); + if (! $returnType instanceof UnionType) { + return; + } + + $newTypes = []; + foreach ($returnType->getTypes() as $type) { + if ($type instanceof VoidType) { + $type = new NullType(); + } + + $newTypes[] = $type; + } + + $type = $this->typeFactory->createMixedPassedOrUnionTypeAndKeepConstant($newTypes); + if (! $type instanceof UnionType) { + return; + } + + $this->phpDocTypeChanger->changeReturnType($classMethod, $phpDocInfo, $type); + } + private function containsYieldOrThrow(ClassMethod $classMethod): bool { return (bool) $this->betterNodeFinder->findInstancesOf(