Skip to content

Commit dc86768

Browse files
committed
extract couple yield-related services to re-use
1 parent bdcfadc commit dc86768

4 files changed

Lines changed: 210 additions & 143 deletions

File tree

rules/TypeDeclaration/Rector/FunctionLike/AddReturnTypeDeclarationFromYieldsRector.php

Lines changed: 8 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,14 @@
55
namespace Rector\TypeDeclaration\Rector\FunctionLike;
66

77
use PhpParser\Node;
8-
use PhpParser\Node\Expr;
9-
use PhpParser\Node\Expr\Closure;
10-
use PhpParser\Node\Expr\Yield_;
11-
use PhpParser\Node\Expr\YieldFrom;
12-
use PhpParser\Node\FunctionLike;
13-
use PhpParser\Node\Identifier;
14-
use PhpParser\Node\Name;
15-
use PhpParser\Node\Stmt;
16-
use PhpParser\Node\Stmt\Class_;
178
use PhpParser\Node\Stmt\ClassMethod;
18-
use PhpParser\Node\Stmt\Expression;
199
use PhpParser\Node\Stmt\Function_;
20-
use PhpParser\NodeVisitor;
21-
use PHPStan\Type\MixedType;
22-
use PHPStan\Type\Type;
23-
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
24-
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
2510
use Rector\PHPStan\ScopeFetcher;
2611
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
2712
use Rector\Rector\AbstractRector;
2813
use Rector\StaticTypeMapper\StaticTypeMapper;
29-
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedGenericObjectType;
30-
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
14+
use Rector\TypeDeclarationDocblocks\NodeFinder\YieldNodeFinder;
15+
use Rector\TypeDeclarationDocblocks\TypeResolver\YieldTypeResolver;
3116
use Rector\ValueObject\PhpVersionFeature;
3217
use Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnTypeOverrideGuard;
3318
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@@ -40,10 +25,10 @@
4025
final class AddReturnTypeDeclarationFromYieldsRector extends AbstractRector implements MinPhpVersionInterface
4126
{
4227
public function __construct(
43-
private readonly TypeFactory $typeFactory,
44-
private readonly SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
4528
private readonly StaticTypeMapper $staticTypeMapper,
46-
private readonly ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard
29+
private readonly ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard,
30+
private readonly YieldNodeFinder $yieldNodeFinder,
31+
private readonly YieldTypeResolver $yieldTypeResolver,
4732
) {
4833
}
4934

@@ -91,7 +76,8 @@ public function getNodeTypes(): array
9176
public function refactor(Node $node): ?Node
9277
{
9378
$scope = ScopeFetcher::fetch($node);
94-
$yieldNodes = $this->findCurrentScopeYieldNodes($node);
79+
80+
$yieldNodes = $this->yieldNodeFinder->find($node);
9581
if ($yieldNodes === []) {
9682
return null;
9783
}
@@ -111,7 +97,7 @@ public function refactor(Node $node): ?Node
11197
return null;
11298
}
11399

114-
$yieldType = $this->resolveYieldType($yieldNodes, $node);
100+
$yieldType = $this->yieldTypeResolver->resolveFromYieldNodes($yieldNodes, $node);
115101
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($yieldType, TypeKind::RETURN);
116102
if (! $returnTypeNode instanceof Node) {
117103
return null;
@@ -125,109 +111,4 @@ public function provideMinPhpVersion(): int
125111
{
126112
return PhpVersionFeature::SCALAR_TYPES;
127113
}
128-
129-
/**
130-
* @return Yield_[]|YieldFrom[]
131-
*/
132-
private function findCurrentScopeYieldNodes(FunctionLike $functionLike): array
133-
{
134-
$yieldNodes = [];
135-
136-
$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), static function (
137-
Node $node
138-
) use (&$yieldNodes): ?int {
139-
// skip anonymous class and inner function
140-
if ($node instanceof Class_) {
141-
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
142-
}
143-
144-
// skip nested scope
145-
if ($node instanceof FunctionLike) {
146-
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
147-
}
148-
149-
if ($node instanceof Stmt && ! $node instanceof Expression) {
150-
$yieldNodes = [];
151-
return NodeVisitor::STOP_TRAVERSAL;
152-
}
153-
154-
if (! $node instanceof Yield_ && ! $node instanceof YieldFrom) {
155-
return null;
156-
}
157-
158-
$yieldNodes[] = $node;
159-
return null;
160-
});
161-
162-
return $yieldNodes;
163-
}
164-
165-
private function resolveYieldValue(Yield_ | YieldFrom $yield): ?Expr
166-
{
167-
if ($yield instanceof Yield_) {
168-
return $yield->value;
169-
}
170-
171-
return $yield->expr;
172-
}
173-
174-
/**
175-
* @param array<Yield_|YieldFrom> $yieldNodes
176-
* @return Type[]
177-
*/
178-
private function resolveYieldedTypes(array $yieldNodes): array
179-
{
180-
$yieldedTypes = [];
181-
182-
foreach ($yieldNodes as $yieldNode) {
183-
$value = $this->resolveYieldValue($yieldNode);
184-
if (! $value instanceof Expr) {
185-
// one of the yields is empty
186-
return [];
187-
}
188-
189-
$resolvedType = $this->nodeTypeResolver->getType($value);
190-
if ($resolvedType instanceof MixedType) {
191-
continue;
192-
}
193-
194-
$yieldedTypes[] = $resolvedType;
195-
}
196-
197-
return $yieldedTypes;
198-
}
199-
200-
/**
201-
* @param array<Yield_|YieldFrom> $yieldNodes
202-
*/
203-
private function resolveYieldType(
204-
array $yieldNodes,
205-
ClassMethod|Function_ $functionLike
206-
): FullyQualifiedObjectType|FullyQualifiedGenericObjectType {
207-
$yieldedTypes = $this->resolveYieldedTypes($yieldNodes);
208-
209-
$className = $this->resolveClassName($functionLike);
210-
211-
if ($yieldedTypes === []) {
212-
return new FullyQualifiedObjectType($className);
213-
}
214-
215-
$yieldedTypes = $this->typeFactory->createMixedPassedOrUnionType($yieldedTypes);
216-
return new FullyQualifiedGenericObjectType($className, [$yieldedTypes]);
217-
}
218-
219-
private function resolveClassName(Function_|ClassMethod|Closure $functionLike): string
220-
{
221-
$returnTypeNode = $functionLike->getReturnType();
222-
223-
if ($returnTypeNode instanceof Identifier && $returnTypeNode->name === 'iterable') {
224-
return 'Iterator';
225-
}
226-
227-
if ($returnTypeNode instanceof Name && ! $this->isName($returnTypeNode, 'Generator')) {
228-
return $this->getName($returnTypeNode);
229-
}
230-
231-
return 'Generator';
232-
}
233114
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\NodeFinder;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\Yield_;
9+
use PhpParser\Node\Expr\YieldFrom;
10+
use PhpParser\Node\FunctionLike;
11+
use PhpParser\Node\Stmt;
12+
use PhpParser\Node\Stmt\Class_;
13+
use PhpParser\Node\Stmt\Expression;
14+
use PhpParser\NodeVisitor;
15+
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
16+
17+
final readonly class YieldNodeFinder
18+
{
19+
public function __construct(
20+
private SimpleCallableNodeTraverser $simpleCallableNodeTraverser
21+
) {
22+
}
23+
24+
/**
25+
* @return Yield_[]|YieldFrom[]
26+
*/
27+
public function find(FunctionLike $functionLike): array
28+
{
29+
$yieldNodes = [];
30+
31+
$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), static function (
32+
Node $node
33+
) use (&$yieldNodes): ?int {
34+
// skip anonymous class and inner function
35+
if ($node instanceof Class_) {
36+
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
37+
}
38+
39+
// skip nested scope
40+
if ($node instanceof FunctionLike) {
41+
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
42+
}
43+
44+
if ($node instanceof Stmt && ! $node instanceof Expression) {
45+
$yieldNodes = [];
46+
return NodeVisitor::STOP_TRAVERSAL;
47+
}
48+
49+
if (! $node instanceof Yield_ && ! $node instanceof YieldFrom) {
50+
return null;
51+
}
52+
53+
$yieldNodes[] = $node;
54+
return null;
55+
});
56+
57+
return $yieldNodes;
58+
}
59+
}

