Skip to content

Commit ab5fdc5

Browse files
[type-declaration] Add AddArrayFilterClosureParamTypeRector (#7111)
* [type-declaration] Add AddArrayFilterClosureParamTypeRector * [enum] re-use existing const * [ci-review] Rector Rectify --------- Co-authored-by: GitHub Action <actions@github.com>
1 parent 34b45d4 commit ab5fdc5

File tree

12 files changed

+321
-32
lines changed

12 files changed

+321
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddArrayFunctionClosureParamTypeRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
4+
5+
final class ArrayFilterWithDocblockType
6+
{
7+
/**
8+
* @param int[] $items
9+
*/
10+
public function run(array $items)
11+
{
12+
$result = array_filter($items, fn ($item) => $item * 2);
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
21+
22+
final class ArrayFilterWithDocblockType
23+
{
24+
/**
25+
* @param int[] $items
26+
*/
27+
public function run(array $items)
28+
{
29+
$result = array_filter($items, fn (int $item) => $item * 2);
30+
}
31+
}
32+
33+
?>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
4+
5+
final class IncludeArrayMap
6+
{
7+
/**
8+
* @param int[] $items
9+
*/
10+
public function run(array $items)
11+
{
12+
$result = array_map(fn ($item) => $item * 2, $items);
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
21+
22+
final class IncludeArrayMap
23+
{
24+
/**
25+
* @param int[] $items
26+
*/
27+
public function run(array $items)
28+
{
29+
$result = array_map(fn (int $item) => $item * 2, $items);
30+
}
31+
}
32+
33+
?>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
4+
5+
final class SkipUnclearParam
6+
{
7+
/**
8+
* @param mixed[] $items
9+
*/
10+
public function run(array $items)
11+
{
12+
$result = array_filter($items, fn ($item) => $item * 2);
13+
}
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
4+
5+
final class SomeFunction
6+
{
7+
public function run()
8+
{
9+
$array = [1, 2, 3];
10+
$result = array_filter($array, fn ($item) => $item * 2);
11+
}
12+
}
13+
14+
?>
15+
-----
16+
<?php
17+
18+
namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\Fixture;
19+
20+
final class SomeFunction
21+
{
22+
public function run()
23+
{
24+
$array = [1, 2, 3];
25+
$result = array_filter($array, fn (int $item) => $item * 2);
26+
}
27+
}
28+
29+
?>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddArrayFunctionClosureParamTypeRector::class]);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclaration\Enum;
6+
7+
final class NativeFuncCallPositions
8+
{
9+
/**
10+
* @var array<string, array<string, int>>
11+
*/
12+
public const ARRAY_AND_CALLBACK_POSITIONS = [
13+
'array_walk' => [
14+
'array' => 0,
15+
'callback' => 1,
16+
],
17+
'array_map' => [
18+
'array' => 1,
19+
'callback' => 0,
20+
],
21+
'usort' => [
22+
'array' => 0,
23+
'callback' => 1,
24+
],
25+
'array_filter' => [
26+
'array' => 0,
27+
'callback' => 1,
28+
],
29+
];
30+
}

rules/TypeDeclaration/Rector/ClassMethod/AddParamArrayDocblockBasedOnCallableNativeFuncCallRector.php

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Rector\NodeAnalyzer\ArgsAnalyzer;
2626
use Rector\Rector\AbstractRector;
2727
use Rector\StaticTypeMapper\StaticTypeMapper;
28+
use Rector\TypeDeclaration\Enum\NativeFuncCallPositions;
2829
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2930
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
3031

@@ -33,28 +34,6 @@
3334
*/
3435
final class AddParamArrayDocblockBasedOnCallableNativeFuncCallRector extends AbstractRector
3536
{
36-
/**
37-
* @var array<string, array<string, int>>
38-
*/
39-
private const NATIVE_FUNC_CALLS_WITH_POSITION = [
40-
'array_walk' => [
41-
'array' => 0,
42-
'callback' => 1,
43-
],
44-
'array_map' => [
45-
'array' => 1,
46-
'callback' => 0,
47-
],
48-
'usort' => [
49-
'array' => 0,
50-
'callback' => 1,
51-
],
52-
'array_filter' => [
53-
'array' => 0,
54-
'callback' => 1,
55-
],
56-
];
57-
5837
public function __construct(
5938
private readonly PhpDocInfoFactory $phpDocInfoFactory,
6039
private readonly ArgsAnalyzer $argsAnalyzer,
@@ -70,8 +49,8 @@ public function getRuleDefinition(): RuleDefinition
7049
<<<'CODE_SAMPLE'
7150
function process(array $items): void
7251
{
73-
array_walk($items, function (stdClass $item) {
74-
echo $item->value;
52+
array_walk($items, function (stdClass $item) {
53+
echo $item->value;
7554
});
7655
}
7756
CODE_SAMPLE
@@ -82,8 +61,8 @@ function process(array $items): void
8261
*/
8362
function process(array $items): void
8463
{
85-
array_walk($items, function (stdClass $item) {
86-
echo $item->value;
64+
array_walk($items, function (stdClass $item) {
65+
echo $item->value;
8766
});
8867
}
8968
CODE_SAMPLE
@@ -131,7 +110,7 @@ function (Node $subNode) use ($variableNamesWithArrayType, $node, &$paramsWithTy
131110
return null;
132111
}
133112

134-
if (! $this->isNames($subNode, array_keys(self::NATIVE_FUNC_CALLS_WITH_POSITION))) {
113+
if (! $this->isNames($subNode, array_keys(NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS))) {
135114
return null;
136115
}
137116

@@ -150,7 +129,7 @@ function (Node $subNode) use ($variableNamesWithArrayType, $node, &$paramsWithTy
150129

151130
$funcCallName = (string) $this->getName($subNode);
152131

153-
$arrayArgValue = $args[self::NATIVE_FUNC_CALLS_WITH_POSITION[$funcCallName]['array']]->value;
132+
$arrayArgValue = $args[NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS[$funcCallName]['array']]->value;
154133
if (! $arrayArgValue instanceof Variable) {
155134
return null;
156135
}
@@ -167,7 +146,7 @@ function (Node $subNode) use ($variableNamesWithArrayType, $node, &$paramsWithTy
167146
return null;
168147
}
169148

170-
$callbackArgValue = $args[self::NATIVE_FUNC_CALLS_WITH_POSITION[$funcCallName]['callback']]->value;
149+
$callbackArgValue = $args[NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS[$funcCallName]['callback']]->value;
171150

172151
if (! $callbackArgValue instanceof ArrowFunction && ! $callbackArgValue instanceof Closure) {
173152
return null;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclaration\Rector\FuncCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\ArrowFunction;
9+
use PhpParser\Node\Expr\Closure;
10+
use PhpParser\Node\Expr\FuncCall;
11+
use PHPStan\Type\ArrayType;
12+
use PHPStan\Type\Constant\ConstantArrayType;
13+
use PHPStan\Type\MixedType;
14+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\StaticTypeMapper\StaticTypeMapper;
17+
use Rector\TypeDeclaration\Enum\NativeFuncCallPositions;
18+
use Rector\ValueObject\PhpVersionFeature;
19+
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
20+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
21+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
22+
23+
/**
24+
* @see \Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrayFunctionClosureParamTypeRector\AddArrayFunctionClosureParamTypeRectorTest
25+
*/
26+
final class AddArrayFunctionClosureParamTypeRector extends AbstractRector implements MinPhpVersionInterface
27+
{
28+
public function __construct(
29+
private readonly StaticTypeMapper $staticTypeMapper
30+
) {
31+
}
32+
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition(
36+
'Add array_filter()/array_map() function closure param type, based on passed iterable',
37+
[
38+
new CodeSample(
39+
<<<'CODE_SAMPLE'
40+
$items = [1, 2, 3];
41+
$result = array_filter($items, fn ($item) => $item > 1);
42+
CODE_SAMPLE
43+
44+
,
45+
<<<'CODE_SAMPLE'
46+
$items = [1, 2, 3];
47+
$result = array_filter($items, fn (int $item) => $item > 1
48+
CODE_SAMPLE
49+
),
50+
51+
]
52+
);
53+
}
54+
55+
/**
56+
* @return array<class-string<Node>>
57+
*/
58+
public function getNodeTypes(): array
59+
{
60+
return [FuncCall::class];
61+
}
62+
63+
/**
64+
* @param FuncCall $node
65+
*/
66+
public function refactor(Node $node): ?Node
67+
{
68+
foreach (NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS as $functionName => $positions) {
69+
if (! $this->isName($node, $functionName)) {
70+
continue;
71+
}
72+
73+
if ($node->isFirstClassCallable()) {
74+
continue;
75+
}
76+
77+
$arrayPosition = $positions['array'];
78+
$callbackPosition = $positions['callback'];
79+
80+
$firstArgExpr = $node->getArgs()[$callbackPosition]
81+
->value;
82+
if (! $firstArgExpr instanceof ArrowFunction && ! $firstArgExpr instanceof Closure) {
83+
continue;
84+
}
85+
86+
$arrowFunction = $firstArgExpr;
87+
$arrowFunctionParam = $arrowFunction->getParams()[0];
88+
89+
// param is known already
90+
if ($arrowFunctionParam->type instanceof Node) {
91+
continue;
92+
}
93+
94+
$passedExprType = $this->getType($node->getArgs()[$arrayPosition]->value);
95+
if ($passedExprType instanceof ConstantArrayType || $passedExprType instanceof ArrayType) {
96+
$singlePassedExprType = $passedExprType->getItemType();
97+
98+
if ($singlePassedExprType instanceof MixedType) {
99+
continue;
100+
}
101+
102+
$paramType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
103+
$singlePassedExprType,
104+
TypeKind::PARAM
105+
);
106+
107+
if (! $paramType instanceof Node) {
108+
continue;
109+
}
110+
111+
$arrowFunctionParam->type = $paramType;
112+
113+
return $node;
114+
}
115+
116+
return null;
117+
}
118+
119+
return $node;
120+
}
121+
122+
public function provideMinPhpVersion(): int
123+
{
124+
return PhpVersionFeature::SCALAR_TYPES;
125+
}
126+
}

0 commit comments

Comments
 (0)