2020-12-08 12:03:00 +00:00
< ? php
2021-05-09 20:15:43 +00:00
declare ( strict_types = 1 );
2021-04-21 22:02:38 +00:00
namespace Rector\DowngradePhp72\Rector\Class_ ;
2020-12-08 12:03:00 +00:00
use PhpParser\Node ;
2021-06-01 23:57:14 +00:00
use PhpParser\Node\Name\FullyQualified ;
2020-12-08 12:03:00 +00:00
use PhpParser\Node\Param ;
2021-04-21 22:02:38 +00:00
use PhpParser\Node\Stmt\Class_ ;
2021-06-04 11:46:51 +00:00
use PhpParser\Node\Stmt\ClassLike ;
2020-12-08 12:03:00 +00:00
use PhpParser\Node\Stmt\ClassMethod ;
2021-04-23 23:14:40 +00:00
use PhpParser\Node\Stmt\Interface_ ;
2020-12-08 12:03:00 +00:00
use PHPStan\Analyser\Scope ;
use PHPStan\Reflection\ClassReflection ;
2021-04-24 07:13:59 +00:00
use Rector\Core\Exception\ShouldNotHappenException ;
2020-12-08 12:03:00 +00:00
use Rector\Core\Rector\AbstractRector ;
2021-04-21 22:02:38 +00:00
use Rector\DowngradePhp72\NodeAnalyzer\ClassLikeWithTraitsClassMethodResolver ;
2021-04-23 17:54:30 +00:00
use Rector\DowngradePhp72\NodeAnalyzer\ParamContravariantDetector ;
2021-04-23 09:03:45 +00:00
use Rector\DowngradePhp72\NodeAnalyzer\ParentChildClassMethodTypeResolver ;
use Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator ;
2020-12-08 12:03:00 +00:00
use Rector\NodeTypeResolver\Node\AttributeKey ;
2021-04-23 17:54:30 +00:00
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory ;
2020-12-08 12:03:00 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
/**
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-04-21 22:02:38 +00:00
* @ see \Rector\Tests\DowngradePhp72\Rector\Class_\DowngradeParameterTypeWideningRector\DowngradeParameterTypeWideningRectorTest
2020-12-08 12:03:00 +00:00
*/
2021-05-10 22:23:08 +00:00
final class DowngradeParameterTypeWideningRector extends \Rector\Core\Rector\AbstractRector
2020-12-08 12:03:00 +00:00
{
2021-01-18 20:06:02 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\DowngradePhp72\NodeAnalyzer\ClassLikeWithTraitsClassMethodResolver
2021-01-18 20:06:02 +00:00
*/
2021-04-23 09:03:45 +00:00
private $classLikeWithTraitsClassMethodResolver ;
2021-03-17 10:12:39 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\DowngradePhp72\NodeAnalyzer\ParentChildClassMethodTypeResolver
2021-03-17 10:12:39 +00:00
*/
2021-04-23 09:03:45 +00:00
private $parentChildClassMethodTypeResolver ;
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-04-23 17:54:30 +00:00
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\DowngradePhp72\NodeAnalyzer\ParamContravariantDetector
2021-04-23 17:54:30 +00:00
*/
private $paramContravariantDetector ;
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\NodeTypeResolver\PHPStan\Type\TypeFactory
2021-04-23 17:54:30 +00:00
*/
private $typeFactory ;
2021-05-10 22:23:08 +00:00
public function __construct ( \Rector\DowngradePhp72\NodeAnalyzer\ClassLikeWithTraitsClassMethodResolver $classLikeWithTraitsClassMethodResolver , \Rector\DowngradePhp72\NodeAnalyzer\ParentChildClassMethodTypeResolver $parentChildClassMethodTypeResolver , \Rector\DowngradePhp72\PhpDoc\NativeParamToPhpDocDecorator $nativeParamToPhpDocDecorator , \Rector\DowngradePhp72\NodeAnalyzer\ParamContravariantDetector $paramContravariantDetector , \Rector\NodeTypeResolver\PHPStan\Type\TypeFactory $typeFactory )
2021-05-09 20:15:43 +00:00
{
2021-04-21 22:02:38 +00:00
$this -> classLikeWithTraitsClassMethodResolver = $classLikeWithTraitsClassMethodResolver ;
2021-04-23 09:03:45 +00:00
$this -> parentChildClassMethodTypeResolver = $parentChildClassMethodTypeResolver ;
$this -> nativeParamToPhpDocDecorator = $nativeParamToPhpDocDecorator ;
2021-04-23 17:54:30 +00:00
$this -> paramContravariantDetector = $paramContravariantDetector ;
$this -> typeFactory = $typeFactory ;
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-05-10 22:23:08 +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\CodeSample ( <<< '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-05-09 20:15:43 +00:00
)]);
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-05-10 22:23:08 +00:00
return [ \PhpParser\Node\Stmt\Class_ :: class , \PhpParser\Node\Stmt\Interface_ :: class ];
2020-12-08 12:03:00 +00:00
}
/**
2021-04-23 23:14:40 +00:00
* @ param Class_ | Interface_ $node
2020-12-08 12:03:00 +00:00
*/
2021-05-10 22:23:08 +00:00
public function refactor ( \PhpParser\Node $node ) : ? \PhpParser\Node
2020-12-08 12:03:00 +00:00
{
2021-05-10 22:23:08 +00:00
$scope = $node -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: SCOPE );
if ( ! $scope instanceof \PHPStan\Analyser\Scope ) {
2021-04-23 09:03:45 +00:00
return null ;
}
2021-06-04 11:46:51 +00:00
$classReflection = $scope -> getClassReflection ();
if ( ! $classReflection instanceof \PHPStan\Reflection\ClassReflection ) {
return null ;
}
if ( $this -> isEmptyClassReflection ( $classReflection )) {
2021-04-23 09:03:45 +00:00
return null ;
}
2021-06-01 15:28:43 +00:00
if ( $this -> hasExtendExternal ( $node )) {
return null ;
}
2021-05-09 20:15:43 +00:00
$hasChanged = \false ;
2021-06-04 11:46:51 +00:00
/** @var ClassReflection[] $ancestors */
$ancestors = $classReflection -> getAncestors ();
$classMethods = $this -> classLikeWithTraitsClassMethodResolver -> resolve ( $ancestors );
$classLikes = $this -> nodeRepository -> findClassesAndInterfacesByType ( $classReflection -> getName ());
$interfaces = $classReflection -> getInterfaces ();
2021-04-21 22:02:38 +00:00
foreach ( $classMethods as $classMethod ) {
2021-06-04 11:46:51 +00:00
if ( $this -> skipClassMethod ( $classMethod , $classReflection , $ancestors , $classLikes )) {
2021-04-23 17:54:30 +00:00
continue ;
}
// refactor here
2021-06-04 11:46:51 +00:00
if ( $this -> refactorClassMethod ( $classMethod , $classReflection , $ancestors , $interfaces ) !== null ) {
2021-05-09 20:15:43 +00:00
$hasChanged = \true ;
2021-04-23 23:14:40 +00:00
}
2021-04-23 17:54:30 +00:00
}
if ( $hasChanged ) {
return $node ;
2020-12-08 12:03:00 +00:00
}
2021-04-23 17:54:30 +00:00
return null ;
2020-12-08 12:03:00 +00:00
}
2021-06-01 15:28:43 +00:00
/**
* @ param Class_ | Interface_ $node
*/
private function hasExtendExternal ( \PhpParser\Node $node ) : bool
{
if ( $node -> extends instanceof \PhpParser\Node\Name\FullyQualified ) {
$className = ( string ) $this -> getName ( $node -> extends );
$parentFound = ( bool ) $this -> nodeRepository -> findClass ( $className );
if ( ! $parentFound ) {
return \true ;
}
}
return \false ;
}
2021-03-17 10:12:39 +00:00
/**
* The topmost class is the source of truth , so we go only down to avoid up / down collission
2021-06-04 11:46:51 +00:00
* @ param ClassReflection [] $ancestors
* @ param ClassReflection [] $interfaces
2021-03-17 10:12:39 +00:00
*/
2021-06-04 11:46:51 +00:00
private function refactorClassMethod ( \PhpParser\Node\Stmt\ClassMethod $classMethod , \PHPStan\Reflection\ClassReflection $classReflection , array $ancestors , array $interfaces ) : ? \PhpParser\Node\Stmt\ClassMethod
2020-12-08 12:03:00 +00:00
{
2021-03-17 10:12:39 +00:00
/** @var string $methodName */
$methodName = $this -> nodeNameResolver -> getName ( $classMethod );
2021-05-09 20:15:43 +00:00
$hasChanged = \false ;
2021-04-24 07:13:59 +00:00
foreach ( $classMethod -> params as $position => $param ) {
2021-05-09 20:15:43 +00:00
if ( ! \is_int ( $position )) {
2021-05-10 22:23:08 +00:00
throw new \Rector\Core\Exception\ShouldNotHappenException ();
2021-04-24 07:13:59 +00:00
}
2021-04-23 17:54:30 +00:00
// Resolve the types in:
// - all ancestors + their descendant classes
// @todo - all implemented interfaces + their implementing classes
2021-06-04 11:46:51 +00:00
$parameterTypesByParentClassLikes = $this -> parentChildClassMethodTypeResolver -> resolve ( $classReflection , $methodName , $position , $ancestors , $interfaces );
2021-04-23 17:54:30 +00:00
$uniqueTypes = $this -> typeFactory -> uniquateTypes ( $parameterTypesByParentClassLikes );
2021-06-04 11:46:51 +00:00
if ( ! isset ( $uniqueTypes [ 1 ])) {
2021-04-23 17:54:30 +00:00
continue ;
2021-04-23 09:03:45 +00:00
}
2021-04-23 17:54:30 +00:00
$this -> removeParamTypeFromMethod ( $classMethod , $param );
2021-05-09 20:15:43 +00:00
$hasChanged = \true ;
2021-01-24 20:12:02 +00:00
}
2021-04-23 17:54:30 +00:00
if ( $hasChanged ) {
return $classMethod ;
2020-12-08 12:03:00 +00:00
}
2021-04-23 17:54:30 +00:00
return null ;
}
2021-05-10 22:23:08 +00:00
private function removeParamTypeFromMethod ( \PhpParser\Node\Stmt\ClassMethod $classMethod , \PhpParser\Node\Param $param ) : void
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-05-10 22:23:08 +00:00
$originalParam = $param -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: ORIGINAL_NODE );
2021-06-04 11:46:51 +00:00
if ( $originalParam instanceof \PhpParser\Node\Param && $originalParam -> type === null ) {
return ;
}
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-06-04 11:46:51 +00:00
/**
* @ param ClassReflection [] $ancestors
* @ param ClassLike [] $classLikes
*/
private function skipClassMethod ( \PhpParser\Node\Stmt\ClassMethod $classMethod , \PHPStan\Reflection\ClassReflection $classReflection , array $ancestors , array $classLikes ) : 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
}
if ( $classMethod -> params === []) {
2021-05-09 20:15:43 +00:00
return \true ;
2020-12-08 12:03:00 +00:00
}
2021-06-04 11:46:51 +00:00
/** @var string $classMethodName */
$classMethodName = $this -> nodeNameResolver -> getName ( $classMethod );
if ( $this -> paramContravariantDetector -> hasChildMethod ( $classLikes , $classMethodName )) {
2021-05-09 20:15:43 +00:00
return \false ;
2021-04-23 23:14:40 +00:00
}
2021-06-04 11:46:51 +00:00
return ! $this -> paramContravariantDetector -> hasParentMethod ( $classReflection , $ancestors , $classMethodName );
2020-12-08 12:03:00 +00:00
}
2021-06-04 11:46:51 +00:00
private function isEmptyClassReflection ( \PHPStan\Reflection\ClassReflection $classReflection ) : bool
2020-12-08 12:03:00 +00:00
{
2021-04-23 23:14:40 +00:00
if ( $classReflection -> isInterface ()) {
2021-05-09 20:15:43 +00:00
return \false ;
2021-04-23 23:14:40 +00:00
}
2021-05-09 20:15:43 +00:00
return \count ( $classReflection -> getAncestors ()) === 1 ;
2020-12-08 12:03:00 +00:00
}
}