Skip to content

Commit 13ca0a6

Browse files
committed
fixup! fixup! fixup! fixup! [tdd] Add AddParamArrayDocblockBasedOnArrayMapRector
1 parent 39c70bf commit 13ca0a6

1 file changed

Lines changed: 105 additions & 3 deletions

File tree

rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockBasedOnArrayMapRector.php

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@
55
namespace Rector\TypeDeclarationDocblocks\Rector\ClassMethod;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Identifier;
89
use PhpParser\Node\Stmt\ClassMethod;
910
use PhpParser\Node\Stmt\Function_;
11+
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
12+
use PHPStan\Type\ArrayType;
13+
use PHPStan\Type\MixedType;
14+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
15+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
1016
use Rector\PhpParser\Node\BetterNodeFinder;
1117
use Rector\Rector\AbstractRector;
18+
use Rector\StaticTypeMapper\StaticTypeMapper;
1219
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1320
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
1421

1522
final class AddParamArrayDocblockBasedOnArrayMapRector extends AbstractRector
1623
{
1724
public function __construct(
18-
private readonly BetterNodeFinder $betterNodeFinder
25+
private readonly BetterNodeFinder $betterNodeFinder,
26+
private readonly StaticTypeMapper $staticTypeMapper,
27+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
28+
private readonly DocBlockUpdater $docBlockUpdater,
1929
) {
2030

2131
}
@@ -67,16 +77,108 @@ public function refactor(Node $node)
6777
return null;
6878
}
6979

80+
$hasChanged = false;
81+
$functionPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
82+
7083
foreach ($node->params as $param) {
7184
// handle only arrays
72-
if (! $param->type instanceof Node\Identifier || ! $this->isName($param->type, 'array')) {
85+
if (! $param->type instanceof Identifier || ! $this->isName($param->type, 'array')) {
7386
continue;
7487
}
7588

76-
// find array_map usage
7789
$paramName = $this->getName($param->var);
7890

91+
$arrayMapClosures = $this->findArrayMapFuncCallClosuresByVariableName($node, $paramName);
92+
if ($arrayMapClosures === []) {
93+
continue;
94+
}
95+
96+
foreach ($arrayMapClosures as $arrayMapClosure) {
97+
$params = $arrayMapClosure->getParams();
98+
if ($params===[]) {
99+
continue;
100+
}
101+
102+
$firstParam = $params[0];
103+
$paramTypeNode = $firstParam->type;
104+
105+
if ($paramTypeNode === null) {
106+
continue;
107+
}
108+
109+
$paramType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($paramTypeNode);
110+
111+
$paramDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($paramType);
112+
113+
$currentParamType = $functionPhpDocInfo->getParamType($paramName);
114+
115+
// has alreayd better type than arary?
116+
if (! $currentParamType instanceof MixedType && ($currentParamType instanceof ArrayType && (! $currentParamType->getItemType() instanceof MixedType || ! $currentParamType->getKeyType() instanceof MixedType ))){
117+
continue;
118+
}
119+
120+
$paramTagValueNode = new ParamTagValueNode(
121+
$paramDocTypeNode,
122+
$param->variadic,
123+
'$' . $paramName,
124+
'',
125+
$param->byRef
126+
);
127+
$functionPhpDocInfo->addTagValueNode($paramTagValueNode);
128+
129+
$hasChanged = true;
130+
}
131+
132+
}
133+
134+
if (! $hasChanged) {
135+
return null;
136+
}
137+
138+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
139+
140+
return $node;
141+
}
142+
143+
/**
144+
* @return array<Node\Expr\Closure|Node\Expr\ArrowFunction>
145+
*/
146+
private function findArrayMapFuncCallClosuresByVariableName(ClassMethod|Function_|Node $node, string $paramName): array
147+
{
148+
// find array_map usage
149+
/** @var Node\Expr\FuncCall[] $funcCalls */
150+
$funcCalls = $this->betterNodeFinder->findInstancesOfScoped($node->stmts, Node\Expr\FuncCall::class);
151+
152+
$arrayMapClosures = [];
153+
154+
foreach ($funcCalls as $funcCall) {
155+
if (!$this->isName($funcCall, 'array_map')) {
156+
continue;
157+
}
158+
159+
if (!$funcCall->isFirstClassCallable()) {
160+
continue;
161+
}
162+
163+
$secondArg = $funcCall->getArgs()[1];
164+
if (!$secondArg->value instanceof Node\Expr\Variable) {
165+
continue;
166+
}
167+
168+
if (!$this->isName($secondArg->value, $paramName)) {
169+
continue;
170+
}
171+
172+
$firstArg = $funcCall->getArgs()[1];
173+
174+
175+
if (!$firstArg->value instanceof Node\Expr\Closure && !$firstArg->value instanceof Node\Expr\ArrowFunction) {
176+
continue;
177+
}
178+
179+
$arrayMapClosures[] = $firstArg->value;
79180
}
80181

182+
return $arrayMapClosures;
81183
}
82184
}

0 commit comments

Comments
 (0)