2020-04-24 22:57:13 +00:00
< ? php
2021-05-09 20:15:43 +00:00
declare ( strict_types = 1 );
2020-04-24 22:57:13 +00:00
namespace Rector\Php80\Rector\Class_ ;
use PhpParser\Node ;
2021-07-08 16:33:18 +00:00
use PhpParser\Node\AttributeGroup ;
2020-10-21 21:06:24 +00:00
use PhpParser\Node\Expr\ArrowFunction ;
use PhpParser\Node\Expr\Closure ;
2021-07-12 08:29:31 +00:00
use PhpParser\Node\Param ;
2020-04-24 22:57:13 +00:00
use PhpParser\Node\Stmt\Class_ ;
use PhpParser\Node\Stmt\ClassMethod ;
2020-10-21 21:06:24 +00:00
use PhpParser\Node\Stmt\Function_ ;
2020-04-24 22:57:13 +00:00
use PhpParser\Node\Stmt\Property ;
2021-07-08 16:33:18 +00:00
use PHPStan\PhpDocParser\Ast\Node as DocNode ;
2021-06-22 16:12:07 +00:00
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode ;
2021-07-08 16:33:18 +00:00
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode ;
2021-10-29 11:12:52 +00:00
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode ;
2021-11-08 14:26:22 +00:00
use Rector\BetterPhpDocParser\Annotation\ChangeResolvedClassInParticularContextForAnnotation ;
2021-10-13 19:26:54 +00:00
use Rector\BetterPhpDocParser\AnnotationAnalyzer\DoctrineAnnotationTagValueNodeAnalyzer ;
2021-07-01 23:51:20 +00:00
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ;
2021-03-20 23:16:21 +00:00
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo ;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface ;
2020-04-24 22:57:13 +00:00
use Rector\Core\Rector\AbstractRector ;
2021-03-20 23:16:21 +00:00
use Rector\Core\ValueObject\PhpVersionFeature ;
2021-07-08 12:36:20 +00:00
use Rector\Php80\NodeFactory\AttrGroupsFactory ;
use Rector\Php80\PhpDocCleaner\ConvertedAnnotationToAttributeParentRemover ;
2021-03-20 23:16:21 +00:00
use Rector\Php80\ValueObject\AnnotationToAttribute ;
2021-07-01 16:34:59 +00:00
use Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute ;
2021-03-20 23:16:21 +00:00
use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory ;
2021-07-21 09:35:57 +00:00
use Rector\VersionBonding\Contract\MinPhpVersionInterface ;
2021-03-20 23:16:21 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ;
2020-11-16 17:50:38 +00:00
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2021-11-18 07:58:51 +00:00
use RectorPrefix20211118\Symplify\SimplePhpDocParser\PhpDocNodeTraverser ;
use RectorPrefix20211118\Webmozart\Assert\Assert ;
2020-04-24 22:57:13 +00:00
/**
2021-06-14 17:05:09 +00:00
* @ changelog https :// wiki . php . net / rfc / attributes_v2
2020-04-24 22:57:13 +00:00
*
2021-03-12 22:20:25 +00:00
* @ see \Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\AnnotationToAttributeRectorTest
2020-04-24 22:57:13 +00:00
*/
2021-07-21 09:35:57 +00:00
final class AnnotationToAttributeRector extends \Rector\Core\Rector\AbstractRector implements \Rector\Core\Contract\Rector\ConfigurableRectorInterface , \Rector\VersionBonding\Contract\MinPhpVersionInterface
2020-04-24 22:57:13 +00:00
{
/**
2021-03-20 23:16:21 +00:00
* @ var string
2020-04-24 22:57:13 +00:00
*/
2021-10-09 13:51:00 +00:00
public const ANNOTATION_TO_ATTRIBUTE = 'annotations_to_attributes' ;
2021-07-02 12:11:47 +00:00
/**
* List of annotations that should not be unwrapped
2021-07-02 20:55:14 +00:00
* @ var string []
2021-07-02 12:11:47 +00:00
*/
private const SKIP_UNWRAP_ANNOTATIONS = [ 'Symfony\\Component\\Validator\\Constraints\\All' , 'Symfony\\Component\\Validator\\Constraints\\AtLeastOneOf' , 'Symfony\\Component\\Validator\\Constraints\\Collection' , 'Symfony\\Component\\Validator\\Constraints\\Sequentially' ];
2021-03-20 23:16:21 +00:00
/**
* @ var AnnotationToAttribute []
*/
private $annotationsToAttributes = [];
/**
2021-05-10 23:39:21 +00:00
* @ var \Rector\PhpAttribute\Printer\PhpAttributeGroupFactory
2021-03-20 23:16:21 +00:00
*/
private $phpAttributeGroupFactory ;
2021-07-08 12:36:20 +00:00
/**
* @ var \Rector\Php80\PhpDocCleaner\ConvertedAnnotationToAttributeParentRemover
*/
private $convertedAnnotationToAttributeParentRemover ;
/**
* @ var \Rector\Php80\NodeFactory\AttrGroupsFactory
*/
private $attrGroupsFactory ;
2021-10-13 19:26:54 +00:00
/**
* @ var \Rector\BetterPhpDocParser\AnnotationAnalyzer\DoctrineAnnotationTagValueNodeAnalyzer
*/
private $doctrineAnnotationTagValueNodeAnalyzer ;
2021-11-08 14:26:22 +00:00
/**
* @ var \Rector\BetterPhpDocParser\Annotation\ChangeResolvedClassInParticularContextForAnnotation
*/
private $changeResolvedClassInParticularContextForAnnotation ;
public function __construct ( \Rector\PhpAttribute\Printer\PhpAttributeGroupFactory $phpAttributeGroupFactory , \Rector\Php80\PhpDocCleaner\ConvertedAnnotationToAttributeParentRemover $convertedAnnotationToAttributeParentRemover , \Rector\Php80\NodeFactory\AttrGroupsFactory $attrGroupsFactory , \Rector\BetterPhpDocParser\AnnotationAnalyzer\DoctrineAnnotationTagValueNodeAnalyzer $doctrineAnnotationTagValueNodeAnalyzer , \Rector\BetterPhpDocParser\Annotation\ChangeResolvedClassInParticularContextForAnnotation $changeResolvedClassInParticularContextForAnnotation )
2021-05-09 20:15:43 +00:00
{
2021-03-20 23:16:21 +00:00
$this -> phpAttributeGroupFactory = $phpAttributeGroupFactory ;
2021-07-08 12:36:20 +00:00
$this -> convertedAnnotationToAttributeParentRemover = $convertedAnnotationToAttributeParentRemover ;
$this -> attrGroupsFactory = $attrGroupsFactory ;
2021-10-13 19:26:54 +00:00
$this -> doctrineAnnotationTagValueNodeAnalyzer = $doctrineAnnotationTagValueNodeAnalyzer ;
2021-11-08 14:26:22 +00:00
$this -> changeResolvedClassInParticularContextForAnnotation = $changeResolvedClassInParticularContextForAnnotation ;
2020-04-24 22:57:13 +00:00
}
2021-05-10 22:23:08 +00:00
public function getRuleDefinition () : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
2020-04-24 22:57:13 +00:00
{
2021-05-10 22:23:08 +00:00
return new \Symplify\RuleDocGenerator\ValueObject\RuleDefinition ( 'Change annotation to attribute' , [ new \Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ( <<< 'CODE_SAMPLE'
2021-02-28 07:47:48 +00:00
use Symfony\Component\Routing\Annotation\Route ;
2020-04-24 22:57:13 +00:00
2021-02-28 07:47:48 +00:00
class SymfonyRoute
2020-04-24 22:57:13 +00:00
{
2021-02-28 07:47:48 +00:00
/**
* @ Route ( " /path " , name = " action " )
*/
public function action ()
{
}
2020-04-24 22:57:13 +00:00
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
, <<< 'CODE_SAMPLE'
2021-02-28 07:47:48 +00:00
use Symfony\Component\Routing\Annotation\Route ;
2020-04-24 22:57:13 +00:00
2021-02-28 07:47:48 +00:00
class SymfonyRoute
2020-04-24 22:57:13 +00:00
{
2021-02-28 07:47:48 +00:00
#[Route(path: '/path', name: 'action')]
public function action ()
{
}
2020-04-24 22:57:13 +00:00
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-06-05 21:47:54 +00:00
, [ self :: ANNOTATION_TO_ATTRIBUTE => [ new \Rector\Php80\ValueObject\AnnotationToAttribute ( 'Symfony\\Component\\Routing\\Annotation\\Route' )]])]);
2020-04-24 22:57:13 +00:00
}
/**
2021-02-27 00:06:15 +00:00
* @ return array < class - string < Node >>
2020-04-24 22:57:13 +00:00
*/
2021-05-09 20:15:43 +00:00
public function getNodeTypes () : array
2020-04-24 22:57:13 +00:00
{
2021-07-12 08:29:31 +00:00
return [ \PhpParser\Node\Stmt\Class_ :: class , \PhpParser\Node\Stmt\Property :: class , \PhpParser\Node\Param :: class , \PhpParser\Node\Stmt\ClassMethod :: class , \PhpParser\Node\Stmt\Function_ :: class , \PhpParser\Node\Expr\Closure :: class , \PhpParser\Node\Expr\ArrowFunction :: class ];
2020-04-24 22:57:13 +00:00
}
/**
2021-07-12 08:29:31 +00:00
* @ param Class_ | Property | Param | ClassMethod | Function_ | Closure | ArrowFunction $node
2020-04-24 22:57:13 +00:00
*/
2021-07-05 22:50:18 +00:00
public function refactor ( \PhpParser\Node $node ) : ? \PhpParser\Node
2020-04-24 22:57:13 +00:00
{
2021-03-20 23:16:21 +00:00
$phpDocInfo = $this -> phpDocInfoFactory -> createFromNode ( $node );
2021-05-10 22:23:08 +00:00
if ( ! $phpDocInfo instanceof \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo ) {
2021-03-20 23:16:21 +00:00
return null ;
}
2021-06-30 21:52:31 +00:00
// 1. generic tags
2021-07-08 16:33:18 +00:00
$genericAttributeGroups = $this -> processGenericTags ( $phpDocInfo );
2021-06-30 21:52:31 +00:00
// 2. Doctrine annotation classes
2021-07-08 16:33:18 +00:00
$annotationAttributeGroups = $this -> processDoctrineAnnotationClasses ( $phpDocInfo );
$attributeGroups = \array_merge ( $genericAttributeGroups , $annotationAttributeGroups );
if ( $attributeGroups === []) {
return null ;
2021-03-20 23:16:21 +00:00
}
2021-07-08 16:33:18 +00:00
$node -> attrGroups = \array_merge ( $node -> attrGroups , $attributeGroups );
$this -> convertedAnnotationToAttributeParentRemover -> processPhpDocNode ( $phpDocInfo -> getPhpDocNode (), $this -> annotationsToAttributes , self :: SKIP_UNWRAP_ANNOTATIONS );
return $node ;
2021-03-20 23:16:21 +00:00
}
/**
* @ param array < string , AnnotationToAttribute [] > $configuration
*/
2021-05-09 20:15:43 +00:00
public function configure ( array $configuration ) : void
2021-03-20 23:16:21 +00:00
{
$annotationsToAttributes = $configuration [ self :: ANNOTATION_TO_ATTRIBUTE ] ? ? [];
2021-11-18 07:58:51 +00:00
\RectorPrefix20211118\Webmozart\Assert\Assert :: allIsInstanceOf ( $annotationsToAttributes , \Rector\Php80\ValueObject\AnnotationToAttribute :: class );
2021-03-20 23:16:21 +00:00
$this -> annotationsToAttributes = $annotationsToAttributes ;
2020-04-24 22:57:13 +00:00
}
2021-07-21 09:35:57 +00:00
public function provideMinPhpVersion () : int
{
return \Rector\Core\ValueObject\PhpVersionFeature :: ATTRIBUTES ;
}
2021-05-28 12:08:33 +00:00
/**
2021-07-08 16:33:18 +00:00
* @ return AttributeGroup []
2021-05-28 12:08:33 +00:00
*/
2021-07-08 16:33:18 +00:00
private function processGenericTags ( \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo $phpDocInfo ) : array
2021-05-28 12:08:33 +00:00
{
2021-07-08 16:33:18 +00:00
$attributeGroups = [];
2021-11-18 07:58:51 +00:00
$phpDocNodeTraverser = new \RectorPrefix20211118\Symplify\SimplePhpDocParser\PhpDocNodeTraverser ();
2021-07-08 16:33:18 +00:00
$phpDocNodeTraverser -> traverseWithCallable ( $phpDocInfo -> getPhpDocNode (), '' , function ( \PHPStan\PhpDocParser\Ast\Node $docNode ) use ( & $attributeGroups , $phpDocInfo ) : ? int {
if ( ! $docNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode ) {
return null ;
}
if ( ! $docNode -> value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode ) {
return null ;
}
$tag = \trim ( $docNode -> name , '@' );
// not a basic one
if ( \strpos ( $tag , '\\' ) !== \false ) {
return null ;
}
2021-05-28 12:08:33 +00:00
foreach ( $this -> annotationsToAttributes as $annotationToAttribute ) {
2021-06-30 21:52:31 +00:00
$desiredTag = $annotationToAttribute -> getTag ();
2021-07-08 16:33:18 +00:00
if ( $desiredTag !== $tag ) {
2021-06-30 21:52:31 +00:00
continue ;
2021-05-28 12:08:33 +00:00
}
2021-07-08 16:33:18 +00:00
$attributeGroups [] = $this -> phpAttributeGroupFactory -> createFromSimpleTag ( $annotationToAttribute );
$phpDocInfo -> markAsChanged ();
2021-11-18 07:58:51 +00:00
return \RectorPrefix20211118\Symplify\SimplePhpDocParser\PhpDocNodeTraverser :: NODE_REMOVE ;
2021-05-28 12:08:33 +00:00
}
2021-07-08 16:33:18 +00:00
return null ;
});
return $attributeGroups ;
2021-06-22 16:12:07 +00:00
}
2021-06-30 21:52:31 +00:00
/**
2021-07-08 16:33:18 +00:00
* @ return AttributeGroup []
2021-06-30 21:52:31 +00:00
*/
2021-07-08 16:33:18 +00:00
private function processDoctrineAnnotationClasses ( \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo $phpDocInfo ) : array
2021-05-28 12:08:33 +00:00
{
2021-07-01 23:51:20 +00:00
$doctrineTagAndAnnotationToAttributes = [];
2021-11-18 07:58:51 +00:00
$phpDocNodeTraverser = new \RectorPrefix20211118\Symplify\SimplePhpDocParser\PhpDocNodeTraverser ();
2021-10-13 19:26:54 +00:00
$phpDocNodeTraverser -> traverseWithCallable ( $phpDocInfo -> getPhpDocNode (), '' , function ( \PHPStan\PhpDocParser\Ast\Node $node ) use ( & $doctrineTagAndAnnotationToAttributes , $phpDocInfo ) : ? int {
$docNode = $this -> doctrineAnnotationTagValueNodeAnalyzer -> resolveDoctrineAnnotationTagValueNode ( $node );
if ( ! $docNode instanceof \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ) {
2021-07-08 15:36:45 +00:00
return null ;
2021-07-01 23:51:20 +00:00
}
2021-10-13 19:26:54 +00:00
if ( $docNode -> hasClassNames ( self :: SKIP_UNWRAP_ANNOTATIONS )) {
2021-11-18 07:58:51 +00:00
return \RectorPrefix20211118\Symplify\SimplePhpDocParser\PhpDocNodeTraverser :: DONT_TRAVERSE_CURRENT_AND_CHILDREN ;
2021-07-08 16:33:18 +00:00
}
2021-07-01 23:51:20 +00:00
foreach ( $this -> annotationsToAttributes as $annotationToAttribute ) {
2021-10-13 19:26:54 +00:00
if ( ! $docNode -> hasClassName ( $annotationToAttribute -> getTag ())) {
2021-07-01 23:51:20 +00:00
continue ;
}
2021-11-08 14:26:22 +00:00
$this -> changeResolvedClassInParticularContextForAnnotation -> changeResolvedClassIfNeed ( $annotationToAttribute , $docNode );
2021-10-13 19:26:54 +00:00
if ( $this -> doctrineAnnotationTagValueNodeAnalyzer -> isNested ( $docNode , $this -> annotationsToAttributes )) {
2021-10-29 11:12:52 +00:00
$stringValues = $this -> getStringFromNestedDoctrineTagAnnotationToAttribute ( $docNode );
$newDoctrineTagValueNode = $this -> resolveNewDoctrineTagValueNode ( $docNode , $stringValues );
2021-10-13 12:11:53 +00:00
$doctrineTagAndAnnotationToAttributes [] = new \Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute ( $newDoctrineTagValueNode , $annotationToAttribute );
2021-10-13 19:26:54 +00:00
/** @var DoctrineAnnotationTagValueNode $docNode */
$doctrineTagAndAnnotationToAttributes = $this -> addNestedDoctrineTagAndAnnotationToAttribute ( $docNode , $doctrineTagAndAnnotationToAttributes );
2021-10-13 12:11:53 +00:00
} else {
2021-10-13 19:26:54 +00:00
$doctrineTagAndAnnotationToAttributes [] = new \Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute ( $docNode , $annotationToAttribute );
2021-10-13 12:11:53 +00:00
}
2021-07-01 23:51:20 +00:00
$phpDocInfo -> markAsChanged ();
// remove the original doctrine annotation, it becomes an attribute
2021-11-18 07:58:51 +00:00
return \RectorPrefix20211118\Symplify\SimplePhpDocParser\PhpDocNodeTraverser :: NODE_REMOVE ;
2021-07-01 23:51:20 +00:00
}
2021-07-08 15:36:45 +00:00
return null ;
2021-07-01 23:51:20 +00:00
});
2021-07-08 16:33:18 +00:00
return $this -> attrGroupsFactory -> create ( $doctrineTagAndAnnotationToAttributes );
2021-07-02 12:11:47 +00:00
}
2021-10-29 11:12:52 +00:00
/**
* @ param string [] $stringValues
*/
private function resolveNewDoctrineTagValueNode ( \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode , array $stringValues ) : \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode
{
if ( $stringValues === []) {
return new \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ( $doctrineAnnotationTagValueNode -> identifierTypeNode );
}
2021-11-17 07:16:56 +00:00
return new \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ( $doctrineAnnotationTagValueNode -> identifierTypeNode , null , $stringValues );
2021-10-29 11:12:52 +00:00
}
/**
* @ return string []
*/
private function getStringFromNestedDoctrineTagAnnotationToAttribute ( \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode ) : array
{
$values = $doctrineAnnotationTagValueNode -> getValues ();
2021-11-17 07:16:56 +00:00
return \array_filter ( $values , 'is_string' );
2021-10-29 11:12:52 +00:00
}
2021-10-13 19:26:54 +00:00
/**
* @ param DoctrineTagAndAnnotationToAttribute [] $doctrineTagAndAnnotationToAttributes
* @ return DoctrineTagAndAnnotationToAttribute [] $doctrineTagAndAnnotationToAttributes
*/
private function addNestedDoctrineTagAndAnnotationToAttribute ( \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode , array $doctrineTagAndAnnotationToAttributes ) : array
2021-10-13 12:11:53 +00:00
{
$values = $doctrineAnnotationTagValueNode -> getValues ();
foreach ( $values as $value ) {
2021-10-29 11:12:52 +00:00
if ( \is_string ( $value )) {
$originalValue = new \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ( new \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode ( $value ));
$doctrineTagAndAnnotationToAttributes = $this -> collectDoctrineTagAndAnnotationToAttributes ( $originalValue , $doctrineTagAndAnnotationToAttributes );
continue ;
}
2021-10-13 12:11:53 +00:00
$originalValues = $value -> getOriginalValues ();
foreach ( $originalValues as $originalValue ) {
2021-10-13 19:26:54 +00:00
$doctrineTagAndAnnotationToAttributes = $this -> collectDoctrineTagAndAnnotationToAttributes ( $originalValue , $doctrineTagAndAnnotationToAttributes );
}
}
return $doctrineTagAndAnnotationToAttributes ;
}
/**
* @ param DoctrineTagAndAnnotationToAttribute [] $doctrineTagAndAnnotationToAttributes
* @ return DoctrineTagAndAnnotationToAttribute [] $doctrineTagAndAnnotationToAttributes
*/
private function collectDoctrineTagAndAnnotationToAttributes ( \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode , array $doctrineTagAndAnnotationToAttributes ) : array
{
foreach ( $this -> annotationsToAttributes as $annotationToAttribute ) {
$tag = $annotationToAttribute -> getTag ();
if ( ! $doctrineAnnotationTagValueNode -> hasClassName ( $tag )) {
continue ;
2021-10-13 12:11:53 +00:00
}
2021-10-13 19:26:54 +00:00
$doctrineTagAndAnnotationToAttributes [] = new \Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute ( $doctrineAnnotationTagValueNode , $annotationToAttribute );
2021-10-13 12:11:53 +00:00
}
2021-10-13 19:26:54 +00:00
return $doctrineTagAndAnnotationToAttributes ;
2021-10-13 12:11:53 +00:00
}
2020-04-24 22:57:13 +00:00
}