2021-05-10 00:23:30 +00:00
< ? php
declare ( strict_types = 1 );
2022-06-06 17:12:56 +00:00
namespace Rector\Symfony\Rector\ClassMethod ;
2021-05-10 00:23:30 +00:00
2022-06-06 17:12:56 +00:00
use PhpParser\Node ;
use PhpParser\Node\Expr ;
use PhpParser\Node\Expr\BinaryOp\Coalesce ;
use PhpParser\Node\Expr\Cast\Int_ ;
use PhpParser\Node\Expr\Ternary ;
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 PhpParser\NodeTraverser ;
use PHPStan\Type\IntegerType ;
use PHPStan\Type\ObjectType ;
use Rector\Core\Rector\AbstractRector ;
use Rector\NodeTypeResolver\Node\AttributeKey ;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2021-05-10 00:23:30 +00:00
/**
* @ see https :// github . com / symfony / symfony / pull / 33775 / files
* @ see \Rector\Symfony\Tests\Rector\ClassMethod\ConsoleExecuteReturnIntRector\ConsoleExecuteReturnIntRectorTest
*/
2022-06-06 17:12:56 +00:00
final class ConsoleExecuteReturnIntRector extends \Rector\Core\Rector\AbstractRector
2021-05-10 00:23:30 +00:00
{
2022-06-06 17:12:56 +00:00
public function getRuleDefinition () : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
2021-05-10 00:23:30 +00:00
{
2022-06-06 17:12:56 +00:00
return new \Symplify\RuleDocGenerator\ValueObject\RuleDefinition ( 'Returns int from Command::execute command' , [ new \Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample ( <<< 'CODE_SAMPLE'
2021-05-10 00:23:30 +00:00
class SomeCommand extends Command
{
public function execute ( InputInterface $input , OutputInterface $output )
{
return null ;
}
}
CODE_SAMPLE
, <<< 'CODE_SAMPLE'
class SomeCommand extends Command
{
public function execute ( InputInterface $input , OutputInterface $output ) : int
{
return 0 ;
}
}
CODE_SAMPLE
)]);
}
/**
* @ return array < class - string < Node >>
*/
public function getNodeTypes () : array
{
2022-06-06 17:12:56 +00:00
return [ \PhpParser\Node\Stmt\ClassMethod :: class ];
2021-05-10 00:23:30 +00:00
}
/**
2021-12-10 10:22:23 +00:00
* @ param ClassMethod $node
2021-05-10 00:23:30 +00:00
*/
2022-06-06 17:12:56 +00:00
public function refactor ( \PhpParser\Node $node ) : ? \PhpParser\Node
2021-05-10 00:23:30 +00:00
{
if ( ! $this -> isName ( $node , 'execute' )) {
return null ;
}
2022-06-06 17:12:56 +00:00
$class = $this -> betterNodeFinder -> findParentType ( $node , \PhpParser\Node\Stmt\Class_ :: class );
if ( ! $class instanceof \PhpParser\Node\Stmt\Class_ ) {
2021-05-10 00:23:30 +00:00
return null ;
}
2022-06-06 17:12:56 +00:00
if ( ! $this -> isObjectType ( $class , new \PHPStan\Type\ObjectType ( 'Symfony\\Component\\Console\\Command\\Command' ))) {
2021-05-10 00:23:30 +00:00
return null ;
}
$this -> refactorReturnTypeDeclaration ( $node );
$this -> addReturn0ToMethod ( $node );
return $node ;
}
2022-06-06 17:12:56 +00:00
private function refactorReturnTypeDeclaration ( \PhpParser\Node\Stmt\ClassMethod $classMethod ) : void
2021-05-10 00:23:30 +00:00
{
// already set
if ( $classMethod -> returnType !== null && $this -> isName ( $classMethod -> returnType , 'int' )) {
return ;
}
2022-06-06 17:12:56 +00:00
$classMethod -> returnType = new \PhpParser\Node\Identifier ( 'int' );
2021-05-10 00:23:30 +00:00
}
2022-06-06 17:12:56 +00:00
private function addReturn0ToMethod ( \PhpParser\Node\Stmt\ClassMethod $classMethod ) : void
2021-05-10 00:23:30 +00:00
{
$hasReturn = \false ;
2022-06-06 17:12:56 +00:00
$this -> traverseNodesWithCallable (( array ) $classMethod -> getStmts (), function ( \PhpParser\Node $node ) use ( $classMethod , & $hasReturn ) : ? int {
if ( $node instanceof \PhpParser\Node\FunctionLike ) {
return \PhpParser\NodeTraverser :: DONT_TRAVERSE_CHILDREN ;
2021-05-10 00:23:30 +00:00
}
2022-06-06 17:12:56 +00:00
$parentNode = $node -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: PARENT_NODE );
if ( $parentNode instanceof \PhpParser\Node && $this -> isReturnWithExprIntEquals ( $parentNode , $node )) {
2021-05-10 00:23:30 +00:00
$hasReturn = \true ;
return null ;
}
2022-06-06 17:12:56 +00:00
if ( ! $node instanceof \PhpParser\Node\Stmt\Return_ ) {
2021-05-10 00:23:30 +00:00
return null ;
}
2022-06-06 17:12:56 +00:00
if ( $node -> expr instanceof \PhpParser\Node\Expr\Cast\Int_ ) {
2021-05-10 00:23:30 +00:00
return null ;
}
2022-06-06 17:12:56 +00:00
if ( $node -> expr instanceof \PhpParser\Node\Expr\Ternary && $this -> isIntegerTernaryIfElse ( $node -> expr )) {
2021-05-10 00:23:30 +00:00
$hasReturn = \true ;
return null ;
}
// is there return without nesting?
if ( $this -> nodeComparator -> areNodesEqual ( $parentNode , $classMethod )) {
$hasReturn = \true ;
}
$this -> setReturnTo0InsteadOfNull ( $node );
return null ;
});
$this -> processReturn0ToMethod ( $hasReturn , $classMethod );
}
2022-06-06 17:12:56 +00:00
private function isIntegerTernaryIfElse ( \PhpParser\Node\Expr\Ternary $ternary ) : bool
2021-05-10 00:23:30 +00:00
{
/** @var Expr|null $if */
$if = $ternary -> if ;
2022-06-06 17:12:56 +00:00
if ( ! $if instanceof \PhpParser\Node\Expr ) {
2021-05-10 00:23:30 +00:00
$if = $ternary -> cond ;
}
/** @var Expr $else */
$else = $ternary -> else ;
2021-10-07 19:06:33 +00:00
$ifType = $this -> getType ( $if );
$elseType = $this -> getType ( $else );
2022-06-06 17:12:56 +00:00
return $ifType instanceof \PHPStan\Type\IntegerType && $elseType instanceof \PHPStan\Type\IntegerType ;
2021-05-10 00:23:30 +00:00
}
2022-06-06 17:12:56 +00:00
private function processReturn0ToMethod ( bool $hasReturn , \PhpParser\Node\Stmt\ClassMethod $classMethod ) : void
2021-05-10 00:23:30 +00:00
{
if ( $hasReturn ) {
return ;
}
2022-06-06 17:12:56 +00:00
$classMethod -> stmts [] = new \PhpParser\Node\Stmt\Return_ ( new \PhpParser\Node\Scalar\LNumber ( 0 ));
2021-05-10 00:23:30 +00:00
}
2022-06-06 17:12:56 +00:00
private function isReturnWithExprIntEquals ( \PhpParser\Node $parentNode , \PhpParser\Node $node ) : bool
2021-05-10 00:23:30 +00:00
{
2022-06-06 17:12:56 +00:00
if ( ! $parentNode instanceof \PhpParser\Node\Stmt\Return_ ) {
2021-05-10 00:23:30 +00:00
return \false ;
}
if ( ! $this -> nodeComparator -> areNodesEqual ( $parentNode -> expr , $node )) {
return \false ;
}
2022-06-06 17:12:56 +00:00
return $node instanceof \PhpParser\Node\Expr\Cast\Int_ ;
2021-05-10 00:23:30 +00:00
}
2022-06-06 17:12:56 +00:00
private function setReturnTo0InsteadOfNull ( \PhpParser\Node\Stmt\Return_ $return ) : void
2021-05-10 00:23:30 +00:00
{
if ( $return -> expr === null ) {
2022-06-06 17:12:56 +00:00
$return -> expr = new \PhpParser\Node\Scalar\LNumber ( 0 );
2021-05-10 00:23:30 +00:00
return ;
}
if ( $this -> valueResolver -> isNull ( $return -> expr )) {
2022-06-06 17:12:56 +00:00
$return -> expr = new \PhpParser\Node\Scalar\LNumber ( 0 );
2021-05-10 00:23:30 +00:00
return ;
}
2022-06-06 17:12:56 +00:00
if ( $return -> expr instanceof \PhpParser\Node\Expr\BinaryOp\Coalesce && $this -> valueResolver -> isNull ( $return -> expr -> right )) {
$return -> expr -> right = new \PhpParser\Node\Scalar\LNumber ( 0 );
2021-05-10 00:23:30 +00:00
return ;
}
2022-06-06 17:12:56 +00:00
if ( $return -> expr instanceof \PhpParser\Node\Expr\Ternary ) {
2021-05-10 00:23:30 +00:00
$hasChanged = $this -> isSuccessfulRefactorTernaryReturn ( $return -> expr );
if ( $hasChanged ) {
return ;
}
}
2021-10-07 19:06:33 +00:00
$staticType = $this -> getType ( $return -> expr );
2022-06-06 17:12:56 +00:00
if ( ! $staticType instanceof \PHPStan\Type\IntegerType ) {
$return -> expr = new \PhpParser\Node\Expr\Cast\Int_ ( $return -> expr );
2021-05-10 00:23:30 +00:00
}
}
2022-06-06 17:12:56 +00:00
private function isSuccessfulRefactorTernaryReturn ( \PhpParser\Node\Expr\Ternary $ternary ) : bool
2021-05-10 00:23:30 +00:00
{
$hasChanged = \false ;
2022-06-06 17:12:56 +00:00
if ( $ternary -> if instanceof \PhpParser\Node\Expr && $this -> valueResolver -> isNull ( $ternary -> if )) {
$ternary -> if = new \PhpParser\Node\Scalar\LNumber ( 0 );
2021-05-10 00:23:30 +00:00
$hasChanged = \true ;
}
if ( $this -> valueResolver -> isNull ( $ternary -> else )) {
2022-06-06 17:12:56 +00:00
$ternary -> else = new \PhpParser\Node\Scalar\LNumber ( 0 );
2021-05-10 00:23:30 +00:00
$hasChanged = \true ;
}
return $hasChanged ;
}
}