4
4
5
5
namespace TypeLang \PHPDoc \Parser \Content ;
6
6
7
+ use TypeLang \Parser \Node \FullQualifiedName ;
7
8
use 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 ;
10
10
use TypeLang \Parser \Node \Stmt \NamedTypeNode ;
11
11
use TypeLang \Parser \ParserInterface as TypesParserInterface ;
12
12
use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ClassConstantElementReference ;
13
13
use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ClassMethodElementReference ;
14
14
use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \ClassPropertyElementReference ;
15
15
use 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 ;
17
17
use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \TypeElementReference ;
18
18
use TypeLang \PHPDoc \DocBlock \Tag \Shared \Reference \VariableReference ;
19
19
22
22
*/
23
23
final class ElementReferenceReader implements ReaderInterface
24
24
{
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 ' ;
31
38
32
39
public function __invoke (Stream $ stream ): ElementReference
33
40
{
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 ;
63
85
}
64
86
65
87
throw $ stream ->toException (\sprintf (
@@ -68,38 +90,63 @@ public function __invoke(Stream $stream): ElementReference
68
90
));
69
91
}
70
92
71
- private function createFromFunction ( CallableTypeNode $ type ): ElementReference
93
+ private function getReference ( string $ context , Stream $ stream ): ElementReference
72
94
{
73
- if ($ type ->type !== null || $ type ->parameters ->items !== []) {
74
- return new TypeElementReference ($ type );
75
- }
95
+ $ class = new Name ($ context );
76
96
77
- return new FunctionReference ($ type ->name );
78
- }
79
-
80
- private function createFromNamedType (NamedTypeNode $ type , Stream $ stream ): ElementReference
81
- {
82
97
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 );
86
102
87
- $ variable = $ stream -> apply ( new OptionalVariableNameReader () );
103
+ $ variable = $ this -> fetchIdentifier ( $ stream -> value );
88
104
89
105
if ($ variable !== null ) {
90
106
return new ClassPropertyElementReference (
91
- class: $ type -> name ,
107
+ class: $ class ,
92
108
property: $ variable ,
93
109
);
94
110
}
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
+ );
95
134
}
96
135
97
136
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 ' ,
99
138
$ stream ->tag ,
100
139
));
101
140
}
102
141
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
+ );
104
151
}
105
152
}
0 commit comments