Merge pull request #352 from TysonAndre/enum-support

Support parsing PHP 8.1 enums
This commit is contained in:
Rob Lourens 2021-03-29 18:53:02 -07:00 коммит произвёл GitHub
Родитель dccc6f25ac 5bfe9fdf0e
Коммит 35646d5501
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
27 изменённых файлов: 1080 добавлений и 16 удалений

Просмотреть файл

@ -0,0 +1,39 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;
class EnumCaseDeclaration extends Node {
/** @var AttributeGroup[]|null */
public $attributes;
/** @var Token */
public $caseKeyword;
/** @var QualifiedName */
public $name;
/** @var Token|null */
public $equalsToken;
/** @var Token|Node|null */
public $assignment;
/** @var Token */
public $semicolon;
const CHILD_NAMES = [
'attributes',
'caseKeyword',
'name',
'equalsToken',
'assignment',
'semicolon',
];
}

27
src/Node/EnumMembers.php Normal file
Просмотреть файл

@ -0,0 +1,27 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;
class EnumMembers extends Node {
/** @var Token */
public $openBrace;
/** @var Node[] */
public $enumMemberDeclarations;
/** @var Token */
public $closeBrace;
const CHILD_NAMES = [
'openBrace',
'enumMemberDeclarations',
'closeBrace',
];
}

Просмотреть файл

@ -0,0 +1,50 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft\PhpParser\Node\Statement;
use Microsoft\PhpParser\ClassLike;
use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node\AttributeGroup;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Node\EnumMembers;
use Microsoft\PhpParser\Token;
class EnumDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
use NamespacedNameTrait;
/** @var AttributeGroup[]|null */
public $attributes;
/** @var Token */
public $enumKeyword;
/** @var Token */
public $name;
/** @var Token|null */
public $colonToken;
/** @var Token|null */
public $enumType;
/** @var EnumMembers */
public $enumMembers;
const CHILD_NAMES = [
'attributes',
'enumKeyword',
'name',
'colonToken',
'enumType',
'enumMembers',
];
public function getNameParts() : array {
return [$this->name];
}
}

Просмотреть файл

@ -20,4 +20,5 @@ class ParseContext {
const InterfaceMembers = 10;
const TraitMembers = 11;
const Count = 12;
const EnumMembers = 13;
}

Просмотреть файл

