Skip to content

Commit 48cf807

Browse files
authored
[symfony 7.3] Add CommandDefaultNameAndDescriptionToAsCommandAttributeRector (#892)
1 parent 7b1246e commit 48cf807

7 files changed

Lines changed: 219 additions & 7 deletions

File tree

config/sets/symfony/symfony7/symfony73/symfony73-console.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
declare(strict_types=1);
44

55
use Rector\Config\RectorConfig;
6+
use Rector\Symfony\Symfony73\Rector\Class_\CommandDefaultNameAndDescriptionToAsCommandAttributeRector;
67
use Rector\Symfony\Symfony73\Rector\Class_\CommandHelpToAttributeRector;
78
use Rector\Symfony\Symfony73\Rector\Class_\InvokableCommandInputAttributeRector;
89

910
return static function (RectorConfig $rectorConfig): void {
10-
$rectorConfig->rules([CommandHelpToAttributeRector::class, InvokableCommandInputAttributeRector::class]);
11+
$rectorConfig->rules([
12+
CommandHelpToAttributeRector::class,
13+
InvokableCommandInputAttributeRector::class,
14+
CommandDefaultNameAndDescriptionToAsCommandAttributeRector::class,
15+
]);
1116
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Tests\Symfony73\Rector\Class_\CommandDefaultNameAndDescriptionToAsCommandAttributeRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class CommandDefaultNameAndDescriptionToAsCommandAttributeRectorTest 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\Symfony\Tests\Symfony73\Rector\Class_\CommandDefaultNameAndDescriptionToAsCommandAttributeRector\Fixture;
4+
5+
use Symfony\Component\Console\Command\Command;
6+
7+
final class SomeCommandWithDefaults extends Command
8+
{
9+
public static function getDefaultName(): string
10+
{
11+
return 'app:some-command';
12+
}
13+
14+
public static function getDefaultDescription(): string
15+
{
16+
return 'This is some command description';
17+
}
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace Rector\Symfony\Tests\Symfony73\Rector\Class_\CommandDefaultNameAndDescriptionToAsCommandAttributeRector\Fixture;
25+
26+
use Symfony\Component\Console\Command\Command;
27+
28+
#[\Symfony\Component\Console\Attribute\AsCommand(name: 'app:some-command', description: 'This is some command description')]
29+
final class SomeCommandWithDefaults extends Command
30+
{
31+
}
32+
33+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Symfony\Symfony73\Rector\Class_\CommandDefaultNameAndDescriptionToAsCommandAttributeRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(CommandDefaultNameAndDescriptionToAsCommandAttributeRector::class);
10+
};

rules/CodeQuality/Rector/Class_/ControllerMethodInjectionToConstructorRector.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function __construct(
4646
private readonly StaticTypeMapper $staticTypeMapper,
4747
private readonly AttributeFinder $attributeFinder,
4848
private readonly ValueResolver $valueResolver,
49-
private readonly ParentClassMethodTypeOverrideGuard $parentClassMethodOverrideGuard
49+
private readonly ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard
5050
) {
5151
}
5252

@@ -228,9 +228,7 @@ private function shouldSkipClassMethod(ClassMethod $classMethod): bool
228228
return true;
229229
}
230230

231-
return $this->parentClassMethodOverrideGuard->hasParentClassMethod(
232-
$classMethod
233-
);
231+
return $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod);
234232
}
235233

