Skip to content

Commit 953c043

Browse files
committed
[code-quality] Extract standalone SortAttributeNamedArgsRector from SortNamedParamRector, to slightly different areas
1 parent 458f17a commit 953c043

File tree

12 files changed

+229
-101
lines changed

12 files changed

+229
-101
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\Fixture;
4+
5+
use Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\Source\MyAttribute;
6+
7+
#[MyAttribute(bar: 1, foo: 2)]
8+
#[MyAttribute(1, baz: 2, bar: 3)]
9+
final class HandleAttribute
10+
{
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\Fixture;
18+
19+
use Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\Source\MyAttribute;
20+
21+
#[MyAttribute(foo: 2, bar: 1)]
22+
#[MyAttribute(1, bar: 3, baz: 2)]
23+
final class HandleAttribute
24+
{
25+
}
26+
27+
?>
Lines changed: 28 additions & 0 deletions
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\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class SortAttributeNamedArgsRectorTest 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+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\Source;
4+
5+
#[\Attribute]
6+
class MyAttribute
7+
{
8+
public function __construct($foo = null, $bar = null, $baz = null)
9+
{
10+
}
11+
}
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\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector;
6+
use Rector\Config\RectorConfig;
7+
8+
return RectorConfig::configure()
9+
->withRules([SortAttributeNamedArgsRector::class]);

rules-tests/CodeQuality/Rector/FuncCall/SortNamedParamRector/Fixture/attribute.php.inc

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\FuncCall\SortNamedParamRector\Fixture;
4+
5+
use Rector\Tests\CodeQuality\Rector\FuncCall\SortNamedParamRector\Source\MyAttribute;
6+
7+
#[MyAttribute(bar: 1, foo: 2)]
8+
#[MyAttribute(1, baz: 2, bar: 3)]
9+
class SkipAttribute
10+
{
11+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodeQuality\NodeManipulator;
6+
7+
use PhpParser\Node\Arg;
8+
use PhpParser\Node\Identifier;
9+
use PHPStan\Reflection\FunctionReflection;
10+
use PHPStan\Reflection\MethodReflection;
11+
use PHPStan\Reflection\ParametersAcceptorSelector;
12+
13+
final class NamedArgsSorter
14+
{
15+
/**
16+
* @param Arg[] $currentArgs
17+
* @return list<Arg>
18+
*/
19+
public function sortArgsToMatchReflectionParameters(
20+
array $currentArgs,
21+
FunctionReflection | MethodReflection $functionLikeReflection,
22+
): array {
23+
$extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors(
24+
$functionLikeReflection->getVariants()
25+
);
26+
27+
$parameters = $extendedParametersAcceptor->getParameters();
28+
29+
$order = [];
30+
foreach ($parameters as $key => $parameter) {
31+
$order[$parameter->getName()] = $key;
32+
}
33+
34+
$sortedArgs = [];
35+
$toSortArgs = [];
36+
foreach ($currentArgs as $currentArg) {
37+
if (! $currentArg->name instanceof Identifier) {
38+
$sortedArgs[] = $currentArg;
39+
continue;
40+
}
41+
42+
$toSortArgs[] = $currentArg;
43+
}
44+
45+
usort(
46+
$toSortArgs,
47+
static function (Arg $arg1, Arg $arg2) use ($order): int {
48+
/** @var Identifier $argName1 */
49+
$argName1 = $arg1->name;
50+
/** @var Identifier $argName2 */
51+
$argName2 = $arg2->name;
52+
53+
$order1 = $order[$argName1->name] ?? PHP_INT_MAX;
54+
$order2 = $order[$argName2->name] ?? PHP_INT_MAX;
55+
56+
return $order1 <=> $order2;
57+
}
58+
);
59+
60+
return [...$sortedArgs, ...$toSortArgs];
61+
}
62+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodeQuality\Rector\Attribute;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Attribute;
9+
use PHPStan\Reflection\MethodReflection;
10+
use Rector\CodeQuality\NodeManipulator\NamedArgsSorter;
11+
use Rector\NodeAnalyzer\ArgsAnalyzer;
12+
use Rector\Rector\AbstractRector;
13+
use Rector\Reflection\ReflectionResolver;
14+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
15+
16+
/**
17+
* @see \Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\SortAttributeNamedArgsRectorTest
18+
*/
19+
final class SortAttributeNamedArgsRector extends AbstractRector
20+
{
21+
public function __construct(
22+
private readonly ArgsAnalyzer $argsAnalyzer,
23+
private readonly ReflectionResolver $reflectionResolver,
24+
private readonly NamedArgsSorter $namedArgsSorter
25+
) {
26+
27+
}
28+
29+
public function getRuleDefinition(): RuleDefinition
30+
{
31+
return new RuleDefinition('Sort named arguments in PHP 8 attributes to match their declaration order', []);
32+
}
33+
34+
public function getNodeTypes(): array
35+
{
36+
return [Attribute::class];
37+
}
38+
39+
/**
40+
* @param Node\Attribute $node
41+
*/
42+
public function refactor(Node $node): ?Node
43+
{
44+
$args = $node->args;
45+
if (count($args) <= 1) {
46+
return null;
47+
}
48+
49+
if (! $this->argsAnalyzer->hasNamedArg($args)) {
50+
return null;
51+
}
52+
53+
$functionLikeReflection = $this->reflectionResolver->resolveConstructorReflectionFromAttribute($node);
54+
if (! $functionLikeReflection instanceof MethodReflection) {
55+
return null;
56+
}
57+
58+
$args = $this->namedArgsSorter->sortArgsToMatchReflectionParameters($args, $functionLikeReflection);
59+
if ($node->args === $args) {
60+
return null;
61+
}
62+
63+
$node->args = $args;
64+
65+
return $node;
66+
67+
}
68+
}

rules/CodeQuality/Rector/FuncCall/SortNamedParamRector.php

Lines changed: 9 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@
55
namespace Rector\CodeQuality\Rector\FuncCall;
66

77
use PhpParser\Node;
8-
use PhpParser\Node\Arg;
9-
use PhpParser\Node\Attribute;
10-
use PhpParser\Node\Expr\CallLike;
118
use PhpParser\Node\Expr\FuncCall;
129
use PhpParser\Node\Expr\MethodCall;
1310
use PhpParser\Node\Expr\New_;
1411
use PhpParser\Node\Expr\StaticCall;
15-
use PhpParser\Node\Identifier;
1612
use PHPStan\Reflection\FunctionReflection;
1713
use PHPStan\Reflection\MethodReflection;
18-
use PHPStan\Reflection\ParametersAcceptorSelector;
14+
use Rector\CodeQuality\NodeManipulator\NamedArgsSorter;
1915
use Rector\NodeAnalyzer\ArgsAnalyzer;
2016
use Rector\Rector\AbstractRector;
2117
use Rector\Reflection\ReflectionResolver;
@@ -29,30 +25,27 @@ final class SortNamedParamRector extends AbstractRector
2925
{
3026
public function __construct(
3127
private readonly ReflectionResolver $reflectionResolver,
32-
private readonly ArgsAnalyzer $argsAnalyzer
28+
private readonly ArgsAnalyzer $argsAnalyzer,
29+
private readonly NamedArgsSorter $namedArgsSorter,
3330
) {
3431
}
3532

3633
public function getRuleDefinition(): RuleDefinition
3734
{
3835
return new RuleDefinition(
39-
'Sort named parameters usage in a function or method call',
36+
'Sort named arguments to match their order in a function or method call or class constructors',
4037
[
4138
new CodeSample(
4239
<<<'CODE_SAMPLE'
4340
function run($foo = null, $bar = null, $baz = null) {}
4441
4542
run(bar: $bar, foo: $foo);
46-
47-
run($foo, baz: $baz, bar: $bar);
4843
CODE_SAMPLE
4944
,
5045
<<<'CODE_SAMPLE'
5146
function run($foo = null, $bar = null, $baz = null) {}
5247
5348
run(foo: $foo, bar: $bar);
54-
55-
run($foo, bar: $bar, baz: $baz);
5649
CODE_SAMPLE
5750
),
5851
]
@@ -64,20 +57,19 @@ function run($foo = null, $bar = null, $baz = null) {}
6457
*/
6558
public function getNodeTypes(): array
6659
{
67-
return [MethodCall::class, StaticCall::class, New_::class, FuncCall::class, Attribute::class];
60+
return [MethodCall::class, StaticCall::class, New_::class, FuncCall::class];
6861
}
6962

7063
/**
71-
* @param MethodCall|StaticCall|New_|FuncCall|Attribute $node
64+
* @param MethodCall|StaticCall|New_|FuncCall $node
7265
*/
7366
public function refactor(Node $node): ?Node
7467
{
75-
if ($node instanceof CallLike && $node->isFirstClassCallable()) {
68+
if ($node->isFirstClassCallable()) {
7669
return null;
7770
}
7871

79-
$args = $node instanceof Attribute ? $node->args : $node->getArgs();
80-
72+
$args = $node->getArgs();
8173
if (count($args) <= 1) {
8274
return null;
8375
}
@@ -88,8 +80,6 @@ public function refactor(Node $node): ?Node
8880

8981
if ($node instanceof New_) {
9082
$functionLikeReflection = $this->reflectionResolver->resolveMethodReflectionFromNew($node);
91-
} elseif ($node instanceof Attribute) {
92-
$functionLikeReflection = $this->reflectionResolver->resolveMethodReflectionFromAttribute($node);
9383
} else {
9484
$functionLikeReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node);
9585
}
@@ -98,7 +88,7 @@ public function refactor(Node $node): ?Node
9888
return null;
9989
}
10090

101-
$args = $this->sortNamedArguments($functionLikeReflection, $args);
91+
$args = $this->namedArgsSorter->sortArgsToMatchReflectionParameters($args, $functionLikeReflection);
10292
if ($node->args === $args) {
10393
return null;
10494
}
@@ -107,52 +97,4 @@ public function refactor(Node $node): ?Node
10797

10898
return $node;
10999
}
110-
111-
/**
112-
* @param Arg[] $currentArgs
113-
* @return Arg[]
114-
*/
115-
public function sortNamedArguments(
116-
FunctionReflection | MethodReflection $functionLikeReflection,
117-
array $currentArgs
118-
): array {
119-
$extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors(
120-
$functionLikeReflection->getVariants()
121-
);
122-
123-
$parameters = $extendedParametersAcceptor->getParameters();
124-
125-
$order = [];
126-
foreach ($parameters as $key => $parameter) {
127-
$order[$parameter->getName()] = $key;
128-
}
129-
130-
$sortedArgs = [];
131-
$toSortArgs = [];
132-
foreach ($currentArgs as $currentArg) {
133-
if (! $currentArg->name instanceof Identifier) {
134-
$sortedArgs[] = $currentArg;
135-
continue;
136-
}
137-
138-
$toSortArgs[] = $currentArg;
139-
}
140-
141-
usort(
142-
$toSortArgs,
143-
static function (Arg $arg1, Arg $arg2) use ($order): int {
144-
/** @var Identifier $argName1 */
145-
$argName1 = $arg1->name;
146-
/** @var Identifier $argName2 */
147-
$argName2 = $arg2->name;
148-
149-
$order1 = $order[$argName1->name] ?? PHP_INT_MAX;
150-
$order2 = $order[$argName2->name] ?? PHP_INT_MAX;
151-
152-
return $order1 <=> $order2;
153-
}
154-
);
155-
156-
return [...$sortedArgs, ...$toSortArgs];
157-
}
158100
}

0 commit comments

Comments
 (0)