Skip to content

Commit bc57ed7

Browse files
authored
Add native return type declaration support to NarrowReturnUnionToCollectionRector (#440)
* add array-filter to ArrayMapOnCollectionToArrayRector * Add native return type declaration support to NarrowReturnUnionToCollectionRector
1 parent 71da7d8 commit bc57ed7

File tree

4 files changed

+158
-12
lines changed

4 files changed

+158
-12
lines changed

rules-tests/TypedCollections/Rector/ClassMethod/NarrowReturnUnionToCollectionRector/Fixture/array_and_collection.php.inc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ final class ArrayAndCollection
1111
*/
1212
public function someMethod()
1313
{
14-
1514
}
1615
}
1716

@@ -30,7 +29,6 @@ final class ArrayAndCollection
3029
*/
3130
public function someMethod()
3231
{
33-
3432
}
3533
}
3634

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowReturnUnionToCollectionRector\Fixture;
4+
5+
use Doctrine\Common\Collections\Collection;
6+
use Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\Source\AnyEntity;
7+
8+
final class NullableUnionCollection
9+
{
10+
/**
11+
* @return Collection<int, AnyEntity>
12+
*/
13+
public function someMethod(): Collection|null
14+
{
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowReturnUnionToCollectionRector\Fixture;
23+
24+
use Doctrine\Common\Collections\Collection;
25+
use Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\Source\AnyEntity;
26+
27+
final class NullableUnionCollection
28+
{
29+
/**
30+
* @return Collection<int, AnyEntity>
31+
*/
32+
public function someMethod(): Collection
33+
{
34+
}
35+
}
36+
37+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowReturnUnionToCollectionRector\Fixture;
4+
5+
use Doctrine\Common\Collections\Collection;
6+
use Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\Source\AnyEntity;
7+
8+
final class ReturnNonNullableCollection
9+
{
10+
/**
11+
* @return Collection<int, AnyEntity>
12+
*/
13+
public function someMethod(): ?Collection
14+
{
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowReturnUnionToCollectionRector\Fixture;
23+
24+
use Doctrine\Common\Collections\Collection;
25+
use Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\Source\AnyEntity;
26+
27+
final class ReturnNonNullableCollection
28+
{
29+
/**
30+
* @return Collection<int, AnyEntity>
31+
*/
32+
public function someMethod(): Collection
33+
{
34+
}
35+
}
36+
37+
?>

rules/TypedCollections/Rector/ClassMethod/NarrowReturnUnionToCollectionRector.php

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
namespace Rector\Doctrine\TypedCollections\Rector\ClassMethod;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\NullableType;
89
use PhpParser\Node\Stmt\ClassMethod;
10+
use PhpParser\Node\UnionType;
911
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
12+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
1013
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
1114
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
1215
use Rector\Doctrine\Enum\DoctrineClass;
@@ -74,26 +77,97 @@ public function getNodeTypes(): array
7477
*/
7578
public function refactor(Node $node): ?ClassMethod
7679
{
77-
$classMethodPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
78-
$returnTagValueNode = $classMethodPhpDocInfo->getReturnTagValue();
80+
$hasChanged = false;
81+
if ($this->refactorReturnDocblockTag($node)) {
82+
$hasChanged = true;
83+
}
7984

80-
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
85+
if ($this->refactorNativeReturn($node)) {
86+
$hasChanged = true;
87+
}
88+
89+
if (! $hasChanged) {
8190
return null;
8291
}
8392

84-
if ($node->returnType !== null && $this->isName($node->returnType, DoctrineClass::COLLECTION)) {
85-
$hasNativeCollectionType = true;
86-
} else {
87-
$hasNativeCollectionType = false;
93+
return $node;
94+
}
95+
96+
private function refactorReturnDocblockTag(ClassMethod $classMethod): bool
97+
{
98+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);
99+
if (! $phpDocInfo instanceof PhpDocInfo) {
100+
return false;
88101
}
89102

103+
$returnTagValueNode = $phpDocInfo->getReturnTagValue();
104+
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
105+
return false;
106+
}
107+
108+
$hasNativeCollectionType = $this->hasNativeCollectionType($classMethod);
109+
90110
$hasChanged = $this->unionCollectionTagValueNodeNarrower->narrow($returnTagValueNode, $hasNativeCollectionType);
91111
if ($hasChanged === false) {
92-
return null;
112+
return false;
93113
}
94114

95-
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
115+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod);
96116

97-
return $node;
117+
return true;
118+
}
119+
120+
private function hasNativeCollectionType(ClassMethod $classMethod): bool
121+
{
122+
if (! $classMethod->returnType instanceof Node) {
123+
return false;
124+
}
125+
126+
return $this->isName($classMethod->returnType, DoctrineClass::COLLECTION);
127+
}
128+
129+
private function refactorNativeReturn(ClassMethod $classMethod): bool
130+
{
131+
if (! $classMethod->returnType instanceof Node) {
132+
return false;
133+
}
134+
135+
if ($classMethod->returnType instanceof NullableType && $this->isName(
136+
$classMethod->returnType->type,
137+
DoctrineClass::COLLECTION
138+
)) {
139+
// unwrap nullable type
140+
$classMethod->returnType = $classMethod->returnType->type;
141+
return true;
142+
}
143+
144+
if (! $classMethod->returnType instanceof UnionType) {
145+
return false;
146+
}
147+
148+
$unionType = $classMethod->returnType;
149+
if (! $this->hasNativeReturnCollectionType($unionType)) {
150+
return false;
151+
}
152+
153+
// remove null from union type
154+
foreach ($unionType->types as $key => $unionedType) {
155+
if ($this->isName($unionedType, 'null')) {
156+
unset($unionType->types[$key]);
157+
}
158+
}
159+
160+
return true;
161+
}
162+
163+
private function hasNativeReturnCollectionType(UnionType $unionType): bool
164+
{
165+
foreach ($unionType->types as $unionedType) {
166+
if ($this->isName($unionedType, DoctrineClass::COLLECTION)) {
167+
return true;
168+
}
169+
}
170+
171+
return false;
98172
}
99173
}

0 commit comments

Comments
 (0)