Skip to content

Commit 8627ee8

Browse files
Support array_values in ArrayFirstLastRector (#7830)
* Support array_values in ArrayFirstLastRector * Refactor shouldSkip logic in ArrayFirstLastRector * Replace bool cast with explicit return * Fix scope node
1 parent cb73a3a commit 8627ee8

File tree

3 files changed

+142
-8
lines changed

3 files changed

+142
-8
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector\Fixture;
6+
7+
final class ArrayValuesPattern
8+
{
9+
public function run(array $array)
10+
{
11+
echo array_values($array)[0];
12+
echo array_values($array)[count($array) - 1];
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
declare(strict_types=1);
21+
22+
namespace Rector\Tests\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector\Fixture;
23+
24+
final class ArrayValuesPattern
25+
{
26+
public function run(array $array)
27+
{
28+
echo array_first($array);
29+
echo array_last($array);
30+
}
31+
}
32+
33+
?>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Php85\Rector\ArrayDimFetch\ArrayFirstLastRector\Fixture;
6+
7+
final class SkipArrayValuesDifferentVariable
8+
{
9+
public function run(array $array, array $differentVariable)
10+
{
11+
echo array_values($array)[count($differentVariable) - 1];
12+
}
13+
}

rules/Php85/Rector/ArrayDimFetch/ArrayFirstLastRector.php

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
use PhpParser\Node;
88
use PhpParser\Node\Expr\ArrayDimFetch;
9+
use PhpParser\Node\Expr\BinaryOp\Minus;
910
use PhpParser\Node\Expr\FuncCall;
11+
use PhpParser\Node\Scalar\Int_;
1012
use Rector\NodeTypeResolver\Node\AttributeKey;
1113
use Rector\PHPStan\ScopeFetcher;
1214
use Rector\Rector\AbstractRector;
@@ -34,11 +36,15 @@ public function getRuleDefinition(): RuleDefinition
3436
<<<'CODE_SAMPLE'
3537
echo $array[array_key_first($array)];
3638
echo $array[array_key_last($array)];
39+
echo array_values($array)[0];
40+
echo array_values($array)[count($array) - 1];
3741
CODE_SAMPLE
3842
,
3943
<<<'CODE_SAMPLE'
4044
echo array_first($array);
4145
echo array_last($array);
46+
echo array_first($array);
47+
echo array_last($array);
4248
CODE_SAMPLE
4349
),
4450
]
@@ -57,6 +63,24 @@ public function getNodeTypes(): array
5763
* @param ArrayDimFetch $node
5864
*/
5965
public function refactor(Node $node): ?FuncCall
66+
{
67+
if ($node->dim instanceof FuncCall) {
68+
return $this->refactorArrayKeyPattern($node);
69+
}
70+
71+
if ($node->var instanceof FuncCall && ($node->dim instanceof Int_ || $node->dim instanceof Minus)) {
72+
return $this->refactorArrayValuesPattern($node);
73+
}
74+
75+
return null;
76+
}
77+
78+
public function provideMinPhpVersion(): int
79+
{
80+
return PhpVersionFeature::ARRAY_FIRST_LAST;
81+
}
82+
83+
private function refactorArrayKeyPattern(ArrayDimFetch $node): ?FuncCall
6084
{
6185
if (! $node->dim instanceof FuncCall) {
6286
return null;
@@ -78,12 +102,7 @@ public function refactor(Node $node): ?FuncCall
78102
return null;
79103
}
80104

81-
$scope = ScopeFetcher::fetch($node->var);
82-
if ($scope->isInExpressionAssign($node)) {
83-
return null;
84-
}
85-
86-
if ($node->getAttribute(AttributeKey::IS_UNSET_VAR)) {
105+
if ($this->shouldSkip($node, $node->var)) {
87106
return null;
88107
}
89108

@@ -94,8 +113,77 @@ public function refactor(Node $node): ?FuncCall
94113
return $this->nodeFactory->createFuncCall($functionName, [$node->var]);
95114
}
96115

97-
public function provideMinPhpVersion(): int
116+
private function refactorArrayValuesPattern(ArrayDimFetch $node): ?FuncCall
98117
{
99-
return PhpVersionFeature::ARRAY_FIRST_LAST;
118+
if (! $node->var instanceof FuncCall) {
119+
return null;
120+
}
121+
122+
if (! $this->isName($node->var, 'array_values')) {
123+
return null;
124+
}
125+
126+
if ($node->var->isFirstClassCallable()) {
127+
return null;
128+
}
129+
130+
if (count($node->var->getArgs()) !== 1) {
131+
return null;
132+
}
133+
134+
if ($this->shouldSkip($node, $node)) {
135+
return null;
136+
}
137+
138+
$arrayArg = $node->var->getArgs()[0]
139+
->value;
140+
141+
if ($node->dim instanceof Int_ && $node->dim->value === 0) {
142+
return $this->nodeFactory->createFuncCall('array_first', [$arrayArg]);
143+
}
144+
145+
if ($node->dim instanceof Minus) {
146+
if (! $node->dim->left instanceof FuncCall) {
147+
return null;
148+
}
149+
150+
if (! $this->isName($node->dim->left, 'count')) {
151+
return null;
152+
}
153+
154+
if ($node->dim->left->isFirstClassCallable()) {
155+
return null;
156+
}
157+
158+
if (count($node->dim->left->getArgs()) !== 1) {
159+
return null;
160+
}
161+
162+
if (! $node->dim->right instanceof Int_ || $node->dim->right->value !== 1) {
163+
return null;
164+
}
165+
166+
if (! $this->nodeComparator->areNodesEqual($arrayArg, $node->dim->left->getArgs()[0]->value)) {
167+
return null;
168+
}
169+
170+
return $this->nodeFactory->createFuncCall('array_last', [$arrayArg]);
171+
}
172+
173+
return null;
174+
}
175+
176+
private function shouldSkip(ArrayDimFetch $node, Node $scopeNode): bool
177+
{
178+
$scope = ScopeFetcher::fetch($scopeNode);
179+
if ($scope->isInExpressionAssign($node)) {
180+
return true;
181+
}
182+
183+
if ($node->getAttribute(AttributeKey::IS_UNSET_VAR)) {
184+
return true;
185+
}
186+
187+
return false;
100188
}
101189
}

0 commit comments

Comments
 (0)