2021-11-18 09:50:32 +00:00
< ? php
declare ( strict_types = 1 );
2022-06-06 17:12:56 +00:00
namespace Rector\Compatibility\Rector\Class_ ;
2021-11-18 09:50:32 +00:00
2022-06-06 17:12:56 +00:00
use PhpParser\Node ;
use PhpParser\Node\Expr\Variable ;
use PhpParser\Node\NullableType ;
use PhpParser\Node\Param ;
use PhpParser\Node\Stmt\Class_ ;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode ;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo ;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover ;
use Rector\Compatibility\NodeAnalyzer\RequiredAnnotationPropertyAnalyzer ;
use Rector\Compatibility\NodeFactory\ConstructorClassMethodFactory ;
use Rector\Compatibility\ValueObject\PropertyWithPhpDocInfo ;
use Rector\Core\Rector\AbstractRector ;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer ;
2022-08-19 07:49:30 +00:00
use Rector\Php81\Enum\AttributeName ;
2022-06-07 09:18:30 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2021-11-18 09:50:32 +00:00
/**
* @ see \Rector\Tests\Compatibility\Rector\Class_\AttributeCompatibleAnnotationRector\AttributeCompatibleAnnotationRectorTest
*/
2022-06-07 08:22:29 +00:00
final class AttributeCompatibleAnnotationRector extends AbstractRector
2021-11-18 09:50:32 +00:00
{
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-11-18 09:50:32 +00:00
* @ var \Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer
*/
private $phpAttributeAnalyzer ;
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-11-18 09:50:32 +00:00
* @ var \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover
*/
private $phpDocTagRemover ;
2021-11-18 19:01:15 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-11-18 19:01:15 +00:00
* @ var \Rector\Compatibility\NodeAnalyzer\RequiredAnnotationPropertyAnalyzer
*/
private $requiredAnnotationPropertyAnalyzer ;
2021-11-18 19:24:25 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-11-18 19:24:25 +00:00
* @ var \Rector\Compatibility\NodeFactory\ConstructorClassMethodFactory
*/
private $constructorClassMethodFactory ;
2022-06-07 08:22:29 +00:00
public function __construct ( PhpAttributeAnalyzer $phpAttributeAnalyzer , PhpDocTagRemover $phpDocTagRemover , RequiredAnnotationPropertyAnalyzer $requiredAnnotationPropertyAnalyzer , ConstructorClassMethodFactory $constructorClassMethodFactory )
2021-11-18 09:50:32 +00:00
{
$this -> phpAttributeAnalyzer = $phpAttributeAnalyzer ;
$this -> phpDocTagRemover = $phpDocTagRemover ;
2021-11-18 19:01:15 +00:00
$this -> requiredAnnotationPropertyAnalyzer = $requiredAnnotationPropertyAnalyzer ;
2021-11-18 19:24:25 +00:00
$this -> constructorClassMethodFactory = $constructorClassMethodFactory ;
2021-11-18 09:50:32 +00:00
}
2022-06-07 08:22:29 +00:00
public function getRuleDefinition () : RuleDefinition
2021-11-18 09:50:32 +00:00
{
2022-06-07 08:22:29 +00:00
return new RuleDefinition ( 'Change annotation to attribute compatible form, see https://tomasvotruba.com/blog/doctrine-annotations-and-attributes-living-together-in-peace/' , [ new CodeSample ( <<< 'CODE_SAMPLE'
2021-11-18 09:50:32 +00:00
use Doctrine\Common\Annotations\Annotation\Required ;
/**
* @ annotation
*/
class SomeAnnotation
{
/**
* @ var string []
* @ Required ()
*/
public array $enum ;
}
CODE_SAMPLE
, <<< 'CODE_SAMPLE'
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor ;
/**
* @ annotation
* @ NamedArgumentConstructor
*/
class SomeAnnotation
{
/**
* @ param string [] $enum
*/
public function __construct (
public array $enum
) {
}
}
CODE_SAMPLE
)]);
}
/**
* @ return array < class - string < Node >>
*/
public function getNodeTypes () : array
{
2022-06-07 08:22:29 +00:00
return [ Class_ :: class ];
2021-11-18 09:50:32 +00:00
}
/**
2021-12-10 10:22:23 +00:00
* @ param Class_ $node
2021-11-18 09:50:32 +00:00
*/
2022-06-07 08:22:29 +00:00
public function refactor ( Node $node ) : ? Node
2021-11-18 09:50:32 +00:00
{
$phpDocInfo = $this -> phpDocInfoFactory -> createFromNode ( $node );
2022-06-07 08:22:29 +00:00
if ( ! $phpDocInfo instanceof PhpDocInfo ) {
2021-11-18 09:50:32 +00:00
return null ;
}
if ( $this -> shouldSkipClass ( $phpDocInfo , $node )) {
return null ;
}
// add "NamedArgumentConstructor"
2022-06-07 09:46:15 +00:00
$phpDocInfo -> addTagValueNode ( new DoctrineAnnotationTagValueNode ( new IdentifierTypeNode ( 'Doctrine\\Common\\Annotations\\Annotation\\NamedArgumentConstructor' )));
2021-11-18 09:50:32 +00:00
// resolve required properties
$requiredPropertiesWithPhpDocInfos = [];
foreach ( $node -> getProperties () as $property ) {
if ( ! $property -> isPublic ()) {
continue ;
}
$propertyPhpDocInfo = $this -> phpDocInfoFactory -> createFromNodeOrEmpty ( $property );
2021-11-18 19:01:15 +00:00
if ( ! $this -> requiredAnnotationPropertyAnalyzer -> isRequiredProperty ( $propertyPhpDocInfo , $property )) {
2021-11-18 09:50:32 +00:00
continue ;
}
$propertyName = $this -> getName ( $property );
2022-06-07 08:22:29 +00:00
$requiredPropertiesWithPhpDocInfos [] = new PropertyWithPhpDocInfo ( $propertyName , $property , $propertyPhpDocInfo );
2021-11-18 09:50:32 +00:00
}
2021-11-18 19:24:25 +00:00
$params = $this -> createConstructParams ( $requiredPropertiesWithPhpDocInfos );
$constructorClassMethod = $this -> constructorClassMethodFactory -> createConstructorClassMethod ( $requiredPropertiesWithPhpDocInfos , $params );
2021-11-18 09:50:32 +00:00
$node -> stmts = \array_merge ( $node -> stmts , [ $constructorClassMethod ]);
return $node ;
}
2022-06-07 08:22:29 +00:00
private function shouldSkipClass ( PhpDocInfo $phpDocInfo , Class_ $class ) : bool
2021-11-18 09:50:32 +00:00
{
if ( ! $phpDocInfo -> hasByNames ([ 'Annotation' , 'annotation' ])) {
return \true ;
}
2022-06-07 09:46:15 +00:00
if ( $phpDocInfo -> hasByAnnotationClass ( 'Doctrine\\Common\\Annotations\\Annotation\\NamedArgumentConstructor' )) {
2021-11-18 09:50:32 +00:00
return \true ;
}
// has attribute? skip it
2022-08-19 07:49:30 +00:00
return $this -> phpAttributeAnalyzer -> hasPhpAttribute ( $class , AttributeName :: ATTRIBUTE );
2021-11-18 09:50:32 +00:00
}
/**
* @ param PropertyWithPhpDocInfo [] $requiredPropertiesWithPhpDocInfos
* @ return Param []
*/
private function createConstructParams ( array $requiredPropertiesWithPhpDocInfos ) : array
{
$params = [];
foreach ( $requiredPropertiesWithPhpDocInfos as $requiredPropertyWithPhpDocInfo ) {
$property = $requiredPropertyWithPhpDocInfo -> getProperty ();
$propertyName = $this -> getName ( $property );
// unwrap nullable type, as variable is required
$propertyType = $property -> type ;
2022-06-07 08:22:29 +00:00
if ( $propertyType instanceof NullableType ) {
2021-11-18 09:50:32 +00:00
$propertyType = $propertyType -> type ;
}
2022-07-03 12:14:45 +00:00
$params [] = new Param ( new Variable ( $propertyName ), null , $propertyType , \false , \false , [], $property -> flags );
2021-11-18 09:50:32 +00:00
$propertyPhpDocInfo = $requiredPropertyWithPhpDocInfo -> getPhpDocInfo ();
// remove required
2022-06-07 09:46:15 +00:00
$this -> phpDocTagRemover -> removeByName ( $propertyPhpDocInfo , 'Doctrine\\Common\\Annotations\\Annotation\\Required' );
2021-11-18 09:50:32 +00:00
$this -> removeNode ( $property );
}
return $params ;
}
}