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-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 ;
2021-11-18 21:01:01 +00:00
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover ;
2021-03-20 23:16:21 +00:00
use Rector\Core\Contract\Rector\ConfigurableRectorInterface ;
2022-04-28 20:11:07 +00:00
use Rector\Core\Php\PhpVersionProvider ;
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 ;
2022-04-23 22:28:13 +00:00
use Rector\Php80\NodeManipulator\AttributeGroupNamedArgumentManipulator ;
2021-11-19 10:19:13 +00:00
use Rector\Php80\PhpDoc\PhpDocNodeFinder ;
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-12-19 12:21:32 +00:00
use Rector\PhpAttribute\RemovableAnnotationAnalyzer ;
2021-12-19 01:17:40 +00:00
use Rector\PhpAttribute\UnwrapableAnnotationAnalyzer ;
2021-07-21 09:35:57 +00:00
use Rector\VersionBonding\Contract\MinPhpVersionInterface ;
2022-05-27 10:39:50 +00:00
use RectorPrefix20220527\Symplify\Astral\PhpDocParser\PhpDocNodeTraverser ;
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 ;
2022-05-27 10:39:50 +00:00
use RectorPrefix20220527\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
*/
2022-05-27 11:51:31 +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 AnnotationToAttribute []
*/
private $annotationsToAttributes = [];
/**
2021-12-04 12:47:17 +00:00
* @ readonly
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
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-07-08 12:36:20 +00:00
* @ var \Rector\Php80\NodeFactory\AttrGroupsFactory
*/
private $attrGroupsFactory ;
2021-10-13 19:26:54 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-11-18 21:01:01 +00:00
* @ var \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover
2021-10-13 19:26:54 +00:00
*/
2021-11-18 21:01:01 +00:00
private $phpDocTagRemover ;
2021-11-19 10:19:13 +00:00
/**
2021-12-04 12:47:17 +00:00
* @ readonly
2021-11-19 10:19:13 +00:00
* @ var \Rector\Php80\PhpDoc\PhpDocNodeFinder
*/
private $phpDocNodeFinder ;
2021-12-19 01:17:40 +00:00
/**
* @ readonly
* @ var \Rector\PhpAttribute\UnwrapableAnnotationAnalyzer
*/
private $unwrapableAnnotationAnalyzer ;
2021-12-19 12:21:32 +00:00
/**
* @ readonly
* @ var \Rector\PhpAttribute\RemovableAnnotationAnalyzer
*/
private $removableAnnotationAnalyzer ;
2022-04-23 22:28:13 +00:00
/**
* @ readonly
* @ var \Rector\Php80\NodeManipulator\AttributeGroupNamedArgumentManipulator
*/
private $attributeGroupNamedArgumentManipulator ;
2022-04-28 20:11:07 +00:00
/**
* @ readonly
* @ var \Rector\Core\Php\PhpVersionProvider
*/
private $phpVersionProvider ;
2022-05-27 11:51:31 +00:00
public function __construct ( \Rector\PhpAttribute\Printer\PhpAttributeGroupFactory $phpAttributeGroupFactory , \Rector\Php80\NodeFactory\AttrGroupsFactory $attrGroupsFactory , \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover $phpDocTagRemover , \Rector\Php80\PhpDoc\PhpDocNodeFinder $phpDocNodeFinder , \Rector\PhpAttribute\UnwrapableAnnotationAnalyzer $unwrapableAnnotationAnalyzer , \Rector\PhpAttribute\RemovableAnnotationAnalyzer $removableAnnotationAnalyzer , \Rector\Php80\NodeManipulator\AttributeGroupNamedArgumentManipulator $attributeGroupNamedArgumentManipulator , \Rector\Core\Php\PhpVersionProvider $phpVersionProvider )
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 -> attrGroupsFactory = $attrGroupsFactory ;
2021-11-18 21:01:01 +00:00
$this -> phpDocTagRemover = $phpDocTagRemover ;
2021-11-19 10:19:13 +00:00
$this -> phpDocNodeFinder = $phpDocNodeFinder ;
2021-12-19 01:17:40 +00:00
$this -> unwrapableAnnotationAnalyzer = $unwrapableAnnotationAnalyzer ;
2021-12-19 12:21:32 +00:00
$this -> removableAnnotationAnalyzer = $removableAnnotationAnalyzer ;
2022-04-23 22:28:13 +00:00
$this -> attributeGroupNamedArgumentManipulator = $attributeGroupNamedArgumentManipulator ;
2022-04-28 20:11:07 +00:00
$this -> phpVersionProvider = $phpVersionProvider ;
2020-04-24 22:57:13 +00:00
}
2022-05-27 11:51:31 +00:00
public function getRuleDefinition () : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
2020-04-24 22:57:13 +00:00
{
2022-05-27 11:51:31 +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
2022-05-27 11:51:31 +00:00
, [ 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
{
2022-05-27 11:51: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-12-10 10:22:23 +00:00
* @ param Class_ | Property | Param | ClassMethod | Function_ | Closure | ArrowFunction $node
2020-04-24 22:57:13 +00:00
*/
2022-05-27 11:51:31 +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 );
2022-05-27 11:51:31 +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
}
2022-04-23 22:28:13 +00:00
$attributeGroups = $this -> attributeGroupNamedArgumentManipulator -> processSpecialClassTypes ( $attributeGroups );
2021-07-08 16:33:18 +00:00
$node -> attrGroups = \array_merge ( $node -> attrGroups , $attributeGroups );
return $node ;
2021-03-20 23:16:21 +00:00
}
/**
2021-11-28 17:01:20 +00:00
* @ param mixed [] $configuration
2021-03-20 23:16:21 +00:00
*/
2021-12-10 10:22:23 +00:00
public function configure ( array $configuration ) : void
2021-03-20 23:16:21 +00:00
{
2022-05-27 11:51:31 +00:00
\RectorPrefix20220527\Webmozart\Assert\Assert :: allIsAOf ( $configuration , \Rector\Php80\ValueObject\AnnotationToAttribute :: class );
2022-02-18 01:46:17 +00:00
$this -> annotationsToAttributes = $configuration ;
$this -> unwrapableAnnotationAnalyzer -> configure ( $configuration );
$this -> removableAnnotationAnalyzer -> configure ( $configuration );
2020-04-24 22:57:13 +00:00
}
2021-07-21 09:35:57 +00:00
public function provideMinPhpVersion () : int
{
2022-05-27 11:51:31 +00:00
return \Rector\Core\ValueObject\PhpVersionFeature :: ATTRIBUTES ;
2021-07-21 09:35:57 +00:00
}
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
*/
2022-05-27 11:51:31 +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 = [];
2022-05-27 11:51:31 +00:00
$phpDocNodeTraverser = new \RectorPrefix20220527\Symplify\Astral\PhpDocParser\PhpDocNodeTraverser ();
$phpDocNodeTraverser -> traverseWithCallable ( $phpDocInfo -> getPhpDocNode (), '' , function ( \PHPStan\PhpDocParser\Ast\Node $docNode ) use ( & $attributeGroups , $phpDocInfo ) : ? int {
if ( ! $docNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode ) {
2021-07-08 16:33:18 +00:00
return null ;
}
2022-05-27 11:51:31 +00:00
if ( ! $docNode -> value instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode ) {
2021-07-08 16:33:18 +00:00
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 ();
2022-05-27 11:51:31 +00:00
return \RectorPrefix20220527\Symplify\Astral\PhpDocParser\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
*/
2022-05-27 11:51:31 +00:00
private function processDoctrineAnnotationClasses ( \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo $phpDocInfo ) : array
2021-05-28 12:08:33 +00:00
{
2021-11-18 19:27:39 +00:00
if ( $phpDocInfo -> getPhpDocNode () -> children === []) {
return [];
}
2021-07-01 23:51:20 +00:00
$doctrineTagAndAnnotationToAttributes = [];
2021-11-18 21:01:01 +00:00
foreach ( $phpDocInfo -> getPhpDocNode () -> children as $phpDocChildNode ) {
2022-05-27 11:51:31 +00:00
if ( ! $phpDocChildNode instanceof \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode ) {
2021-11-18 21:01:01 +00:00
continue ;
2021-07-01 23:51:20 +00:00
}
2022-05-27 11:51:31 +00:00
if ( ! $phpDocChildNode -> value instanceof \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode ) {
2021-11-18 21:01:01 +00:00
continue ;
}
2021-11-20 10:45:53 +00:00
$doctrineTagValueNode = $phpDocChildNode -> value ;
$annotationToAttribute = $this -> matchAnnotationToAttribute ( $doctrineTagValueNode );
2022-05-27 11:51:31 +00:00
if ( ! $annotationToAttribute instanceof \Rector\Php80\ValueObject\AnnotationToAttribute ) {
2021-11-18 21:01:01 +00:00
continue ;
2021-07-08 16:33:18 +00:00
}
2022-05-27 11:51:31 +00:00
$nestedDoctrineAnnotationTagValueNodes = $this -> phpDocNodeFinder -> findByType ( $doctrineTagValueNode , \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode :: class );
2021-12-19 01:17:40 +00:00
$shouldInlinedNested = \false ;
2021-11-20 10:45:53 +00:00
// depends on PHP 8.1+ - nested values, skip for now
2022-05-27 11:51:31 +00:00
if ( $nestedDoctrineAnnotationTagValueNodes !== [] && ! $this -> phpVersionProvider -> isAtLeastPhpVersion ( \Rector\Core\ValueObject\PhpVersionFeature :: NEW_INITIALIZERS )) {
2021-12-19 01:17:40 +00:00
if ( ! $this -> unwrapableAnnotationAnalyzer -> areUnwrappable ( $nestedDoctrineAnnotationTagValueNodes )) {
continue ;
}
$shouldInlinedNested = \true ;
2021-07-01 23:51:20 +00:00
}
2021-12-19 12:21:32 +00:00
if ( ! $this -> removableAnnotationAnalyzer -> isRemovable ( $doctrineTagValueNode )) {
2022-05-27 11:51:31 +00:00
$doctrineTagAndAnnotationToAttributes [] = new \Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute ( $doctrineTagValueNode , $annotationToAttribute );
2021-12-19 12:21:32 +00:00
} else {
$shouldInlinedNested = \true ;
}
2021-12-19 01:17:40 +00:00
if ( $shouldInlinedNested ) {
// inline nested
foreach ( $nestedDoctrineAnnotationTagValueNodes as $nestedDoctrineAnnotationTagValueNode ) {
2022-05-27 11:51:31 +00:00
$doctrineTagAndAnnotationToAttributes [] = new \Rector\Php80\ValueObject\DoctrineTagAndAnnotationToAttribute ( $nestedDoctrineAnnotationTagValueNode , $annotationToAttribute );
2021-12-19 01:17:40 +00:00
}
}
2021-11-20 10:45:53 +00:00
$this -> phpDocTagRemover -> removeTagValueFromNode ( $phpDocInfo , $doctrineTagValueNode );
2021-10-29 11:12:52 +00:00
}
2021-11-18 21:01:01 +00:00
return $this -> attrGroupsFactory -> create ( $doctrineTagAndAnnotationToAttributes );
2021-10-13 19:26:54 +00:00
}
2021-11-20 10:45:53 +00:00
/**
* @ return \Rector\Php80\ValueObject\AnnotationToAttribute | null
*/
2022-05-27 11:51:31 +00:00
private function matchAnnotationToAttribute ( \Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode )
2021-11-20 10:45:53 +00:00
{
foreach ( $this -> annotationsToAttributes as $annotationToAttribute ) {
if ( ! $doctrineAnnotationTagValueNode -> hasClassName ( $annotationToAttribute -> getTag ())) {
continue ;
}
return $annotationToAttribute ;
}
return null ;
}
2020-04-24 22:57:13 +00:00
}