Skip to content

Commit 35646d5

Browse files
authored
Merge pull request microsoft#352 from TysonAndre/enum-support
Support parsing PHP 8.1 enums
2 parents dccc6f2 + 5bfe9fd commit 35646d5

27 files changed

+1080
-16
lines changed

src/Node/EnumCaseDeclaration.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node;
8+
9+
use Microsoft\PhpParser\Node;
10+
use Microsoft\PhpParser\Token;
11+
12+
class EnumCaseDeclaration extends Node {
13+
/** @var AttributeGroup[]|null */
14+
public $attributes;
15+
16+
/** @var Token */
17+
public $caseKeyword;
18+
19+
/** @var QualifiedName */
20+
public $name;
21+
22+
/** @var Token|null */
23+
public $equalsToken;
24+
25+
/** @var Token|Node|null */
26+
public $assignment;
27+
28+
/** @var Token */
29+
public $semicolon;
30+
31+
const CHILD_NAMES = [
32+
'attributes',
33+
'caseKeyword',
34+
'name',
35+
'equalsToken',
36+
'assignment',
37+
'semicolon',
38+
];
39+
}

src/Node/EnumMembers.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node;
8+
9+
use Microsoft\PhpParser\Node;
10+
use Microsoft\PhpParser\Token;
11+
12+
class EnumMembers extends Node {
13+
/** @var Token */
14+
public $openBrace;
15+
16+
/** @var Node[] */
17+
public $enumMemberDeclarations;
18+
19+
/** @var Token */
20+
public $closeBrace;
21+
22+
const CHILD_NAMES = [
23+
'openBrace',
24+
'enumMemberDeclarations',
25+
'closeBrace',
26+
];
27+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node\Statement;
8+
9+
use Microsoft\PhpParser\ClassLike;
10+
use Microsoft\PhpParser\NamespacedNameInterface;
11+
use Microsoft\PhpParser\NamespacedNameTrait;
12+
use Microsoft\PhpParser\Node\AttributeGroup;
13+
use Microsoft\PhpParser\Node\StatementNode;
14+
use Microsoft\PhpParser\Node\EnumMembers;
15+
use Microsoft\PhpParser\Token;
16+
17+
class EnumDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
18+
use NamespacedNameTrait;
19+
20+
/** @var AttributeGroup[]|null */
21+
public $attributes;
22+
23+
/** @var Token */
24+
public $enumKeyword;
25+
26+
/** @var Token */
27+
public $name;
28+
29+
/** @var Token|null */
30+
public $colonToken;
31+
32+
/** @var Token|null */
33+
public $enumType;
34+
35+
/** @var EnumMembers */
36+
public $enumMembers;
37+
38+
const CHILD_NAMES = [
39+
'attributes',
40+
'enumKeyword',
41+
'name',
42+
'colonToken',
43+
'enumType',
44+
'enumMembers',
45+
];
46+
47+
public function getNameParts() : array {
48+
return [$this->name];
49+
}
50+
}

src/ParseContext.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ class ParseContext {
2020
const InterfaceMembers = 10;
2121
const TraitMembers = 11;
2222
const Count = 12;
23+
const EnumMembers = 13;
2324
}

