Add ConsoleExecuteReturnIntRector

Fixes: https://github.com/rectorphp/rector/issues/2119
This commit is contained in:
Jonas Elfering 2019-10-10 15:34:56 +02:00
parent 5e440fb900
commit 33a41ddb4a
9 changed files with 399 additions and 0 deletions

View File

@ -0,0 +1,5 @@
services:
# https://github.com/symfony/symfony/blob/4.4/UPGRADE-4.4.md
# https://github.com/symfony/symfony/pull/33775
Rector\Symfony\Rector\Console\ConsoleExecuteReturnIntRector: ~

View File

@ -0,0 +1,125 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Rector\Console;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* Covers:
* - https://github.com/symfony/symfony/pull/33775/files
* @see \Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\ConsoleExecuteReturnIntRectorTest
*/
final class ConsoleExecuteReturnIntRector extends AbstractRector
{
private const COMMAND_CLASS = '\Symfony\Component\Console\Command\Command';
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Returns int from Command::execute command', [
new CodeSample(
<<<'PHP'
class SomeCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
return null;
}
}
PHP
,
<<<'PHP'
class SomeCommand extends Command
{
public function index(InputInterface $input, OutputInterface $output): int
{
return 0;
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if ($this->getName($node) !== 'execute') {
return null;
}
$class = $node->getAttribute(AttributeKey::CLASS_NODE);
if (! $class || ! $class instanceof Class_) {
return null;
}
if (!$class->extends || $class->extends->toCodeString() !== self::COMMAND_CLASS) {
return null;
}
$node->returnType = new Identifier('int');
return $this->addReturn0ToMethod($node);
}
private function setReturnTo0InsteadOfNull(Return_ $return)
{
if (! $return->expr ) {
$return->expr = new LNumber(0);
}
if ($return->expr instanceof ConstFetch && $this->getName($return->expr) === 'null') {
$return->expr = new LNumber(0);
}
if ($return->expr instanceof MethodCall || $return->expr instanceof StaticCall) {
// how to get the Node of teh called method?!
}
}
private function addReturn0ToMethod(FunctionLike $node): FunctionLike
{
$hasReturn = false;
$this->traverseNodesWithCallable($node->getStmts(), function (Node &$stmt) use ($node, &$hasReturn) {
if (!$stmt instanceof Return_) {
return;
}
if ($this->areNodesEqual($stmt->getAttribute(AttributeKey::PARENT_NODE), $node)) {
$hasReturn = true;
}
$this->setReturnTo0InsteadOfNull($stmt);
});
if ($hasReturn) {
return $node;
}
$node->stmts[] = new Return_(new LNumber(0));
return $node;
}
}

View File

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector;
use Iterator;
use Rector\Symfony\Rector\Console\ConsoleExecuteReturnIntRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class ConsoleExecuteReturnIntRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideDataForTest(): Iterator
{
yield [__DIR__ . '/Fixture/explicit-return-null.php.inc'];
yield [__DIR__ . '/Fixture/no-return.php.inc'];
yield [__DIR__ . '/Fixture/empty-return.php.inc'];
yield [__DIR__ . '/Fixture/multiple-returns.php.inc'];
yield [__DIR__ . '/Fixture/add-return-type.php.inc'];
yield [__DIR__ . '/Fixture/return-function-call.php.inc'];
}
protected function getRectorClass(): string
{
return ConsoleExecuteReturnIntRector::class;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class AddReturnTypeCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
return 2;
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class AddReturnTypeCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
return 2;
}
}
?>

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class EmptyReturnCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
return;
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class EmptyReturnCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
return 0;
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class ExplicitReturnNullCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
// execute
return null;
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class ExplicitReturnNullCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
// execute
return 0;
}
}
?>

View File

@ -0,0 +1,48 @@
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class MultipleReturnCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
if ($earlyReturn) {
return null;
}
if ($erroCondition) {
return 1;
}
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class MultipleReturnCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
if ($earlyReturn) {
return 0;
}
if ($erroCondition) {
return 1;
}
return 0;
}
}
?>

View File

@ -0,0 +1,36 @@
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class NoReturnCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
$this->doSomething();
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class NoReturnCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
$this->doSomething();
return 0;
}
}
?>

View File

@ -0,0 +1,45 @@
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class ReturnFunctionCallCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
return $this->doSomething();
}
private function doSomething()
{
return null;
}
}
?>
-----
<?php
namespace Rector\Symfony\Tests\Rector\Console\ConsoleExecuteReturnIntRector\Fixture;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class ReturnFunctionCallCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
return $this->doSomething();
}
private function doSomething()
{
return 0;
}
}
?>