[Symfony] Add MakeCommandLazyRector

This commit is contained in:
Tomas Votruba 2019-06-01 00:26:19 +03:00
parent f53260958e
commit 068329b141
8 changed files with 305 additions and 0 deletions

View File

@ -1,2 +1,3 @@
services:
Rector\Symfony\Rector\BinaryOp\ResponseStatusCodeRector: ~
Rector\Symfony\Rector\Class_\MakeCommandLazyRector: ~

View File

@ -101,6 +101,7 @@ parameters:
Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
# tough logic
- 'packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php'
- 'packages/Legacy/src/Rector/ClassMethod/ChangeSingletonToServiceRector.php'
- 'src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php'
- 'src/PhpParser/Node/Resolver/NameResolver.php'

View File

@ -0,0 +1,155 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see https://symfony.com/doc/current/console/commands_as_services.html
*/
final class MakeCommandLazyRector extends AbstractRector
{
/**
* @var string
*/
private const COMMAND_CLASS = 'Symfony\Component\Console\Command\Command';
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
public function __construct(CallableNodeTraverser $callableNodeTraverser)
{
$this->callableNodeTraverser = $callableNodeTraverser;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Make Symfony commands lazy', [
new CodeSample(
<<<'CODE_SAMPLE'
use Symfony\Component\Console\Command\Command
class SunshineCommand extends Command
{
public function configure()
{
$this->setName('sunshine');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Symfony\Component\Console\Command\Command
class SunshineCommand extends Command
{
protected static $defaultName = 'sunshine';
public function configure()
{
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isType($node, self::COMMAND_CLASS)) {
return null;
}
$commandName = $this->resolveCommandNameAndRemove($node);
if ($commandName === null) {
return null;
}
$defaultNameProperty = $this->createDefaultNameProperty($commandName);
$node->stmts = array_merge([$defaultNameProperty], (array) $node->stmts);
return $node;
}
private function createDefaultNameProperty(Node $commandNameNode): Node\Stmt\Property
{
return $this->builderFactory->property('defaultName')
->makeProtected()
->makeStatic()
->setDefault($commandNameNode)
->getNode();
}
private function resolveCommandNameAndRemove(Class_ $class): ?Node
{
$commandName = null;
$this->callableNodeTraverser->traverseNodesWithCallable((array) $class->stmts, function (Node $node) use (
&$commandName
) {
if ($node instanceof MethodCall) {
if (! $this->isName($node, 'setName')) {
return null;
}
$commandName = $node->args[0]->value;
$this->removeNode($node);
}
if ($node instanceof ClassMethod && $this->isName($node, '__construct')) {
if (count((array) $node->stmts) !== 1) {
return null;
}
$onlyNode = $node->stmts[0];
if ($onlyNode instanceof Expression) {
$onlyNode = $onlyNode->expr;
if (! $this->isName($onlyNode, '__construct')) {
return null;
}
$commandName = $onlyNode->args[0]->value;
if (! is_string($this->getValue($commandName))) {
return null;
}
if (count($node->params) === 0) {
$this->removeNode($node);
}
}
}
if ($node instanceof StaticCall) {
if (! $this->isName($node, '__construct')) {
return null;
}
$commandName = $node->args[0]->value;
array_shift($node->args);
}
});
return $commandName;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
class ConstantDefinedName extends \Symfony\Component\Console\Command\Command
{
private const COMMAND_NAME = 'regge';
public function configure()
{
$this->setName(self::COMMAND_NAME);
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
class ConstantDefinedName extends \Symfony\Component\Console\Command\Command
{
protected static $defaultName = self::COMMAND_NAME;
private const COMMAND_NAME = 'regge';
public function configure()
{
}
}
?>

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
class SunshineCommand extends \Symfony\Component\Console\Command\Command
{
public function configure()
{
$this->setName('sunshine');
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
class SunshineCommand extends \Symfony\Component\Console\Command\Command
{
protected static $defaultName = 'sunshine';
public function configure()
{
}
}
?>

View File

@ -0,0 +1,28 @@
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
use Symfony\Component\Console\Command\Command;
final class InConstructCommand extends Command
{
public function __construct()
{
parent::__construct('moonshine');
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
use Symfony\Component\Console\Command\Command;
final class InConstructCommand extends Command
{
protected static $defaultName = 'moonshine';
}
?>

View File

@ -0,0 +1,38 @@
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
use Symfony\Component\Console\Command\Command;
final class InConstructWithParamCommand extends Command
{
private $someService;
public function __construct($someService)
{
parent::__construct('moonshine');
$this->someService = $someService;
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;
use Symfony\Component\Console\Command\Command;
final class InConstructWithParamCommand extends Command
{
protected static $defaultName = 'moonshine';
private $someService;
public function __construct($someService)
{
parent::__construct();
$this->someService = $someService;
}
}
?>

View File

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector;
use Rector\Symfony\Rector\Class_\MakeCommandLazyRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class MakeCommandLazyRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/in_construct.php.inc',
__DIR__ . '/Fixture/in_construct_with_param.php.inc',
__DIR__ . '/Fixture/constant_defined_name.php.inc',
]);
}
protected function getRectorClass(): string
{
return MakeCommandLazyRector::class;
}
}