CoreLibs composer v8.0.5 update test
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -14,7 +14,7 @@ class YieldExpression extends Expression {
|
||||
/** @var Token */
|
||||
public $yieldOrYieldFromKeyword;
|
||||
|
||||
/** @var ArrayElement */
|
||||
/** @var ArrayElement|null */
|
||||
public $arrayElement;
|
||||
|
||||
const CHILD_NAMES = ['yieldOrYieldFromKeyword', 'arrayElement'];
|
||||
|
||||
29
vendor/microsoft/tolerant-php-parser/src/Node/ParenthesizedIntersectionType.php
vendored
Normal file
29
vendor/microsoft/tolerant-php-parser/src/Node/ParenthesizedIntersectionType.php
vendored
Normal 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'
|
||||
];
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class SourceFileNode extends Node {
|
||||
/** @var string */
|
||||
public $fileContents;
|
||||
|
||||
/** @var string */
|
||||
/** @var ?string */
|
||||
public $uri;
|
||||
|
||||
/** @var Node[] */
|
||||
|
||||
@@ -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',
|
||||
|
||||
45
vendor/microsoft/tolerant-php-parser/src/Node/Statement/HaltCompilerStatement.php
vendored
Normal file
45
vendor/microsoft/tolerant-php-parser/src/Node/Statement/HaltCompilerStatement.php
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
276
vendor/microsoft/tolerant-php-parser/src/Parser.php
vendored
276
vendor/microsoft/tolerant-php-parser/src/Parser.php
vendored
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user