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 ;
2021-11-06 12:10:48 +00:00
use PhpParser\Node\Stmt\ClassLike ;
2020-12-08 12:03:00 +00:00
use PhpParser\Node\Stmt\ClassMethod ;
use PHPStan\Reflection\ClassReflection ;
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-11-07 04:15:35 +00:00
use Rector\Core\Reflection\ReflectionResolver ;
2021-08-26 08:02:58 +00:00
use Rector\DowngradePhp72\NodeAnalyzer\BuiltInMethodAnalyzer ;
2021-08-27 09:14:46 +00:00
use Rector\DowngradePhp72\NodeAnalyzer\OverrideFromAnonymousClassMethodAnalyzer ;
use Rector\DowngradePhp72\NodeAnalyzer\SealedClassAnalyzer ;
2021-04-23 09:03:45 +00:00
use Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator ;
2021-07-25 07:47:04 +00:00
use Rector\TypeDeclaration\NodeAnalyzer\AutowiredClassMethodOrPropertyAnalyzer ;
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-12-10 00:25:09 +00:00
use RectorPrefix20211210\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-11-02 15:11:40 +00:00
* @ var 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
/**
2021-11-02 15:11:40 +00:00
* @ var array < string , string [] >
2021-07-06 13:47:20 +00:00
*/
private $safeTypesToMethods = [];
2021-04-21 22:02:38 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
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-12-04 12:47:17 +00:00
* @ readonly
2021-11-07 04:15:35 +00:00
* @ var \Rector\Core\Reflection\ReflectionResolver
2021-06-08 13:00:02 +00:00
*/
2021-11-07 04:15:35 +00:00
private $reflectionResolver ;
2021-07-06 13:09:53 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-07-25 07:47:04 +00:00
* @ var \Rector\TypeDeclaration\NodeAnalyzer\AutowiredClassMethodOrPropertyAnalyzer
2021-07-06 13:09:53 +00:00
*/
2021-07-25 11:00:04 +00:00
private $autowiredClassMethodOrPropertyAnalyzer ;
2021-08-26 08:02:58 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-08-26 08:02:58 +00:00
* @ var \Rector\DowngradePhp72\NodeAnalyzer\BuiltInMethodAnalyzer
*/
private $builtInMethodAnalyzer ;
2021-08-27 09:14:46 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-08-27 09:14:46 +00:00
* @ var \Rector\DowngradePhp72\NodeAnalyzer\OverrideFromAnonymousClassMethodAnalyzer
*/
private $overrideFromAnonymousClassMethodAnalyzer ;
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-08-27 09:14:46 +00:00
* @ var \Rector\DowngradePhp72\NodeAnalyzer\SealedClassAnalyzer
*/
private $sealedClassAnalyzer ;
2021-11-07 04:15:35 +00:00
public function __construct ( \Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator $nativeParamToPhpDocDecorator , \Rector\Core\Reflection\ReflectionResolver $reflectionResolver , \Rector\TypeDeclaration\NodeAnalyzer\AutowiredClassMethodOrPropertyAnalyzer $autowiredClassMethodOrPropertyAnalyzer , \Rector\DowngradePhp72\NodeAnalyzer\BuiltInMethodAnalyzer $builtInMethodAnalyzer , \Rector\DowngradePhp72\NodeAnalyzer\OverrideFromAnonymousClassMethodAnalyzer $overrideFromAnonymousClassMethodAnalyzer , \Rector\DowngradePhp72\NodeAnalyzer\SealedClassAnalyzer $sealedClassAnalyzer )
2021-05-09 20:15:43 +00:00
{
2021-04-23 09:03:45 +00:00
$this -> nativeParamToPhpDocDecorator = $nativeParamToPhpDocDecorator ;
2021-11-07 04:15:35 +00:00
$this -> reflectionResolver = $reflectionResolver ;
2021-07-25 11:00:04 +00:00
$this -> autowiredClassMethodOrPropertyAnalyzer = $autowiredClassMethodOrPropertyAnalyzer ;
2021-08-26 08:02:58 +00:00
$this -> builtInMethodAnalyzer = $builtInMethodAnalyzer ;
2021-08-27 09:14:46 +00:00
$this -> overrideFromAnonymousClassMethodAnalyzer = $overrideFromAnonymousClassMethodAnalyzer ;
$this -> sealedClassAnalyzer = $sealedClassAnalyzer ;
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-11-06 12:10:48 +00:00
$classLike = $this -> betterNodeFinder -> findParentType ( $node , \PhpParser\Node\Stmt\ClassLike :: class );
if ( ! $classLike instanceof \PhpParser\Node\Stmt\ClassLike ) {
2021-04-23 09:03:45 +00:00
return null ;
}
2021-09-02 06:26:12 +00:00
$ancestorOverridableAnonymousClass = $this -> overrideFromAnonymousClassMethodAnalyzer -> matchAncestorClassReflectionOverrideable ( $classLike , $node );
if ( $ancestorOverridableAnonymousClass instanceof \PHPStan\Reflection\ClassReflection ) {
return $this -> processRemoveParamTypeFromMethod ( $ancestorOverridableAnonymousClass , $node );
2021-08-27 09:14:46 +00:00
}
2021-11-07 04:15:35 +00:00
$classReflection = $this -> reflectionResolver -> resolveClassAndAnonymousClass ( $classLike );
2021-09-02 06:26:12 +00:00
return $this -> processRemoveParamTypeFromMethod ( $classReflection , $node );
2020-12-08 12:03:00 +00:00
}
2021-03-17 10:12:39 +00:00
/**
2021-11-28 17:01:20 +00:00
* @ param 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-11-30 13:55:08 +00:00
$safeTypes = $configuration [ self :: SAFE_TYPES ] ? ? [];
2021-12-10 00:25:09 +00:00
\RectorPrefix20211210\Webmozart\Assert\Assert :: isArray ( $safeTypes );
\RectorPrefix20211210\Webmozart\Assert\Assert :: allString ( $safeTypes );
2021-07-05 22:50:18 +00:00
$this -> safeTypes = $safeTypes ;
2021-11-30 13:55:08 +00:00
$safeTypesToMethods = $configuration [ self :: SAFE_TYPES_TO_METHODS ] ? ? [];
2021-12-10 00:25:09 +00:00
\RectorPrefix20211210\Webmozart\Assert\Assert :: isArray ( $safeTypesToMethods );
2021-07-06 13:47:20 +00:00
foreach ( $safeTypesToMethods as $key => $value ) {
2021-12-10 00:25:09 +00:00
\RectorPrefix20211210\Webmozart\Assert\Assert :: string ( $key );
\RectorPrefix20211210\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-08-27 09:14:46 +00:00
private function shouldSkip ( \PHPStan\Reflection\ClassReflection $classReflection , \PhpParser\Node\Stmt\ClassMethod $classMethod ) : bool
{
2021-11-05 10:25:23 +00:00
if ( $classMethod -> params === []) {
return \true ;
}
if ( $classMethod -> isPrivate ()) {
return \true ;
}
if ( $classMethod -> isMagic ()) {
return \true ;
}
2021-08-27 09:14:46 +00:00
if ( $this -> sealedClassAnalyzer -> isSealedClass ( $classReflection )) {
return \true ;
}
2021-11-05 10:25:23 +00:00
if ( $this -> autowiredClassMethodOrPropertyAnalyzer -> detect ( $classMethod )) {
2021-08-27 09:14:46 +00:00
return \true ;
}
2021-11-05 10:25:23 +00:00
if ( $this -> hasParamAlreadyNonTyped ( $classMethod )) {
2021-08-27 09:14:46 +00:00
return \true ;
}
2021-11-05 10:25:23 +00:00
return $this -> isSafeType ( $classReflection , $classMethod );
2021-08-27 09:14:46 +00:00
}
2021-09-02 06:26:12 +00:00
private function processRemoveParamTypeFromMethod ( \PHPStan\Reflection\ClassReflection $classReflection , \PhpParser\Node\Stmt\ClassMethod $classMethod ) : ? \PhpParser\Node\Stmt\ClassMethod
2021-08-27 09:14:46 +00:00
{
2021-09-02 06:26:12 +00:00
if ( $this -> shouldSkip ( $classReflection , $classMethod )) {
return null ;
}
if ( $this -> builtInMethodAnalyzer -> isImplementsBuiltInInterface ( $classReflection , $classMethod )) {
return null ;
}
2021-08-27 09:14:46 +00:00
// Downgrade every scalar parameter, just to be sure
foreach ( \array_keys ( $classMethod -> params ) as $paramPosition ) {
$this -> removeParamTypeFromMethod ( $classMethod , $paramPosition );
}
return $classMethod ;
}
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 ;
}
2020-12-08 12:03:00 +00:00
// 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-11-05 10:25:23 +00:00
private function hasParamAlreadyNonTyped ( \PhpParser\Node\Stmt\ClassMethod $classMethod ) : bool
2021-04-23 09:03:45 +00:00
{
2021-07-06 13:09:53 +00:00
foreach ( $classMethod -> params as $param ) {
if ( $param -> type !== null ) {
return \false ;
}
}
return \true ;
2021-07-04 18:27:51 +00:00
}
2021-07-25 16:57:05 +00:00
private function isSafeType ( \PHPStan\Reflection\ClassReflection $classReflection , \PhpParser\Node\Stmt\ClassMethod $classMethod ) : bool
2021-07-05 23:37:59 +00:00
{
2021-11-05 10:25:23 +00:00
$classReflectionName = $classReflection -> getName ();
2021-07-05 23:37:59 +00:00
foreach ( $this -> safeTypes as $safeType ) {
if ( $classReflection -> isSubclassOf ( $safeType )) {
return \true ;
}
// skip self too
2021-11-05 10:25:23 +00:00
if ( $classReflectionName === $safeType ) {
2021-07-05 23:37:59 +00:00
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
2021-11-05 10:25:23 +00:00
if ( $classReflectionName === $safeType ) {
2021-07-06 13:47:20 +00:00
return \true ;
}
}
2021-07-05 23:37:59 +00:00
return \false ;
}
2020-12-08 12:03:00 +00:00
}