rules/TypeDeclarationDocblocks/Rector/Class_/AddReturnDocblockDataProviderRector.php

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
use PhpParser\Node;
88
use PhpParser\Node\Expr;
99
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\ClassMethod;
1011
use PhpParser\Node\Stmt\Return_;
1112
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
13+
use PHPStan\Type\Type;
14+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
1215
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
1316
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
1417
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
@@ -17,6 +20,8 @@
1720
use Rector\StaticTypeMapper\StaticTypeMapper;
1821
use Rector\TypeDeclarationDocblocks\NodeFinder\DataProviderMethodsFinder;
1922
use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder;
23+
use Rector\TypeDeclarationDocblocks\NodeFinder\YieldNodeFinder;
24+
use Rector\TypeDeclarationDocblocks\TypeResolver\YieldTypeResolver;
2025
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2126
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2227

@@ -32,7 +37,9 @@ public function __construct(
3237
private readonly ReturnNodeFinder $returnNodeFinder,
3338
private readonly StaticTypeMapper $staticTypeMapper,
3439
private readonly DocBlockUpdater $docBlockUpdater,
35-
private readonly TypeNormalizer $typeNormalizer
40+
private readonly TypeNormalizer $typeNormalizer,
41+
private readonly YieldTypeResolver $yieldTypeResolver,
42+
private readonly YieldNodeFinder $yieldNodeFinder,
3643
) {
3744
}
3845

