|
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 | }
|
@@ -2186,6 +2192,7 @@ private function parseBinaryExpressionOrHigher($precedence, $parentNode) {
|
2186 | 2192 | // the original operator, and the newly constructed exponentiation-expression as the operand.
|
2187 | 2193 | $shouldOperatorTakePrecedenceOverUnary = false;
|
2188 | 2194 | switch ($token->kind) {
|
| 2195 | + case TokenKind::OpenBraceToken: |
2189 | 2196 | case TokenKind::AsteriskAsteriskToken:
|
2190 | 2197 | $shouldOperatorTakePrecedenceOverUnary = $leftOperand instanceof UnaryExpression;
|
2191 | 2198 | break;
|
@@ -3050,6 +3057,9 @@ private function parsePostfixExpressionRest($expression, $allowUpdateExpression
|
3050 | 3057 | )) {
|
3051 | 3058 | return $expression;
|
3052 | 3059 | }
|
| 3060 | + if ($tokenKind === TokenKind::OpenBraceToken) { |
| 3061 | + return $expression; |
| 3062 | + } |
3053 | 3063 | if ($tokenKind === TokenKind::ColonColonToken) {
|
3054 | 3064 | $expression = $this->parseScopedPropertyAccessExpression($expression, null);
|
3055 | 3065 | return $this->parsePostfixExpressionRest($expression);
|
@@ -3437,12 +3447,115 @@ private function parsePropertyDeclaration($parentNode, $modifiers, $questionToke
|
3437 | 3447 | } elseif ($questionToken) {
|
3438 | 3448 | $propertyDeclaration->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
|
3439 | 3449 | }
|
3440 |
| - $propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration); |
3441 |
| - $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3450 | + $propertyDeclaration->propertyElements = $this->parsePropertyNameList($propertyDeclaration); |
| 3451 | + if ($this->token->kind === TokenKind::OpenBraceToken) { |
| 3452 | + $propertyDeclaration->propertyHooks = $this->parsePropertyHooks($propertyDeclaration); |
| 3453 | + } else { |
| 3454 | + $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3455 | + } |
3442 | 3456 |
|
3443 | 3457 | return $propertyDeclaration;
|
3444 | 3458 | }
|
3445 | 3459 |
|
| 3460 | + /** |
| 3461 | + * @param PropertyDeclaration $parentNode |
| 3462 | + * @return DelimitedList\VariableNameList |
| 3463 | + */ |
| 3464 | + private function parsePropertyNameList($parentNode) { |
| 3465 | + // XXX this used to be implemented with parseExpressionList so keep the same classes. |
| 3466 | + return $this->parseDelimitedList( |
| 3467 | + DelimitedList\ExpressionList::class, |
| 3468 | + TokenKind::CommaToken, |
| 3469 | + function (Token $token) { |
| 3470 | + return $token->kind === TokenKind::VariableName; |
| 3471 | + }, |
| 3472 | + $this->parsePropertyVariableNameAndDefault(), |
| 3473 | + $parentNode |
| 3474 | + ); |
| 3475 | + } |
| 3476 | + |
| 3477 | + private function parsePropertyVariableNameAndDefault() { |
| 3478 | + return function ($parentNode) { |
| 3479 | + // Imitate the format that parseExpression would have returned from when |
| 3480 | + // parseExpression was originally used. |
| 3481 | + // This is more precise and rules out parse errors such as `public $propName + 2;` |
| 3482 | + // This approach also avoids conflict with the deprecated $x{expr} array access syntax. |
| 3483 | + $variable = new Variable(); |
| 3484 | + $variable->name = $this->eat1(TokenKind::VariableName); |
| 3485 | + $equalsToken = $this->eatOptional1(TokenKind::EqualsToken); |
| 3486 | + if ($equalsToken === null) { |
| 3487 | + $variable->parent = $parentNode; |
| 3488 | + return $variable; |
| 3489 | + } |
| 3490 | + |
| 3491 | + $byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); // byRef default is nonsense, but this is a compile-time error instead of a parse error. |
| 3492 | + $rightOperand = $this->parseExpression($parentNode); // gets overridden in makeBinaryExpression |
| 3493 | + return $this->makeBinaryExpression($variable, $equalsToken, $byRefToken, $rightOperand, $parentNode); |
| 3494 | + }; |
| 3495 | + } |
| 3496 | + |
| 3497 | + /** |
| 3498 | + * @param PropertyDeclaration|Parameter $parent |
| 3499 | + * @return PropertyHooks |
| 3500 | + */ |
| 3501 | + private function parsePropertyHooks(Node $parent) { |
| 3502 | + $propertyHooks = new PropertyHooks(); |
| 3503 | + $propertyHooks->parent = $parent; |
| 3504 | + $propertyHooks->openBrace = $this->eat1(TokenKind::OpenBraceToken); |
| 3505 | + $hooks = []; |
| 3506 | + while (in_array($this->getCurrentToken()->kind, self::PROPERTY_HOOK_START_TOKENS, true)) { |
| 3507 | + $hooks[] = $this->parsePropertyHook($propertyHooks); |
| 3508 | + } |
| 3509 | + $propertyHooks->hookDeclarations = $hooks; |
| 3510 | + $propertyHooks->closeBrace = $this->eat1(TokenKind::CloseBraceToken); |
| 3511 | + return $propertyHooks; |
| 3512 | + } |
| 3513 | + |
| 3514 | + const PROPERTY_HOOK_START_TOKENS = [ |
| 3515 | + TokenKind::Name, |
| 3516 | + TokenKind::AmpersandToken, // by reference |
| 3517 | + TokenKind::AttributeToken, |
| 3518 | + ]; |
| 3519 | + |
| 3520 | + private function isPropertyHookStart() { |
| 3521 | + return function ($token) { |
| 3522 | + return \in_array($token->kind, self::PROPERTY_HOOK_START_TOKENS, true); |
| 3523 | + }; |
| 3524 | + } |
| 3525 | + |
| 3526 | + /** |
| 3527 | + * @param PropertyHooks $parentNode |
| 3528 | + * @return PropertyHook |
| 3529 | + */ |
| 3530 | + private function parsePropertyHook($parentNode) { |
| 3531 | + $node = new PropertyHook(); |
| 3532 | + $node->parent = $parentNode; |
| 3533 | + if ($this->getCurrentToken()->kind === TokenKind::AttributeToken) { |
| 3534 | + $node->attributes = $this->parseAttributeGroups($node); |
| 3535 | + } |
| 3536 | + $node->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); |
| 3537 | + $node->name = $this->eat1(TokenKind::Name); // "get" or "set" - other values are compile errors, not parse errors. |
| 3538 | + $node->openParen = $this->eatOptional1(TokenKind::OpenParenToken); |
| 3539 | + if ($node->openParen) { |
| 3540 | + $node->parameters = $this->parseDelimitedList( |
| 3541 | + DelimitedList\ParameterDeclarationList::class, |
| 3542 | + TokenKind::CommaToken, |
| 3543 | + $this->isParameterStartFn(), |
| 3544 | + $this->parseParameterFn(), |
| 3545 | + $node); |
| 3546 | + $node->closeParen = $this->eat1(TokenKind::CloseParenToken); |
| 3547 | + } |
| 3548 | + // e.g. `get => expr;` or `get { return $expr; }` |
| 3549 | + $node->arrowToken = $this->eatOptional1(TokenKind::DoubleArrowToken); |
| 3550 | + if ($node->arrowToken) { |
| 3551 | + $node->body = $this->parseExpression($node); |
| 3552 | + $node->semicolon = $this->eat1(TokenKind::SemicolonToken); |
| 3553 | + } else { |
| 3554 | + $node->body = $this->parseCompoundStatement($node); |
| 3555 | + } |
| 3556 | + return $node; |
| 3557 | + } |
| 3558 | + |
3446 | 3559 | /**
|
3447 | 3560 | * Parse a comma separated qualified name list (e.g. interfaces implemented by a class)
|
3448 | 3561 | *
|
|
0 commit comments