src/Parser.php

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Microsoft\PhpParser\Node\ClassInterfaceClause;
1717
use Microsoft\PhpParser\Node\ClassMembersNode;
1818
use Microsoft\PhpParser\Node\ConstElement;
19+
use Microsoft\PhpParser\Node\EnumCaseDeclaration;
20+
use Microsoft\PhpParser\Node\EnumMembers;
1921
use Microsoft\PhpParser\Node\Expression;
2022
use Microsoft\PhpParser\Node\Expression\{
2123
AnonymousFunctionCreationExpression,
@@ -88,6 +90,7 @@
8890
DeclareStatement,
8991
DoStatement,
9092
EmptyStatement,
93+
EnumDeclaration,
9194
ExpressionStatement,
9295
ForeachStatement,
9396
ForStatement,
@@ -292,6 +295,7 @@ private function isListTerminator(int $parseContext) {
292295
case ParseContext::ClassMembers:
293296
case ParseContext::BlockStatements:
294297
case ParseContext::TraitMembers:
298+
case ParseContext::EnumMembers:
295299
return $tokenKind === TokenKind::CloseBraceToken;
296300
case ParseContext::SwitchStatementElements:
297301
return $tokenKind === TokenKind::CloseBraceToken || $tokenKind === TokenKind::EndSwitchKeyword;
@@ -343,6 +347,9 @@ private function isValidListElement($context, Token $token) {
343347
case ParseContext::TraitMembers:
344348
return $this->isTraitMemberDeclarationStart($token);
345349

350+
case ParseContext::EnumMembers:
351+
return $this->isEnumMemberDeclarationStart($token);
352+
346353
case ParseContext::InterfaceMembers:
347354
return $this->isInterfaceMemberDeclarationStart($token);
348355

@@ -374,6 +381,9 @@ private function getParseListElementFn($context) {
374381
case ParseContext::InterfaceMembers:
375382
return $this->parseInterfaceElementFn();
376383

384+
case ParseContext::EnumMembers:
385+
return $this->parseEnumElementFn();
386+
377387
case ParseContext::SwitchStatementElements:
378388
return $this->parseCaseOrDefaultStatement();
379389
default:
@@ -583,6 +593,9 @@ private function parseStatementFn() {
583593
case TokenKind::TraitKeyword:
584594
return $this->parseTraitDeclaration($parentNode);
585595

596+
case TokenKind::EnumKeyword:
597+
return $this->parseEnumDeclaration($parentNode);
598+
586599
// global-declaration
587600
case TokenKind::GlobalKeyword:
588601
return $this->parseGlobalDeclaration($parentNode);
@@ -715,12 +728,15 @@ private function parseAttributeStatement($parentNode) {
715728
} elseif ($parentNode instanceof TraitMembers) {
716729
// Create a trait element or a MissingMemberDeclaration
717730
$statement = $this->parseTraitElementFn()($parentNode);
731+
} elseif ($parentNode instanceof EnumMembers) {
732+
// Create a enum element or a MissingMemberDeclaration
733+
$statement = $this->parseEnumElementFn()($parentNode);
718734
} elseif ($parentNode instanceof InterfaceMembers) {
719735
// Create an interface element or a MissingMemberDeclaration
720736
$statement = $this->parseInterfaceElementFn()($parentNode);
721737
} else {
722738
// Classlikes, anonymous functions, global functions, and arrow functions can have attributes. Global constants cannot.
723-
if (in_array($this->token->kind, [TokenKind::ClassKeyword, TokenKind::TraitKeyword, TokenKind::InterfaceKeyword, TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::FunctionKeyword, TokenKind::FnKeyword], true) ||
739+
if (in_array($this->token->kind, [TokenKind::ClassKeyword, TokenKind::TraitKeyword, TokenKind::InterfaceKeyword, TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::FunctionKeyword, TokenKind::FnKeyword, TokenKind::EnumKeyword], true) ||
724740
$this->token->kind === TokenKind::StaticKeyword && $this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword])) {
725741
$statement = $this->parseStatement($parentNode);
726742
} else {
@@ -734,6 +750,8 @@ private function parseAttributeStatement($parentNode) {
734750
if ($statement instanceof FunctionLike ||
735751
$statement instanceof ClassDeclaration ||
736752
$statement instanceof TraitDeclaration ||
753+
$statement instanceof EnumDeclaration ||
754+
$statement instanceof EnumCaseDeclaration ||
737755
$statement instanceof InterfaceDeclaration ||
738756
$statement instanceof ClassConstDeclaration ||
739757
$statement instanceof PropertyDeclaration ||
@@ -1012,6 +1030,9 @@ private function isStatementStart(Token $token) {
10121030
// trait-declaration
10131031
case TokenKind::TraitKeyword:
10141032

1033+
// enum-declaration
1034+
case TokenKind::EnumKeyword:
1035+
10151036
// namespace-definition
10161037
case TokenKind::NamespaceKeyword:
10171038

@@ -3198,6 +3219,21 @@ private function parseClassConstDeclaration($parentNode, $modifiers) {
31983219
return $classConstDeclaration;
31993220
}
32003221

3222+
private function parseEnumCaseDeclaration($parentNode) {
3223+
$classConstDeclaration = new EnumCaseDeclaration();
3224+
$classConstDeclaration->parent = $parentNode;
3225+
$classConstDeclaration->caseKeyword = $this->eat1(TokenKind::CaseKeyword);
3226+
$classConstDeclaration->name = $this->eat($this->nameOrKeywordOrReservedWordTokens);
3227+
$classConstDeclaration->equalsToken = $this->eatOptional1(TokenKind::EqualsToken);
3228+
if ($classConstDeclaration->equalsToken !== null) {
3229+
// TODO add post-parse rule that checks for invalid assignments
3230+
$classConstDeclaration->assignment = $this->parseExpression($classConstDeclaration);
3231+
}
3232+
$classConstDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
3233+
3234+
return $classConstDeclaration;
3235+
}
3236+
32013237
/**
32023238
* @param Node $parentNode
32033239
* @param Token[] $modifiers
@@ -3520,6 +3556,102 @@ private function parseTraitElementFn() {
35203556
};
35213557
}
35223558

3559+
private function parseEnumDeclaration($parentNode) {
3560+
$enumDeclaration = new EnumDeclaration();
3561+
$enumDeclaration->parent = $parentNode;
3562+
3563+
$enumDeclaration->enumKeyword = $this->eat1(TokenKind::EnumKeyword);
3564+
$enumDeclaration->name = $this->eat1(TokenKind::Name);
3565+
$enumDeclaration->colonToken = $this->eatOptional1(TokenKind::ColonToken);
3566+
if ($enumDeclaration->colonToken !== null) {
3567+
$enumDeclaration->enumType = $this->tryParseParameterTypeDeclaration($enumDeclaration)
3568+
?: new MissingToken(TokenKind::EnumType, $this->token->fullStart);
3569+
}
3570+
3571+
$enumDeclaration->enumMembers = $this->parseEnumMembers($enumDeclaration);
3572+
3573+
return $enumDeclaration;
3574+
}
3575+
3576+
private function parseEnumMembers($parentNode) {
3577+
$enumMembers = new EnumMembers();
3578+
$enumMembers->parent = $parentNode;
3579+
3580+
$enumMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
3581+
3582+
$enumMembers->enumMemberDeclarations = $this->parseList($enumMembers, ParseContext::EnumMembers);
3583+
3584+
$enumMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
3585+
3586+
return $enumMembers;
3587+
}
3588+
3589+
private function isEnumMemberDeclarationStart($token) {
3590+
switch ($token->kind) {
3591+
// modifiers
3592+
case TokenKind::PublicKeyword:
3593+
case TokenKind::ProtectedKeyword:
3594+
case TokenKind::PrivateKeyword:
3595+
// case TokenKind::VarKeyword:
3596+
case TokenKind::StaticKeyword:
3597+
case TokenKind::AbstractKeyword:
3598+
case TokenKind::FinalKeyword:
3599+
3600+
// method-declaration
3601+
case TokenKind::FunctionKeyword:
3602+
3603+
// trait-use-clauses (enums can use traits)
3604+
case TokenKind::UseKeyword:
3605+
3606+
// cases and constants
3607+
case TokenKind::CaseKeyword:
3608+
case TokenKind::ConstKeyword:
3609+
3610+
// attributes
3611+
case TokenKind::AttributeToken:
3612+
return true;
3613+
}
3614+
return false;
3615+
}
3616+
3617+
private function parseEnumElementFn() {
3618+
return function ($parentNode) {
3619+
$modifiers = $this->parseModifiers();
3620+
3621+
$token = $this->getCurrentToken();
3622+
switch ($token->kind) {
3623+
// TODO: CaseKeyword
3624+
case TokenKind::CaseKeyword:
3625+
return $this->parseEnumCaseDeclaration($parentNode);
3626+
3627+
case TokenKind::ConstKeyword:
3628+
return $this->parseClassConstDeclaration($parentNode, $modifiers);
3629+
3630+
case TokenKind::FunctionKeyword:
3631+
return $this->parseMethodDeclaration($parentNode, $modifiers);
3632+
3633+
case TokenKind::QuestionToken:
3634+
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
3635+
$parentNode,
3636+
$modifiers,
3637+
$this->eat1(TokenKind::QuestionToken)
3638+
);
3639+
case TokenKind::VariableName:
3640+
return $this->parsePropertyDeclaration($parentNode, $modifiers);
3641+
3642+
case TokenKind::UseKeyword:
3643+
return $this->parseTraitUseClause($parentNode);
3644+
3645+
case TokenKind::AttributeToken:
3646+
return $this->parseAttributeStatement($parentNode);
3647+
3648+
default:
3649+
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
3650+
}
3651+
};
3652+
}
3653+
3654+
35233655
/**
35243656
* @param Node $parentNode
35253657
* @param Token[] $modifiers

src/PhpTokenizer.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
define(__NAMESPACE__ . '\T_FN', defined('T_FN') ? constant('T_FN') : 'T_FN');
1313
// If this predates PHP 8.0, T_MATCH is unavailable. The replacement value is arbitrary - it just has to be different from other values of token constants.
1414
define(__NAMESPACE__ . '\T_MATCH', defined('T_MATCH') ? constant('T_MATCH') : 'T_MATCH');
15-
define(__NAMESPACE__ . '\T_NULLSAFE_OBJECT_OPERATOR', defined('T_NULLSAFE_OBJECT_OPERATOR') ? constant('T_NULLSAFE_OBJECT_OPERATOR') : 'T_MATCH');
15+
define(__NAMESPACE__ . '\T_NULLSAFE_OBJECT_OPERATOR', defined('T_NULLSAFE_OBJECT_OPERATOR') ? constant('T_NULLSAFE_OBJECT_OPERATOR') : 'T_NULLSAFE_OBJECT_OPERATOR');
1616
define(__NAMESPACE__ . '\T_ATTRIBUTE', defined('T_ATTRIBUTE') ? constant('T_ATTRIBUTE') : 'T_ATTRIBUTE');
17+
// If this predates PHP 8.1, T_ENUM is unavailable. The replacement value is arbitrary - it just has to be different from other values of token constants.
18+
define(__NAMESPACE__ . '\T_ENUM', defined('T_ENUM') ? constant('T_ENUM') : 'T_ENUM');
1719

1820
/**
1921
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
@@ -257,6 +259,7 @@ protected static function tokenGetAll(string $content, $parseContext): array
257259
T_ENDIF => TokenKind::EndIfKeyword,
258260
T_ENDSWITCH => TokenKind::EndSwitchKeyword,
259261
T_ENDWHILE => TokenKind::EndWhileKeyword,
262+
T_ENUM => TokenKind::EnumKeyword,
260263
T_EVAL => TokenKind::EvalKeyword,
261264
T_EXIT => TokenKind::ExitKeyword,
262265
T_EXTENDS => TokenKind::ExtendsKeyword,

src/TokenKind.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class TokenKind {
8989
const MatchKeyword = 169;
9090
/** @deprecated use IterableReservedWord */
9191
const IterableKeyword = self::IterableReservedWord;
92+
const EnumKeyword = 171;
9293

9394
const OpenBracketToken = 201;
9495
const CloseBracketToken = 202;
@@ -195,6 +196,7 @@ class TokenKind {
195196
const ReturnType = 336;
196197
const InlineHtml = 337;
197198
const PropertyType = 338;
199+
const EnumType = 339;
198200

199201
// const DollarOpenCurly = 339;
200202
const EncapsedAndWhitespace = 400;

src/TokenStringMaps.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class TokenStringMaps {
3636
"endif" => TokenKind::EndIfKeyword,
3737
"endswitch" => TokenKind::EndSwitchKeyword,
3838
"endwhile" => TokenKind::EndWhileKeyword,
39+
"enum" => TokenKind::EnumKeyword,
3940
"eval" => TokenKind::EvalKeyword,
4041
"exit" => TokenKind::ExitKeyword,
4142
"extends" => TokenKind::ExtendsKeyword,

0 commit comments

Comments
 (0)