2020-12-08 12:03:00 +00:00
< ? php
2021-05-09 20:15:43 +00:00
declare ( strict_types = 1 );
2021-07-04 18:27:51 +00:00
namespace Rector\DowngradePhp72\Rector\ClassMethod ;
2020-12-08 12:03:00 +00:00
use PhpParser\Node ;
use PhpParser\Node\Param ;
use PhpParser\Node\Stmt\ClassMethod ;
use PHPStan\Reflection\ClassReflection ;
2021-07-06 08:19:07 +00:00
use PHPStan\Reflection\ReflectionProvider ;
2021-07-05 22:50:18 +00:00
use Rector\Core\Contract\Rector\ConfigurableRectorInterface ;
2020-12-08 12:03:00 +00:00
use Rector\Core\Rector\AbstractRector ;
2021-04-23 09:03:45 +00:00
use Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator ;
2021-07-06 08:19:07 +00:00
use Rector\NodeTypeResolver\Node\AttributeKey ;
2021-07-06 13:09:53 +00:00
use Rector\TypeDeclaration\NodeAnalyzer\AutowiredClassMethodAnalyzer ;
2021-07-05 22:50:18 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ;
2020-12-08 12:03:00 +00:00
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2021-07-24 11:53:32 +00:00
use RectorPrefix20210724\Webmozart\Assert\Assert ;
2020-12-08 12:03:00 +00:00
/**
2021-04-10 18:47:17 +00:00
* @ changelog https :// www . php . net / manual / en / migration72 . new - features . php #migration72.new-features.param-type-widening
2021-03-17 10:12:39 +00:00
* @ see https :// 3 v4l . org / fOgSE
2020-12-08 12:03:00 +00:00
*
2021-07-04 18:27:51 +00:00
* @ see \Rector\Tests\DowngradePhp72\Rector\ClassMethod\DowngradeParameterTypeWideningRector\DowngradeParameterTypeWideningRectorTest
2020-12-08 12:03:00 +00:00
*/
2021-07-05 22:50:18 +00:00
final class DowngradeParameterTypeWideningRector extends \Rector\Core\Rector\AbstractRector implements \Rector\Core\Contract\Rector\ConfigurableRectorInterface
2020-12-08 12:03:00 +00:00
{
2021-01-18 20:06:02 +00:00
/**
2021-07-05 22:50:18 +00:00
* @ var string
2021-01-18 20:06:02 +00:00
*/
2021-07-05 22:50:18 +00:00
public const SAFE_TYPES = 'safe_types' ;
2021-07-06 13:47:20 +00:00
/**
* @ var string
*/
public const SAFE_TYPES_TO_METHODS = 'safe_types_to_methods' ;
2021-03-17 10:12:39 +00:00
/**
2021-07-05 22:50:18 +00:00
* @ var class - string []
2021-03-17 10:12:39 +00:00
*/
2021-07-05 22:50:18 +00:00
private $safeTypes = [];
2021-07-06 13:47:20 +00:00
/**
* @ var array < class - string , string [] >
*/
private $safeTypesToMethods = [];
2021-04-21 22:02:38 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator
2021-04-21 22:02:38 +00:00
*/
2021-04-23 09:03:45 +00:00
private $nativeParamToPhpDocDecorator ;
2021-06-08 13:00:02 +00:00
/**
2021-07-06 08:19:07 +00:00
* @ var \PHPStan\Reflection\ReflectionProvider
2021-06-08 13:00:02 +00:00
*/
2021-07-06 08:19:07 +00:00
private $reflectionProvider ;
2021-07-06 13:09:53 +00:00
/**
* @ var \Rector\TypeDeclaration\NodeAnalyzer\AutowiredClassMethodAnalyzer
*/
private $autowiredClassMethodAnalyzer ;
public function __construct ( \Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator $nativeParamToPhpDocDecorator , \PHPStan\Reflection\ReflectionProvider $reflectionProvider , \Rector\TypeDeclaration\NodeAnalyzer\AutowiredClassMethodAnalyzer $autowiredClassMethodAnalyzer )
2021-05-09 20:15:43 +00:00
{
2021-04-23 09:03:45 +00:00
$this -> nativeParamToPhpDocDecorator = $nativeParamToPhpDocDecorator ;
2021-07-06 08:19:07 +00:00
$this -> reflectionProvider = $reflectionProvider ;
2021-07-06 13:09:53 +00:00
$this -> autowiredClassMethodAnalyzer = $autowiredClassMethodAnalyzer ;
2020-12-08 12:03:00 +00:00
}
2021-05-10 22:23:08 +00:00
public function getRuleDefinition () : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
2020-12-08 12:03:00 +00:00
{
2021-07-05 22:50:18 +00:00
return new \Symplify\RuleDocGenerator\ValueObject\RuleDefinition ( 'Change param type to match the lowest type in whole family tree' , [ new \Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ( <<< 'CODE_SAMPLE'
2021-04-23 17:54:30 +00:00
interface SomeInterface
2020-12-08 12:03:00 +00:00
{
public function test ( array $input );
}
2021-04-23 17:54:30 +00:00
final class SomeClass implements SomeInterface
2020-12-08 12:03:00 +00:00
{
2021-04-23 17:54:30 +00:00
public function test ( $input )
{
}
2020-12-08 12:03:00 +00:00
}
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
, <<< 'CODE_SAMPLE'
2021-04-23 17:54:30 +00:00
interface SomeInterface
2020-12-08 12:03:00 +00:00
{
2021-04-23 17:54:30 +00:00
/**
* @ param mixed [] $input
*/
public function test ( $input );
2020-12-08 12:03:00 +00:00
}
2021-04-23 17:54:30 +00:00
final class SomeClass implements SomeInterface
2020-12-08 12:03:00 +00:00
{
2021-04-23 17:54:30 +00:00
public function test ( $input )
{
}
2020-12-08 12:03:00 +00:00
}
CODE_SAMPLE
2021-07-06 13:47:20 +00:00
, [ self :: SAFE_TYPES => [], self :: SAFE_TYPES_TO_METHODS => []])]);
2020-12-08 12:03:00 +00:00
}
/**
2021-02-27 00:06:15 +00:00
* @ return array < class - string < Node >>
2020-12-08 12:03:00 +00:00
*/
2021-05-09 20:15:43 +00:00
public function getNodeTypes () : array
2020-12-08 12:03:00 +00:00
{
2021-07-04 18:27:51 +00:00
return [ \PhpParser\Node\Stmt\ClassMethod :: class ];
2020-12-08 12:03:00 +00:00
}
/**
2021-07-05 22:50:18 +00:00
* @ param ClassMethod $node
2020-12-08 12:03:00 +00:00
*/
2021-07-05 22:50:18 +00:00
public function refactor ( \PhpParser\Node $node ) : ? \PhpParser\Node
2020-12-08 12:03:00 +00:00
{
2021-07-06 08:19:07 +00:00
$classLike = $node -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: CLASS_NODE );
if ( $classLike === null ) {
2021-04-23 09:03:45 +00:00
return null ;
}
2021-07-06 08:19:07 +00:00
$className = $this -> nodeNameResolver -> getName ( $classLike );
if ( $className === null ) {
2021-06-04 11:46:51 +00:00
return null ;
}
2021-07-06 08:19:07 +00:00
if ( ! $this -> reflectionProvider -> hasClass ( $className )) {
return null ;
}
$classReflection = $this -> reflectionProvider -> getClass ( $className );
2021-07-05 22:50:18 +00:00
if ( $this -> isSealedClass ( $classReflection )) {
return null ;
}
2021-07-06 13:47:20 +00:00
if ( $this -> skipSafeType ( $classReflection , $node )) {
2021-07-05 23:37:59 +00:00
return null ;
2021-07-05 22:50:18 +00:00
}
2021-07-05 23:06:30 +00:00
if ( $node -> isPrivate ()) {
return null ;
}
2021-07-05 07:47:59 +00:00
if ( $this -> skipClassMethod ( $node )) {
2021-04-23 09:03:45 +00:00
return null ;
}
2021-07-05 22:50:18 +00:00
// Downgrade every scalar parameter, just to be sure
foreach ( \array_keys ( $node -> params ) as $paramPosition ) {
$this -> removeParamTypeFromMethod ( $node , $paramPosition );
}
return $node ;
2020-12-08 12:03:00 +00:00
}
2021-03-17 10:12:39 +00:00
/**
2021-07-06 14:05:28 +00:00
* @ param array < string , mixed > $configuration
2021-03-17 10:12:39 +00:00
*/
2021-07-05 22:50:18 +00:00
public function configure ( array $configuration ) : void
2020-12-08 12:03:00 +00:00
{
2021-07-05 22:50:18 +00:00
$safeTypes = $configuration [ self :: SAFE_TYPES ] ? ? [];
2021-07-24 11:53:32 +00:00
\RectorPrefix20210724\Webmozart\Assert\Assert :: allString ( $safeTypes );
2021-07-05 22:50:18 +00:00
$this -> safeTypes = $safeTypes ;
2021-07-06 13:47:20 +00:00
$safeTypesToMethods = $configuration [ self :: SAFE_TYPES_TO_METHODS ] ? ? [];
2021-07-24 11:53:32 +00:00
\RectorPrefix20210724\Webmozart\Assert\Assert :: isArray ( $safeTypesToMethods );
2021-07-06 13:47:20 +00:00
foreach ( $safeTypesToMethods as $key => $value ) {
2021-07-24 11:53:32 +00:00
\RectorPrefix20210724\Webmozart\Assert\Assert :: string ( $key );
\RectorPrefix20210724\Webmozart\Assert\Assert :: allString ( $value );
2021-07-06 13:47:20 +00:00
}
$this -> safeTypesToMethods = $safeTypesToMethods ;
2021-04-23 17:54:30 +00:00
}
2021-07-05 07:47:59 +00:00
private function removeParamTypeFromMethod ( \PhpParser\Node\Stmt\ClassMethod $classMethod , int $paramPosition ) : void
2021-04-23 17:54:30 +00:00
{
2021-07-05 07:47:59 +00:00
$param = $classMethod -> params [ $paramPosition ] ? ? null ;
if ( ! $param instanceof \PhpParser\Node\Param ) {
return ;
}
2021-04-23 17:54:30 +00:00
// It already has no type => nothing to do - check original param, as it could have been removed by this rule
2021-06-04 11:46:51 +00:00
if ( $param -> type === null ) {
2020-12-08 12:03:00 +00:00
return ;
}
// Add the current type in the PHPDoc
2021-04-23 09:03:45 +00:00
$this -> nativeParamToPhpDocDecorator -> decorate ( $classMethod , $param );
2020-12-08 12:03:00 +00:00
$param -> type = null ;
}
2021-07-05 07:47:59 +00:00
private function skipClassMethod ( \PhpParser\Node\Stmt\ClassMethod $classMethod ) : bool
2021-04-23 09:03:45 +00:00
{
if ( $classMethod -> isMagic ()) {
2021-05-09 20:15:43 +00:00
return \true ;
2021-04-23 09:03:45 +00:00
}
2021-07-06 13:09:53 +00:00
if ( $classMethod -> params === []) {
return \true ;
}
if ( $this -> autowiredClassMethodAnalyzer -> detect ( $classMethod )) {
return \true ;
}
foreach ( $classMethod -> params as $param ) {
if ( $param -> type !== null ) {
return \false ;
}
}
return \true ;
2021-07-04 18:27:51 +00:00
}
2021-07-05 22:50:18 +00:00
/**
* This method is perfectly sealed , nothing to downgrade here
*/
private function isSealedClass ( \PHPStan\Reflection\ClassReflection $classReflection ) : bool
{
if ( ! $classReflection -> isClass ()) {
return \false ;
}
if ( ! $classReflection -> isFinal ()) {
return \false ;
}
return \count ( $classReflection -> getAncestors ()) === 1 ;
}
2021-07-06 13:47:20 +00:00
private function skipSafeType ( \PHPStan\Reflection\ClassReflection $classReflection , \PhpParser\Node\Stmt\ClassMethod $classMethod ) : bool
2021-07-05 23:37:59 +00:00
{
foreach ( $this -> safeTypes as $safeType ) {
if ( $classReflection -> isSubclassOf ( $safeType )) {
return \true ;
}
// skip self too
if ( $classReflection -> getName () === $safeType ) {
return \true ;
}
}
2021-07-06 13:47:20 +00:00
foreach ( $this -> safeTypesToMethods as $safeType => $safeMethods ) {
if ( ! $this -> isNames ( $classMethod , $safeMethods )) {
continue ;
}
if ( $classReflection -> isSubclassOf ( $safeType )) {
return \true ;
}
// skip self too
if ( $classReflection -> getName () === $safeType ) {
return \true ;
}
}
2021-07-05 23:37:59 +00:00
return \false ;
}
2020-12-08 12:03:00 +00:00
}