1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\PhpDocParser\Parser; |
4: | |
5: | use PHPStan\PhpDocParser\Ast; |
6: | use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; |
7: | use PHPStan\PhpDocParser\Lexer\Lexer; |
8: | use PHPStan\ShouldNotHappenException; |
9: | use function array_key_exists; |
10: | use function array_values; |
11: | use function count; |
12: | use function trim; |
13: | |
14: | class PhpDocParser |
15: | { |
16: | |
17: | private const DISALLOWED_DESCRIPTION_START_TOKENS = [ |
18: | Lexer::TOKEN_UNION, |
19: | Lexer::TOKEN_INTERSECTION, |
20: | ]; |
21: | |
22: | |
23: | private $typeParser; |
24: | |
25: | |
26: | private $constantExprParser; |
27: | |
28: | |
29: | private $requireWhitespaceBeforeDescription; |
30: | |
31: | public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser, bool $requireWhitespaceBeforeDescription = false) |
32: | { |
33: | $this->typeParser = $typeParser; |
34: | $this->constantExprParser = $constantExprParser; |
35: | $this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription; |
36: | } |
37: | |
38: | |
39: | public function parse(TokenIterator $tokens): Ast\PhpDoc\PhpDocNode |
40: | { |
41: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PHPDOC); |
42: | $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); |
43: | |
44: | $children = []; |
45: | |
46: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
47: | $children[] = $this->parseChild($tokens); |
48: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { |
49: | $children[] = $this->parseChild($tokens); |
50: | } |
51: | } |
52: | |
53: | try { |
54: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC); |
55: | } catch (ParserException $e) { |
56: | $name = ''; |
57: | if (count($children) > 0) { |
58: | $lastChild = $children[count($children) - 1]; |
59: | if ($lastChild instanceof Ast\PhpDoc\PhpDocTagNode) { |
60: | $name = $lastChild->name; |
61: | } |
62: | } |
63: | $tokens->forwardToTheEnd(); |
64: | return new Ast\PhpDoc\PhpDocNode([ |
65: | new Ast\PhpDoc\PhpDocTagNode($name, new Ast\PhpDoc\InvalidTagValueNode($e->getMessage(), $e)), |
66: | ]); |
67: | } |
68: | |
69: | return new Ast\PhpDoc\PhpDocNode(array_values($children)); |
70: | } |
71: | |
72: | |
73: | private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode |
74: | { |
75: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) { |
76: | return $this->parseTag($tokens); |
77: | |
78: | } |
79: | |
80: | return $this->parseText($tokens); |
81: | } |
82: | |
83: | |
84: | private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode |
85: | { |
86: | $text = ''; |
87: | |
88: | while (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { |
89: | $text .= $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END); |
90: | |
91: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { |
92: | break; |
93: | } |
94: | |
95: | $tokens->pushSavePoint(); |
96: | $tokens->next(); |
97: | |
98: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END)) { |
99: | $tokens->rollback(); |
100: | break; |
101: | } |
102: | |
103: | $tokens->dropSavePoint(); |
104: | $text .= "\n"; |
105: | } |
106: | |
107: | return new Ast\PhpDoc\PhpDocTextNode(trim($text, " \t")); |
108: | } |
109: | |
110: | |
111: | public function parseTag(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagNode |
112: | { |
113: | $tag = $tokens->currentTokenValue(); |
114: | $tokens->next(); |
115: | $value = $this->parseTagValue($tokens, $tag); |
116: | |
117: | return new Ast\PhpDoc\PhpDocTagNode($tag, $value); |
118: | } |
119: | |
120: | |
121: | public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\PhpDocTagValueNode |
122: | { |
123: | try { |
124: | $tokens->pushSavePoint(); |
125: | |
126: | switch ($tag) { |
127: | case '@param': |
128: | case '@phpstan-param': |
129: | case '@psalm-param': |
130: | $tagValue = $this->parseParamTagValue($tokens); |
131: | break; |
132: | |
133: | case '@var': |
134: | case '@phpstan-var': |
135: | case '@psalm-var': |
136: | $tagValue = $this->parseVarTagValue($tokens); |
137: | break; |
138: | |
139: | case '@return': |
140: | case '@phpstan-return': |
141: | case '@psalm-return': |
142: | $tagValue = $this->parseReturnTagValue($tokens); |
143: | break; |
144: | |
145: | case '@throws': |
146: | case '@phpstan-throws': |
147: | $tagValue = $this->parseThrowsTagValue($tokens); |
148: | break; |
149: | |
150: | case '@mixin': |
151: | $tagValue = $this->parseMixinTagValue($tokens); |
152: | break; |
153: | |
154: | case '@deprecated': |
155: | $tagValue = $this->parseDeprecatedTagValue($tokens); |
156: | break; |
157: | |
158: | case '@property': |
159: | case '@property-read': |
160: | case '@property-write': |
161: | case '@phpstan-property': |
162: | case '@phpstan-property-read': |
163: | case '@phpstan-property-write': |
164: | case '@psalm-property': |
165: | case '@psalm-property-read': |
166: | case '@psalm-property-write': |
167: | $tagValue = $this->parsePropertyTagValue($tokens); |
168: | break; |
169: | |
170: | case '@method': |
171: | case '@phpstan-method': |
172: | case '@psalm-method': |
173: | $tagValue = $this->parseMethodTagValue($tokens); |
174: | break; |
175: | |
176: | case '@template': |
177: | case '@phpstan-template': |
178: | case '@psalm-template': |
179: | case '@template-covariant': |
180: | case '@phpstan-template-covariant': |
181: | case '@psalm-template-covariant': |
182: | case '@template-contravariant': |
183: | case '@phpstan-template-contravariant': |
184: | case '@psalm-template-contravariant': |
185: | $tagValue = $this->parseTemplateTagValue($tokens, true); |
186: | break; |
187: | |
188: | case '@extends': |
189: | case '@phpstan-extends': |
190: | case '@template-extends': |
191: | $tagValue = $this->parseExtendsTagValue('@extends', $tokens); |
192: | break; |
193: | |
194: | case '@implements': |
195: | case '@phpstan-implements': |
196: | case '@template-implements': |
197: | $tagValue = $this->parseExtendsTagValue('@implements', $tokens); |
198: | break; |
199: | |
200: | case '@use': |
201: | case '@phpstan-use': |
202: | case '@template-use': |
203: | $tagValue = $this->parseExtendsTagValue('@use', $tokens); |
204: | break; |
205: | |
206: | case '@phpstan-type': |
207: | case '@psalm-type': |
208: | $tagValue = $this->parseTypeAliasTagValue($tokens); |
209: | break; |
210: | |
211: | case '@phpstan-import-type': |
212: | case '@psalm-import-type': |
213: | $tagValue = $this->parseTypeAliasImportTagValue($tokens); |
214: | break; |
215: | |
216: | case '@phpstan-assert': |
217: | case '@phpstan-assert-if-true': |
218: | case '@phpstan-assert-if-false': |
219: | case '@psalm-assert': |
220: | case '@psalm-assert-if-true': |
221: | case '@psalm-assert-if-false': |
222: | $tagValue = $this->parseAssertTagValue($tokens); |
223: | break; |
224: | |
225: | case '@phpstan-this-out': |
226: | case '@phpstan-self-out': |
227: | case '@psalm-this-out': |
228: | case '@psalm-self-out': |
229: | $tagValue = $this->parseSelfOutTagValue($tokens); |
230: | break; |
231: | |
232: | case '@param-out': |
233: | case '@phpstan-param-out': |
234: | case '@psalm-param-out': |
235: | $tagValue = $this->parseParamOutTagValue($tokens); |
236: | break; |
237: | |
238: | default: |
239: | $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens)); |
240: | break; |
241: | } |
242: | |
243: | $tokens->dropSavePoint(); |
244: | |
245: | } catch (ParserException $e) { |
246: | $tokens->rollback(); |
247: | $tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens), $e); |
248: | } |
249: | |
250: | return $tagValue; |
251: | } |
252: | |
253: | |
254: | |
255: | |
256: | |
257: | private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode |
258: | { |
259: | if ( |
260: | $tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE) |
261: | || $tokens->isCurrentTokenType(Lexer::TOKEN_VARIADIC) |
262: | || $tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE) |
263: | ) { |
264: | $type = null; |
265: | } else { |
266: | $type = $this->typeParser->parse($tokens); |
267: | } |
268: | |
269: | $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); |
270: | $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); |
271: | $parameterName = $this->parseRequiredVariableName($tokens); |
272: | $description = $this->parseOptionalDescription($tokens); |
273: | |
274: | if ($type !== null) { |
275: | return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference); |
276: | } |
277: | |
278: | return new Ast\PhpDoc\TypelessParamTagValueNode($isVariadic, $parameterName, $description, $isReference); |
279: | } |
280: | |
281: | |
282: | private function parseVarTagValue(TokenIterator $tokens): Ast\PhpDoc\VarTagValueNode |
283: | { |
284: | $type = $this->typeParser->parse($tokens); |
285: | $variableName = $this->parseOptionalVariableName($tokens); |
286: | $description = $this->parseOptionalDescription($tokens, $variableName === ''); |
287: | return new Ast\PhpDoc\VarTagValueNode($type, $variableName, $description); |
288: | } |
289: | |
290: | |
291: | private function parseReturnTagValue(TokenIterator $tokens): Ast\PhpDoc\ReturnTagValueNode |
292: | { |
293: | $type = $this->typeParser->parse($tokens); |
294: | $description = $this->parseOptionalDescription($tokens, true); |
295: | return new Ast\PhpDoc\ReturnTagValueNode($type, $description); |
296: | } |
297: | |
298: | |
299: | private function parseThrowsTagValue(TokenIterator $tokens): Ast\PhpDoc\ThrowsTagValueNode |
300: | { |
301: | $type = $this->typeParser->parse($tokens); |
302: | $description = $this->parseOptionalDescription($tokens, true); |
303: | return new Ast\PhpDoc\ThrowsTagValueNode($type, $description); |
304: | } |
305: | |
306: | private function parseMixinTagValue(TokenIterator $tokens): Ast\PhpDoc\MixinTagValueNode |
307: | { |
308: | $type = $this->typeParser->parse($tokens); |
309: | $description = $this->parseOptionalDescription($tokens, true); |
310: | return new Ast\PhpDoc\MixinTagValueNode($type, $description); |
311: | } |
312: | |
313: | private function parseDeprecatedTagValue(TokenIterator $tokens): Ast\PhpDoc\DeprecatedTagValueNode |
314: | { |
315: | $description = $this->parseOptionalDescription($tokens); |
316: | return new Ast\PhpDoc\DeprecatedTagValueNode($description); |
317: | } |
318: | |
319: | |
320: | private function parsePropertyTagValue(TokenIterator $tokens): Ast\PhpDoc\PropertyTagValueNode |
321: | { |
322: | $type = $this->typeParser->parse($tokens); |
323: | $parameterName = $this->parseRequiredVariableName($tokens); |
324: | $description = $this->parseOptionalDescription($tokens); |
325: | return new Ast\PhpDoc\PropertyTagValueNode($type, $parameterName, $description); |
326: | } |
327: | |
328: | |
329: | private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueNode |
330: | { |
331: | $isStatic = $tokens->tryConsumeTokenValue('static'); |
332: | $returnTypeOrMethodName = $this->typeParser->parse($tokens); |
333: | |
334: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { |
335: | $returnType = $returnTypeOrMethodName; |
336: | $methodName = $tokens->currentTokenValue(); |
337: | $tokens->next(); |
338: | |
339: | } elseif ($returnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode) { |
340: | $returnType = $isStatic ? new Ast\Type\IdentifierTypeNode('static') : null; |
341: | $methodName = $returnTypeOrMethodName->name; |
342: | $isStatic = false; |
343: | |
344: | } else { |
345: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
346: | exit; |
347: | } |
348: | |
349: | $templateTypes = []; |
350: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { |
351: | do { |
352: | $templateTypes[] = $this->parseTemplateTagValue($tokens, false); |
353: | } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); |
354: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); |
355: | } |
356: | |
357: | $parameters = []; |
358: | $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); |
359: | if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { |
360: | $parameters[] = $this->parseMethodTagValueParameter($tokens); |
361: | while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { |
362: | $parameters[] = $this->parseMethodTagValueParameter($tokens); |
363: | } |
364: | } |
365: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
366: | |
367: | $description = $this->parseOptionalDescription($tokens); |
368: | return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes); |
369: | } |
370: | |
371: | private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode |
372: | { |
373: | switch ($tokens->currentTokenType()) { |
374: | case Lexer::TOKEN_IDENTIFIER: |
375: | case Lexer::TOKEN_OPEN_PARENTHESES: |
376: | case Lexer::TOKEN_NULLABLE: |
377: | $parameterType = $this->typeParser->parse($tokens); |
378: | break; |
379: | |
380: | default: |
381: | $parameterType = null; |
382: | } |
383: | |
384: | $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); |
385: | $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); |
386: | |
387: | $parameterName = $tokens->currentTokenValue(); |
388: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
389: | |
390: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL)) { |
391: | $defaultValue = $this->constantExprParser->parse($tokens); |
392: | |
393: | } else { |
394: | $defaultValue = null; |
395: | } |
396: | |
397: | return new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue); |
398: | } |
399: | |
400: | private function parseTemplateTagValue(TokenIterator $tokens, bool $parseDescription): Ast\PhpDoc\TemplateTagValueNode |
401: | { |
402: | $name = $tokens->currentTokenValue(); |
403: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
404: | |
405: | if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) { |
406: | $bound = $this->typeParser->parse($tokens); |
407: | |
408: | } else { |
409: | $bound = null; |
410: | } |
411: | |
412: | if ($tokens->tryConsumeTokenValue('=')) { |
413: | $default = $this->typeParser->parse($tokens); |
414: | } else { |
415: | $default = null; |
416: | } |
417: | |
418: | if ($parseDescription) { |
419: | $description = $this->parseOptionalDescription($tokens); |
420: | } else { |
421: | $description = ''; |
422: | } |
423: | |
424: | return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description, $default); |
425: | } |
426: | |
427: | private function parseExtendsTagValue(string $tagName, TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode |
428: | { |
429: | $baseType = new IdentifierTypeNode($tokens->currentTokenValue()); |
430: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
431: | |
432: | $type = $this->typeParser->parseGeneric($tokens, $baseType); |
433: | |
434: | $description = $this->parseOptionalDescription($tokens); |
435: | |
436: | switch ($tagName) { |
437: | case '@extends': |
438: | return new Ast\PhpDoc\ExtendsTagValueNode($type, $description); |
439: | case '@implements': |
440: | return new Ast\PhpDoc\ImplementsTagValueNode($type, $description); |
441: | case '@use': |
442: | return new Ast\PhpDoc\UsesTagValueNode($type, $description); |
443: | } |
444: | |
445: | throw new ShouldNotHappenException(); |
446: | } |
447: | |
448: | private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasTagValueNode |
449: | { |
450: | $alias = $tokens->currentTokenValue(); |
451: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
452: | |
453: | |
454: | $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); |
455: | |
456: | $type = $this->typeParser->parse($tokens); |
457: | |
458: | return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); |
459: | } |
460: | |
461: | private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasImportTagValueNode |
462: | { |
463: | $importedAlias = $tokens->currentTokenValue(); |
464: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
465: | |
466: | $tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'from'); |
467: | |
468: | $importedFrom = $tokens->currentTokenValue(); |
469: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
470: | |
471: | $importedAs = null; |
472: | if ($tokens->tryConsumeTokenValue('as')) { |
473: | $importedAs = $tokens->currentTokenValue(); |
474: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
475: | } |
476: | |
477: | return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, new IdentifierTypeNode($importedFrom), $importedAs); |
478: | } |
479: | |
480: | |
481: | |
482: | |
483: | private function parseAssertTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode |
484: | { |
485: | $isNegated = $tokens->tryConsumeTokenType(Lexer::TOKEN_NEGATED); |
486: | $isEquality = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); |
487: | $type = $this->typeParser->parse($tokens); |
488: | $parameter = $this->parseAssertParameter($tokens); |
489: | $description = $this->parseOptionalDescription($tokens); |
490: | |
491: | if (array_key_exists('method', $parameter)) { |
492: | return new Ast\PhpDoc\AssertTagMethodValueNode($type, $parameter['parameter'], $parameter['method'], $isNegated, $description, $isEquality); |
493: | } elseif (array_key_exists('property', $parameter)) { |
494: | return new Ast\PhpDoc\AssertTagPropertyValueNode($type, $parameter['parameter'], $parameter['property'], $isNegated, $description, $isEquality); |
495: | } |
496: | |
497: | return new Ast\PhpDoc\AssertTagValueNode($type, $parameter['parameter'], $isNegated, $description, $isEquality); |
498: | } |
499: | |
500: | |
501: | |
502: | |
503: | private function parseAssertParameter(TokenIterator $tokens): array |
504: | { |
505: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) { |
506: | $parameter = '$this'; |
507: | $requirePropertyOrMethod = true; |
508: | $tokens->next(); |
509: | } else { |
510: | $parameter = $tokens->currentTokenValue(); |
511: | $requirePropertyOrMethod = false; |
512: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
513: | } |
514: | |
515: | if ($requirePropertyOrMethod || $tokens->isCurrentTokenType(Lexer::TOKEN_ARROW)) { |
516: | $tokens->consumeTokenType(Lexer::TOKEN_ARROW); |
517: | |
518: | $propertyOrMethod = $tokens->currentTokenValue(); |
519: | $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); |
520: | |
521: | if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { |
522: | $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); |
523: | |
524: | return ['parameter' => $parameter, 'method' => $propertyOrMethod]; |
525: | } |
526: | |
527: | return ['parameter' => $parameter, 'property' => $propertyOrMethod]; |
528: | } |
529: | |
530: | return ['parameter' => $parameter]; |
531: | } |
532: | |
533: | private function parseSelfOutTagValue(TokenIterator $tokens): Ast\PhpDoc\SelfOutTagValueNode |
534: | { |
535: | $type = $this->typeParser->parse($tokens); |
536: | $description = $this->parseOptionalDescription($tokens); |
537: | |
538: | return new Ast\PhpDoc\SelfOutTagValueNode($type, $description); |
539: | } |
540: | |
541: | private function parseParamOutTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamOutTagValueNode |
542: | { |
543: | $type = $this->typeParser->parse($tokens); |
544: | $parameterName = $this->parseRequiredVariableName($tokens); |
545: | $description = $this->parseOptionalDescription($tokens); |
546: | |
547: | return new Ast\PhpDoc\ParamOutTagValueNode($type, $parameterName, $description); |
548: | } |
549: | |
550: | private function parseOptionalVariableName(TokenIterator $tokens): string |
551: | { |
552: | if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { |
553: | $parameterName = $tokens->currentTokenValue(); |
554: | $tokens->next(); |
555: | } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_THIS_VARIABLE)) { |
556: | $parameterName = '$this'; |
557: | $tokens->next(); |
558: | |
559: | } else { |
560: | $parameterName = ''; |
561: | } |
562: | |
563: | return $parameterName; |
564: | } |
565: | |
566: | |
567: | private function parseRequiredVariableName(TokenIterator $tokens): string |
568: | { |
569: | $parameterName = $tokens->currentTokenValue(); |
570: | $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); |
571: | |
572: | return $parameterName; |
573: | } |
574: | |
575: | private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken = false): string |
576: | { |
577: | if ($limitStartToken) { |
578: | foreach (self::DISALLOWED_DESCRIPTION_START_TOKENS as $disallowedStartToken) { |
579: | if (!$tokens->isCurrentTokenType($disallowedStartToken)) { |
580: | continue; |
581: | } |
582: | |
583: | $tokens->consumeTokenType(Lexer::TOKEN_OTHER); |
584: | } |
585: | |
586: | if ( |
587: | $this->requireWhitespaceBeforeDescription |
588: | && !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END) |
589: | && !$tokens->isPrecededByHorizontalWhitespace() |
590: | ) { |
591: | $tokens->consumeTokenType(Lexer::TOKEN_HORIZONTAL_WS); |
592: | } |
593: | } |
594: | |
595: | return $this->parseText($tokens)->text; |
596: | } |
597: | |
598: | } |
599: | |