Skip to content

Commit 4ffd470

Browse files
authored
[code-quality] Extract standalone SortAttributeNamedArgsRector from SortNamedParamRector, to slightly different areas (#7750)
1 parent 458f17a commit 4ffd470

File tree

12 files changed

+264
-101
lines changed

12 files changed

+264
-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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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\CodeSample\CodeSample;
15+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
16+
17+
/**
18+
* @see \Rector\Tests\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector\SortAttributeNamedArgsRectorTest
19+
*/
20+
final class SortAttributeNamedArgsRector extends AbstractRector
21+
{
22+
public function __construct(
23+
private readonly ArgsAnalyzer $argsAnalyzer,
24+
private readonly ReflectionResolver $reflectionResolver,
25+
private readonly NamedArgsSorter $namedArgsSorter
26+
) {
27+
28+
}
29+
30+
public function getRuleDefinition(): RuleDefinition
31+
{
32+
return new RuleDefinition('Sort named arguments in PHP 8 attributes to match their declaration order', [
33+
new CodeSample(
34+
<<<'CODE_SAMPLE'
35+
#[SomeAttribute(bar: $bar, foo: $foo)]
36+
class SomeClass
37+
{
38+
}
39+
40+
#[Attribute]
41+
class SomeAttribute
42+
{
43+
public function __construct(public $foo, public $bar)
44+
{
45+
}
46+
}
47+
CODE_SAMPLE
48+
,
49+
<<<'CODE_SAMPLE'
50+
#[SomeAttribute(foo: $foo, bar: $bar)]
51+
class SomeClass
52+
{
53+
}
54+
55+
#[Attribute]
56+
class SomeAttribute
57+
{
58+
public function __construct(public $foo, public $bar)
59+
{
60+
}
61+
}
62+
CODE_SAMPLE
63+
),
64+
65+
]);
66+
}
67+
68+
public function getNodeTypes(): array
69+
{
70+
return [Attribute::class];
71+
}
72+
73+
/**
74+
* @param Node\Attribute $node
75+
*/
76+
public function refactor(Node $node): ?Node
77+
{
78+
$args = $node->args;
79+
if (count($args) <= 1) {
80+
return null;
81+
}
82+
83+
if (! $this->argsAnalyzer->hasNamedArg($args)) {
84+
return null;
85+
}
86+
87+
$functionLikeReflection = $this->reflectionResolver->resolveConstructorReflectionFromAttribute($node);
88+
if (! $functionLikeReflection instanceof MethodReflection) {
89+
return null;
90+
}
91+
92+
$args = $this->namedArgsSorter->sortArgsToMatchReflectionParameters($args, $functionLikeReflection);
93+
if ($node->args === $args) {
94+
return null;
95+
}
96+
97+
$node->args = $args;
98+
99+
return $node;
100+
101+
}
102+
}

0 commit comments

Comments
 (0)