1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\PhpDocParser\Parser; |
4: | |
5: | use LogicException; |
6: | use PHPStan\PhpDocParser\Ast; |
7: | use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; |
8: | use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; |
9: | use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; |
10: | use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine; |
11: | use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; |
12: | use PHPStan\PhpDocParser\Lexer\Lexer; |
13: | use PHPStan\PhpDocParser\ParserConfig; |
14: | use PHPStan\ShouldNotHappenException; |
15: | use function array_key_exists; |
16: | use function count; |
17: | use function rtrim; |
18: | use function str_replace; |
19: | use function trim; |
20: | |
21: | |
22: | |
23: | |
24: | class PhpDocParser |
25: | { |
26: | |
27: | private const DISALLOWED_DESCRIPTION_START_TOKENS = [ |
28: | Lexer::TOKEN_UNION, |
29: | Lexer::TOKEN_INTERSECTION, |
30: | ]; |
31: | |
32: | private ParserConfig $config; |
33: | |
34: | private TypeParser $typeParser; |
35: | |
36: | private ConstExprParser $constantExprParser; |
37: | |
38: | private ConstExprParser $doctrineConstantExprParser; |
39: | |
40: | public function __construct( |
41: | ParserConfig $config, |
42: | TypeParser $typeParser, |
43: | ConstExprParser $constantExprParser |
44: | ) |
45: | { |
46: | $this->config = $config; |
47: | $this->typeParser = $typeParser; |
48: | $this->constantExprParser = $constantExprParser; |
49: | $this->doctrineConstantExprParser = $constantExprParser->toDoctrine(); |
50: | } |
51: | |
52: | |
53: | public function parse(TokenIterator $tokens): Ast\PhpDoc\PhpDocNode |
54: | { |
55: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PHPDOC); |
56: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
57: | |
58: | $children = []; |
59: | |
60: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
61: | $lastChild = $this->parseChild($tokens); |
62: | $children[] = $lastChild; |
63: | while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
64: | if ( |
65: | $lastChild instanceof Ast\PhpDoc\PhpDocTagNode |
66: | && ( |
67: | $lastChild->value instanceof Doctrine\DoctrineTagValueNode |
68: | || $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode |
69: | ) |
70: | ) { |
71: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
72: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
73: | break; |
74: | } |
75: | $lastChild = $this->parseChild($tokens); |
76: | $children[] = $lastChild; |
77: | continue; |
78: | } |
79: | |
80: | if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) { |
81: | break; |
82: | } |
83: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
84: | break; |
85: | } |
86: | |
87: | $lastChild = $this->parseChild($tokens); |
88: | $children[] = $lastChild; |
89: | } |
90: | } |
91: | |
92: | try { |
93: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC); |
94: | } catch (ParserException $e) { |
95: | $name = ''; |
96: | $startLine = $tokens->currentTokenLine(); |
97: | $startIndex = $tokens->currentTokenIndex(); |
98: | if (count($children) > 0) { |
99: | $lastChild = $children[count($children) - 1]; |
100: | if ($lastChild instanceof Ast\PhpDoc\PhpDocTagNode) { |
101: | $name = $lastChild->name; |
102: | $startLine = $tokens->currentTokenLine(); |
103: | $startIndex = $tokens->currentTokenIndex(); |
104: | } |
105: | } |
106: | |
107: | $tag = new Ast\PhpDoc\PhpDocTagNode( |
108: | $name, |
109: | $this->enrichWithAttributes( |
110: | $tokens, |
111: | new Ast\PhpDoc\InvalidTagValueNode($e->getMessage(), $e), |
112: | $startLine, |
113: | $startIndex, |
114: | ), |
115: | ); |
116: | |
117: | $tokens->forwardToTheEnd(); |
118: | |
119: | $comments = $tokens->flushComments(); |
120: | if ($comments !== []) { |
121: | throw new LogicException('Comments should already be flushed'); |
122: | } |
123: | |
124: | return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode([$this->enrichWithAttributes($tokens, $tag, $startLine, $startIndex)]), 1, 0); |
125: | } |
126: | |
127: | $comments = $tokens->flushComments(); |
128: | if ($comments !== []) { |
129: | throw new LogicException('Comments should already be flushed'); |
130: | } |
131: | |
132: | return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode($children), 1, 0); |
133: | } |
134: | |
135: | |
136: | |
137: | private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode |
138: | { |
139: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) { |
140: | $startLine = $tokens->currentTokenLine(); |
141: | $startIndex = $tokens->currentTokenIndex(); |
142: | return $this->enrichWithAttributes($tokens, $this->parseTag($tokens), $startLine, $startIndex); |
143: | } |
144: | |
145: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_TAG)) { |
146: | $startLine = $tokens->currentTokenLine(); |
147: | $startIndex = $tokens->currentTokenIndex(); |
148: | $tag = $tokens->currentTokenValue(); |
149: | $tokens->next(); |
150: | |
151: | $tagStartLine = $tokens->currentTokenLine(); |
152: | $tagStartIndex = $tokens->currentTokenIndex(); |
153: | |
154: | return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocTagNode( |
155: | $tag, |
156: | $this->enrichWithAttributes( |
157: | $tokens, |
158: | $this->parseDoctrineTagValue($tokens, $tag), |
159: | $tagStartLine, |
160: | $tagStartIndex, |
161: | ), |
162: | ), $startLine, $startIndex); |
163: | } |
164: | |
165: | $startLine = $tokens->currentTokenLine(); |
166: | $startIndex = $tokens->currentTokenIndex(); |
167: | $text = $this->parseText($tokens); |
168: | |
169: | return $this->enrichWithAttributes($tokens, $text, $startLine, $startIndex); |
170: | } |
171: | |
172: | |
173: | |
174: | |
175: | |
176: | |
177: | private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex): Ast\Node |
178: | { |
179: | if ($this->config->useLinesAttributes) { |
180: | $tag->setAttribute(Ast\Attribute::START_LINE, $startLine); |
181: | $tag->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine()); |
182: | } |
183: | |
184: | if ($this->config->useIndexAttributes) { |
185: | $tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex); |
186: | $tag->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken()); |
187: | } |
188: | |
189: | return $tag; |
190: | } |
191: | |
192: | |
193: | private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode |
194: | { |
195: | $text = ''; |
196: | |
197: | $endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END]; |
198: | |
199: | $savepoint = false; |
200: | |
201: | |
202: | while (true) { |
203: | $tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens); |
204: | $text .= $tmpText; |
205: | |
206: | |
207: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC)) { |
208: | break; |
209: | } |
210: | |
211: | if (!$savepoint) { |
212: | $tokens->pushSavePoint(); |
213: | $savepoint = true; |
214: | } elseif ($tmpText !== '') { |
215: | $tokens->dropSavePoint(); |
216: | $tokens->pushSavePoint(); |
217: | } |
218: | |
219: | $tokens->pushSavePoint(); |
220: | $tokens->next(); |
221: | |
222: | |
223: | |
224: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) { |
225: | $tokens->rollback(); |
226: | break; |
227: | } |
228: | |
229: | |
230: | |
231: | $tokens->dropSavePoint(); |
232: | $text .= $tokens->getDetectedNewline() ?? "\n"; |
233: | } |
234: | |
235: | if ($savepoint) { |
236: | $tokens->rollback(); |
237: | $text = rtrim($text, $tokens->getDetectedNewline() ?? "\n"); |
238: | } |
239: | |
240: | return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t")); |
241: | } |
242: | |
243: | |
244: | private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens): string |
245: | { |
246: | $text = ''; |
247: | |
248: | $endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END]; |
249: | |
250: | $savepoint = false; |
251: | |
252: | |
253: | while (true) { |
254: | $tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens); |
255: | $text .= $tmpText; |
256: | |
257: | |
258: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC)) { |
259: | if (!$tokens->isPrecededByHorizontalWhitespace()) { |
260: | return trim($text . $this->parseText($tokens)->text, " \t"); |
261: | } |
262: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) { |
263: | $tokens->pushSavePoint(); |
264: | $child = $this->parseChild($tokens); |
265: | if ($child instanceof Ast\PhpDoc\PhpDocTagNode) { |
266: | if ( |
267: | $child->value instanceof Ast\PhpDoc\GenericTagValueNode |
268: | || $child->value instanceof Doctrine\DoctrineTagValueNode |
269: | ) { |
270: | $tokens->rollback(); |
271: | break; |
272: | } |
273: | if ($child->value instanceof Ast\PhpDoc\InvalidTagValueNode) { |
274: | $tokens->rollback(); |
275: | $tokens->pushSavePoint(); |
276: | $tokens->next(); |
277: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
278: | $tokens->rollback(); |
279: | break; |
280: | } |
281: | $tokens->rollback(); |
282: | return trim($text . $this->parseText($tokens)->text, " \t"); |
283: | } |
284: | } |
285: | |
286: | $tokens->rollback(); |
287: | return trim($text . $this->parseText($tokens)->text, " \t"); |
288: | } |
289: | break; |
290: | } |
291: | |
292: | if (!$savepoint) { |
293: | $tokens->pushSavePoint(); |
294: | $savepoint = true; |
295: | } elseif ($tmpText !== '') { |
296: | $tokens->dropSavePoint(); |
297: | $tokens->pushSavePoint(); |
298: | } |
299: | |
300: | $tokens->pushSavePoint(); |
301: | $tokens->next(); |
302: | |
303: | |
304: | |
305: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, ...$endTokens)) { |
306: | $tokens->rollback(); |
307: | break; |
308: | } |
309: | |
310: | |
311: | |
312: | $tokens->dropSavePoint(); |
313: | $text .= $tokens->getDetectedNewline() ?? "\n"; |
314: | } |
315: | |
316: | if ($savepoint) { |
317: | $tokens->rollback(); |
318: | $text = rtrim($text, $tokens->getDetectedNewline() ?? "\n"); |
319: | } |
320: | |
321: | return trim($text, " \t"); |
322: | } |
323: | |
324: | |
325: | public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode |
326: | { |
327: | $tag = $tokens->currentTokenValue(); |
328: | $tokens->next(); |
329: | $value = $this->parseTagValue($tokens, $tag); |
330: | |
331: | return new Ast\PhpDoc\PhpDocTagNode($tag, $value); |
332: | } |
333: | |
334: | |
335: | public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode |
336: | { |
337: | $startLine = $tokens->currentTokenLine(); |
338: | $startIndex = $tokens->currentTokenIndex(); |
339: | |
340: | try { |
341: | $tokens->pushSavePoint(); |
342: | |
343: | switch ($tag) { |
344: | case '@param': |
345: | case '@phpstan-param': |
346: | case '@psalm-param': |
347: | case '@phan-param': |
348: | $tagValue = $this->parseParamTagValue($tokens); |
349: | break; |
350: | |
351: | case '@param-immediately-invoked-callable': |
352: | case '@phpstan-param-immediately-invoked-callable': |
353: | $tagValue = $this->parseParamImmediatelyInvokedCallableTagValue($tokens); |
354: | break; |
355: | |
356: | case '@param-later-invoked-callable': |
357: | case '@phpstan-param-later-invoked-callable': |
358: | $tagValue = $this->parseParamLaterInvokedCallableTagValue($tokens); |
359: | break; |
360: | |
361: | case '@param-closure-this': |
362: | case '@phpstan-param-closure-this': |
363: | $tagValue = $this->parseParamClosureThisTagValue($tokens); |
364: | break; |
365: | |
366: | case '@pure-unless-callable-is-impure': |
367: | case '@phpstan-pure-unless-callable-is-impure': |
368: | $tagValue = $this->parsePureUnlessCallableIsImpureTagValue($tokens); |
369: | break; |
370: | |
371: | case '@var': |
372: | case '@phpstan-var': |
373: | case '@psalm-var': |
374: | case '@phan-var': |
375: | $tagValue = $this->parseVarTagValue($tokens); |
376: | break; |
377: | |
378: | case '@return': |
379: | case '@phpstan-return': |
380: | case '@psalm-return': |
381: | case '@phan-return': |
382: | case '@phan-real-return': |
383: | $tagValue = $this->parseReturnTagValue($tokens); |
384: | break; |
385: | |
386: | case '@throws': |
387: | case '@phpstan-throws': |
388: | $tagValue = $this->parseThrowsTagValue($tokens); |
389: | break; |
390: | |
391: | case '@mixin': |
392: | case '@phan-mixin': |
393: | $tagValue = $this->parseMixinTagValue($tokens); |
394: | break; |
395: | |
396: | case '@psalm-require-extends': |
397: | case '@phpstan-require-extends': |
398: | $tagValue = $this->parseRequireExtendsTagValue($tokens); |
399: | break; |
400: | |
401: | case '@psalm-require-implements': |
402: | case '@phpstan-require-implements': |
403: | $tagValue = $this->parseRequireImplementsTagValue($tokens); |
404: | break; |
405: | |
406: | case '@psalm-inheritors': |
407: | case '@phpstan-sealed': |
408: | $tagValue = $this->parseSealedTagValue($tokens); |
409: | break; |
410: | |
411: | case '@deprecated': |
412: | $tagValue = $this->parseDeprecatedTagValue($tokens); |
413: | break; |
414: | |
415: | case '@property': |
416: | case '@property-read': |
417: | case '@property-write': |
418: | case '@phpstan-property': |
419: | case '@phpstan-property-read': |
420: | case '@phpstan-property-write': |
421: | case '@psalm-property': |
422: | case '@psalm-property-read': |
423: | case '@psalm-property-write': |
424: | case '@phan-property': |
425: | case '@phan-property-read': |
426: | case '@phan-property-write': |
427: | $tagValue = $this->parsePropertyTagValue($tokens); |
428: | break; |
429: | |
430: | case '@method': |
431: | case '@phpstan-method': |
432: | case '@psalm-method': |
433: | case '@phan-method': |
434: | $tagValue = $this->parseMethodTagValue($tokens); |
435: | break; |
436: | |
437: | case '@template': |
438: | case '@phpstan-template': |
439: | case '@psalm-template': |
440: | case '@phan-template': |
441: | case '@template-covariant': |
442: | case '@phpstan-template-covariant': |
443: | case '@psalm-template-covariant': |
444: | case '@template-contravariant': |
445: | case '@phpstan-template-contravariant': |
446: | case '@psalm-template-contravariant': |
447: | $tagValue = $this->typeParser->parseTemplateTagValue( |
448: | $tokens, |
449: | fn ($tokens) => $this->parseOptionalDescription($tokens, true), |
450: | ); |
451: | break; |
452: | |
453: | case '@extends': |
454: | case '@phpstan-extends': |
455: | case '@phan-extends': |
456: | case '@phan-inherits': |
457: | case '@template-extends': |
458: | $tagValue = $this->parseExtendsTagValue('@extends', $tokens); |
459: | break; |
460: | |
461: | case '@implements': |
462: | case '@phpstan-implements': |
463: | case '@template-implements': |
464: | $tagValue = $this->parseExtendsTagValue('@implements', $tokens); |
465: | break; |
466: | |
467: | case '@use': |
468: | case '@phpstan-use': |
469: | case '@template-use': |
470: | $tagValue = $this->parseExtendsTagValue('@use', $tokens); |
471: | break; |
472: | |
473: | case '@phpstan-type': |
474: | case '@psalm-type': |
475: | case '@phan-type': |
476: | $tagValue = $this->parseTypeAliasTagValue($tokens); |
477: | break; |
478: | |
479: | case '@phpstan-import-type': |
480: | case '@psalm-import-type': |
481: | $tagValue = $this->parseTypeAliasImportTagValue($tokens); |
482: | break; |
483: | |
484: | case '@phpstan-assert': |
485: | case '@phpstan-assert-if-true': |
486: | case '@phpstan-assert-if-false': |
487: | case '@psalm-assert': |
488: | case '@psalm-assert-if-true': |
489: | case '@psalm-assert-if-false': |
490: | case '@phan-assert': |
491: | case '@phan-assert-if-true': |
492: | case '@phan-assert-if-false': |
493: | $tagValue = $this->parseAssertTagValue($tokens); |
494: | break; |
495: | |
496: | case '@phpstan-this-out': |
497: | case '@phpstan-self-out': |
498: | case '@psalm-this-out': |
499: | case '@psalm-self-out': |
500: | $tagValue = $this->parseSelfOutTagValue($tokens); |
501: | break; |
502: | |
503: | case '@param-out': |
504: | case '@phpstan-param-out': |
505: | case '@psalm-param-out': |
506: | $tagValue = $this->parseParamOutTagValue($tokens); |
507: | break; |
508: | |
509: | default: |
510: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
511: | $tagValue = $this->parseDoctrineTagValue($tokens, $tag); |
512: | } else { |
513: | $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens)); |
514: | } |
515: | break; |
516: | } |
517: | |
518: | $tokens->dropSavePoint(); |
519: | |
520: | } catch (ParserException $e) { |
521: | $tokens->rollback(); |
522: | $tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens, false), $e); |
523: | } |
524: | |
525: | return $this->enrichWithAttributes($tokens, $tagValue, $startLine, $startIndex); |
526: | } |
527: | |
528: | |
529: | private function parseDoctrineTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode |
530: | { |
531: | $startLine = $tokens->currentTokenLine(); |
532: | $startIndex = $tokens->currentTokenIndex(); |
533: | |
534: | return new Doctrine\DoctrineTagValueNode( |
535: | $this->enrichWithAttributes( |
536: | $tokens, |
537: | new Doctrine\DoctrineAnnotation($tag, $this->parseDoctrineArguments($tokens, false)), |
538: | $startLine, |
539: | $startIndex, |
540: | ), |
541: | $this->parseOptionalDescriptionAfterDoctrineTag($tokens), |
542: | ); |
543: | } |
544: | |
545: | |
546: | |
547: | |
548: | |
549: | private function parseDoctrineArguments(TokenIterator $tokens, bool $deep): array |
550: | { |
551: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
552: | return []; |
553: | } |
554: | |
555: | if (!$deep) { |
556: | $tokens->addEndOfLineToSkippedTokens(); |
557: | } |
558: | |
559: | $arguments = []; |
560: | |
561: | try { |
562: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); |
563: | |
564: | do { |
565: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { |
566: | break; |
567: | } |
568: | $arguments[] = $this->parseDoctrineArgument($tokens); |
569: | } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); |
570: | } finally { |
571: | if (!$deep) { |
572: | $tokens->removeEndOfLineFromSkippedTokens(); |
573: | } |
574: | } |
575: | |
576: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
577: | |
578: | return $arguments; |
579: | } |
580: | |
581: | |
582: | private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\DoctrineArgument |
583: | { |
584: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { |
585: | $startLine = $tokens->currentTokenLine(); |
586: | $startIndex = $tokens->currentTokenIndex(); |
587: | |
588: | return $this->enrichWithAttributes( |
589: | $tokens, |
590: | new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)), |
591: | $startLine, |
592: | $startIndex, |
593: | ); |
594: | } |
595: | |
596: | $startLine = $tokens->currentTokenLine(); |
597: | $startIndex = $tokens->currentTokenIndex(); |
598: | |
599: | try { |
600: | $tokens->pushSavePoint(); |
601: | $currentValue = $tokens->currentTokenValue(); |
602: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
603: | |
604: | $key = $this->enrichWithAttributes( |
605: | $tokens, |
606: | new IdentifierTypeNode($currentValue), |
607: | $startLine, |
608: | $startIndex, |
609: | ); |
610: | $tokens->consumeTokenType(Lexer::TOKEN_EQUAL); |
611: | |
612: | $value = $this->parseDoctrineArgumentValue($tokens); |
613: | |
614: | $tokens->dropSavePoint(); |
615: | |
616: | return $this->enrichWithAttributes( |
617: | $tokens, |
618: | new Doctrine\DoctrineArgument($key, $value), |
619: | $startLine, |
620: | $startIndex, |
621: | ); |
622: | } catch (ParserException $e) { |
623: | $tokens->rollback(); |
624: | |
625: | return $this->enrichWithAttributes( |
626: | $tokens, |
627: | new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)), |
628: | $startLine, |
629: | $startIndex, |
630: | ); |
631: | } |
632: | } |
633: | |
634: | |
635: | |
636: | |
637: | |
638: | private function parseDoctrineArgumentValue(TokenIterator $tokens) |
639: | { |
640: | $startLine = $tokens->currentTokenLine(); |
641: | $startIndex = $tokens->currentTokenIndex(); |
642: | |
643: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG)) { |
644: | $name = $tokens->currentTokenValue(); |
645: | $tokens->next(); |
646: | |
647: | return $this->enrichWithAttributes( |
648: | $tokens, |
649: | new Doctrine\DoctrineAnnotation($name, $this->parseDoctrineArguments($tokens, true)), |
650: | $startLine, |
651: | $startIndex, |
652: | ); |
653: | } |
654: | |
655: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) { |
656: | $items = []; |
657: | do { |
658: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { |
659: | break; |
660: | } |
661: | $items[] = $this->parseDoctrineArrayItem($tokens); |
662: | } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); |
663: | |
664: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET); |
665: | |
666: | return $this->enrichWithAttributes( |
667: | $tokens, |
668: | new Doctrine\DoctrineArray($items), |
669: | $startLine, |
670: | $startIndex, |
671: | ); |
672: | } |
673: | |
674: | $currentTokenValue = $tokens->currentTokenValue(); |
675: | $tokens->pushSavePoint(); |
676: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { |
677: | $identifier = $this->enrichWithAttributes( |
678: | $tokens, |
679: | new Ast\Type\IdentifierTypeNode($currentTokenValue), |
680: | $startLine, |
681: | $startIndex, |
682: | ); |
683: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { |
684: | $tokens->dropSavePoint(); |
685: | return $identifier; |
686: | } |
687: | |
688: | $tokens->rollback(); |
689: | } else { |
690: | $tokens->dropSavePoint(); |
691: | } |
692: | |
693: | $currentTokenValue = $tokens->currentTokenValue(); |
694: | $currentTokenType = $tokens->currentTokenType(); |
695: | $currentTokenOffset = $tokens->currentTokenOffset(); |
696: | $currentTokenLine = $tokens->currentTokenLine(); |
697: | |
698: | try { |
699: | $constExpr = $this->doctrineConstantExprParser->parse($tokens); |
700: | if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { |
701: | throw new ParserException( |
702: | $currentTokenValue, |
703: | $currentTokenType, |
704: | $currentTokenOffset, |
705: | Lexer::TOKEN_IDENTIFIER, |
706: | null, |
707: | $currentTokenLine, |
708: | ); |
709: | } |
710: | |
711: | return $constExpr; |
712: | } catch (LogicException $e) { |
713: | throw new ParserException( |
714: | $currentTokenValue, |
715: | $currentTokenType, |
716: | $currentTokenOffset, |
717: | Lexer::TOKEN_IDENTIFIER, |
718: | null, |
719: | $currentTokenLine, |
720: | ); |
721: | } |
722: | } |
723: | |
724: | |
725: | private function parseDoctrineArrayItem(TokenIterator $tokens): Doctrine\DoctrineArrayItem |
726: | { |
727: | $startLine = $tokens->currentTokenLine(); |
728: | $startIndex = $tokens->currentTokenIndex(); |
729: | |
730: | try { |
731: | $tokens->pushSavePoint(); |
732: | |
733: | $key = $this->parseDoctrineArrayKey($tokens); |
734: | if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) { |
735: | if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COLON)) { |
736: | $tokens->consumeTokenType(Lexer::TOKEN_EQUAL); |
737: | } |
738: | } |
739: | |
740: | $value = $this->parseDoctrineArgumentValue($tokens); |
741: | |
742: | $tokens->dropSavePoint(); |
743: | |
744: | return $this->enrichWithAttributes( |
745: | $tokens, |
746: | new Doctrine\DoctrineArrayItem($key, $value), |
747: | $startLine, |
748: | $startIndex, |
749: | ); |
750: | } catch (ParserException $e) { |
751: | $tokens->rollback(); |
752: | |
753: | return $this->enrichWithAttributes( |
754: | $tokens, |
755: | new Doctrine\DoctrineArrayItem(null, $this->parseDoctrineArgumentValue($tokens)), |
756: | $startLine, |
757: | $startIndex, |
758: | ); |
759: | } |
760: | } |
761: | |
762: | |
763: | |
764: | |
765: | |
766: | private function parseDoctrineArrayKey(TokenIterator $tokens) |
767: | { |
768: | $startLine = $tokens->currentTokenLine(); |
769: | $startIndex = $tokens->currentTokenIndex(); |
770: | |
771: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { |
772: | $key = new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $tokens->currentTokenValue())); |
773: | $tokens->next(); |
774: | |
775: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) { |
776: | $key = $this->doctrineConstantExprParser->parseDoctrineString($tokens->currentTokenValue(), $tokens); |
777: | |
778: | $tokens->next(); |
779: | |
780: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { |
781: | $key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED); |
782: | $tokens->next(); |
783: | |
784: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { |
785: | $value = $tokens->currentTokenValue(); |
786: | $tokens->next(); |
787: | $key = $this->doctrineConstantExprParser->parseDoctrineString($value, $tokens); |
788: | |
789: | } else { |
790: | $currentTokenValue = $tokens->currentTokenValue(); |
791: | $tokens->pushSavePoint(); |
792: | if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) { |
793: | $tokens->dropSavePoint(); |
794: | throw new ParserException( |
795: | $tokens->currentTokenValue(), |
796: | $tokens->currentTokenType(), |
797: | $tokens->currentTokenOffset(), |
798: | Lexer::TOKEN_IDENTIFIER, |
799: | null, |
800: | $tokens->currentTokenLine(), |
801: | ); |
802: | } |
803: | |
804: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) { |
805: | $tokens->dropSavePoint(); |
806: | |
807: | return $this->enrichWithAttributes( |
808: | $tokens, |
809: | new IdentifierTypeNode($currentTokenValue), |
810: | $startLine, |
811: | $startIndex, |
812: | ); |
813: | } |
814: | |
815: | $tokens->rollback(); |
816: | $constExpr = $this->doctrineConstantExprParser->parse($tokens); |
817: | if (!$constExpr instanceof Ast\ConstExpr\ConstFetchNode) { |
818: | throw new ParserException( |
819: | $tokens->currentTokenValue(), |
820: | $tokens->currentTokenType(), |
821: | $tokens->currentTokenOffset(), |
822: | Lexer::TOKEN_IDENTIFIER, |
823: | null, |
824: | $tokens->currentTokenLine(), |
825: | ); |
826: | } |
827: | |
828: | return $constExpr; |
829: | } |
830: | |
831: | return $this->enrichWithAttributes($tokens, $key, $startLine, $startIndex); |
832: | } |
833: | |
834: | |
835: | |
836: | |
837: | |
838: | private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode |
839: | { |
840: | if ( |
841: | $tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE, Lexer::TOKEN_VARIADIC, Lexer::TOKEN_VARIABLE) |
842: | ) { |
843: | $type = null; |
844: | } else { |
845: | $type = $this->typeParser->parse($tokens); |
846: | } |
847: | |
848: | $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); |
849: | $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); |
850: | $parameterName = $this->parseRequiredVariableName($tokens); |
851: | $description = $this->parseOptionalDescription($tokens, false); |
852: | |
853: | if ($type !== null) { |
854: | return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference); |
855: | } |
856: | |
857: | return new Ast\PhpDoc\TypelessParamTagValueNode($isVariadic, $parameterName, $description, $isReference); |
858: | } |
859: | |
860: | |
861: | private function parseParamImmediatelyInvokedCallableTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode |
862: | { |
863: | $parameterName = $this->parseRequiredVariableName($tokens); |
864: | $description = $this->parseOptionalDescription($tokens, false); |
865: | |
866: | return new Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode($parameterName, $description); |
867: | } |
868: | |
869: | |
870: | private function parseParamLaterInvokedCallableTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode |
871: | { |
872: | $parameterName = $this->parseRequiredVariableName($tokens); |
873: | $description = $this->parseOptionalDescription($tokens, false); |
874: | |
875: | return new Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode($parameterName, $description); |
876: | } |
877: | |
878: | |
879: | private function parseParamClosureThisTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamClosureThisTagValueNode |
880: | { |
881: | $type = $this->typeParser->parse($tokens); |
882: | $parameterName = $this->parseRequiredVariableName($tokens); |
883: | $description = $this->parseOptionalDescription($tokens, false); |
884: | |
885: | return new Ast\PhpDoc\ParamClosureThisTagValueNode($type, $parameterName, $description); |
886: | } |
887: | |
888: | private function parsePureUnlessCallableIsImpureTagValue(TokenIterator $tokens): Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode |
889: | { |
890: | $parameterName = $this->parseRequiredVariableName($tokens); |
891: | $description = $this->parseOptionalDescription($tokens, false); |
892: | |
893: | return new Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode($parameterName, $description); |
894: | } |
895: | |
896: | private function parseVarTagValue(TokenIterator $tokens): Ast\PhpDoc\VarTagValueNode |
897: | { |
898: | $type = $this->typeParser->parse($tokens); |
899: | $variableName = $this->parseOptionalVariableName($tokens); |
900: | $description = $this->parseOptionalDescription($tokens, $variableName === ''); |
901: | return new Ast\PhpDoc\VarTagValueNode($type, $variableName, $description); |
902: | } |
903: | |
904: | |
905: | private function parseReturnTagValue(TokenIterator $tokens): Ast\PhpDoc\ReturnTagValueNode |
906: | { |
907: | $type = $this->typeParser->parse($tokens); |
908: | $description = $this->parseOptionalDescription($tokens, true); |
909: | return new Ast\PhpDoc\ReturnTagValueNode($type, $description); |
910: | } |
911: | |
912: | |
913: | private function parseThrowsTagValue(TokenIterator $tokens): Ast\PhpDoc\ThrowsTagValueNode |
914: | { |
915: | $type = $this->typeParser->parse($tokens); |
916: | $description = $this->parseOptionalDescription($tokens, true); |
917: | return new Ast\PhpDoc\ThrowsTagValueNode($type, $description); |
918: | } |
919: | |
920: | private function parseMixinTagValue(TokenIterator $tokens): Ast\PhpDoc\MixinTagValueNode |
921: | { |
922: | $type = $this->typeParser->parse($tokens); |
923: | $description = $this->parseOptionalDescription($tokens, true); |
924: | return new Ast\PhpDoc\MixinTagValueNode($type, $description); |
925: | } |
926: | |
927: | private function parseRequireExtendsTagValue(TokenIterator $tokens): Ast\PhpDoc\RequireExtendsTagValueNode |
928: | { |
929: | $type = $this->typeParser->parse($tokens); |
930: | $description = $this->parseOptionalDescription($tokens, true); |
931: | return new Ast\PhpDoc\RequireExtendsTagValueNode($type, $description); |
932: | } |
933: | |
934: | private function parseRequireImplementsTagValue(TokenIterator $tokens): Ast\PhpDoc\RequireImplementsTagValueNode |
935: | { |
936: | $type = $this->typeParser->parse($tokens); |
937: | $description = $this->parseOptionalDescription($tokens, true); |
938: | return new Ast\PhpDoc\RequireImplementsTagValueNode($type, $description); |
939: | } |
940: | |
941: | private function parseSealedTagValue(TokenIterator $tokens): Ast\PhpDoc\SealedTagValueNode |
942: | { |
943: | $type = $this->typeParser->parse($tokens); |
944: | $description = $this->parseOptionalDescription($tokens, true); |
945: | return new Ast\PhpDoc\SealedTagValueNode($type, $description); |
946: | } |
947: | |
948: | private function parseDeprecatedTagValue(TokenIterator $tokens): Ast\PhpDoc\DeprecatedTagValueNode |
949: | { |
950: | $description = $this->parseOptionalDescription($tokens, false); |
951: | return new Ast\PhpDoc\DeprecatedTagValueNode($description); |
952: | } |
953: | |
954: | |
955: | private function parsePropertyTagValue(TokenIterator $tokens): Ast\PhpDoc\PropertyTagValueNode |
956: | { |
957: | $type = $this->typeParser->parse($tokens); |
958: | $parameterName = $this->parseRequiredVariableName($tokens); |
959: | $description = $this->parseOptionalDescription($tokens, false); |
960: | return new Ast\PhpDoc\PropertyTagValueNode($type, $parameterName, $description); |
961: | } |
962: | |
963: | |
964: | private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueNode |
965: | { |
966: | $staticKeywordOrReturnTypeOrMethodName = $this->typeParser->parse($tokens); |
967: | |
968: | if ($staticKeywordOrReturnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode && $staticKeywordOrReturnTypeOrMethodName->name === 'static') { |
969: | $isStatic = true; |
970: | $returnTypeOrMethodName = $this->typeParser->parse($tokens); |
971: | |
972: | } else { |
973: | $isStatic = false; |
974: | $returnTypeOrMethodName = $staticKeywordOrReturnTypeOrMethodName; |
975: | } |
976: | |
977: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { |
978: | $returnType = $returnTypeOrMethodName; |
979: | $methodName = $tokens->currentTokenValue(); |
980: | $tokens->next(); |
981: | |
982: | } elseif ($returnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode) { |
983: | $returnType = $isStatic ? $staticKeywordOrReturnTypeOrMethodName : null; |
984: | $methodName = $returnTypeOrMethodName->name; |
985: | $isStatic = false; |
986: | |
987: | } else { |
988: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
989: | exit; |
990: | } |
991: | |
992: | $templateTypes = []; |
993: | |
994: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { |
995: | do { |
996: | $startLine = $tokens->currentTokenLine(); |
997: | $startIndex = $tokens->currentTokenIndex(); |
998: | $templateTypes[] = $this->enrichWithAttributes( |
999: | $tokens, |
1000: | $this->typeParser->parseTemplateTagValue($tokens), |
1001: | $startLine, |
1002: | $startIndex, |
1003: | ); |
1004: | } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); |
1005: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); |
1006: | } |
1007: | |
1008: | $parameters = []; |
1009: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); |
1010: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { |
1011: | $parameters[] = $this->parseMethodTagValueParameter($tokens); |
1012: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { |
1013: | $parameters[] = $this->parseMethodTagValueParameter($tokens); |
1014: | } |
1015: | } |
1016: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
1017: | |
1018: | $description = $this->parseOptionalDescription($tokens, false); |
1019: | return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes); |
1020: | } |
1021: | |
1022: | private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode |
1023: | { |
1024: | $startLine = $tokens->currentTokenLine(); |
1025: | $startIndex = $tokens->currentTokenIndex(); |
1026: | |
1027: | switch ($tokens->currentTokenType()) { |
1028: | case Lexer::TOKEN_IDENTIFIER: |
1029: | case Lexer::TOKEN_OPEN_PARENTHESES: |
1030: | case Lexer::TOKEN_NULLABLE: |
1031: | $parameterType = $this->typeParser->parse($tokens); |
1032: | break; |
1033: | |
1034: | default: |
1035: | $parameterType = null; |
1036: | } |
1037: | |
1038: | $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); |
1039: | $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); |
1040: | |
1041: | $parameterName = $tokens->currentTokenValue(); |
1042: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
1043: | |
1044: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) { |
1045: | $defaultValue = $this->constantExprParser->parse($tokens); |
1046: | |
1047: | } else { |
1048: | $defaultValue = null; |
1049: | } |
1050: | |
1051: | return $this->enrichWithAttributes( |
1052: | $tokens, |
1053: | new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue), |
1054: | $startLine, |
1055: | $startIndex, |
1056: | ); |
1057: | } |
1058: | |
1059: | private function parseExtendsTagValue(string $tagName, TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode |
1060: | { |
1061: | $startLine = $tokens->currentTokenLine(); |
1062: | $startIndex = $tokens->currentTokenIndex(); |
1063: | $baseType = new IdentifierTypeNode($tokens->currentTokenValue()); |
1064: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
1065: | |
1066: | $type = $this->typeParser->parseGeneric( |
1067: | $tokens, |
1068: | $this->typeParser->enrichWithAttributes($tokens, $baseType, $startLine, $startIndex), |
1069: | ); |
1070: | |
1071: | $description = $this->parseOptionalDescription($tokens, true); |
1072: | |
1073: | switch ($tagName) { |
1074: | case '@extends': |
1075: | return new Ast\PhpDoc\ExtendsTagValueNode($type, $description); |
1076: | case '@implements': |
1077: | return new Ast\PhpDoc\ImplementsTagValueNode($type, $description); |
1078: | case '@use': |
1079: | return new Ast\PhpDoc\UsesTagValueNode($type, $description); |
1080: | } |
1081: | |
1082: | throw new ShouldNotHappenException(); |
1083: | } |
1084: | |
1085: | private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasTagValueNode |
1086: | { |
1087: | $alias = $tokens->currentTokenValue(); |
1088: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
1089: | |
1090: | |
1091: | $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); |
1092: | |
1093: | $startLine = $tokens->currentTokenLine(); |
1094: | $startIndex = $tokens->currentTokenIndex(); |
1095: | try { |
1096: | $type = $this->typeParser->parse($tokens); |
1097: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
1098: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { |
1099: | throw new ParserException( |
1100: | $tokens->currentTokenValue(), |
1101: | $tokens->currentTokenType(), |
1102: | $tokens->currentTokenOffset(), |
1103: | Lexer::TOKEN_PHPDOC_EOL, |
1104: | null, |
1105: | $tokens->currentTokenLine(), |
1106: | ); |
1107: | } |
1108: | } |
1109: | |
1110: | return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); |
1111: | } catch (ParserException $e) { |
1112: | $this->parseOptionalDescription($tokens, false); |
1113: | return new Ast\PhpDoc\TypeAliasTagValueNode( |
1114: | $alias, |
1115: | $this->enrichWithAttributes($tokens, new Ast\Type\InvalidTypeNode($e), $startLine, $startIndex), |
1116: | ); |
1117: | } |
1118: | } |
1119: | |
1120: | private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasImportTagValueNode |
1121: | { |
1122: | $importedAlias = $tokens->currentTokenValue(); |
1123: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
1124: | |
1125: | $tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'from'); |
1126: | |
1127: | $identifierStartLine = $tokens->currentTokenLine(); |
1128: | $identifierStartIndex = $tokens->currentTokenIndex(); |
1129: | $importedFrom = $tokens->currentTokenValue(); |
1130: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
1131: | $importedFromType = $this->enrichWithAttributes( |
1132: | $tokens, |
1133: | new IdentifierTypeNode($importedFrom), |
1134: | $identifierStartLine, |
1135: | $identifierStartIndex, |
1136: | ); |
1137: | |
1138: | $importedAs = null; |
1139: | if ($tokens->tryConsumeTokenValue('as')) { |
1140: | $importedAs = $tokens->currentTokenValue(); |
1141: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
1142: | } |
1143: | |
1144: | return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, $importedFromType, $importedAs); |
1145: | } |
1146: | |
1147: | |
1148: | |
1149: | |
1150: | private function parseAssertTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode |
1151: | { |
1152: | $isNegated = $tokens->tryConsumeTokenType(Lexer::TOKEN_NEGATED); |
1153: | $isEquality = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); |
1154: | $type = $this->typeParser->parse($tokens); |
1155: | $parameter = $this->parseAssertParameter($tokens); |
1156: | $description = $this->parseOptionalDescription($tokens, false); |
1157: | |
1158: | if (array_key_exists('method', $parameter)) { |
1159: | return new Ast\PhpDoc\AssertTagMethodValueNode($type, $parameter['parameter'], $parameter['method'], $isNegated, $description, $isEquality); |
1160: | } elseif (array_key_exists('property', $parameter)) { |
1161: | return new Ast\PhpDoc\AssertTagPropertyValueNode($type, $parameter['parameter'], $parameter['property'], $isNegated, $description, $isEquality); |
1162: | } |
1163: | |
1164: | return new Ast\PhpDoc\AssertTagValueNode($type, $parameter['parameter'], $isNegated, $description, $isEquality); |
1165: | } |
1166: | |
1167: | |
1168: | |
1169: | |
1170: | private function parseAssertParameter(TokenIterator $tokens): array |
1171: | { |
1172: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) { |
1173: | $parameter = '$this'; |
1174: | $tokens->next(); |
1175: | } else { |
1176: | $parameter = $tokens->currentTokenValue(); |
1177: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
1178: | } |
1179: | |
1180: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_ARROW)) { |
1181: | $tokens->consumeTokenType(Lexer::TOKEN_ARROW); |
1182: | |
1183: | $propertyOrMethod = $tokens->currentTokenValue(); |
1184: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
1185: | |
1186: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
1187: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
1188: | |
1189: | return ['parameter' => $parameter, 'method' => $propertyOrMethod]; |
1190: | } |
1191: | |
1192: | return ['parameter' => $parameter, 'property' => $propertyOrMethod]; |
1193: | } |
1194: | |
1195: | return ['parameter' => $parameter]; |
1196: | } |
1197: | |
1198: | private function parseSelfOutTagValue(TokenIterator $tokens): Ast\PhpDoc\SelfOutTagValueNode |
1199: | { |
1200: | $type = $this->typeParser->parse($tokens); |
1201: | $description = $this->parseOptionalDescription($tokens, true); |
1202: | |
1203: | return new Ast\PhpDoc\SelfOutTagValueNode($type, $description); |
1204: | } |
1205: | |
1206: | private function parseParamOutTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamOutTagValueNode |
1207: | { |
1208: | $type = $this->typeParser->parse($tokens); |
1209: | $parameterName = $this->parseRequiredVariableName($tokens); |
1210: | $description = $this->parseOptionalDescription($tokens, false); |
1211: | |
1212: | return new Ast\PhpDoc\ParamOutTagValueNode($type, $parameterName, $description); |
1213: | } |
1214: | |
1215: | private function parseOptionalVariableName(TokenIterator $tokens): string |
1216: | { |
1217: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { |
1218: | $parameterName = $tokens->currentTokenValue(); |
1219: | $tokens->next(); |
1220: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) { |
1221: | $parameterName = '$this'; |
1222: | $tokens->next(); |
1223: | |
1224: | } else { |
1225: | $parameterName = ''; |
1226: | } |
1227: | |
1228: | return $parameterName; |
1229: | } |
1230: | |
1231: | |
1232: | private function parseRequiredVariableName(TokenIterator $tokens): string |
1233: | { |
1234: | $parameterName = $tokens->currentTokenValue(); |
1235: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
1236: | |
1237: | return $parameterName; |
1238: | } |
1239: | |
1240: | |
1241: | |
1242: | |
1243: | private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken): string |
1244: | { |
1245: | if ($limitStartToken) { |
1246: | foreach (self::DISALLOWED_DESCRIPTION_START_TOKENS as $disallowedStartToken) { |
1247: | if (!$tokens->isCurrentTokenType($disallowedStartToken)) { |
1248: | continue; |
1249: | } |
1250: | |
1251: | $tokens->consumeTokenType(Lexer::TOKEN_OTHER); |
1252: | } |
1253: | |
1254: | if ( |
1255: | !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END) |
1256: | && !$tokens->isPrecededByHorizontalWhitespace() |
1257: | ) { |
1258: | $tokens->consumeTokenType(Lexer::TOKEN_HORIZONTAL_WS); |
1259: | } |
1260: | } |
1261: | |
1262: | return $this->parseText($tokens)->text; |
1263: | } |
1264: | |
1265: | } |
1266: | |