Skip to content

Commit a53fae6

Browse files
committed
[symfony 7.3] Add InvokableCommandRector
1 parent d4e5a0f commit a53fae6

File tree

13 files changed

+450
-14
lines changed

13 files changed

+450
-14
lines changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
"rector/rector-src": "dev-main",
1616
"symfony/config": "^6.4",
1717
"symfony/dependency-injection": "^6.4",
18-
"symfony/http-kernel": "~6.3",
18+
"symfony/http-kernel": "^6.4",
1919
"symfony/routing": "^6.4",
2020
"symfony/security-core": "^6.4",
2121
"symfony/security-http": "^6.4",
2222
"symfony/validator": "^6.4",
23-
"symplify/easy-coding-standard": "^12.3",
23+
"phpecs/phpecs": "^2.0.1",
2424
"symplify/vendor-patches": "^11.3",
25-
"tomasvotruba/class-leak": "^1.0",
25+
"tomasvotruba/class-leak": "^2.0",
2626
"tracy/tracy": "^2.10"
2727
},
2828
"autoload": {

config/sets/symfony/symfony73.php

Lines changed: 10 additions & 0 deletions
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+
7+
// @see https://github.com/symfony/symfony/blame/7.3/UPGRADE-7.3.md
8+
9+
return RectorConfig::configure()
10+
->withRules([]);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Rector\Symfony\Tests\Symfony73\Rector\Class_\InvokableCommandRector\Fixture;
4+
5+
use Symfony\Component\Console\Attribute\AsCommand;
6+
use Symfony\Component\Console\Command\Command;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\OutputInterface;
9+
use Symfony\Component\Console\Input\InputArgument;
10+
use Symfony\Component\Console\Input\InputOption;
11+
12+
#[AsCommand(name: 'some_name')]
13+
final class SomeCommand extends Command
14+
{
15+
public function configure()
16+
{
17+
$this->addArgument('argument', InputArgument::REQUIRED, 'Argument description');
18+
$this->addOption('option', 'o', InputOption::VALUE_NONE, 'Option description');
19+
}
20+
21+
public function execute(InputInterface $input, OutputInterface $output)
22+
{
23+
$someArgument = $input->getArgument('argument');
24+
$someOption = $input->getOption('option');
25+
26+
// ...
27+
28+
return 1;
29+
}
30+
}
31+
32+
?>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Rector\Symfony\Tests\Symfony73\Rector\Class_\InvokableCommandRector\Fixture;
4+
5+
use Symfony\Component\Console\Attribute\AsCommand;
6+
use Symfony\Component\Console\Command\Command;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\OutputInterface;
9+
use Symfony\Component\Console\Input\InputArgument;
10+
use Symfony\Component\Console\Input\InputOption;
11+
12+
#[AsCommand(name: 'some_name')]
13+
final class SomeCommand extends Command
14+
{
15+
public function configure()
16+
{
17+
$this->addArgument('argument', InputArgument::REQUIRED, 'Argument description');
18+
$this->addOption('option', 'o', InputOption::VALUE_NONE, 'Option description');
19+
}
20+
21+
public function execute(InputInterface $input, OutputInterface $output)
22+
{
23+
$someArgument = $input->getArgument('argument');
24+
$someOption = $input->getOption('option');
25+
26+
// ...
27+
28+
return 1;
29+
}
30+
}
31+
32+
?>
33+
-----
34+
<?php
35+
36+
namespace Rector\Symfony\Tests\Symfony73\Rector\Class_\InvokableCommandRector\Fixture;
37+
38+
use Symfony\Component\Console\Command\Command;
39+
use Symfony\Component\Console\Command\Argument;
40+
use Symfony\Component\Console\Command\Option;
41+
42+
final class SomeCommand
43+
{
44+
public function __invoke(
45+
#[Argument]
46+
string $argument,
47+
#[Option]
48+
bool $option = false,
49+
) {
50+
$someArgument = $argument;
51+
$someOption = $option;
52+
53+
// ...
54+
55+
return 1;
56+
}
57+
}
58+
59+
?>
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\Symfony\Tests\Symfony73\Rector\Class_\InvokableCommandRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class InvokableCommandRectorTest 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: 10 additions & 0 deletions
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_\InvokableCommandRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(InvokableCommandRector::class);
10+
};

rules/Symfony61/Rector/Class_/CommandConfigureToAttributeRector.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
2121
use Rector\Rector\AbstractRector;
2222
use Rector\Symfony\Enum\SymfonyAnnotation;
23+
use Rector\Symfony\Enum\SymfonyAttribute;
2324
use Rector\ValueObject\PhpVersionFeature;
2425
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
2526
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2627
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2728

