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