@@ -125,26 +132,30 @@ public function refactor(Node $node): ?Node
125132
$soleReturn = $this->returnNodeFinder->findOnlyReturnWithExpr($dataProviderClassMethod);
126133

127134
// unable to resolve type
128-
if (! $soleReturn instanceof Return_) {
129-
continue;
130-
}
135+
if ($soleReturn instanceof Return_) {
136+
if (! $soleReturn->expr instanceof Expr) {
137+
continue;
138+
}
131139

132-
if (! $soleReturn->expr instanceof Expr) {
133-
continue;
134-
}
140+
$soleReturnType = $this->getType($soleReturn->expr);
135141

136-
$soleReturnType = $this->getType($soleReturn->expr);
142+
$this->addGeneratedTypeReturnDocblockType(
143+
$soleReturnType,
144+
$classMethodPhpDocInfo,
145+
$dataProviderClassMethod
146+
);
137147

138-
$generalizedReturnType = $this->typeNormalizer->generalizeConstantBoolTypes($soleReturnType);
139-
140-
// turn into rather generic short return type, to keep it open to extension later and readable to human
141-
$docType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($generalizedReturnType);
148+
$hasChanged = true;
149+
continue;
150+
}
142151

143-
$returnTagValueNode = new ReturnTagValueNode($docType, '');
144-
$classMethodPhpDocInfo->addTagValueNode($returnTagValueNode);
152+
$yields = $this->yieldNodeFinder->find($dataProviderClassMethod);
153+
if ($yields !== []) {
154+
$yieldType = $this->yieldTypeResolver->resolveFromYieldNodes($yields, $dataProviderClassMethod);
145155

146-
$hasChanged = true;
147-
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($dataProviderClassMethod);
156+
$this->addGeneratedTypeReturnDocblockType($yieldType, $classMethodPhpDocInfo, $dataProviderClassMethod);
157+
$hasChanged = true;
158+
}
148159
}
149160

150161
if (! $hasChanged) {
@@ -153,4 +164,20 @@ public function refactor(Node $node): ?Node
153164

154165
return $node;
155166
}
167+
168+
private function addGeneratedTypeReturnDocblockType(
169+
Type $soleReturnType,
170+
PhpDocInfo $classMethodPhpDocInfo,
171+
ClassMethod $dataProviderClassMethod
172+
): void {
173+
$generalizedReturnType = $this->typeNormalizer->generalizeConstantBoolTypes($soleReturnType);
174+
175+
// turn into rather generic short return type, to keep it open to extension later and readable to human
176+
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($generalizedReturnType);
177+
178+
$returnTagValueNode = new ReturnTagValueNode($typeNode, '');
179+
$classMethodPhpDocInfo->addTagValueNode($returnTagValueNode);
180+
181+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($dataProviderClassMethod);
182+
}
156183
}

0 commit comments

Comments
 (0)