66
77use Nette \Utils \Strings ;
88use PhpParser \Node ;
9+ use PHPStan \PhpDocParser \Ast \Attribute ;
10+ use PHPStan \PhpDocParser \Ast \Node as PhpDocParserNode ;
911use PHPStan \PhpDocParser \Ast \PhpDoc \GenericTagValueNode ;
10- use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocChildNode ;
1112use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocNode ;
1213use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTagNode ;
13- use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTagValueNode ;
1414use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTextNode ;
1515use PHPStan \PhpDocParser \Lexer \Lexer ;
16- use PHPStan \PhpDocParser \Parser \ConstExprParser ;
1716use PHPStan \PhpDocParser \Parser \ParserException ;
1817use PHPStan \PhpDocParser \Parser \PhpDocParser ;
1918use PHPStan \PhpDocParser \Parser \TokenIterator ;
20- use PHPStan \PhpDocParser \Parser \TypeParser ;
21- use PHPStan \PhpDocParser \ParserConfig ;
2219use Rector \BetterPhpDocParser \Contract \PhpDocParser \PhpDocNodeDecoratorInterface ;
2320use Rector \BetterPhpDocParser \NodeDecorator \ArrayItemClassNameDecorator ;
2421use Rector \BetterPhpDocParser \NodeDecorator \ConstExprClassNameDecorator ;
2522use Rector \BetterPhpDocParser \NodeDecorator \DoctrineAnnotationDecorator ;
2623use Rector \BetterPhpDocParser \NodeDecorator \PhpDocTagGenericUsesDecorator ;
2724use Rector \BetterPhpDocParser \ValueObject \PhpDocAttributeKey ;
2825use Rector \BetterPhpDocParser \ValueObject \StartAndEnd ;
29- use Rector \Util \Reflection \PrivatesAccessor ;
3026
3127/**
3228 * @see \Rector\Tests\BetterPhpDocParser\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest
3329 */
34- final class BetterPhpDocParser extends PhpDocParser
30+ final readonly class BetterPhpDocParser
3531{
3632 /**
3733 * @see https://regex101.com/r/JOKSmr/5
3834 */
3935 private const string MULTI_NEW_LINES_REGEX = '#(?<new_line>\r\n|\n){2,}# ' ;
4036
37+ /**
38+ * @var int[]
39+ */
40+ private const array NO_TEXT_TOKEN_TYPES = [
41+ Lexer::TOKEN_OPEN_PHPDOC ,
42+ Lexer::TOKEN_CLOSE_PHPDOC ,
43+ Lexer::TOKEN_PHPDOC_EOL ,
44+ Lexer::TOKEN_HORIZONTAL_WS ,
45+ Lexer::TOKEN_END ,
46+ ];
47+
4148 /**
4249 * @var PhpDocNodeDecoratorInterface[]
4350 */
44- private readonly array $ phpDocNodeDecorators ;
51+ private array $ phpDocNodeDecorators ;
4552
4653 public function __construct (
47- ParserConfig $ parserConfig ,
48- TypeParser $ typeParser ,
49- ConstExprParser $ constExprParser ,
54+ private PhpDocParser $ phpDocParser ,
5055 ConstExprClassNameDecorator $ constExprClassNameDecorator ,
5156 DoctrineAnnotationDecorator $ doctrineAnnotationDecorator ,
5257 ArrayItemClassNameDecorator $ arrayItemClassNameDecorator ,
5358 PhpDocTagGenericUsesDecorator $ phpDocTagGenericUsesDecorator ,
54- private readonly PrivatesAccessor $ privatesAccessor ,
5559 ) {
5660 // The decorator order below is significant; keep it as is. DoctrineAnnotationDecorator must
5761 // run before ArrayItemClassNameDecorator, which resolves class names inside the array items the
@@ -63,84 +67,81 @@ public function __construct(
6367 $ arrayItemClassNameDecorator ,
6468 $ phpDocTagGenericUsesDecorator ,
6569 ];
66-
67- parent ::__construct (
68- // ParserConfig
69- $ parserConfig ,
70- // TypeParser
71- $ typeParser ,
72- // ConstExprParser
73- $ constExprParser ,
74- );
7570 }
7671
7772 public function parseWithNode (TokenIterator $ tokenIterator , Node $ node ): PhpDocNode
7873 {
79- $ tokenIterator ->consumeTokenType (Lexer::TOKEN_OPEN_PHPDOC );
80- $ tokenIterator ->tryConsumeTokenType (Lexer::TOKEN_PHPDOC_EOL );
74+ try {
75+ $ phpDocNode = $ this ->phpDocParser ->parse ($ tokenIterator );
76+ } catch (ParserException ) {
77+ // invalid Doctrine annotation arguments; keep the docblock as it is
78+ return $ this ->createTextPhpDocNode ($ tokenIterator );
79+ }
8180
82- $ children = [];
83- if (! $ tokenIterator ->isCurrentTokenType (Lexer::TOKEN_CLOSE_PHPDOC )) {
84- $ children [] = $ this ->parseChildAndStoreItsPositions ($ tokenIterator );
81+ foreach ($ phpDocNode ->children as $ phpDocChildNode ) {
82+ $ this ->setStartAndEnd ($ phpDocChildNode );
8583
86- while ($ tokenIterator ->tryConsumeTokenType (
87- Lexer::TOKEN_PHPDOC_EOL
88- ) && ! $ tokenIterator ->isCurrentTokenType (Lexer::TOKEN_CLOSE_PHPDOC )) {
89- $ children [] = $ this ->parseChildAndStoreItsPositions ($ tokenIterator );
84+ if (! $ phpDocChildNode instanceof PhpDocTagNode) {
85+ continue ;
9086 }
91- }
9287
93- // might be in the middle of annotations
94- $ tokenIterator ->tryConsumeTokenType (Lexer::TOKEN_CLOSE_PHPDOC );
88+ $ this ->setStartAndEnd ($ phpDocChildNode ->value );
89+
90+ if ($ phpDocChildNode ->value instanceof GenericTagValueNode) {
91+ $ phpDocChildNode ->value ->value = Strings ::replace (
92+ $ phpDocChildNode ->value ->value ,
93+ self ::MULTI_NEW_LINES_REGEX ,
94+ static fn (array $ match ): string => (string ) $ match ['new_line ' ]
95+ );
96+ }
97+ }
9598
96- $ phpDocNode = new PhpDocNode ($ children );
9799 foreach ($ this ->phpDocNodeDecorators as $ phpDocNodeDecorator ) {
98100 $ phpDocNodeDecorator ->decorate ($ phpDocNode , $ node );
99101 }
100102
101103 return $ phpDocNode ;
102104 }
103105
104- public function parseTagValue (TokenIterator $ tokenIterator , string $ tag ): PhpDocTagValueNode
106+ /**
107+ * The Doctrine annotation decorator moves these positions to the nodes it creates
108+ */
109+ private function setStartAndEnd (PhpDocParserNode $ phpDocParserNode ): void
105110 {
106- $ startPosition = $ tokenIterator ->currentTokenIndex ();
107- $ phpDocTagValueNode = parent ::parseTagValue ($ tokenIterator , $ tag );
108-
109- $ endPosition = $ tokenIterator ->currentTokenIndex ();
110-
111- $ startAndEnd = new StartAndEnd ($ startPosition , $ endPosition );
112- $ phpDocTagValueNode ->setAttribute (PhpDocAttributeKey::START_AND_END , $ startAndEnd );
113-
114- if ($ phpDocTagValueNode instanceof GenericTagValueNode) {
115- $ phpDocTagValueNode ->value = Strings ::replace (
116- $ phpDocTagValueNode ->value ,
117- self ::MULTI_NEW_LINES_REGEX ,
118- static fn (array $ match ): string => (string ) $ match ['new_line ' ]
119- );
111+ $ startIndex = $ phpDocParserNode ->getAttribute (Attribute::START_INDEX );
112+ $ endIndex = $ phpDocParserNode ->getAttribute (Attribute::END_INDEX );
113+ if (! is_int ($ startIndex ) || ! is_int ($ endIndex )) {
114+ return ;
120115 }
121116
122- return $ phpDocTagValueNode ;
117+ // an empty node has no tokens, its end index is before its start index
118+ $ phpDocParserNode ->setAttribute (
119+ PhpDocAttributeKey::START_AND_END ,
120+ new StartAndEnd ($ startIndex , max ($ startIndex , $ endIndex + 1 ))
121+ );
123122 }
124123
125124 /**
126- * @return PhpDocTextNode|PhpDocTagNode
125+ * One text node with the content between "/**" and "*\/"
127126 */
128- private function parseChildAndStoreItsPositions (TokenIterator $ tokenIterator ): PhpDocChildNode
127+ private function createTextPhpDocNode (TokenIterator $ tokenIterator ): PhpDocNode
129128 {
130- $ startPosition = $ tokenIterator ->currentTokenIndex ();
129+ $ tokens = $ tokenIterator ->getTokens ();
130+ $ startIndex = 0 ;
131+ $ endIndex = count ($ tokens ) - 1 ;
131132
132- try {
133- /** @var PhpDocTextNode|PhpDocTagNode $phpDocNode */
134- $ phpDocNode = $ this ->privatesAccessor ->callPrivateMethod ($ this , 'parseChild ' , [$ tokenIterator ]);
135- } catch (ParserException ) {
136- $ phpDocNode = new PhpDocTextNode ('' );
133+ while ($ startIndex < $ endIndex && in_array ($ tokens [$ startIndex ][Lexer::TYPE_OFFSET ], self ::NO_TEXT_TOKEN_TYPES , true )) {
134+ ++$ startIndex ;
137135 }
138136
139- $ endPosition = $ tokenIterator ->currentTokenIndex ();
137+ while ($ endIndex > $ startIndex && in_array ($ tokens [$ endIndex ][Lexer::TYPE_OFFSET ], self ::NO_TEXT_TOKEN_TYPES , true )) {
138+ --$ endIndex ;
139+ }
140140
141- $ startAndEnd = new StartAndEnd ($ startPosition , $ endPosition );
142- $ phpDocNode ->setAttribute (PhpDocAttributeKey::START_AND_END , $ startAndEnd );
141+ $ phpDocTextNode = new PhpDocTextNode ($ tokenIterator ->getContentBetween ($ startIndex , $ endIndex + 1 ));
142+ $ phpDocTextNode ->setAttribute (Attribute::START_INDEX , $ startIndex );
143+ $ phpDocTextNode ->setAttribute (Attribute::END_INDEX , $ endIndex );
143144
144- return $ phpDocNode ;
145+ return new PhpDocNode ([ $ phpDocTextNode ]) ;
145146 }
146147}
0 commit comments