|
74 | 74 | use Microsoft\PhpParser\Node\NumericLiteral; |
75 | 75 | use Microsoft\PhpParser\Node\ParenthesizedIntersectionType; |
76 | 76 | use Microsoft\PhpParser\Node\PropertyDeclaration; |
| 77 | +use Microsoft\PhpParser\Node\PropertyHooks; |
| 78 | +use Microsoft\PhpParser\Node\PropertyHook; |
77 | 79 | use Microsoft\PhpParser\Node\ReservedWord; |
78 | 80 | use Microsoft\PhpParser\Node\StringLiteral; |
79 | 81 | use Microsoft\PhpParser\Node\MethodDeclaration; |
@@ -877,6 +879,10 @@ private function parseParameterFn() { |
877 | 879 | // TODO add post-parse rule that checks for invalid assignments |
878 | 880 | $parameter->default = $this->parseExpression($parameter); |
879 | 881 | } |
| 882 | + |
| 883 | + if ($this->token->kind === TokenKind::OpenBraceToken) { |
| 884 | + $parameter->propertyHooks = $this->parsePropertyHooks($parameter); |
| 885 | + } |
880 | 886 | return $parameter; |
881 | 887 | }; |
882 | 888 | } |
@@ -3437,12 +3443,115 @@ private function parsePropertyDeclaration($parentNode, $modifiers, $questionToke |
3437 | 3443 | } elseif ($questionToken) { |
3438 | 3444 | $propertyDeclaration->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart); |
3439 | 3445 | } |
3440 | | - $propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration); |
3441 | | - $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3446 | + $propertyDeclaration->propertyElements = $this->parsePropertyNameList($propertyDeclaration); |
| 3447 | + if ($this->token->kind === TokenKind::OpenBraceToken) { |
| 3448 | + $propertyDeclaration->propertyHooks = $this->parsePropertyHooks($propertyDeclaration); |
| 3449 | + } else { |
| 3450 | + $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3451 | + } |
3442 | 3452 |
|
3443 | 3453 | return $propertyDeclaration; |
3444 | 3454 | } |
3445 | 3455 |
|
| 3456 | + /** |
| 3457 | + * @param PropertyDeclaration $parentNode |
| 3458 | + * @return DelimitedList\VariableNameList |
| 3459 | + */ |
| 3460 | + private function parsePropertyNameList($parentNode) { |
| 3461 | + // XXX this used to be implemented with parseExpressionList so keep the same classes. |
| 3462 | + return $this->parseDelimitedList( |
| 3463 | + DelimitedList\ExpressionList::class, |
| 3464 | + TokenKind::CommaToken, |
| 3465 | + function ($token) { |
| 3466 | + return $token->kind === TokenKind::VariableName; |
| 3467 | + }, |
| 3468 | + $this->parsePropertyVariableNameAndDefault(), |
| 3469 | + $parentNode |
| 3470 | + ); |
| 3471 | + } |
| 3472 | + |
| 3473 | + private function parsePropertyVariableNameAndDefault() { |
| 3474 | + return function ($parentNode) { |
| 3475 | + // Imitate the format that parseExpression would have returned from when |
| 3476 | + // parseExpression was originally used. |
| 3477 | + // This is more precise and rules out parse errors such as `public $propName + 2;` |
| 3478 | + // This approach also avoids conflict with the deprecated $x{expr} array access syntax. |
| 3479 | + $variable = new Variable(); |
| 3480 | + $variable->name = $this->eat1(TokenKind::VariableName); |
| 3481 | + $equalsToken = $this->eatOptional1(TokenKind::EqualsToken); |
| 3482 | + if ($equalsToken === null) { |
| 3483 | + $variable->parent = $parentNode; |
| 3484 | + return $variable; |
| 3485 | + } |
| 3486 | + |
| 3487 | + $byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); // byRef default is nonsense, but this is a compile-time error instead of a parse error. |
| 3488 | + $rightOperand = $this->parseExpression($parentNode); // gets overridden in makeBinaryExpression |
| 3489 | + return $this->makeBinaryExpression($variable, $equalsToken, $byRefToken, $rightOperand, $parentNode); |
| 3490 | + }; |
| 3491 | + } |
| 3492 | + |
| 3493 | + /** |
| 3494 | + * @param PropertyDeclaration|Parameter $parent |
| 3495 | + * @return PropertyHooks |
| 3496 | + */ |
| 3497 | + private function parsePropertyHooks(Node $parent) { |
| 3498 | + $propertyHooks = new PropertyHooks(); |
| 3499 | + $propertyHooks->parent = $parent; |
| 3500 | + $propertyHooks->openBrace = $this->eat1(TokenKind::OpenBraceToken); |
| 3501 | + $hooks = []; |
| 3502 | + while (in_array($this->getCurrentToken()->kind, self::PROPERTY_HOOK_START_TOKENS, true)) { |
| 3503 | + $hooks[] = $this->parsePropertyHook($propertyHooks); |
| 3504 | + } |
| 3505 | + $propertyHooks->hookDeclarations = $hooks; |
| 3506 | + $propertyHooks->closeBrace = $this->eat1(TokenKind::CloseBraceToken); |
| 3507 | + return $propertyHooks; |
| 3508 | + } |
| 3509 | + |
| 3510 | + const PROPERTY_HOOK_START_TOKENS = [ |
| 3511 | + TokenKind::Name, |
| 3512 | + TokenKind::AmpersandToken, // by reference |
| 3513 | + TokenKind::AttributeToken, |
| 3514 | + ]; |
| 3515 | + |
| 3516 | + private function isPropertyHookStart() { |
| 3517 | + return function ($token) { |
| 3518 | + return \in_array($token->kind, self::PROPERTY_HOOK_START_TOKENS, true); |
| 3519 | + }; |
| 3520 | + } |
| 3521 | + |
| 3522 | + /** |
| 3523 | + * @param PropertyHooks $parentNode |
| 3524 | + * @return PropertyHook |
| 3525 | + */ |
| 3526 | + private function parsePropertyHook($parentNode) { |
| 3527 | + $node = new PropertyHook(); |
| 3528 | + $node->parent = $parentNode; |
| 3529 | + if ($this->getCurrentToken()->kind === TokenKind::AttributeToken) { |
| 3530 | + $node->attributes = $this->parseAttributeGroups($node); |
| 3531 | + } |
| 3532 | + $node->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); |
| 3533 | + $node->name = $this->eat1(TokenKind::Name); // "get" or "set" - other values are compile errors, not parse errors. |
| 3534 | + $node->openParen = $this->eatOptional1(TokenKind::OpenParenToken); |
| 3535 | + if ($node->openParen) { |
| 3536 | + $node->parameters = $this->parseDelimitedList( |
| 3537 | + DelimitedList\ParameterDeclarationList::class, |
| 3538 | + TokenKind::CommaToken, |
| 3539 | + $this->isParameterStartFn(), |
| 3540 | + $this->parseParameterFn(), |
| 3541 | + $node); |
| 3542 | + $node->closeParen = $this->eat1(TokenKind::CloseParenToken); |
| 3543 | + } |
| 3544 | + // e.g. `get => expr;` or `get { return $expr; }` |
| 3545 | + $node->arrowToken = $this->eatOptional1(TokenKind::DoubleArrowToken); |
| 3546 | + if ($node->arrowToken) { |
| 3547 | + $node->body = $this->parseExpression($node); |
| 3548 | + $node->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3549 | + } else { |
| 3550 | + $node->body = $this->parseCompoundStatement($node); |
| 3551 | + } |
| 3552 | + return $node; |
| 3553 | + } |
| 3554 | + |
3446 | 3555 | /** |
3447 | 3556 | * Parse a comma separated qualified name list (e.g. interfaces implemented by a class) |
3448 | 3557 | * |
|
0 commit comments