Cast return value if its not an int

This commit is contained in:
Jonas Elfering 2019-10-13 11:19:27 +02:00
parent 16e225ba06
commit 1ecef4cedd
6 changed files with 13 additions and 202 deletions

View File

@ -4,25 +4,20 @@ namespace Rector\Symfony\Rector\Console;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\Node\Expr\Cast\Int_;
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\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Exception\ShouldNotHappenException;
use PHPStan\Type\IntegerType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Symfony\Component\Console\Command\Command;
/**
* Covers:
@ -31,16 +26,6 @@ use Rector\RectorDefinition\RectorDefinition;
*/
final class ConsoleExecuteReturnIntRector extends AbstractRector
{
/**
* @var string
*/
private const COMMAND_CLASS = '\Symfony\Component\Console\Command\Command';
/**
* @var ClassLike[]
*/
private $classes = [];
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Returns int from Command::execute command', [
@ -90,7 +75,7 @@ PHP
return null;
}
if (! $class->extends || $class->extends->toCodeString() !== self::COMMAND_CLASS) {
if (! $class->extends || $class->extends->toCodeString() !== '\\' . Command::class) {
return null;
}
@ -99,25 +84,10 @@ PHP
return $this->addReturn0ToMethod($node);
}
public function beforeTraverse(array $nodes): void
{
foreach ($nodes as $node) {
if ($node instanceof Namespace_) {
$this->beforeTraverse($node->stmts);
}
if (! $node instanceof ClassLike) {
continue;
}
$this->classes[$this->getName($node)] = $node;
}
}
private function addReturn0ToMethod(FunctionLike $functionLike): FunctionLike
{
$hasReturn = false;
$this->traverseNodesWithCallable($functionLike->getStmts(), function (Node &$stmt) use (
$this->traverseNodesWithCallable($functionLike->getStmts(), function (Node $stmt) use (
$functionLike,
&$hasReturn
): void {
@ -145,73 +115,24 @@ PHP
{
if (! $return->expr) {
$return->expr = new LNumber(0);
return;
}
if ($return->expr instanceof ConstFetch && $this->getName($return->expr) === 'null') {
$return->expr = new LNumber(0);
return;
}
if ($return->expr instanceof Coalesce && $return->expr->right instanceof ConstFetch && $this->getName(
$return->expr->right
) === 'null') {
$return->expr->right = new LNumber(0);
}
if ($return->expr instanceof MethodCall) {
$object = $this->getObjectType($return->expr->var);
$this->refactorMethodOnObject($return, $object);
}
if ($return->expr instanceof StaticCall) {
$object = $this->getObjectType($return->expr->class);
$this->refactorMethodOnObject($return, $object);
}
}
private function refactorMethodOnObject(Return_ $return, Type $type): void
{
if ($type instanceof ObjectType) {
$this->refactorClassMethod($type->getClassName(), $this->getName($return->expr));
return;
}
if ($type instanceof UnionType) {
$baseClass = $type->getTypes()[0];
if (! $baseClass instanceof ObjectType) {
throw new ShouldNotHappenException();
}
$this->refactorClassMethod($baseClass->getClassName(), $this->getName($return->expr));
if (! $this->getStaticType($return->expr) instanceof IntegerType) {
$return->expr = new Int_($return->expr);
return;
}
}
private function refactorClassMethod(string $className, string $methodName): void
{
if (! array_key_exists($className, $this->classes)) {
throw new ShouldNotHappenException();
}
$methods = $this->betterNodeFinder->find($this->classes[$className]->getMethods(), function (Node $node) use (
$methodName
) {
if (! $node instanceof ClassMethod) {
return null;
}
if ($this->getName($node) === $methodName) {
return $node;
}
return null;
});
if (count($methods) !== 1) {
throw new ShouldNotHappenException();
}
$this->addReturn0ToMethod($methods[0]);
}
}

View File

@ -24,9 +24,7 @@ final class ConsoleExecuteReturnIntRectorTest extends AbstractRectorTestCase
yield [__DIR__ . '/Fixture/multiple-returns.php.inc'];
yield [__DIR__ . '/Fixture/add-return-type.php.inc'];
yield [__DIR__ . '/Fixture/return-function-call.php.inc'];
yield [__DIR__ . '/Fixture/return-external-function-call.php.inc'];
yield [__DIR__ . '/Fixture/return-static-function-call.php.inc'];
yield [__DIR__ . '/Fixture/return-external-static-function-call.php.inc'];
}
protected function getRectorClass(): string

View File

@ -1,57 +0,0 @@
<?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 ReturnExternalFunctionCall extends Command
{
/** @var ExternalClass */
private $external;
public function execute(InputInterface $input, OutputInterface $output)
{
return $this->external->doSomething();
}
}
final class ExternalCLass
{
public 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 ReturnExternalFunctionCall extends Command
{
/** @var ExternalClass */
private $external;
public function execute(InputInterface $input, OutputInterface $output): int
{
return $this->external->doSomething();
}
}
final class ExternalCLass
{
public function doSomething()
{
return 0;
}
}
?>

View File

@ -1,51 +0,0 @@
<?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 ReturnExternalStaticFunctionCall extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
{
return ExternalStaticCLass::doSomething();
}
}
final class ExternalStaticCLass
{
public static 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 ReturnExternalStaticFunctionCall extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
return ExternalStaticCLass::doSomething();
}
}
final class ExternalStaticCLass
{
public static function doSomething()
{
return 0;
}
}
?>

View File

@ -33,12 +33,12 @@ final class ReturnFunctionCallCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
return $this->doSomething();
return (int) $this->doSomething();
}
private function doSomething()
{
return 0;
return null;
}
}

View File

@ -33,12 +33,12 @@ final class ReturnStaticFunctionCallCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output): int
{
return static::doSomething();
return (int) static::doSomething();
}
private static function doSomething()
{
return 0;
return null;
}
}