236234
/**

rules/DowngradeSymfony70/Rector/Class_/DowngradeSymfonyCommandAttributeRector.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Reflection\ClassReflection;
1717
use Rector\Rector\AbstractRector;
1818
use Rector\Reflection\ReflectionResolver;
19+
use Rector\Symfony\Enum\SymfonyAttribute;
1920
use Rector\ValueObject\Visibility;
2021
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2122
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -111,7 +112,7 @@ public function refactor(Node $node): ?Node
111112

112113
foreach ($node->attrGroups as $keyAttribute => $attrGroup) {
113114
foreach ($attrGroup->attrs as $key => $attr) {
114-
if ($attr->name->toString() === 'Symfony\Component\Console\Attribute\AsCommand') {
115+
if ($this->isName($attr->name, SymfonyAttribute::AS_COMMAND)) {
115116
unset($attrGroup->attrs[$key]);
116117
}
117118
}
@@ -134,7 +135,7 @@ private function resolveNameAndDescription(Class_ $class): array
134135

135136
foreach ($class->attrGroups as $attrGroup) {
136137
foreach ($attrGroup->attrs as $attr) {
137-
if ($attr->name->toString() !== 'Symfony\Component\Console\Attribute\AsCommand') {
138+
if (! $this->isName($attr->name, SymfonyAttribute::AS_COMMAND)) {
138139
continue;
139140
}
140141

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Symfony73\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Attribute;
10+
use PhpParser\Node\AttributeGroup;
11+
use PhpParser\Node\Expr;
12+
use PhpParser\Node\Identifier;
13+
use PhpParser\Node\Name\FullyQualified;
14+
use PhpParser\Node\Stmt\Class_;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use PhpParser\Node\Stmt\Return_;
17+
use PHPStan\Type\ObjectType;
18+
use Rector\Rector\AbstractRector;
19+
use Rector\Symfony\Enum\SymfonyAttribute;
20+
use Rector\Symfony\Enum\SymfonyClass;
21+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
22+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
23+
24+
/**
25+
* @see https://github.com/symfony/symfony/blob/7.4/UPGRADE-7.3.md#console
26+
*
27+
* @see \Rector\Symfony\Tests\Symfony73\Rector\Class_\CommandDefaultNameAndDescriptionToAsCommandAttributeRector\CommandDefaultNameAndDescriptionToAsCommandAttributeRectorTest
28+
*/
29+
final class CommandDefaultNameAndDescriptionToAsCommandAttributeRector extends AbstractRector
30+
{
31+
public function getRuleDefinition(): RuleDefinition
32+
{
33+
return new RuleDefinition(
34+
'Replace getDefaultName() and getDefaultDescription() by #[AsCommand] attribute',
35+
[
36+
new CodeSample(
37+
<<<'CODE_SAMPLE'
38+
use Symfony\Component\Console\Command\Command;
39+
40+
final class SomeCommand extends Command
41+
{
42+
public static function getDefaultName(): string
43+
{
44+
return 'app:some-command';
45+
}
46+
47+
public static function getDefaultDescription(): string
48+
{
49+
return 'This is some command description';
50+
}
51+
}
52+
CODE_SAMPLE
53+
,
54+
<<<'CODE_SAMPLE'
55+
use Symfony\Component\Console\Attribute\AsCommand;
56+
use Symfony\Component\Console\Command\Command;
57+
58+
#[AsCommand(
59+
name: 'app:some-command',
60+
description: 'This is some command description'
61+
)]
62+
final class SomeCommand extends Command
63+
{
64+
}
65+
CODE_SAMPLE
66+
),
67+
68+
]
69+
);
70+
}
71+
72+
/**
73+
* @return array<class-string<Node>>
74+
*/
75+
public function getNodeTypes(): array
76+
{
77+
return [Class_::class];
78+
}
79+
80+
/**
81+
* @param Class_ $node
82+
*/
83+
public function refactor(Node $node): ?Class_
84+
{
85+
if (! $this->isObjectType($node, new ObjectType(SymfonyClass::COMMAND))) {
86+
return null;
87+
}
88+
89+
$defaultName = null;
90+
$defaultDescription = null;
91+
92+
foreach ($node->stmts as $key => $classStmt) {
93+
if (! $classStmt instanceof ClassMethod) {
94+
continue;
95+
}
96+
97+
if ($classStmt->stmts === null) {
98+
continue;
99+
}
100+
101+
if ($this->isName($classStmt, 'getDefaultName')) {
102+
$soleStmt = $classStmt->stmts[0];
103+
if ($soleStmt instanceof Return_) {
104+
$defaultName = $soleStmt->expr;
105+
unset($node->stmts[$key]);
106+
}
107+
}
108+
109+
if ($this->isName($classStmt, 'getDefaultDescription')) {
110+
$soleStmt = $classStmt->stmts[0];
111+
if ($soleStmt instanceof Return_) {
112+
$defaultDescription = $soleStmt->expr;
113+
unset($node->stmts[$key]);
114+
}
115+
}
116+
}
117+
118+
if (! $defaultName instanceof Expr && ! $defaultDescription instanceof Expr) {
119+
return null;
120+
}
121+
122+
$args = [];
123+
if ($defaultName instanceof Expr) {
124+
$args[] = new Arg($defaultName, false, false, [], new Identifier('name'));
125+
}
126+
127+
if ($defaultDescription instanceof Expr) {
128+
$args[] = new Arg($defaultDescription, false, false, [], new Identifier('description'));
129+
}
130+
131+
$node->attrGroups[] = new AttributeGroup([
132+
new Attribute(new FullyQualified(SymfonyAttribute::AS_COMMAND), $args),
133+
]);
134+
135+
return $node;
136+
}
137+
}

0 commit comments

Comments
 (0)