CoreLibs composer v8.0.5 update test

This commit is contained in:
Clemens Schwaighofer
2023-03-10 15:29:15 +09:00
parent 1cbe4e5c06
commit 2aab94a842
142 changed files with 2979 additions and 1170 deletions

View File

@@ -31,7 +31,7 @@ trait NamespacedNameTrait {
return ResolvedName::buildName($this->getNameParts(), $content);
}
if ($namespaceDefinition->name !== null) {
if ($namespaceDefinition->name instanceof QualifiedName) {
$resolvedName = ResolvedName::buildName($namespaceDefinition->name->nameParts, $content);
} else {
$resolvedName = ResolvedName::buildName([], $content);
@@ -47,4 +47,4 @@ trait NamespacedNameTrait {
}
return $resolvedName;
}
}
}

View File

@@ -91,7 +91,7 @@ abstract class Node implements \JsonSerializable {
* Gets first child that is an instance of one of the provided classes.
* Returns null if there is no match.
*
* @param array ...$classNames
* @param string ...$classNames
* @return Node|null
*/
public function getFirstChildNode(...$classNames) {
@@ -117,7 +117,7 @@ abstract class Node implements \JsonSerializable {
* Gets first descendant node that is an instance of one of the provided classes.
* Returns null if there is no match.
*
* @param array ...$classNames
* @param string ...$classNames
* @return Node|null
*/
public function getFirstDescendantNode(...$classNames) {
@@ -417,7 +417,7 @@ abstract class Node implements \JsonSerializable {
return $this->getRoot()->fileContents;
}
public function getUri() : string {
public function getUri() : ?string {
return $this->getRoot()->uri;
}

View File

@@ -8,10 +8,11 @@ namespace Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Token;
class CallExpression extends Expression {
/** @var Expression */
/** @var QualifiedName|Expression */
public $callableExpression;
/** @var Token */

View File

@@ -14,7 +14,7 @@ class YieldExpression extends Expression {
/** @var Token */
public $yieldOrYieldFromKeyword;
/** @var ArrayElement */
/** @var ArrayElement|null */
public $arrayElement;
const CHILD_NAMES = ['yieldOrYieldFromKeyword', 'arrayElement'];

View File

@@ -0,0 +1,29 @@
<?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\MissingToken;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
use Microsoft\PhpParser\Token;
class ParenthesizedIntersectionType extends Node{
/** @var Token */
public $openParen;
/** @var QualifiedNameList|MissingToken */
public $children;
/** @var Token */
public $closeParen;
const CHILD_NAMES = [
'openParen',
'children',
'closeParen'
];
}

View File

@@ -13,7 +13,7 @@ class SourceFileNode extends Node {
/** @var string */
public $fileContents;
/** @var string */
/** @var ?string */
public $uri;
/** @var Node[] */

View File

@@ -22,9 +22,12 @@ class ClassDeclaration extends StatementNode implements NamespacedNameInterface,
/** @var AttributeGroup[]|null */
public $attributes;
/** @var Token */
/** @var Token abstract/final/readonly modifier */
public $abstractOrFinalModifier;
/** @var Token[] additional abstract/final/readonly modifiers */
public $modifiers;
/** @var Token */
public $classKeyword;
@@ -43,6 +46,7 @@ class ClassDeclaration extends StatementNode implements NamespacedNameInterface,
const CHILD_NAMES = [
'attributes',
'abstractOrFinalModifier',
'modifiers',
'classKeyword',
'name',
'classBaseClause',

View File

@@ -0,0 +1,45 @@
<?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\Node\Expression;
use Microsoft\PhpParser\Token;
class HaltCompilerStatement extends Expression {
/** @var Token */
public $haltCompilerKeyword;
/** @var Token */
public $openParen;
/** @var Token */
public $closeParen;
/** @var Token (there is an implicit ')' before php close tags (`?>`)) */
public $semicolonOrCloseTag;
/** @var Token|null TokenKind::InlineHtml data unless there are no bytes (This is optional if there is nothing after the semicolon) */
public $data;
const CHILD_NAMES = [
'haltCompilerKeyword',
'openParen',
'closeParen',
'semicolonOrCloseTag',
'data',
];
/**
* @return int
*/
public function getHaltCompilerOffset() {
// This accounts for the fact that PHP close tags may include a single newline,
// and that $this->data may be null.
return $this->semicolonOrCloseTag->getEndPosition();
}
}

View File

@@ -6,6 +6,7 @@
namespace Microsoft\PhpParser;
use Closure;
use Microsoft\PhpParser\Node\AnonymousFunctionUseClause;
use Microsoft\PhpParser\Node\ArrayElement;
use Microsoft\PhpParser\Node\Attribute;
@@ -70,6 +71,7 @@ use Microsoft\PhpParser\Node\MissingMemberDeclaration;
use Microsoft\PhpParser\Node\NamespaceAliasingClause;
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
use Microsoft\PhpParser\Node\NumericLiteral;
use Microsoft\PhpParser\Node\ParenthesizedIntersectionType;
use Microsoft\PhpParser\Node\PropertyDeclaration;
use Microsoft\PhpParser\Node\ReservedWord;
use Microsoft\PhpParser\Node\StringLiteral;
@@ -95,6 +97,7 @@ use Microsoft\PhpParser\Node\Statement\{
ForStatement,
FunctionDeclaration,
GotoStatement,
HaltCompilerStatement,
IfStatementNode,
InlineHtml,
InterfaceDeclaration,
@@ -144,8 +147,9 @@ class Parser {
[TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord,
TokenKind::ObjectReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord,
TokenKind::IterableReservedWord, TokenKind::MixedReservedWord]; // TODO update spec
$this->returnTypeDeclarationTokens = \array_merge([TokenKind::VoidReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord, TokenKind::StaticKeyword], $this->parameterTypeDeclarationTokens);
TokenKind::TrueReservedWord, TokenKind::IterableReservedWord, TokenKind::MixedReservedWord,
TokenKind::VoidReservedWord, TokenKind::NeverReservedWord]; // TODO update spec
$this->returnTypeDeclarationTokens = \array_merge([TokenKind::StaticKeyword], $this->parameterTypeDeclarationTokens);
}
/**
@@ -557,10 +561,8 @@ class Parser {
// class-declaration
case TokenKind::FinalKeyword:
case TokenKind::AbstractKeyword:
if (!$this->lookahead(TokenKind::ClassKeyword)) {
$this->advanceToken();
return new SkippedToken($token);
}
case TokenKind::ReadonlyKeyword:
// fallthrough
case TokenKind::ClassKeyword:
return $this->parseClassDeclaration($parentNode);
@@ -614,6 +616,15 @@ class Parser {
case TokenKind::UnsetKeyword:
return $this->parseUnsetStatement($parentNode);
case TokenKind::HaltCompilerKeyword:
if ($parentNode instanceof SourceFileNode) {
return $this->parseHaltCompilerStatement($parentNode);
}
// __halt_compiler is a fatal compile error anywhere other than the top level.
// It won't be seen elsewhere in other programs - warn about the token being unexpected.
$this->advanceToken();
return new SkippedToken($token);
}
$expressionStatement = new ExpressionStatement();
@@ -657,10 +668,20 @@ class Parser {
};
}
/** @return Token[] */
private function parseClassModifiers(): array {
$modifiers = [];
while ($token = $this->eatOptional(TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::ReadonlyKeyword)) {
$modifiers[] = $token;
}
return $modifiers;
}
private function parseClassDeclaration($parentNode) : Node {
$classNode = new ClassDeclaration(); // TODO verify not nested
$classNode->parent = $parentNode;
$classNode->abstractOrFinalModifier = $this->eatOptional(TokenKind::AbstractKeyword, TokenKind::FinalKeyword);
$classNode->abstractOrFinalModifier = $this->eatOptional(TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::ReadonlyKeyword);
$classNode->modifiers = $this->parseClassModifiers();
$classNode->classKeyword = $this->eat1(TokenKind::ClassKeyword);
$classNode->name = $this->eat($this->nameOrReservedWordTokens); // TODO should be any
$classNode->name->kind = TokenKind::Name;
@@ -838,9 +859,6 @@ class Parser {
if (end($children) instanceof MissingToken && ($children[\count($children) - 2]->kind ?? null) === TokenKind::AmpersandToken) {
array_pop($parameter->typeDeclarationList->children);
$parameter->byRefToken = array_pop($parameter->typeDeclarationList->children);
if (!$parameter->typeDeclarationList->children) {
unset($parameter->typeDeclarationList);
}
}
} elseif ($parameter->questionToken) {
// TODO ParameterType?
@@ -882,27 +900,22 @@ class Parser {
/**
* Attempt to parse the return type after the `:` and optional `?` token.
*
* TODO: Consider changing the return type to a new class TypeList in a future major release?
* ParenthesizedIntersectionType is not a qualified name.
* @return DelimitedList\QualifiedNameList|null
*/
private function parseReturnTypeDeclarationList($parentNode) {
$result = $this->parseDelimitedList(
DelimitedList\QualifiedNameList::class,
self::TYPE_DELIMITER_TOKENS,
function ($token) {
return \in_array($token->kind, $this->returnTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
return $this->parseUnionTypeDeclarationList(
$parentNode,
function ($token): bool {
return \in_array($token->kind, $this->returnTypeDeclarationTokens, true) ||
$this->isQualifiedNameStart($token);
},
function ($parentNode) {
return $this->parseReturnTypeDeclaration($parentNode);
},
$parentNode,
false);
// Add a MissingToken so that this will warn about `function () : T| {}`
// TODO: Make this a reusable abstraction?
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
$result->children[] = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
}
return $result;
TokenKind::ReturnType
);
}
private function parseReturnTypeDeclaration($parentNode) {
@@ -917,28 +930,109 @@ class Parser {
}
/**
* Parse a union type such as A, A|B, A&B, A|(B&C), rejecting invalid syntax combinations.
*
* @param Node $parentNode
* @param Closure(Token):bool $isTypeStart
* @param Closure(Node):(Node|Token|null) $parseType
* @param int $expectedTypeKind expected kind for token type
* @return DelimitedList\QualifiedNameList|null
*/
private function parseUnionTypeDeclarationList($parentNode, Closure $isTypeStart, Closure $parseType, int $expectedTypeKind) {
$result = new DelimitedList\QualifiedNameList();
$token = $this->getCurrentToken();
$delimiter = self::TYPE_DELIMITER_TOKENS;
do {
if ($token->kind === TokenKind::OpenParenToken || $isTypeStart($token)) {
// Forbid mixing A&(B&C) if '&' was already seen
$openParen = in_array(TokenKind::BarToken, $delimiter, true)
? $this->eatOptional(TokenKind::OpenParenToken)
: null;
if ($openParen) {
$element = $this->parseParenthesizedIntersectionType($result, $openParen, $isTypeStart, $parseType);
// Forbid mixing (A&B)&C by forbidding `&` separator after a parenthesized intersection type.
$delimiter = [TokenKind::BarToken];
} else {
$element = $parseType($result);
}
$result->addElement($element);
} else {
break;
}
$delimiterToken = $this->eatOptional($delimiter);
if ($delimiterToken !== null) {
$result->addElement($delimiterToken);
$delimiter = [$delimiterToken->kind];
}
$token = $this->getCurrentToken();
} while ($delimiterToken !== null);
$result->parent = $parentNode;
if ($result->children === null) {
return null;
}
if (in_array(end($result->children)->kind ?? null, $delimiter, true)) {
// Add a MissingToken so that this will warn about `function () : T| {}`
$result->children[] = new MissingToken($expectedTypeKind, $this->token->fullStart);
} elseif (count($result->children) === 1 && $result->children[0] instanceof ParenthesizedIntersectionType) {
// dnf types with parenthesized intersection types are a union type of at least 2 types.
$result->children[] = new MissingToken(TokenKind::BarToken, $this->token->fullStart);
}
return $result;
}
/**
* @param Node $parentNode
* @param Token $openParen
* @param Closure(Token):bool $isTypeStart
* @param Closure(Node):(Node|Token|null) $parseType
*/
private function parseParenthesizedIntersectionType($parentNode, Token $openParen, Closure $isTypeStart, Closure $parseType): ParenthesizedIntersectionType {
$node = new ParenthesizedIntersectionType();
$node->parent = $parentNode;
$node->openParen = $openParen;
$node->children = $this->parseDelimitedList(
DelimitedList\QualifiedNameList::class,
TokenKind::AmpersandToken,
$isTypeStart,
$parseType,
$node,
true);
if ($node->children) {
// https://wiki.php.net/rfc/dnf_types
if ((end($node->children->children)->kind ?? null) === TokenKind::OpenParenToken) {
// Add a MissingToken so that this will Warn about `function (A|(B&) $x) {}`
$node->children->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
} elseif (count($node->children->children) === 1) {
// Must have at least 2 parts for A|(B&C)
$node->children->children[] = new MissingToken(TokenKind::AmpersandToken, $this->token->fullStart);
}
} else {
// Having less than 2 types (no types) in A|() is a parse error
$node->children = new MissingToken(TokenKind::Name, $this->token->fullStart);
}
$node->closeParen = $this->eat(TokenKind::CloseParenToken);
return $node;
}
/**
* @param Node|null $parentNode
* @return DelimitedList\QualifiedNameList|null
*/
private function tryParseParameterTypeDeclarationList($parentNode) {
$result = $this->parseDelimitedList(
DelimitedList\QualifiedNameList::class,
self::TYPE_DELIMITER_TOKENS,
return $this->parseUnionTypeDeclarationList(
$parentNode,
function ($token) {
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true) ||
$this->isQualifiedNameStart($token);
},
function ($parentNode) {
return $this->tryParseParameterTypeDeclaration($parentNode);
},
$parentNode,
true);
// Add a MissingToken so that this will Warn about `function (T| $x) {}`
// TODO: Make this a reusable abstraction?
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
}
return $result;
TokenKind::Name
);
}
private function parseCompoundStatement($parentNode) {
@@ -950,12 +1044,6 @@ class Parser {
return $compoundStatement;
}
private function array_push_list(& $array, $list) {
foreach ($list as $item) {
$array[] = $item;
}
}
private function isClassMemberDeclarationStart(Token $token) {
switch ($token->kind) {
// const-modifier
@@ -1036,6 +1124,7 @@ class Parser {
case TokenKind::ClassKeyword:
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
case TokenKind::ReadonlyKeyword:
// interface-declaration
case TokenKind::InterfaceKeyword:
@@ -1062,6 +1151,9 @@ class Parser {
// attributes
case TokenKind::AttributeToken:
// __halt_compiler
case TokenKind::HaltCompilerKeyword:
return true;
default:
@@ -1324,8 +1416,6 @@ class Parser {
case TokenKind::DollarOpenBraceToken:
case TokenKind::OpenBraceDollarToken:
$expression->children[] = $this->eat(TokenKind::DollarOpenBraceToken, TokenKind::OpenBraceDollarToken);
// TODO: Reject ${var->prop} and ${(var->prop)} without rejecting ${var+otherVar}
// Currently, this fails to reject ${var->prop} (because `var` has TokenKind::Name instead of StringVarname)
if ($this->getCurrentToken()->kind === TokenKind::StringVarname) {
$expression->children[] = $this->parseComplexDollarTemplateStringExpression($expression);
} else {
@@ -1536,6 +1626,9 @@ class Parser {
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:
case TokenKind::AttributeToken:
// dnf types (A&B)|C
case TokenKind::OpenParenToken:
return true;
}
@@ -1546,7 +1639,7 @@ class Parser {
/**
* @param string $className (name of subclass of DelimitedList)
* @param int $delimiter
* @param int|int[] $delimiter
* @param callable $isElementStartFn
* @param callable $parseElementFn
* @param Node $parentNode
@@ -1560,7 +1653,7 @@ class Parser {
do {
if ($isElementStartFn($token)) {
$node->addElement($parseElementFn($node));
} elseif (!$allowEmptyElements || ($allowEmptyElements && !$this->checkToken($delimiter))) {
} elseif (!$allowEmptyElements || ($allowEmptyElements && !$this->checkAnyToken($delimiter))) {
break;
}
@@ -1572,7 +1665,6 @@ class Parser {
// TODO ERROR CASE - no delimiter, but a param follows
} while ($delimiterToken !== null);
$node->parent = $parentNode;
if ($node->children === null) {
return null;
@@ -1772,10 +1864,17 @@ class Parser {
return $succeeded;
}
/** @param int $expectedKind */
private function checkToken($expectedKind) : bool {
return $this->getCurrentToken()->kind === $expectedKind;
}
/** @param int|int[] $expectedKind */
private function checkAnyToken($expectedKind) : bool {
$kind = $this->getCurrentToken()->kind;
return \is_array($expectedKind) ? \in_array($kind, $expectedKind, true) : $kind === $expectedKind;
}
private function parseIfStatement($parentNode) {
$ifStatement = new IfStatementNode();
$ifStatement->parent = $parentNode;
@@ -2114,7 +2213,7 @@ class Parser {
}
break;
case TokenKind::QuestionToken:
if ($parentNode instanceof TernaryExpression) {
if ($parentNode instanceof TernaryExpression && !isset($parentNode->questionToken)) {
// Workaround to parse "a ? b : c ? d : e" as "(a ? b : c) ? d : e"
break 2;
}
@@ -2122,6 +2221,7 @@ class Parser {
}
if ($shouldOperatorTakePrecedenceOverUnary) {
/** @var UnaryOpExpression $unaryExpression */
$unaryExpression = $leftOperand;
$leftOperand = $unaryExpression->operand;
}
@@ -2143,6 +2243,7 @@ class Parser {
// Rebuild the unary expression if we deconstructed it earlier.
if ($shouldOperatorTakePrecedenceOverUnary) {
/** @var UnaryOpExpression $unaryExpression */
$leftOperand->parent = $unaryExpression;
$unaryExpression->operand = $leftOperand;
$leftOperand = $unaryExpression;
@@ -2269,6 +2370,14 @@ class Parser {
// InstanceOf has other remaining issues, but this heuristic is an improvement for many common cases such as `$x && $y = $z`
];
/**
* @param Token|Node $leftOperand
* @param Token $operatorToken
* @param Token|null $byRefToken
* @param Token|Node $rightOperand
* @param Node $parentNode
* @return BinaryExpression|AssignmentExpression
*/
private function makeBinaryExpression($leftOperand, $operatorToken, $byRefToken, $rightOperand, $parentNode) {
$assignmentExpression = $operatorToken->kind === TokenKind::EqualsToken;
if ($assignmentExpression || \array_key_exists($operatorToken->kind, self::KNOWN_ASSIGNMENT_TOKEN_SET)) {
@@ -2283,8 +2392,12 @@ class Parser {
}
$binaryExpression = $assignmentExpression ? new AssignmentExpression() : new BinaryExpression();
$binaryExpression->parent = $parentNode;
$leftOperand->parent = $binaryExpression;
$rightOperand->parent = $binaryExpression;
if ($leftOperand instanceof Node) {
$leftOperand->parent = $binaryExpression;
}
if ($rightOperand instanceof Node) {
$rightOperand->parent = $binaryExpression;
}
$binaryExpression->leftOperand = $leftOperand;
$binaryExpression->operator = $operatorToken;
if ($binaryExpression instanceof AssignmentExpression && isset($byRefToken)) {
@@ -2744,6 +2857,30 @@ class Parser {
return $unsetStatement;
}
private function parseHaltCompilerStatement($parentNode) {
$haltCompilerStatement = new HaltCompilerStatement();
$haltCompilerStatement->parent = $parentNode;
$haltCompilerStatement->haltCompilerKeyword = $this->eat1(TokenKind::HaltCompilerKeyword);
$haltCompilerStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
$haltCompilerStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
// There is an implicit ';' before the closing php tag.
$haltCompilerStatement->semicolonOrCloseTag = $this->eat(TokenKind::SemicolonToken, TokenKind::ScriptSectionEndTag);
// token_get_all() will return up to 3 tokens after __halt_compiler regardless of whether they're the right ones.
// For invalid php snippets, combine the remaining tokens into InlineHtml
$remainingTokens = [];
while ($this->token->kind !== TokenKind::EndOfFileToken) {
$remainingTokens[] = $this->token;
$this->advanceToken();
}
if ($remainingTokens) {
$firstToken = $remainingTokens[0];
$lastToken = end($remainingTokens);
$haltCompilerStatement->data = new Token(TokenKind::InlineHtml, $firstToken->fullStart, $firstToken->fullStart, $lastToken->fullStart + $lastToken->length - $firstToken->fullStart);
}
return $haltCompilerStatement;
}
private function parseArrayCreationExpression($parentNode) {
$arrayExpression = new ArrayCreationExpression();
$arrayExpression->parent = $parentNode;
@@ -3207,7 +3344,7 @@ class Parser {
if ($classBaseClause->extendsKeyword === null) {
return null;
}
$classBaseClause->baseClass = $this->parseQualifiedName($classBaseClause);
$classBaseClause->baseClass = $this->parseQualifiedName($classBaseClause) ?? new MissingToken(TokenKind::QualifiedName, $this->token->fullStart);
return $classBaseClause;
}
@@ -3278,6 +3415,8 @@ class Parser {
}
/**
* Parse a comma separated qualified name list (e.g. interfaces implemented by a class)
*
* @param Node $parentNode
* @return DelimitedList\QualifiedNameList
*/
@@ -3291,6 +3430,7 @@ class Parser {
}
private function parseQualifiedNameCatchList($parentNode) {
// catch blocks don't support intersection types.
$result = $this->parseDelimitedList(
DelimitedList\QualifiedNameList::class,
TokenKind::BarToken,
@@ -3397,12 +3537,18 @@ class Parser {
$namespaceDefinition->namespaceKeyword = $this->eat1(TokenKind::NamespaceKeyword);
if (!$this->checkToken(TokenKind::NamespaceKeyword)) {
$namespaceDefinition->name = $this->parseQualifiedName($namespaceDefinition); // TODO only optional with compound statement block
$namespaceDefinition->name = $this->parseQualifiedName($namespaceDefinition);
}
$namespaceDefinition->compoundStatementOrSemicolon =
$this->checkToken(TokenKind::OpenBraceToken) ?
$this->parseCompoundStatement($namespaceDefinition) : $this->eatSemicolonOrAbortStatement();
if ($this->checkToken(TokenKind::OpenBraceToken)) {
$namespaceDefinition->compoundStatementOrSemicolon = $this->parseCompoundStatement($namespaceDefinition);
} else {
if (!$namespaceDefinition->name) {
// only optional with compound statement block
$namespaceDefinition->name = new MissingToken(TokenKind::QualifiedName, $this->token->fullStart);
}
$namespaceDefinition->compoundStatementOrSemicolon = $this->eatSemicolonOrAbortStatement();
}
return $namespaceDefinition;
}
@@ -3428,13 +3574,17 @@ class Parser {
$namespaceUseClause = new NamespaceUseClause();
$namespaceUseClause->parent = $parentNode;
$namespaceUseClause->namespaceName = $this->parseQualifiedName($namespaceUseClause);
if ($this->checkToken(TokenKind::AsKeyword)) {
$namespaceUseClause->namespaceAliasingClause = $this->parseNamespaceAliasingClause($namespaceUseClause);
}
elseif ($this->checkToken(TokenKind::OpenBraceToken)) {
if ($this->checkToken(TokenKind::OpenBraceToken)) {
$namespaceUseClause->openBrace = $this->eat1(TokenKind::OpenBraceToken);
$namespaceUseClause->groupClauses = $this->parseNamespaceUseGroupClauseList($namespaceUseClause);
$namespaceUseClause->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
} else {
if (!$namespaceUseClause->namespaceName) {
$namespaceUseClause->namespaceName = new MissingToken(TokenKind::QualifiedName, $this->token->fullStart);
}
if ($this->checkToken(TokenKind::AsKeyword)) {
$namespaceUseClause->namespaceAliasingClause = $this->parseNamespaceAliasingClause($namespaceUseClause);
}
}
return $namespaceUseClause;
@@ -3455,7 +3605,7 @@ class Parser {
$namespaceUseGroupClause->parent = $parentNode;
$namespaceUseGroupClause->functionOrConst = $this->eatOptional(TokenKind::FunctionKeyword, TokenKind::ConstKeyword);
$namespaceUseGroupClause->namespaceName = $this->parseQualifiedName($namespaceUseGroupClause);
$namespaceUseGroupClause->namespaceName = $this->parseQualifiedName($namespaceUseGroupClause) ?? new MissingToken(TokenKind::QualifiedName, $this->token->fullStart);
if ($this->checkToken(TokenKind::AsKeyword)) {
$namespaceUseGroupClause->namespaceAliasingClause = $this->parseNamespaceAliasingClause($namespaceUseGroupClause);
}
@@ -3513,6 +3663,7 @@ class Parser {
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
case TokenKind::ReadonlyKeyword:
case TokenKind::ConstKeyword:
// method-declaration
case TokenKind::FunctionKeyword:
@@ -3533,6 +3684,9 @@ class Parser {
$token = $this->getCurrentToken();
switch ($token->kind) {
case TokenKind::ConstKeyword:
return $this->parseClassConstDeclaration($parentNode, $modifiers);
case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);

View File

@@ -228,7 +228,6 @@ class PhpTokenizer implements TokenStreamProviderInterface {
T_DIR => TokenKind::Name,
T_FILE => TokenKind::Name,
T_FUNC_C => TokenKind::Name,
T_HALT_COMPILER => TokenKind::Name,
T_METHOD_C => TokenKind::Name,
T_NS_C => TokenKind::Name,
T_TRAIT_C => TokenKind::Name,
@@ -274,6 +273,7 @@ class PhpTokenizer implements TokenStreamProviderInterface {
T_FUNCTION => TokenKind::FunctionKeyword,
T_GLOBAL => TokenKind::GlobalKeyword,
T_GOTO => TokenKind::GotoKeyword,
T_HALT_COMPILER => TokenKind::HaltCompilerKeyword,
T_IF => TokenKind::IfKeyword,
T_IMPLEMENTS => TokenKind::ImplementsKeyword,
T_INCLUDE => TokenKind::IncludeKeyword,

View File

@@ -18,7 +18,7 @@ class Token implements \JsonSerializable {
public $fullStart;
/** @var int */
public $start;
/** @var int */
/** @var int the length is equal to $this->getEndPosition() - $this->fullStart. */
public $length;
/**

View File

@@ -91,6 +91,7 @@ class TokenKind {
const IterableKeyword = self::IterableReservedWord;
const EnumKeyword = 171;
const ReadonlyKeyword = 172;
const HaltCompilerKeyword = 173;
const OpenBracketToken = 201;
const CloseBracketToken = 202;
@@ -175,6 +176,7 @@ class TokenKind {
const NullReservedWord = 322;
const MixedReservedWord = 340;
const IterableReservedWord = 170;
const NeverReservedWord = 341;
const ScriptSectionStartTag = 323;
const ScriptSectionEndTag = 324;

View File

@@ -57,6 +57,7 @@ class TokenStringMaps {
"interface" => TokenKind::InterfaceKeyword,
"isset" => TokenKind::IsSetKeyword,
"list" => TokenKind::ListKeyword,
"match" => TokenKind::MatchKeyword,
"namespace" => TokenKind::NamespaceKeyword,
"new" => TokenKind::NewKeyword,
"or" => TokenKind::OrKeyword,
@@ -108,6 +109,7 @@ class TokenStringMaps {
"void" => TokenKind::VoidReservedWord,
"iterable" => TokenKind::IterableReservedWord,
"mixed" => TokenKind::MixedReservedWord,
"never" => TokenKind::NeverReservedWord,
];
const OPERATORS_AND_PUNCTUATORS = [