@ -16,6 +16,8 @@ use Microsoft\PhpParser\Node\ClassBaseClause;
use Microsoft\PhpParser\Node\ClassInterfaceClause;
use Microsoft\PhpParser\Node\ClassMembersNode;
use Microsoft\PhpParser\Node\ConstElement;
use Microsoft\PhpParser\Node\EnumCaseDeclaration;
use Microsoft\PhpParser\Node\EnumMembers;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\Expression\{
AnonymousFunctionCreationExpression,
@ -88,6 +90,7 @@ use Microsoft\PhpParser\Node\Statement\{
DeclareStatement,
DoStatement,
EmptyStatement,
EnumDeclaration,
ExpressionStatement,
ForeachStatement,
ForStatement,
@ -292,6 +295,7 @@ class Parser {
case ParseContext::ClassMembers:
case ParseContext::BlockStatements:
case ParseContext::TraitMembers:
case ParseContext::EnumMembers:
return $tokenKind === TokenKind::CloseBraceToken;
case ParseContext::SwitchStatementElements:
return $tokenKind === TokenKind::CloseBraceToken || $tokenKind === TokenKind::EndSwitchKeyword;
@ -343,6 +347,9 @@ class Parser {
case ParseContext::TraitMembers:
return $this->isTraitMemberDeclarationStart($token);
case ParseContext::EnumMembers:
return $this->isEnumMemberDeclarationStart($token);
case ParseContext::InterfaceMembers:
return $this->isInterfaceMemberDeclarationStart($token);
@ -374,6 +381,9 @@ class Parser {
case ParseContext::InterfaceMembers:
return $this->parseInterfaceElementFn();
case ParseContext::EnumMembers:
return $this->parseEnumElementFn();
case ParseContext::SwitchStatementElements:
return $this->parseCaseOrDefaultStatement();
default:
@ -583,6 +593,9 @@ class Parser {
case TokenKind::TraitKeyword:
return $this->parseTraitDeclaration($parentNode);
case TokenKind::EnumKeyword:
return $this->parseEnumDeclaration($parentNode);
// global-declaration
case TokenKind::GlobalKeyword:
return $this->parseGlobalDeclaration($parentNode);
@ -715,12 +728,15 @@ class Parser {
} elseif ($parentNode instanceof TraitMembers) {
// Create a trait element or a MissingMemberDeclaration
$statement = $this->parseTraitElementFn()($parentNode);
} elseif ($parentNode instanceof EnumMembers) {
// Create a enum element or a MissingMemberDeclaration
$statement = $this->parseEnumElementFn()($parentNode);
} elseif ($parentNode instanceof InterfaceMembers) {
// Create an interface element or a MissingMemberDeclaration
$statement = $this->parseInterfaceElementFn()($parentNode);
} else {
// Classlikes, anonymous functions, global functions, and arrow functions can have attributes. Global constants cannot.
if (in_array($this->token->kind, [TokenKind::ClassKeyword, TokenKind::TraitKeyword, TokenKind::InterfaceKeyword, TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::FunctionKeyword, TokenKind::FnKeyword], true) ||
if (in_array($this->token->kind, [TokenKind::ClassKeyword, TokenKind::TraitKeyword, TokenKind::InterfaceKeyword, TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::FunctionKeyword, TokenKind::FnKeyword, TokenKind::EnumKeyword], true) ||
$this->token->kind === TokenKind::StaticKeyword && $this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword])) {
$statement = $this->parseStatement($parentNode);
} else {
@ -734,6 +750,8 @@ class Parser {
if ($statement instanceof FunctionLike ||
$statement instanceof ClassDeclaration ||
$statement instanceof TraitDeclaration ||
$statement instanceof EnumDeclaration ||
$statement instanceof EnumCaseDeclaration ||
$statement instanceof InterfaceDeclaration ||
$statement instanceof ClassConstDeclaration ||
$statement instanceof PropertyDeclaration ||
@ -1012,6 +1030,9 @@ class Parser {
// trait-declaration
case TokenKind::TraitKeyword:
// enum-declaration
case TokenKind::EnumKeyword:
// namespace-definition
case TokenKind::NamespaceKeyword:
@ -3198,6 +3219,21 @@ class Parser {
return $classConstDeclaration;
}
private function parseEnumCaseDeclaration($parentNode) {
$classConstDeclaration = new EnumCaseDeclaration();
$classConstDeclaration->parent = $parentNode;
$classConstDeclaration->caseKeyword = $this->eat1(TokenKind::CaseKeyword);
$classConstDeclaration->name = $this->eat($this->nameOrKeywordOrReservedWordTokens);
$classConstDeclaration->equalsToken = $this->eatOptional1(TokenKind::EqualsToken);
if ($classConstDeclaration->equalsToken !== null) {
// TODO add post-parse rule that checks for invalid assignments
$classConstDeclaration->assignment = $this->parseExpression($classConstDeclaration);
}
$classConstDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
return $classConstDeclaration;
}
/**
* @param Node $parentNode
* @param Token[] $modifiers
@ -3520,6 +3556,102 @@ class Parser {
};
}
private function parseEnumDeclaration($parentNode) {
$enumDeclaration = new EnumDeclaration();
$enumDeclaration->parent = $parentNode;
$enumDeclaration->enumKeyword = $this->eat1(TokenKind::EnumKeyword);
$enumDeclaration->name = $this->eat1(TokenKind::Name);
$enumDeclaration->colonToken = $this->eatOptional1(TokenKind::ColonToken);
if ($enumDeclaration->colonToken !== null) {
$enumDeclaration->enumType = $this->tryParseParameterTypeDeclaration($enumDeclaration)
?: new MissingToken(TokenKind::EnumType, $this->token->fullStart);
}
$enumDeclaration->enumMembers = $this->parseEnumMembers($enumDeclaration);
return $enumDeclaration;
}
private function parseEnumMembers($parentNode) {
$enumMembers = new EnumMembers();
$enumMembers->parent = $parentNode;
$enumMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
$enumMembers->enumMemberDeclarations = $this->parseList($enumMembers, ParseContext::EnumMembers);
$enumMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
return $enumMembers;
}
private function isEnumMemberDeclarationStart($token) {
switch ($token->kind) {
// modifiers
case TokenKind::PublicKeyword:
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:
// case TokenKind::VarKeyword:
case TokenKind::StaticKeyword:
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
// method-declaration
case TokenKind::FunctionKeyword:
// trait-use-clauses (enums can use traits)
case TokenKind::UseKeyword:
// cases and constants
case TokenKind::CaseKeyword:
case TokenKind::ConstKeyword:
// attributes
case TokenKind::AttributeToken:
return true;
}
return false;
}
private function parseEnumElementFn() {
return function ($parentNode) {
$modifiers = $this->parseModifiers();
$token = $this->getCurrentToken();
switch ($token->kind) {
// TODO: CaseKeyword
case TokenKind::CaseKeyword:
return $this->parseEnumCaseDeclaration($parentNode);
case TokenKind::ConstKeyword:
return $this->parseClassConstDeclaration($parentNode, $modifiers);
case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);
case TokenKind::QuestionToken:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
$parentNode,
$modifiers,
$this->eat1(TokenKind::QuestionToken)
);
case TokenKind::VariableName:
return $this->parsePropertyDeclaration($parentNode, $modifiers);
case TokenKind::UseKeyword:
return $this->parseTraitUseClause($parentNode);
case TokenKind::AttributeToken:
return $this->parseAttributeStatement($parentNode);
default:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
}
};
}
/**
* @param Node $parentNode
* @param Token[] $modifiers

Просмотреть файл

@ -12,8 +12,10 @@ define(__NAMESPACE__ . '\T_COALESCE_EQUAL', defined('T_COALESCE_EQUAL') ? consta
define(__NAMESPACE__ . '\T_FN', defined('T_FN') ? constant('T_FN') : 'T_FN');
// 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.
define(__NAMESPACE__ . '\T_MATCH', defined('T_MATCH') ? constant('T_MATCH') : 'T_MATCH');
define(__NAMESPACE__ . '\T_NULLSAFE_OBJECT_OPERATOR', defined('T_NULLSAFE_OBJECT_OPERATOR') ? constant('T_NULLSAFE_OBJECT_OPERATOR') : 'T_MATCH');
define(__NAMESPACE__ . '\T_NULLSAFE_OBJECT_OPERATOR', defined('T_NULLSAFE_OBJECT_OPERATOR') ? constant('T_NULLSAFE_OBJECT_OPERATOR') : 'T_NULLSAFE_OBJECT_OPERATOR');
define(__NAMESPACE__ . '\T_ATTRIBUTE', defined('T_ATTRIBUTE') ? constant('T_ATTRIBUTE') : 'T_ATTRIBUTE');
// 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.
define(__NAMESPACE__ . '\T_ENUM', defined('T_ENUM') ? constant('T_ENUM') : 'T_ENUM');
/**
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
@ -257,6 +259,7 @@ class PhpTokenizer implements TokenStreamProviderInterface {
T_ENDIF => TokenKind::EndIfKeyword,
T_ENDSWITCH => TokenKind::EndSwitchKeyword,
T_ENDWHILE => TokenKind::EndWhileKeyword,
T_ENUM => TokenKind::EnumKeyword,
T_EVAL => TokenKind::EvalKeyword,
T_EXIT => TokenKind::ExitKeyword,
T_EXTENDS => TokenKind::ExtendsKeyword,

Просмотреть файл

@ -89,6 +89,7 @@ class TokenKind {
const MatchKeyword = 169;
/** @deprecated use IterableReservedWord */
const IterableKeyword = self::IterableReservedWord;
const EnumKeyword = 171;
const OpenBracketToken = 201;
const CloseBracketToken = 202;
@ -195,6 +196,7 @@ class TokenKind {
const ReturnType = 336;
const InlineHtml = 337;
const PropertyType = 338;
const EnumType = 339;
// const DollarOpenCurly = 339;
const EncapsedAndWhitespace = 400;

Просмотреть файл

@ -36,6 +36,7 @@ class TokenStringMaps {
"endif" => TokenKind::EndIfKeyword,
"endswitch" => TokenKind::EndSwitchKeyword,
"endwhile" => TokenKind::EndWhileKeyword,
"enum" => TokenKind::EnumKeyword,
"eval" => TokenKind::EvalKeyword,
"exit" => TokenKind::ExitKeyword,
"extends" => TokenKind::ExtendsKeyword,

Просмотреть файл

@ -76,6 +76,13 @@ class ParserGrammarTest extends TestCase {
const FILE_PATTERN = __DIR__ . "/cases/parser/*";
const PHP74_FILE_PATTERN = __DIR__ . "/cases/parser74/*";
const PHP80_FILE_PATTERN = __DIR__ . "/cases/parser80/*";
const PHP81_FILE_PATTERN = __DIR__ . "/cases/parser81/*";
const PATTERNS_FOR_MINIMUM_PHP_VERSION = [
[70400, self::PHP74_FILE_PATTERN],
[80000, self::PHP80_FILE_PATTERN],
[80100, self::PHP81_FILE_PATTERN],
];
public function treeProvider() {
$testCases = glob(self::FILE_PATTERN . ".php");
@ -89,22 +96,14 @@ class ParserGrammarTest extends TestCase {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
if (PHP_VERSION_ID >= 70400) {
// There are some test cases that depend on the php 7.3/php 7.4 lexer (e.g. the `??=` token).
// If this project goes that route, these could be moved in the regular parser/ directory.
// - It might be possible to emulate being able to parse this token instead (e.g. merge tokens if strpos($contents, `??=`) is not false.
$testCases = glob(self::PHP74_FILE_PATTERN . ".php");
foreach ($testCases as $testCase) {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
foreach (self::PATTERNS_FOR_MINIMUM_PHP_VERSION as list($minVersionId, $filePattern)) {
if (PHP_VERSION_ID >= $minVersionId) {
$testCases = glob($filePattern . ".php");
foreach ($testCases as $testCase) {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
}
}
if (PHP_VERSION_ID >= 80000) {
$testCases = glob(self::PHP80_FILE_PATTERN . ".php");
foreach ($testCases as $testCase) {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
}
return $testProviderArray;
}

Просмотреть файл

@ -0,0 +1,2 @@
<?php
enum X {}

Просмотреть файл

@ -0,0 +1 @@
[]

Просмотреть файл

@ -0,0 +1,48 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"EnumDeclaration": {
"attributes": null,
"enumKeyword": {
"kind": "EnumKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"colonToken": null,
"enumType": null,
"enumMembers": {
"EnumMembers": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"enumMemberDeclarations": [],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
<?php
enum X: int {
const X = 123;
public function test() {};
function other();
}

Просмотреть файл

@ -0,0 +1,14 @@
[
{
"kind": 0,
"message": "'}' expected.",
"start": 68,
"length": 0
},
{
"kind": 0,
"message": "'{' expected.",
"start": 90,
"length": 0
}
]

Просмотреть файл

@ -0,0 +1,200 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"EnumDeclaration": {
"attributes": null,
"enumKeyword": {
"kind": "EnumKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"colonToken": {
"kind": "ColonToken",
"textLength": 1
},
"enumType": {
"kind": "IntReservedWord",
"textLength": 3
},
"enumMembers": {
"EnumMembers": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"enumMemberDeclarations": [
{
"ClassConstDeclaration": {
"attributes": null,
"modifiers": [],
"constKeyword": {
"kind": "ConstKeyword",
"textLength": 5
},
"constElements": {
"ConstElementList": {
"children": [
{
"ConstElement": {
"name": {
"kind": "Name",
"textLength": 1
},
"equalsToken": {
"kind": "EqualsToken",
"textLength": 1
},
"assignment": {
"NumericLiteral": {
"children": {
"kind": "IntegerLiteralToken",
"textLength": 3
}
}
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"MethodDeclaration": {
"attributes": null,
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
}
],
"functionKeyword": {
"kind": "FunctionKeyword",
"textLength": 8
},
"byRefToken": null,
"name": {
"kind": "Name",
"textLength": 4
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"parameters": null,
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colonToken": null,
"questionToken": null,
"returnType": null,
"otherReturnTypes": null,
"compoundStatementOrSemicolon": {
"CompoundStatementNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"statements": [],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"closeBrace": {
"error": "MissingToken",
"kind": "CloseBraceToken",
"textLength": 0
}
}
}
}
},
{
"EmptyStatement": {
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"FunctionDeclaration": {
"attributes": null,
"functionKeyword": {
"kind": "FunctionKeyword",
"textLength": 8
},
"byRefToken": null,
"name": {
"kind": "Name",
"textLength": 5
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"parameters": null,
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colonToken": null,
"questionToken": null,
"returnType": null,
"otherReturnTypes": null,
"compoundStatementOrSemicolon": {
"CompoundStatementNode": {
"openBrace": {
"error": "MissingToken",
"kind": "OpenBraceToken",
"textLength": 0
},
"statements": [
{
"EmptyStatement": {
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
<?php
#[MyAttr]
enum X {
case A;
#[MyAttribute]
case B;
}

Просмотреть файл

@ -0,0 +1 @@
[]

Просмотреть файл

@ -0,0 +1,163 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"EnumDeclaration": {
"attributes": [
{
"AttributeGroup": {
"startToken": {
"kind": "AttributeToken",
"textLength": 2
},
"attributes": {
"AttributeElementList": {
"children": [
{
"Attribute": {
"name": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 6
}
]
}
},
"openParen": null,
"argumentExpressionList": null,
"closeParen": null
}
}
]
}
},
"endToken": {
"kind": "CloseBracketToken",
"textLength": 1
}
}
}
],
"enumKeyword": {
"kind": "EnumKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"colonToken": null,
"enumType": null,
"enumMembers": {
"EnumMembers": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"enumMemberDeclarations": [
{
"EnumCaseDeclaration": {
"attributes": null,
"caseKeyword": {
"kind": "CaseKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"equalsToken": null,
"assignment": null,
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"EnumCaseDeclaration": {
"attributes": [
{
"AttributeGroup": {
"startToken": {
"kind": "AttributeToken",
"textLength": 2
},
"attributes": {
"AttributeElementList": {
"children": [
{
"Attribute": {
"name": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 11
}
]
}
},
"openParen": null,
"argumentExpressionList": null,
"closeParen": null
}
}
]
}
},
"endToken": {
"kind": "CloseBracketToken",
"textLength": 1
}
}
}
],
"caseKeyword": {
"kind": "CaseKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"equalsToken": null,
"assignment": null,
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
<?php
enum X: string {
case Hearts = 'H';
static function example() {
}
}

Просмотреть файл

@ -0,0 +1 @@
[]

Просмотреть файл

@ -0,0 +1,132 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"EnumDeclaration": {
"attributes": null,
"enumKeyword": {
"kind": "EnumKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"colonToken": {
"kind": "ColonToken",
"textLength": 1
},
"enumType": {
"kind": "StringReservedWord",
"textLength": 6
},
"enumMembers": {
"EnumMembers": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"enumMemberDeclarations": [
{
"EnumCaseDeclaration": {
"attributes": null,
"caseKeyword": {
"kind": "CaseKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 6
},
"equalsToken": {
"kind": "EqualsToken",
"textLength": 1
},
"assignment": {
"StringLiteral": {
"startQuote": null,
"children": {
"kind": "StringLiteralToken",
"textLength": 3
},
"endQuote": null
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"MethodDeclaration": {
"attributes": null,
"modifiers": [
{
"kind": "StaticKeyword",
"textLength": 6
}
],
"functionKeyword": {
"kind": "FunctionKeyword",
"textLength": 8
},
"byRefToken": null,
"name": {
"kind": "Name",
"textLength": 7
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"parameters": null,
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colonToken": null,
"questionToken": null,
"returnType": null,
"otherReturnTypes": null,
"compoundStatementOrSemicolon": {
"CompoundStatementNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"statements": [],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
<?php
// missing type
enum Foo : {
// missing expression
case X = ;
}

Просмотреть файл

@ -0,0 +1,14 @@
[
{
"kind": 0,
"message": "'EnumType' expected.",
"start": 32,
"length": 0
},
{
"kind": 0,
"message": "'Expression' expected.",
"start": 73,
"length": 0
}
]

Просмотреть файл

@ -0,0 +1,82 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"EnumDeclaration": {
"attributes": null,
"enumKeyword": {
"kind": "EnumKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 3
},
"colonToken": {
"kind": "ColonToken",
"textLength": 1
},
"enumType": {
"error": "MissingToken",
"kind": "EnumType",
"textLength": 0
},
"enumMembers": {
"EnumMembers": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"enumMemberDeclarations": [
{
"EnumCaseDeclaration": {
"attributes": null,
"caseKeyword": {
"kind": "CaseKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 1
},
"equalsToken": {
"kind": "EqualsToken",
"textLength": 1
},
"assignment": {
"error": "MissingToken",
"kind": "Expression",
"textLength": 0
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<?php
// union type syntax is not allowed by php when parsing enum type.
enum Foo: int|string {
}

Просмотреть файл

@ -0,0 +1,26 @@
[
{
"kind": 0,
"message": "'{' expected.",
"start": 86,
"length": 0
},
{
"kind": 0,
"message": "Unexpected '|'",
"start": 86,
"length": 1
},
{
"kind": 0,
"message": "'}' expected.",
"start": 87,
"length": 0
},
{
"kind": 0,
"message": "';' expected.",
"start": 97,
"length": 0
}
]

Просмотреть файл

@ -0,0 +1,96 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"EnumDeclaration": {
"attributes": null,
"enumKeyword": {
"kind": "EnumKeyword",
"textLength": 4
},
"name": {
"kind": "Name",
"textLength": 3
},
"colonToken": {
"kind": "ColonToken",
"textLength": 1
},
"enumType": {
"kind": "IntReservedWord",
"textLength": 3
},
"enumMembers": {
"EnumMembers": {
"openBrace": {
"error": "MissingToken",
"kind": "OpenBraceToken",
"textLength": 0
},
"enumMemberDeclarations": [
{
"error": "SkippedToken",
"kind": "BarToken",
"textLength": 1
}
],
"closeBrace": {
"error": "MissingToken",
"kind": "CloseBraceToken",
"textLength": 0
}
}
}
}
},
{
"ExpressionStatement": {
"expression": {
"SubscriptExpression": {
"postfixExpression": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 6
}
]
}
},
"openBracketOrBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"accessExpression": null,
"closeBracketOrBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
},
"semicolon": {
"error": "MissingToken",
"kind": "SemicolonToken",
"textLength": 0
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}