2829
/**
29-
* @changelog https://symfony.com/doc/current/console.html#registering-the-command
30+
* @see https://symfony.com/doc/current/console.html#registering-the-command
3031
*
3132
* @see \Rector\Symfony\Tests\Symfony61\Rector\Class_\CommandConfigureToAttributeRector\CommandConfigureToAttributeRectorTest
3233
*/
@@ -120,7 +121,7 @@ public function refactor(Node $node): ?Node
120121
$attributeArgs = [];
121122
foreach ($node->attrGroups as $attrGroup) {
122123
foreach ($attrGroup->attrs as $attribute) {
123-
if (! $this->nodeNameResolver->isName($attribute->name, SymfonyAnnotation::AS_COMMAND)) {
124+
if (! $this->nodeNameResolver->isName($attribute->name, SymfonyAttribute::AS_COMMAND)) {
124125
continue;
125126
}
126127

rules/Symfony61/Rector/Class_/CommandPropertyToAttributeRector.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
1818
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
1919
use Rector\Rector\AbstractRector;
20-
use Rector\Symfony\Enum\SymfonyAnnotation;
20+
use Rector\Symfony\Enum\SymfonyAttribute;
2121
use Rector\Symfony\Enum\SymfonyClass;
2222
use Rector\ValueObject\PhpVersionFeature;
2323
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@@ -91,7 +91,7 @@ public function refactor(Node $node): ?Node
9191
}
9292

9393
// does attribute already exist?
94-
if (! $this->reflectionProvider->hasClass(SymfonyAnnotation::AS_COMMAND)) {
94+
if (! $this->reflectionProvider->hasClass(SymfonyAttribute::AS_COMMAND)) {
9595
return null;
9696
}
9797

@@ -104,7 +104,7 @@ public function refactor(Node $node): ?Node
104104

105105
$existingAsCommandAttribute = $this->attributeFinder->findAttributeByClass(
106106
$node,
107-
SymfonyAnnotation::AS_COMMAND
107+
SymfonyAttribute::AS_COMMAND
108108
);
109109

110110
$attributeArgs = $this->createAttributeArgs($defaultNameExpr, $defaultDescriptionExpr);
@@ -126,7 +126,7 @@ private function createAttributeGroupAsCommand(array $args): AttributeGroup
126126
{
127127
Assert::allIsInstanceOf($args, Arg::class);
128128

129-
$attributeGroup = $this->phpAttributeGroupFactory->createFromClass(SymfonyAnnotation::AS_COMMAND);
129+
$attributeGroup = $this->phpAttributeGroupFactory->createFromClass(SymfonyAttribute::AS_COMMAND);
130130
$attributeGroup->attrs[0]->args = $args;
131131

132132
return $attributeGroup;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Symfony\Symfony73\NodeAnalyzer;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Scalar\String_;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PhpParser\NodeFinder;
12+
use Rector\Exception\ShouldNotHappenException;
13+
use Rector\Symfony\Symfony73\ValueObject\CommandOptionMetadata;
14+
15+
final class CommandArgumentsAndOptionsResolver
16+
{
17+
/**
18+
* @return CommandOptionMetadata[]
19+
*/
20+
public function collectCommandOptionsMetadatas(ClassMethod $configureClassMethod): array
21+
{
22+
$addOptionMethodCalls = $this->findMethodCallsByName($configureClassMethod, 'addOption');
23+
24+
$commandOptionMetadatas = [];
25+
26+
foreach ($addOptionMethodCalls as $addOptionMethodCall) {
27+
// @todo extract name, type and requirements
28+
$addOptionArgs = $addOptionMethodCall->getArgs();
29+
30+
$nameArgValue = $addOptionArgs[0]->value;
31+
if (! $nameArgValue instanceof String_) {
32+
// we need string value, otherwise param will not have a name
33+
throw new ShouldNotHappenException('Option name is required');
34+
}
35+
36+
$optionName = $nameArgValue->value;
37+
38+
$commandOptionMetadatas[] = new CommandOptionMetadata($optionName);
39+
}
40+
41+
return $commandOptionMetadatas;
42+
}
43+
44+
/**
45+
* @return MethodCall[]
46+
*/
47+
private function findMethodCallsByName(ClassMethod $classMethod, string $desiredMethodName): array
48+
{
49+
$nodeFinder = new NodeFinder();
50+
51+
return $nodeFinder->find($classMethod, function (Node $node) use ($desiredMethodName): bool {
52+
if (! $node instanceof MethodCall) {
53+
return false;
54+
}
55+
56+
if (! $node->name instanceof Node\Identifier) {
57+
return false;
58+
}
59+
60+
return $node->name->toString() === $desiredMethodName;
61+
});
62+
}
63+
}

0 commit comments

Comments
 (0)