44
55namespace TypeLang \PHPDoc \Parser \Content ;
66
7+ use TypeLang \Parser \Node \FullQualifiedName ;
78use TypeLang \Parser \Node \Literal \VariableLiteralNode ;
8- use TypeLang \Parser \Node \Stmt \CallableTypeNode ;
9- use TypeLang \Parser \Node \Stmt \ClassConstNode ;
9+ use TypeLang \Parser \Node \Name ;
1010use TypeLang \Parser \Node \Stmt \NamedTypeNode ;
1111use TypeLang \Parser \ParserInterface as TypesParserInterface ;
1212use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ClassConstantElementReference ;
1313use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ClassMethodElementReference ;
1414use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ClassPropertyElementReference ;
1515use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ElementReference ;
16- use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \FunctionReference ;
16+ use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \FunctionElementReference ;
1717use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \TypeElementReference ;
1818use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \VariableReference ;
1919
2222 */
2323final class ElementReferenceReader implements ReaderInterface
2424{
25- private OptionalTypeReader $ types ;
26-
27- public function __construct (TypesParserInterface $ parser )
28- {
29- $ this ->types = new OptionalTypeReader ($ parser );
30- }
25+ private const T_FQN = '(?:[ \\\\]?+[a-zA-Z\x80-\xFF_][0-9a-zA-Z\x80-\xFF_-]*+)++ ' ;
26+ private const T_IDENTIFIER = '[a-zA-Z_ \\x80- \\xff][a-zA-Z0-9 \\-_ \\x80- \\xff]*+ ' ;
27+
28+ private const SIMPLE_TOKENIZER_PCRE = '/^(? '
29+ . '|(?:(?P<T_CLASS> ' . self ::T_FQN . ')::(?: '
30+ . '(?: \\$[a-zA-Z_ \\x80- \\xff][a-zA-Z0-9_ \\x80- \\xff]*+)(*MARK:T_CLASS_PROPERTY) '
31+ . '|(?:[a-zA-Z_ \\x80- \\xff][a-zA-Z0-9_ \\x80- \\xff]*+\(\))(*MARK:T_CLASS_METHOD) '
32+ . '|(?:[a-zA-Z_ \\x80- \\xff][a-zA-Z0-9_ \\x80- \\xff]*+)(*MARK:T_CLASS_CONSTANT) '
33+ . ')) '
34+ . '|(?:(?: \\$ ' . self ::T_IDENTIFIER . ')(*MARK:T_VARIABLE)) '
35+ . '|(?:(?: ' . self ::T_FQN . '\(\))(*MARK:T_FUNCTION)) '
36+ . '|(?:(?: ' . self ::T_FQN . ')(*MARK:T_IDENTIFIER)) '
37+ . ')(?:\s|$)/Ssum ' ;
3138
3239 public function __invoke (Stream $ stream ): ElementReference
3340 {
34- $ type = ($ this ->types )($ stream );
35-
36- if ($ type instanceof CallableTypeNode) {
37- return $ this ->createFromFunction ($ type );
38- }
39-
40- if ($ type instanceof NamedTypeNode) {
41- return $ this ->createFromNamedType ($ type , $ stream );
42- }
43-
44- if (\str_starts_with ($ stream ->value , '$ ' )) {
45- return new VariableReference (VariableLiteralNode::parse (
46- value: $ stream ->apply (new VariableNameReader ()),
47- ));
48- }
49-
50- if ($ type instanceof ClassConstNode) {
51- /**
52- * @var non-empty-string $identifier
53- *
54- * @phpstan-ignore-next-line constant cannot be null
55- */
56- $ identifier = $ type ->constant ->toString ();
57-
58- if (\str_starts_with ($ stream ->value , '() ' )) {
59- return new ClassMethodElementReference ($ type ->class , $ identifier );
60- }
61-
62- return new ClassConstantElementReference ($ type ->class , $ identifier );
41+ \preg_match (self ::SIMPLE_TOKENIZER_PCRE , $ stream ->value , $ matches );
42+
43+ if ($ matches !== []) {
44+ /** @var non-empty-string $body */
45+ $ body = \rtrim ($ matches [0 ]);
46+ $ isFullyQualified = $ body [0 ] === '\\' ;
47+
48+ $ result = match ($ matches ['MARK ' ]) {
49+ 'T_FUNCTION ' => new FunctionElementReference ($ isFullyQualified
50+ ? new FullQualifiedName (\substr ($ body , 0 , -2 ))
51+ : new Name (\substr ($ body , 0 , -2 )),
52+ ),
53+ 'T_IDENTIFIER ' => new TypeElementReference (
54+ type: new NamedTypeNode ($ isFullyQualified
55+ ? new FullQualifiedName ($ body )
56+ : new Name ($ body )
57+ ),
58+ ),
59+ 'T_VARIABLE ' => new VariableReference (
60+ variable: new VariableLiteralNode ($ body ),
61+ ),
62+ 'T_CLASS_CONSTANT ' => new ClassConstantElementReference (
63+ class: $ isFullyQualified
64+ ? new FullQualifiedName ($ matches ['T_CLASS ' ])
65+ : new Name ($ matches ['T_CLASS ' ]),
66+ constant: \substr ($ body , \strlen ($ matches ['T_CLASS ' ]) + 2 ),
67+ ),
68+ 'T_CLASS_METHOD ' => new ClassMethodElementReference (
69+ class: $ isFullyQualified
70+ ? new FullQualifiedName ($ matches ['T_CLASS ' ])
71+ : new Name ($ matches ['T_CLASS ' ]),
72+ method: \substr ($ body , \strlen ($ matches ['T_CLASS ' ]) + 2 , -2 ),
73+ ),
74+ 'T_CLASS_PROPERTY ' => new ClassPropertyElementReference (
75+ class: $ isFullyQualified
76+ ? new FullQualifiedName ($ matches ['T_CLASS ' ])
77+ : new Name ($ matches ['T_CLASS ' ]),
78+ property: \substr ($ body , \strlen ($ matches ['T_CLASS ' ]) + 3 ),
79+ ),
80+ };
81+
82+ $ stream ->shift (\strlen ($ matches [0 ]));
83+
84+ return $ result ;
6385 }
6486
6587 throw $ stream ->toException (\sprintf (
@@ -68,38 +90,63 @@ public function __invoke(Stream $stream): ElementReference
6890 ));
6991 }
7092
71- private function createFromFunction ( CallableTypeNode $ type ): ElementReference
93+ private function getReference ( string $ context , Stream $ stream ): ElementReference
7294 {
73- if ($ type ->type !== null || $ type ->parameters ->items !== []) {
74- return new TypeElementReference ($ type );
75- }
95+ $ class = new Name ($ context );
7696
77- return new FunctionReference ($ type ->name );
78- }
79-
80- private function createFromNamedType (NamedTypeNode $ type , Stream $ stream ): ElementReference
81- {
8297 if (\str_starts_with ($ stream ->value , ':: ' )) {
83- if ($ type ->arguments === null && $ type ->fields === null ) {
84- // Skip "::" delimiter
85- $ stream ->shift (2 );
98+ $ stream ->shift (2 , false );
99+
100+ if (\str_starts_with ($ stream ->value , '$ ' )) {
101+ $ stream ->shift (1 , false );
86102
87- $ variable = $ stream -> apply ( new OptionalVariableNameReader () );
103+ $ variable = $ this -> fetchIdentifier ( $ stream -> value );
88104
89105 if ($ variable !== null ) {
90106 return new ClassPropertyElementReference (
91- class: $ type -> name ,
107+ class: $ class ,
92108 property: $ variable ,
93109 );
94110 }
111+
112+ throw $ stream ->toException (\sprintf (
113+ 'Tag @%s contains invalid property name after class reference ' ,
114+ $ stream ->tag ,
115+ ));
116+ }
117+
118+ $ identifier = $ this ->fetchIdentifier ($ stream ->value );
119+
120+ if ($ identifier !== null ) {
121+ $ stream ->shift (\strlen ($ identifier ), false );
122+
123+ if (\str_starts_with ($ stream ->value , '() ' )) {
124+ return new ClassMethodElementReference (
125+ class: $ class ,
126+ method: $ identifier ,
127+ );
128+ }
129+
130+ return new ClassConstantElementReference (
131+ class: $ class ,
132+ constant: $ identifier ,
133+ );
95134 }
96135
97136 throw $ stream ->toException (\sprintf (
98- 'Tag @%s expects the FQN reference to be defined ' ,
137+ 'Tag @%s contains invalid method or constant name after class reference ' ,
99138 $ stream ->tag ,
100139 ));
101140 }
102141
103- return new TypeElementReference ($ type );
142+ if (\str_starts_with ($ stream ->value , '() ' )) {
143+ $ stream ->shift (2 , false );
144+
145+ return new FunctionElementReference ($ class );
146+ }
147+
148+ return new TypeElementReference (
149+ type: new NamedTypeNode ($ class ),
150+ );
104151 }
105152}
0 commit comments