|
73 | 73 | use Microsoft\PhpParser\Node\NamespaceUseGroupClause; |
74 | 74 | use Microsoft\PhpParser\Node\NumericLiteral; |
75 | 75 | use Microsoft\PhpParser\Node\ParenthesizedIntersectionType; |
| 76 | +use Microsoft\PhpParser\MissingToken; |
76 | 77 | use Microsoft\PhpParser\Node\PropertyDeclaration; |
| 78 | +use Microsoft\PhpParser\Node\PropertyElement; |
| 79 | +use Microsoft\PhpParser\Node\PropertyHook; |
| 80 | +use Microsoft\PhpParser\Node\PropertyHookList; |
77 | 81 | use Microsoft\PhpParser\Node\ReservedWord; |
78 | 82 | use Microsoft\PhpParser\Node\StringLiteral; |
79 | 83 | use Microsoft\PhpParser\Node\MethodDeclaration; |
@@ -3423,12 +3427,135 @@ private function parsePropertyDeclaration($parentNode, $modifiers, $questionToke |
3423 | 3427 | } elseif ($questionToken) { |
3424 | 3428 | $propertyDeclaration->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart); |
3425 | 3429 | } |
3426 | | - $propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration); |
3427 | | - $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3430 | + $propertyDeclaration->propertyElements = $this->parsePropertyElementList($propertyDeclaration); |
| 3431 | + $requiresSemicolon = true; |
| 3432 | + foreach ($propertyDeclaration->propertyElements->children ?? [] as $child) { |
| 3433 | + if ($child instanceof PropertyElement && $child->hookList !== null) { |
| 3434 | + $requiresSemicolon = false; |
| 3435 | + break; |
| 3436 | + } |
| 3437 | + } |
| 3438 | + if ($requiresSemicolon) { |
| 3439 | + $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3440 | + } else { |
| 3441 | + $propertyDeclaration->semicolon = $this->eatOptional1(TokenKind::SemicolonToken); |
| 3442 | + } |
3428 | 3443 |
|
3429 | 3444 | return $propertyDeclaration; |
3430 | 3445 | } |
3431 | 3446 |
|
| 3447 | + private function parsePropertyElementList($parentNode): DelimitedList\PropertyElementList |
| 3448 | + { |
| 3449 | + return $this->parseDelimitedList( |
| 3450 | + DelimitedList\PropertyElementList::class, |
| 3451 | + TokenKind::CommaToken, |
| 3452 | + $this->isPropertyElementStartFn(), |
| 3453 | + function ($list) { |
| 3454 | + return $this->parsePropertyElement($list); |
| 3455 | + }, |
| 3456 | + $parentNode |
| 3457 | + ) ?? new DelimitedList\PropertyElementList(); |
| 3458 | + } |
| 3459 | + |
| 3460 | + private function isPropertyElementStartFn() |
| 3461 | + { |
| 3462 | + return function (Token $token): bool { |
| 3463 | + return $token->kind === TokenKind::VariableName || $token->kind === TokenKind::DollarToken; |
| 3464 | + }; |
| 3465 | + } |
| 3466 | + |
| 3467 | + private function parsePropertyElement($parentNode): PropertyElement |
| 3468 | + { |
| 3469 | + $element = new PropertyElement(); |
| 3470 | + $element->parent = $parentNode; |
| 3471 | + $element->variable = $this->parseSimpleVariable($element); |
| 3472 | + $element->equalsToken = $this->eatOptional1(TokenKind::EqualsToken); |
| 3473 | + if ($element->equalsToken !== null) { |
| 3474 | + $element->initializer = $this->parseExpression($element); |
| 3475 | + } |
| 3476 | + if ($this->getCurrentToken()->kind === TokenKind::OpenBraceToken) { |
| 3477 | + $element->hookList = $this->parsePropertyHookList($element); |
| 3478 | + } |
| 3479 | + return $element; |
| 3480 | + } |
| 3481 | + |
| 3482 | + private function parsePropertyHookList(PropertyElement $parentNode): PropertyHookList |
| 3483 | + { |
| 3484 | + $hookList = new PropertyHookList(); |
| 3485 | + $hookList->parent = $parentNode; |
| 3486 | + $hookList->openBrace = $this->eat1(TokenKind::OpenBraceToken); |
| 3487 | + $hookList->hooks = []; |
| 3488 | + |
| 3489 | + while (true) { |
| 3490 | + $token = $this->getCurrentToken(); |
| 3491 | + if ($token->kind === TokenKind::CloseBraceToken || $token->kind === TokenKind::EndOfFileToken) { |
| 3492 | + break; |
| 3493 | + } |
| 3494 | + $previousFullStart = $token->fullStart; |
| 3495 | + $hook = $this->parsePropertyHook($hookList); |
| 3496 | + if ($hook instanceof PropertyHook) { |
| 3497 | + $hookList->hooks[] = $hook; |
| 3498 | + } else { |
| 3499 | + // Ensure forward progress to avoid infinite loops. |
| 3500 | + $this->advanceToken(); |
| 3501 | + } |
| 3502 | + if ($this->getCurrentToken()->fullStart === $previousFullStart) { |
| 3503 | + // No progress was made; advance once to prevent infinite loops. |
| 3504 | + $this->advanceToken(); |
| 3505 | + } |
| 3506 | + } |
| 3507 | + |
| 3508 | + $hookList->closeBrace = $this->eat1(TokenKind::CloseBraceToken); |
| 3509 | + return $hookList; |
| 3510 | + } |
| 3511 | + |
| 3512 | + private function parsePropertyHook(PropertyHookList $parentNode): ?PropertyHook |
| 3513 | + { |
| 3514 | + $hook = new PropertyHook(); |
| 3515 | + $hook->parent = $parentNode; |
| 3516 | + |
| 3517 | + if ($this->getCurrentToken()->kind === TokenKind::AttributeToken) { |
| 3518 | + $hook->attributes = $this->parseAttributeGroups($hook); |
| 3519 | + } |
| 3520 | + |
| 3521 | + $token = $this->getCurrentToken(); |
| 3522 | + if ($token->kind === TokenKind::Name) { |
| 3523 | + $text = \strtolower(\trim($token->getText($this->sourceFile->fileContents))); |
| 3524 | + if (\in_array($text, ['get', 'set', 'init'], true)) { |
| 3525 | + $hook->hookKeyword = $token; |
| 3526 | + $this->advanceToken(); |
| 3527 | + } else { |
| 3528 | + $hook->hookKeyword = new MissingToken(TokenKind::Name, $token->fullStart); |
| 3529 | + return $hook; |
| 3530 | + } |
| 3531 | + } else { |
| 3532 | + $hook->hookKeyword = new MissingToken(TokenKind::Name, $token->fullStart); |
| 3533 | + return $hook; |
| 3534 | + } |
| 3535 | + |
| 3536 | + if ($this->checkToken(TokenKind::OpenParenToken)) { |
| 3537 | + $hook->openParen = $this->eat1(TokenKind::OpenParenToken); |
| 3538 | + $hook->parameterList = $this->parseDelimitedList( |
| 3539 | + DelimitedList\ParameterDeclarationList::class, |
| 3540 | + TokenKind::CommaToken, |
| 3541 | + $this->isParameterStartFn(), |
| 3542 | + $this->parseParameterFn(), |
| 3543 | + $hook |
| 3544 | + ); |
| 3545 | + $hook->closeParen = $this->eat1(TokenKind::CloseParenToken); |
| 3546 | + } |
| 3547 | + |
| 3548 | + if ($this->checkToken(TokenKind::DoubleArrowToken)) { |
| 3549 | + $hook->arrowToken = $this->eat1(TokenKind::DoubleArrowToken); |
| 3550 | + $hook->expression = $this->parseExpression($hook); |
| 3551 | + $hook->semicolon = $this->eatOptional1(TokenKind::SemicolonToken) ?? new MissingToken(TokenKind::SemicolonToken, $this->getCurrentToken()->fullStart); |
| 3552 | + } else { |
| 3553 | + $hook->compoundStatement = $this->parseCompoundStatement($hook); |
| 3554 | + } |
| 3555 | + |
| 3556 | + return $hook; |
| 3557 | + } |
| 3558 | + |
3432 | 3559 | /** |
3433 | 3560 | * Parse a comma separated qualified name list (e.g. interfaces implemented by a class) |
3434 | 3561 | * |
|
0 commit comments