Dep: update
주로 PHAN
This commit is contained in:
@@ -34,33 +34,11 @@ class FilePositionMap {
|
||||
$this->lineForCurrentOffset = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node the node to get the start line for.
|
||||
* TODO deprecate and merge this and getTokenStartLine into getStartLine
|
||||
* if https://github.com/Microsoft/tolerant-php-parser/issues/166 is fixed,
|
||||
* (i.e. if there is a consistent way to get the start offset)
|
||||
*/
|
||||
public function getNodeStartLine(Node $node) : int {
|
||||
return $this->getLineNumberForOffset($node->getStart());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token $token the token to get the start line for.
|
||||
*/
|
||||
public function getTokenStartLine(Token $token) : int {
|
||||
return $this->getLineNumberForOffset($token->start);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node|Token $node
|
||||
*/
|
||||
public function getStartLine($node) : int {
|
||||
if ($node instanceof Token) {
|
||||
$offset = $node->start;
|
||||
} else {
|
||||
$offset = $node->getStart();
|
||||
}
|
||||
return $this->getLineNumberForOffset($offset);
|
||||
return $this->getLineNumberForOffset($node->getStartPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,12 +46,7 @@ class FilePositionMap {
|
||||
* Similar to getStartLine but includes the column
|
||||
*/
|
||||
public function getStartLineCharacterPositionForOffset($node) : LineCharacterPosition {
|
||||
if ($node instanceof Token) {
|
||||
$offset = $node->start;
|
||||
} else {
|
||||
$offset = $node->getStart();
|
||||
}
|
||||
return $this->getLineCharacterPositionForOffset($offset);
|
||||
return $this->getLineCharacterPositionForOffset($node->getStartPosition());
|
||||
}
|
||||
|
||||
/** @param Node|Token $node */
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
|
||||
namespace Microsoft\PhpParser;
|
||||
|
||||
use Microsoft\PhpParser\Node\AttributeGroup;
|
||||
|
||||
/**
|
||||
* Interface for recognizing functions easily.
|
||||
* Each Node that implements this interface can be considered a function.
|
||||
*
|
||||
* @property AttributeGroup[] $attributes
|
||||
*/
|
||||
interface FunctionLike {}
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
namespace Microsoft\PhpParser;
|
||||
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class MissingToken extends Token {
|
||||
public function __construct(int $kind, int $fullStart) {
|
||||
parent::__construct($kind, $fullStart, $fullStart, 0);
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return array_merge(
|
||||
["error" => $this->getTokenKindNameFromValue(TokenKind::MissingToken)],
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Use the ModifiedTypeTrait for convenience in order to implement this interface.
|
||||
*/
|
||||
interface ModifiedTypeInterface {
|
||||
public function hasModifier(int $targetModifier): bool;
|
||||
public function isPublic(): bool;
|
||||
public function isStatic(): bool;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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;
|
||||
|
||||
trait ModifiedTypeTrait {
|
||||
/** @var Token[] */
|
||||
public $modifiers;
|
||||
|
||||
public function hasModifier(int $targetModifier): bool {
|
||||
if ($this->modifiers === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->modifiers as $modifier) {
|
||||
if ($modifier->kind === $targetModifier) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to check for the existence of the "public" modifier.
|
||||
* Does not necessarily need to be defined for that type.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublic(): bool {
|
||||
return $this->hasModifier(TokenKind::PublicKeyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to check for the existence of the "static" modifier.
|
||||
* Does not necessarily need to be defined for that type.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isStatic(): bool {
|
||||
return $this->hasModifier(TokenKind::StaticKeyword);
|
||||
}
|
||||
}
|
||||
+26
-38
@@ -11,6 +11,7 @@ use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
|
||||
use Microsoft\PhpParser\Node\SourceFileNode;
|
||||
use Microsoft\PhpParser\Node\Statement\NamespaceDefinition;
|
||||
use Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
abstract class Node implements \JsonSerializable {
|
||||
const CHILD_NAMES = [];
|
||||
@@ -31,14 +32,8 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getStart() : int {
|
||||
$child = $this->getChildNodesAndTokens()->current();
|
||||
if ($child instanceof Node) {
|
||||
return $child->getStart();
|
||||
} elseif ($child instanceof Token) {
|
||||
return $child->start;
|
||||
}
|
||||
throw new \Exception("Unknown type in AST");
|
||||
public function getStartPosition() : int {
|
||||
return $this->getChildNodesAndTokens()->current()->getStartPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +41,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getFullStart() : int {
|
||||
public function getFullStartPosition() : int {
|
||||
foreach($this::CHILD_NAMES as $name) {
|
||||
|
||||
if (($child = $this->$name) !== null) {
|
||||
@@ -58,15 +53,7 @@ abstract class Node implements \JsonSerializable {
|
||||
$child = $child[0];
|
||||
}
|
||||
|
||||
if ($child instanceof Node) {
|
||||
return $child->getFullStart();
|
||||
}
|
||||
|
||||
if ($child instanceof Token) {
|
||||
return $child->fullStart;
|
||||
}
|
||||
|
||||
throw new \Exception("Unknown type in AST: " . \gettype($child));
|
||||
return $child->getFullStartPosition();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -330,7 +317,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth() : int {
|
||||
$first = $this->getStart();
|
||||
$first = $this->getStartPosition();
|
||||
$last = $this->getEndPosition();
|
||||
|
||||
return $last - $first;
|
||||
@@ -342,7 +329,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return int
|
||||
*/
|
||||
public function getFullWidth() : int {
|
||||
$first = $this->getFullStart();
|
||||
$first = $this->getFullStartPosition();
|
||||
$last = $this->getEndPosition();
|
||||
|
||||
return $last - $first;
|
||||
@@ -353,7 +340,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return string
|
||||
*/
|
||||
public function getText() : string {
|
||||
$start = $this->getStart();
|
||||
$start = $this->getStartPosition();
|
||||
$end = $this->getEndPosition();
|
||||
|
||||
$fileContents = $this->getFileContents();
|
||||
@@ -365,7 +352,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return string
|
||||
*/
|
||||
public function getFullText() : string {
|
||||
$start = $this->getFullStart();
|
||||
$start = $this->getFullStartPosition();
|
||||
$end = $this->getEndPosition();
|
||||
|
||||
$fileContents = $this->getFileContents();
|
||||
@@ -387,13 +374,14 @@ abstract class Node implements \JsonSerializable {
|
||||
}
|
||||
|
||||
protected function getChildrenKvPairs() {
|
||||
$result = array();
|
||||
$result = [];
|
||||
foreach ($this::CHILD_NAMES as $name) {
|
||||
$result[$name] = $this->$name;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
$kindName = $this->getNodeKindName();
|
||||
return ["$kindName" => $this->getChildrenKvPairs()];
|
||||
@@ -463,7 +451,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return bool
|
||||
*/
|
||||
private function containsPosition(int $pos): bool {
|
||||
return $this->getStart() <= $pos && $pos <= $this->getEndPosition();
|
||||
return $this->getStartPosition() <= $pos && $pos <= $this->getEndPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,7 +464,7 @@ abstract class Node implements \JsonSerializable {
|
||||
public function getDocCommentText() {
|
||||
$leadingTriviaText = $this->getLeadingCommentAndWhitespaceText();
|
||||
$leadingTriviaTokens = PhpTokenizer::getTokensArrayFromContent(
|
||||
$leadingTriviaText, ParseContext::SourceElements, $this->getFullStart(), false
|
||||
$leadingTriviaText, ParseContext::SourceElements, $this->getFullStartPosition(), false
|
||||
);
|
||||
for ($i = \count($leadingTriviaTokens) - 1; $i >= 0; $i--) {
|
||||
$token = $leadingTriviaTokens[$i];
|
||||
@@ -509,13 +497,13 @@ abstract class Node implements \JsonSerializable {
|
||||
$topLevelNamespaceStatements = $namespaceDefinition->compoundStatementOrSemicolon instanceof Token
|
||||
? $namespaceDefinition->parent->statementList // we need to start from the namespace definition.
|
||||
: $namespaceDefinition->compoundStatementOrSemicolon->statements;
|
||||
$namespaceFullStart = $namespaceDefinition->getFullStart();
|
||||
$namespaceFullStart = $namespaceDefinition->getFullStartPosition();
|
||||
} else {
|
||||
$topLevelNamespaceStatements = $this->getRoot()->statementList;
|
||||
$namespaceFullStart = 0;
|
||||
}
|
||||
|
||||
$nodeFullStart = $this->getFullStart();
|
||||
$nodeFullStart = $this->getFullStartPosition();
|
||||
|
||||
// TODO optimize performance
|
||||
// Currently we rebuild the import tables on every call (and therefore every name resolution operation)
|
||||
@@ -535,10 +523,10 @@ abstract class Node implements \JsonSerializable {
|
||||
$contents = $this->getFileContents();
|
||||
|
||||
foreach ($topLevelNamespaceStatements as $useDeclaration) {
|
||||
if ($useDeclaration->getFullStart() <= $namespaceFullStart) {
|
||||
if ($useDeclaration->getFullStartPosition() <= $namespaceFullStart) {
|
||||
continue;
|
||||
}
|
||||
if ($useDeclaration->getFullStart() > $nodeFullStart) {
|
||||
if ($useDeclaration->getFullStartPosition() > $nodeFullStart) {
|
||||
break;
|
||||
} elseif (!($useDeclaration instanceof NamespaceUseDeclaration)) {
|
||||
continue;
|
||||
@@ -596,7 +584,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* @return NamespaceDefinition|null
|
||||
*/
|
||||
public function getNamespaceDefinition() {
|
||||
$namespaceDefinition = $this instanceof NamespaceDefinition
|
||||
$namespaceDefinition = ($this instanceof NamespaceDefinition || $this instanceof SourceFileNode)
|
||||
? $this
|
||||
: $this->getFirstAncestor(NamespaceDefinition::class, SourceFileNode::class);
|
||||
|
||||
@@ -609,11 +597,11 @@ abstract class Node implements \JsonSerializable {
|
||||
throw new \Exception("Invalid tree - SourceFileNode must always exist at root of tree.");
|
||||
}
|
||||
|
||||
$fullStart = $this->getFullStart();
|
||||
$fullStart = $this->getFullStartPosition();
|
||||
$lastNamespaceDefinition = null;
|
||||
if ($namespaceDefinition instanceof SourceFileNode) {
|
||||
foreach ($namespaceDefinition->getChildNodes() as $childNode) {
|
||||
if ($childNode instanceof NamespaceDefinition && $childNode->getFullStart() < $fullStart) {
|
||||
if ($childNode instanceof NamespaceDefinition && $childNode->getFullStartPosition() < $fullStart) {
|
||||
$lastNamespaceDefinition = $childNode;
|
||||
}
|
||||
}
|
||||
@@ -662,7 +650,7 @@ abstract class Node implements \JsonSerializable {
|
||||
* Add the alias and resolved name to the corresponding namespace, function, or const import table.
|
||||
* If the alias already exists, it will get replaced by the most recent using.
|
||||
*
|
||||
* TODO - worth throwing an error here in stead?
|
||||
* TODO - worth throwing an error here instead?
|
||||
*/
|
||||
private function addToImportTable($alias, $functionOrConst, $namespaceNameParts, $contents, & $namespaceImportTable, & $functionImportTable, & $constImportTable):array
|
||||
{
|
||||
@@ -671,20 +659,20 @@ abstract class Node implements \JsonSerializable {
|
||||
// namespaces are case-insensitive
|
||||
// $alias = \strtolower($alias);
|
||||
$namespaceImportTable[$alias] = ResolvedName::buildName($namespaceNameParts, $contents);
|
||||
return array($namespaceImportTable, $functionImportTable, $constImportTable);
|
||||
return [$namespaceImportTable, $functionImportTable, $constImportTable];
|
||||
} elseif ($functionOrConst->kind === TokenKind::FunctionKeyword) {
|
||||
// functions are case-insensitive
|
||||
// $alias = \strtolower($alias);
|
||||
$functionImportTable[$alias] = ResolvedName::buildName($namespaceNameParts, $contents);
|
||||
return array($namespaceImportTable, $functionImportTable, $constImportTable);
|
||||
return [$namespaceImportTable, $functionImportTable, $constImportTable];
|
||||
} elseif ($functionOrConst->kind === TokenKind::ConstKeyword) {
|
||||
// constants are case-sensitive
|
||||
$constImportTable[$alias] = ResolvedName::buildName($namespaceNameParts, $contents);
|
||||
return array($namespaceImportTable, $functionImportTable, $constImportTable);
|
||||
return [$namespaceImportTable, $functionImportTable, $constImportTable];
|
||||
}
|
||||
return array($namespaceImportTable, $functionImportTable, $constImportTable);
|
||||
return [$namespaceImportTable, $functionImportTable, $constImportTable];
|
||||
}
|
||||
return array($namespaceImportTable, $functionImportTable, $constImportTable);
|
||||
return [$namespaceImportTable, $functionImportTable, $constImportTable];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\MissingToken;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\UseVariableNameList;
|
||||
use Microsoft\PhpParser\Token;
|
||||
@@ -17,7 +18,7 @@ class AnonymousFunctionUseClause extends Node {
|
||||
/** @var Token */
|
||||
public $openParen;
|
||||
|
||||
/** @var UseVariableNameList */
|
||||
/** @var UseVariableNameList|MissingToken */
|
||||
public $useVariableNameList;
|
||||
|
||||
/** @var Token */
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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 Attribute extends Node {
|
||||
/** @var Token|Node */
|
||||
public $name;
|
||||
|
||||
/** @var Token|null */
|
||||
public $openParen;
|
||||
|
||||
/** @var DelimitedList\ArgumentExpressionList|null */
|
||||
public $argumentExpressionList;
|
||||
|
||||
/** @var Token|null */
|
||||
public $closeParen;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'name',
|
||||
'openParen',
|
||||
'argumentExpressionList',
|
||||
'closeParen'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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\DelimitedList\AttributeElementList;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class AttributeGroup extends Node {
|
||||
/** @var Token */
|
||||
public $startToken;
|
||||
|
||||
/** @var AttributeElementList */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token */
|
||||
public $endToken;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'startToken',
|
||||
'attributes',
|
||||
'endToken'
|
||||
];
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
|
||||
use Microsoft\PhpParser\MissingToken;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class CatchClause extends Node {
|
||||
@@ -14,17 +16,9 @@ class CatchClause extends Node {
|
||||
public $catch;
|
||||
/** @var Token */
|
||||
public $openParen;
|
||||
/** @var QualifiedName */
|
||||
public $qualifiedName;
|
||||
/**
|
||||
* @var QualifiedName[]|Token[] Remaining tokens and qualified names in the catch clause
|
||||
* (e.g. `catch (FirstException|SecondException $x)` would contain
|
||||
* the representation of `|SecondException`)
|
||||
*
|
||||
* TODO: In the next backwards incompatible release, replace qualifiedName with qualifiedNameList?
|
||||
*/
|
||||
public $otherQualifiedNameList;
|
||||
/** @var Token */
|
||||
/** @var QualifiedNameList[]|MissingToken */
|
||||
public $qualifiedNameList;
|
||||
/** @var Token|null */
|
||||
public $variableName;
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
@@ -34,8 +28,7 @@ class CatchClause extends Node {
|
||||
const CHILD_NAMES = [
|
||||
'catch',
|
||||
'openParen',
|
||||
'qualifiedName',
|
||||
'otherQualifiedNameList',
|
||||
'qualifiedNameList',
|
||||
'variableName',
|
||||
'closeParen',
|
||||
'compoundStatement'
|
||||
|
||||
@@ -6,10 +6,16 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\ModifiedTypeInterface;
|
||||
use Microsoft\PhpParser\ModifiedTypeTrait;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class ClassConstDeclaration extends Node {
|
||||
class ClassConstDeclaration extends Node implements ModifiedTypeInterface {
|
||||
use ModifiedTypeTrait;
|
||||
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token[] */
|
||||
public $modifiers;
|
||||
@@ -24,6 +30,7 @@ class ClassConstDeclaration extends Node {
|
||||
public $semicolon;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'modifiers',
|
||||
'constKeyword',
|
||||
'constElements',
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?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\DelimitedList;
|
||||
|
||||
use Microsoft\PhpParser\Node\DelimitedList;
|
||||
|
||||
class AttributeElementList extends DelimitedList {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?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\DelimitedList;
|
||||
|
||||
use Microsoft\PhpParser\Node\DelimitedList;
|
||||
|
||||
class DeclareDirectiveList extends DelimitedList {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?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\DelimitedList;
|
||||
|
||||
use Microsoft\PhpParser\Node\DelimitedList;
|
||||
|
||||
class MatchArmConditionList extends DelimitedList {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?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\DelimitedList;
|
||||
|
||||
use Microsoft\PhpParser\Node\DelimitedList;
|
||||
|
||||
class MatchExpressionArmList extends DelimitedList {
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
Vendored
+2
-2
@@ -21,6 +21,7 @@ class AnonymousFunctionCreationExpression extends Expression implements Function
|
||||
use FunctionHeader, FunctionUseClause, FunctionReturnType, FunctionBody;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'staticModifier',
|
||||
|
||||
// FunctionHeader
|
||||
@@ -37,8 +38,7 @@ class AnonymousFunctionCreationExpression extends Expression implements Function
|
||||
// FunctionReturnType
|
||||
'colonToken',
|
||||
'questionToken',
|
||||
'returnType',
|
||||
'otherReturnTypes',
|
||||
'returnTypeList',
|
||||
|
||||
// FunctionBody
|
||||
'compoundStatementOrSemicolon'
|
||||
|
||||
+7
-3
@@ -10,17 +10,21 @@ use Microsoft\PhpParser\Node\Expression;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class ArgumentExpression extends Expression {
|
||||
/** @var Token|null for php named arguments. If this is set, dotDotDotToken will not be set. */
|
||||
public $name;
|
||||
|
||||
/** @var Token|null */
|
||||
public $byRefToken; // TODO removed in newer versions of PHP. Also only accept variable, not expression if byRef
|
||||
public $colonToken;
|
||||
|
||||
/** @var Token|null */
|
||||
public $dotDotDotToken;
|
||||
|
||||
/** @var Expression */
|
||||
/** @var Expression|null null for first-class callable syntax */
|
||||
public $expression;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'byRefToken',
|
||||
'name',
|
||||
'colonToken',
|
||||
'dotDotDotToken',
|
||||
'expression'
|
||||
];
|
||||
|
||||
Vendored
+2
-2
@@ -26,6 +26,7 @@ class ArrowFunctionCreationExpression extends Expression implements FunctionLike
|
||||
public $resultExpression;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'staticModifier',
|
||||
|
||||
// FunctionHeader
|
||||
@@ -39,8 +40,7 @@ class ArrowFunctionCreationExpression extends Expression implements FunctionLike
|
||||
// FunctionReturnType
|
||||
'colonToken',
|
||||
'questionToken',
|
||||
'returnType',
|
||||
'otherReturnTypes',
|
||||
'returnTypeList',
|
||||
|
||||
// body
|
||||
'arrowToken',
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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\Expression;
|
||||
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\MatchExpressionArmList;
|
||||
use Microsoft\PhpParser\Node\Expression;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class MatchExpression extends Expression {
|
||||
/** @var Token `match` */
|
||||
public $matchToken;
|
||||
|
||||
/** @var Token */
|
||||
public $openParen;
|
||||
|
||||
/** @var Node|null */
|
||||
public $expression;
|
||||
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
|
||||
/** @var Token */
|
||||
public $openBrace;
|
||||
|
||||
/** @var MatchExpressionArmList|null */
|
||||
public $arms;
|
||||
|
||||
/** @var Token */
|
||||
public $closeBrace;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'matchToken',
|
||||
|
||||
'openParen',
|
||||
'expression',
|
||||
'closeParen',
|
||||
|
||||
'openBrace',
|
||||
'arms',
|
||||
'closeBrace',
|
||||
];
|
||||
}
|
||||
+6
-1
@@ -6,6 +6,7 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node\Expression;
|
||||
|
||||
use Microsoft\PhpParser\Node\AttributeGroup;
|
||||
use Microsoft\PhpParser\Node\ClassBaseClause;
|
||||
use Microsoft\PhpParser\Node\ClassInterfaceClause;
|
||||
use Microsoft\PhpParser\Node\ClassMembersNode;
|
||||
@@ -19,6 +20,9 @@ class ObjectCreationExpression extends Expression {
|
||||
/** @var Token */
|
||||
public $newKeword;
|
||||
|
||||
/** @var AttributeGroup[]|null optional attributes of an anonymous class. */
|
||||
public $attributes;
|
||||
|
||||
/** @var QualifiedName|Variable|Token */
|
||||
public $classTypeDesignator;
|
||||
|
||||
@@ -41,7 +45,8 @@ class ObjectCreationExpression extends Expression {
|
||||
public $classMembers;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'newKeword', // TODO
|
||||
'newKeword',
|
||||
'attributes',
|
||||
'classTypeDesignator',
|
||||
'openParen',
|
||||
'argumentExpressionList',
|
||||
|
||||
+2
-6
@@ -4,23 +4,19 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
namespace Microsoft\PhpParser\Node\Statement;
|
||||
namespace Microsoft\PhpParser\Node\Expression;
|
||||
|
||||
use Microsoft\PhpParser\Node\Expression;
|
||||
use Microsoft\PhpParser\Node\StatementNode;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class ThrowStatement extends StatementNode {
|
||||
class ThrowExpression extends Expression {
|
||||
/** @var Token */
|
||||
public $throwKeyword;
|
||||
/** @var Expression */
|
||||
public $expression;
|
||||
/** @var Token */
|
||||
public $semicolon;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'throwKeyword',
|
||||
'expression',
|
||||
'semicolon'
|
||||
];
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
trait FunctionHeader {
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
/** @var Token */
|
||||
public $functionKeyword;
|
||||
/** @var Token */
|
||||
|
||||
@@ -11,10 +11,9 @@ use Microsoft\PhpParser\Token;
|
||||
trait FunctionReturnType {
|
||||
/** @var Token */
|
||||
public $colonToken;
|
||||
// TODO: This may be the wrong choice if ?type can ever be mixed with other types in union types
|
||||
/** @var Token|null */
|
||||
public $questionToken;
|
||||
/** @var Token|QualifiedName */
|
||||
public $returnType;
|
||||
/** @var DelimitedList\QualifiedNameList|null TODO: Merge with returnType in a future backwards incompatible release */
|
||||
public $otherReturnTypes;
|
||||
/** @var DelimitedList\QualifiedNameList|null */
|
||||
public $returnTypeList;
|
||||
}
|
||||
|
||||
@@ -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\Node;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\MatchArmConditionList;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class MatchArm extends Node {
|
||||
|
||||
/** @var MatchArmConditionList */
|
||||
public $conditionList;
|
||||
|
||||
/** @var Token */
|
||||
public $arrowToken;
|
||||
|
||||
/** @var Expression */
|
||||
public $body;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'conditionList',
|
||||
'arrowToken',
|
||||
'body',
|
||||
];
|
||||
}
|
||||
@@ -10,17 +10,17 @@ use Microsoft\PhpParser\Diagnostic;
|
||||
use Microsoft\PhpParser\DiagnosticKind;
|
||||
use Microsoft\PhpParser\DiagnosticsProvider;
|
||||
use Microsoft\PhpParser\FunctionLike;
|
||||
use Microsoft\PhpParser\ModifiedTypeInterface;
|
||||
use Microsoft\PhpParser\ModifiedTypeTrait;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Token;
|
||||
use Microsoft\PhpParser\TokenKind;
|
||||
|
||||
class MethodDeclaration extends Node implements FunctionLike {
|
||||
/** @var Token[] */
|
||||
public $modifiers;
|
||||
|
||||
use FunctionHeader, FunctionReturnType, FunctionBody;
|
||||
class MethodDeclaration extends Node implements FunctionLike, ModifiedTypeInterface {
|
||||
use FunctionHeader, FunctionReturnType, FunctionBody, ModifiedTypeTrait;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'modifiers',
|
||||
|
||||
// FunctionHeader
|
||||
@@ -34,30 +34,18 @@ class MethodDeclaration extends Node implements FunctionLike {
|
||||
// FunctionReturnType
|
||||
'colonToken',
|
||||
'questionToken',
|
||||
'returnType',
|
||||
'otherReturnTypes',
|
||||
'returnTypeList',
|
||||
|
||||
// FunctionBody
|
||||
'compoundStatementOrSemicolon'
|
||||
];
|
||||
|
||||
public function hasModifier(int $targetModifier) : bool {
|
||||
if ($this->modifiers === null) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->modifiers as $modifier) {
|
||||
if ($modifier->kind === $targetModifier) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isStatic() : bool {
|
||||
return $this->hasModifier(TokenKind::StaticKeyword);
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
/**
|
||||
* Returns the name of the method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->name->getText($this->getFileContents());
|
||||
}
|
||||
|
||||
@@ -79,4 +67,64 @@ class MethodDeclaration extends Node implements FunctionLike {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature parts as an array. Use $this::getSignatureFormatted for a user-friendly string version.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getSignatureParts(): array {
|
||||
$parts = [];
|
||||
|
||||
foreach ($this->getChildNodesAndTokens() as $i => $child) {
|
||||
if ($i === "compoundStatementOrSemicolon") {
|
||||
return $parts;
|
||||
}
|
||||
|
||||
$parts[] = $child instanceof Token
|
||||
? $child->getText($this->getFileContents())
|
||||
: $child->getText();
|
||||
};
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature of the method as a formatted string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSignatureFormatted(): string {
|
||||
$signature = implode(" ", $this->getSignatureParts());
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description part of the doc string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescriptionFormatted(): string {
|
||||
$comment = trim($this->getLeadingCommentAndWhitespaceText(), "\r\n");
|
||||
$commentParts = explode("\n", $comment);
|
||||
|
||||
$description = [];
|
||||
|
||||
foreach ($commentParts as $i => $part) {
|
||||
$part = trim($part, "*\r\t /");
|
||||
|
||||
if (strlen($part) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($part[0] === "@") {
|
||||
break;
|
||||
}
|
||||
|
||||
$description[] = $part;
|
||||
}
|
||||
|
||||
$descriptionFormatted = implode(" ", $description);
|
||||
return $descriptionFormatted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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\MissingToken;
|
||||
|
||||
class MissingDeclaration extends Node {
|
||||
/** @var AttributeGroup[] */
|
||||
public $attributes;
|
||||
|
||||
/** @var MissingToken needed for emitting diagnostics */
|
||||
public $declaration;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'declaration',
|
||||
];
|
||||
}
|
||||
@@ -6,27 +6,27 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\ModifiedTypeInterface;
|
||||
use Microsoft\PhpParser\ModifiedTypeTrait;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class MissingMemberDeclaration extends Node {
|
||||
class MissingMemberDeclaration extends Node implements ModifiedTypeInterface {
|
||||
use ModifiedTypeTrait;
|
||||
|
||||
/** @var Token[] */
|
||||
public $modifiers;
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token|null needed along with typeDeclaration for what looked like typed property declarations but was missing VariableName */
|
||||
public $questionToken;
|
||||
|
||||
/** @var QualifiedName|Token|null */
|
||||
public $typeDeclaration;
|
||||
|
||||
/** @var DelimitedList\QualifiedNameList|null */
|
||||
public $otherTypeDeclarations;
|
||||
public $typeDeclarationList;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'modifiers',
|
||||
'questionToken',
|
||||
'typeDeclaration',
|
||||
'otherTypeDeclarations',
|
||||
'typeDeclarationList',
|
||||
];
|
||||
}
|
||||
|
||||
+13
-9
@@ -6,19 +6,21 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\MissingToken;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class Parameter extends Node {
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
/** @var Token|null */
|
||||
public $visibilityToken;
|
||||
/** @var Token[]|null */
|
||||
public $modifiers;
|
||||
/** @var Token|null */
|
||||
public $questionToken;
|
||||
/** @var QualifiedName|Token|null */
|
||||
public $typeDeclaration;
|
||||
/**
|
||||
* @var DelimitedList\QualifiedNameList a list of other types, to support php 8 union types while remaining backwards compatible.
|
||||
* TODO: Merge with typeDeclaration in a future backwards incompatible release.
|
||||
*/
|
||||
public $otherTypeDeclarations;
|
||||
/** @var DelimitedList\QualifiedNameList|MissingToken|null */
|
||||
public $typeDeclarationList;
|
||||
/** @var Token|null */
|
||||
public $byRefToken;
|
||||
/** @var Token|null */
|
||||
@@ -31,9 +33,11 @@ class Parameter extends Node {
|
||||
public $default;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'visibilityToken',
|
||||
'modifiers',
|
||||
'questionToken',
|
||||
'typeDeclaration',
|
||||
'otherTypeDeclarations',
|
||||
'typeDeclarationList',
|
||||
'byRefToken',
|
||||
'dotDotDotToken',
|
||||
'variableName',
|
||||
|
||||
@@ -6,26 +6,24 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\MissingToken;
|
||||
use Microsoft\PhpParser\ModifiedTypeInterface;
|
||||
use Microsoft\PhpParser\ModifiedTypeTrait;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
|
||||
use Microsoft\PhpParser\Token;
|
||||
use Microsoft\PhpParser\TokenKind;
|
||||
|
||||
class PropertyDeclaration extends Node {
|
||||
class PropertyDeclaration extends Node implements ModifiedTypeInterface {
|
||||
use ModifiedTypeTrait;
|
||||
|
||||
/** @var Token[] */
|
||||
public $modifiers;
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token|null question token for PHP 7.4 type declaration */
|
||||
public $questionToken;
|
||||
|
||||
/** @var QualifiedName|Token|null */
|
||||
public $typeDeclaration;
|
||||
|
||||
/**
|
||||
* @var DelimitedList\QualifiedNameList|null
|
||||
* TODO: Unify with typeDeclaration in a future backwards incompatible release
|
||||
*/
|
||||
public $otherTypeDeclarations;
|
||||
/** @var QualifiedNameList|MissingToken|null */
|
||||
public $typeDeclarationList;
|
||||
|
||||
/** @var DelimitedList\ExpressionList */
|
||||
public $propertyElements;
|
||||
@@ -34,23 +32,11 @@ class PropertyDeclaration extends Node {
|
||||
public $semicolon;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'modifiers',
|
||||
'questionToken',
|
||||
'typeDeclaration',
|
||||
'otherTypeDeclarations',
|
||||
'typeDeclarationList',
|
||||
'propertyElements',
|
||||
'semicolon'
|
||||
];
|
||||
|
||||
public function isStatic() : bool {
|
||||
if ($this->modifiers === null) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->modifiers as $modifier) {
|
||||
if ($modifier->kind === TokenKind::StaticKeyword) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class QualifiedName extends Node implements NamespacedNameInterface {
|
||||
*/
|
||||
public function getResolvedName($namespaceDefinition = null) {
|
||||
// Name resolution not applicable to constructs that define symbol names or aliases.
|
||||
if (($this->parent instanceof Node\Statement\NamespaceDefinition && $this->parent->name->getStart() === $this->getStart()) ||
|
||||
if (($this->parent instanceof Node\Statement\NamespaceDefinition && $this->parent->name->getStartPosition() === $this->getStartPosition()) ||
|
||||
$this->parent instanceof Node\Statement\NamespaceUseDeclaration ||
|
||||
$this->parent instanceof Node\NamespaceUseClause ||
|
||||
$this->parent instanceof Node\NamespaceUseGroupClause ||
|
||||
@@ -110,7 +110,7 @@ class QualifiedName extends Node implements NamespacedNameInterface {
|
||||
return $this->getNamespacedName();
|
||||
}
|
||||
|
||||
list($namespaceImportTable, $functionImportTable, $constImportTable) = $this->getImportTablesForCurrentScope();
|
||||
[$namespaceImportTable, $functionImportTable, $constImportTable] = $this->getImportTablesForCurrentScope();
|
||||
|
||||
// QUALIFIED NAMES
|
||||
// - first segment of the name is translated according to the current class/namespace import table.
|
||||
|
||||
+1
-6
@@ -60,12 +60,7 @@ class BreakOrContinueStatement extends StatementNode {
|
||||
}
|
||||
}
|
||||
|
||||
if ($breakoutLevel instanceof Token) {
|
||||
$start = $breakoutLevel->getStartPosition();
|
||||
}
|
||||
else {
|
||||
$start = $breakoutLevel->getStart();
|
||||
}
|
||||
$start = $breakoutLevel->getStartPosition();
|
||||
$end = $breakoutLevel->getEndPosition();
|
||||
|
||||
return new Diagnostic(
|
||||
|
||||
@@ -9,6 +9,7 @@ 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\ClassBaseClause;
|
||||
use Microsoft\PhpParser\Node\ClassInterfaceClause;
|
||||
use Microsoft\PhpParser\Node\ClassMembersNode;
|
||||
@@ -18,6 +19,9 @@ use Microsoft\PhpParser\Token;
|
||||
class ClassDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
|
||||
use NamespacedNameTrait;
|
||||
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token */
|
||||
public $abstractOrFinalModifier;
|
||||
|
||||
@@ -37,6 +41,7 @@ class ClassDeclaration extends StatementNode implements NamespacedNameInterface,
|
||||
public $classMembers;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'abstractOrFinalModifier',
|
||||
'classKeyword',
|
||||
'name',
|
||||
|
||||
+6
-4
@@ -6,7 +6,8 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node\Statement;
|
||||
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\MissingToken;
|
||||
use Microsoft\PhpParser\Node\DelimitedList;
|
||||
use Microsoft\PhpParser\Node\StatementNode;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
@@ -15,8 +16,9 @@ class DeclareStatement extends StatementNode {
|
||||
public $declareKeyword;
|
||||
/** @var Token */
|
||||
public $openParen;
|
||||
/** @var Node */
|
||||
public $declareDirective;
|
||||
// TODO Maybe create a delimited list with a missing token instead? Probably more consistent.
|
||||
/** @var DelimitedList\DeclareDirectiveList|MissingToken */
|
||||
public $declareDirectiveList;
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
/** @var Token|null */
|
||||
@@ -31,7 +33,7 @@ class DeclareStatement extends StatementNode {
|
||||
const CHILD_NAMES = [
|
||||
'declareKeyword',
|
||||
'openParen',
|
||||
'declareDirective',
|
||||
'declareDirectiveList',
|
||||
'closeParen',
|
||||
'colon',
|
||||
'statements',
|
||||
|
||||
+9
-8
@@ -4,20 +4,17 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
namespace Microsoft\PhpParser\Node\Expression;
|
||||
namespace Microsoft\PhpParser\Node\Statement;
|
||||
|
||||
use Microsoft\PhpParser\Node\Expression;
|
||||
use Microsoft\PhpParser\Node\StatementNode;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\ExpressionList;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
/**
|
||||
* This represents either a literal echo expression (`echo expr`)
|
||||
* This represents either a literal echo statement (`echo expr`)
|
||||
* or a short echo tag (`<?= expr...`)
|
||||
*
|
||||
* TODO: An echo statement cannot be used as an expression.
|
||||
* Consider refactoring this to become EchoStatement in a future backwards incompatible release.
|
||||
*/
|
||||
class EchoExpression extends Expression {
|
||||
class EchoStatement extends StatementNode {
|
||||
|
||||
/**
|
||||
* @var Token|null this is null if generated from `<?=`
|
||||
@@ -27,8 +24,12 @@ class EchoExpression extends Expression {
|
||||
/** @var ExpressionList */
|
||||
public $expressions;
|
||||
|
||||
/** @var Token */
|
||||
public $semicolon;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'echoKeyword',
|
||||
'expressions'
|
||||
'expressions',
|
||||
'semicolon',
|
||||
];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -20,6 +20,7 @@ class FunctionDeclaration extends StatementNode implements NamespacedNameInterfa
|
||||
|
||||
const CHILD_NAMES = [
|
||||
// FunctionHeader
|
||||
'attributes',
|
||||
'functionKeyword',
|
||||
'byRefToken',
|
||||
'name',
|
||||
@@ -30,8 +31,7 @@ class FunctionDeclaration extends StatementNode implements NamespacedNameInterfa
|
||||
// FunctionReturnType
|
||||
'colonToken',
|
||||
'questionToken',
|
||||
'returnType',
|
||||
'otherReturnTypes',
|
||||
'returnTypeList',
|
||||
|
||||
// FunctionBody
|
||||
'compoundStatementOrSemicolon'
|
||||
|
||||
+5
@@ -9,6 +9,7 @@ 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\InterfaceBaseClause;
|
||||
use Microsoft\PhpParser\Node\InterfaceMembers;
|
||||
use Microsoft\PhpParser\Node\StatementNode;
|
||||
@@ -17,6 +18,9 @@ use Microsoft\PhpParser\Token;
|
||||
class InterfaceDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
|
||||
use NamespacedNameTrait;
|
||||
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token */
|
||||
public $interfaceKeyword;
|
||||
|
||||
@@ -30,6 +34,7 @@ class InterfaceDeclaration extends StatementNode implements NamespacedNameInterf
|
||||
public $interfaceMembers;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'interfaceKeyword',
|
||||
'name',
|
||||
'interfaceBaseClause',
|
||||
|
||||
-3
@@ -14,12 +14,9 @@ class NamedLabelStatement extends StatementNode {
|
||||
public $name;
|
||||
/** @var Token */
|
||||
public $colon;
|
||||
/** @var StatementNode */
|
||||
public $statement;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'name',
|
||||
'colon',
|
||||
'statement'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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\TraitMembers;
|
||||
use Microsoft\PhpParser\Token;
|
||||
@@ -16,6 +17,9 @@ use Microsoft\PhpParser\Token;
|
||||
class TraitDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
|
||||
use NamespacedNameTrait;
|
||||
|
||||
/** @var AttributeGroup[]|null */
|
||||
public $attributes;
|
||||
|
||||
/** @var Token */
|
||||
public $traitKeyword;
|
||||
|
||||
@@ -26,6 +30,7 @@ class TraitDeclaration extends StatementNode implements NamespacedNameInterface,
|
||||
public $traitMembers;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'attributes',
|
||||
'traitKeyword',
|
||||
'name',
|
||||
'traitMembers'
|
||||
|
||||
+7
-3
@@ -4,13 +4,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
namespace Microsoft\PhpParser\Node\Expression;
|
||||
namespace Microsoft\PhpParser\Node\Statement;
|
||||
|
||||
use Microsoft\PhpParser\Node\DelimitedList;
|
||||
use Microsoft\PhpParser\Node\Expression;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class UnsetIntrinsicExpression extends Expression {
|
||||
class UnsetStatement extends Expression {
|
||||
|
||||
/** @var Token */
|
||||
public $unsetKeyword;
|
||||
@@ -24,10 +24,14 @@ class UnsetIntrinsicExpression extends Expression {
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
|
||||
/** @var Token */
|
||||
public $semicolon;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'unsetKeyword',
|
||||
'openParen',
|
||||
'expressions',
|
||||
'closeParen'
|
||||
'closeParen',
|
||||
'semicolon',
|
||||
];
|
||||
}
|
||||
+9
-21
@@ -6,42 +6,30 @@
|
||||
|
||||
namespace Microsoft\PhpParser\Node;
|
||||
|
||||
use Microsoft\PhpParser\ModifiedTypeInterface;
|
||||
use Microsoft\PhpParser\ModifiedTypeTrait;
|
||||
use Microsoft\PhpParser\Node;
|
||||
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
|
||||
use Microsoft\PhpParser\Token;
|
||||
|
||||
class TraitSelectOrAliasClause extends Node {
|
||||
class TraitSelectOrAliasClause extends Node implements ModifiedTypeInterface {
|
||||
use ModifiedTypeTrait;
|
||||
|
||||
/** @var QualifiedName|Node\Expression\ScopedPropertyAccessExpression */
|
||||
public $name;
|
||||
|
||||
/** @var Token */
|
||||
public $asOrInsteadOfKeyword;
|
||||
|
||||
/** @var Token[] */
|
||||
public $modifiers;
|
||||
|
||||
/** @var QualifiedName|Node\Expression\ScopedPropertyAccessExpression */
|
||||
public $targetName;
|
||||
|
||||
/**
|
||||
* @var Token[]|QualifiedName[]|null
|
||||
*
|
||||
* This is set if $asOrInsteadOfKeyword is an insteadof keyword.
|
||||
* (E.g. for parsing `use T1, T2, T3{T1::foo insteadof T2, T3}`
|
||||
*
|
||||
* NOTE: This was added as a separate property to minimize
|
||||
* backwards compatibility breaks in applications using this file.
|
||||
*
|
||||
* TODO: Use a more consistent design such as either of the following:
|
||||
* 1. Combine targetName and remainingTargetNames into a DelimitedList
|
||||
* 2. Use two distinct properties for the targets of `as` and `insteadof`
|
||||
* @var QualifiedNameList|QualifiedName depends on the keyword
|
||||
*/
|
||||
public $remainingTargetNames;
|
||||
public $targetNameList;
|
||||
|
||||
const CHILD_NAMES = [
|
||||
'name',
|
||||
'asOrInsteadOfKeyword',
|
||||
'modifiers',
|
||||
'targetName',
|
||||
'remainingTargetNames',
|
||||
'targetNameList',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ class ParseContext {
|
||||
const InterfaceMembers = 10;
|
||||
const TraitMembers = 11;
|
||||
const Count = 12;
|
||||
const EnumMembers = 13;
|
||||
}
|
||||
|
||||
+584
-144
@@ -8,12 +8,16 @@ namespace Microsoft\PhpParser;
|
||||
|
||||
use Microsoft\PhpParser\Node\AnonymousFunctionUseClause;
|
||||
use Microsoft\PhpParser\Node\ArrayElement;
|
||||
use Microsoft\PhpParser\Node\Attribute;
|
||||
use Microsoft\PhpParser\Node\AttributeGroup;
|
||||
use Microsoft\PhpParser\Node\CaseStatementNode;
|
||||
use Microsoft\PhpParser\Node\CatchClause;
|
||||
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,
|
||||
@@ -31,11 +35,11 @@ use Microsoft\PhpParser\Node\Expression\{
|
||||
EvalIntrinsicExpression,
|
||||
ExitIntrinsicExpression,
|
||||
IssetIntrinsicExpression,
|
||||
MatchExpression,
|
||||
MemberAccessExpression,
|
||||
ParenthesizedExpression,
|
||||
PrefixUpdateExpression,
|
||||
PrintIntrinsicExpression,
|
||||
EchoExpression,
|
||||
ListIntrinsicExpression,
|
||||
ObjectCreationExpression,
|
||||
ScriptInclusionExpression,
|
||||
@@ -43,9 +47,9 @@ use Microsoft\PhpParser\Node\Expression\{
|
||||
ScopedPropertyAccessExpression,
|
||||
SubscriptExpression,
|
||||
TernaryExpression,
|
||||
ThrowExpression,
|
||||
UnaryExpression,
|
||||
UnaryOpExpression,
|
||||
UnsetIntrinsicExpression,
|
||||
Variable,
|
||||
YieldExpression
|
||||
};
|
||||
@@ -60,6 +64,8 @@ use Microsoft\PhpParser\Node\ForeachKey;
|
||||
use Microsoft\PhpParser\Node\ForeachValue;
|
||||
use Microsoft\PhpParser\Node\InterfaceBaseClause;
|
||||
use Microsoft\PhpParser\Node\InterfaceMembers;
|
||||
use Microsoft\PhpParser\Node\MatchArm;
|
||||
use Microsoft\PhpParser\Node\MissingDeclaration;
|
||||
use Microsoft\PhpParser\Node\MissingMemberDeclaration;
|
||||
use Microsoft\PhpParser\Node\NamespaceAliasingClause;
|
||||
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
|
||||
@@ -81,7 +87,9 @@ use Microsoft\PhpParser\Node\Statement\{
|
||||
BreakOrContinueStatement,
|
||||
DeclareStatement,
|
||||
DoStatement,
|
||||
EchoStatement,
|
||||
EmptyStatement,
|
||||
EnumDeclaration,
|
||||
ExpressionStatement,
|
||||
ForeachStatement,
|
||||
ForStatement,
|
||||
@@ -95,9 +103,9 @@ use Microsoft\PhpParser\Node\Statement\{
|
||||
NamedLabelStatement,
|
||||
ReturnStatement,
|
||||
SwitchStatementNode,
|
||||
ThrowStatement,
|
||||
TraitDeclaration,
|
||||
TryStatement,
|
||||
UnsetStatement,
|
||||
WhileStatement
|
||||
};
|
||||
use Microsoft\PhpParser\Node\TraitMembers;
|
||||
@@ -118,6 +126,7 @@ class Parser {
|
||||
private $nameOrStaticOrReservedWordTokens;
|
||||
private $reservedWordTokens;
|
||||
private $keywordTokens;
|
||||
private $argumentStartTokensSet;
|
||||
// TODO consider validating parameter and return types on post-parse instead so we can be more permissive
|
||||
private $parameterTypeDeclarationTokens;
|
||||
private $returnTypeDeclarationTokens;
|
||||
@@ -125,16 +134,35 @@ class Parser {
|
||||
public function __construct() {
|
||||
$this->reservedWordTokens = \array_values(TokenStringMaps::RESERVED_WORDS);
|
||||
$this->keywordTokens = \array_values(TokenStringMaps::KEYWORDS);
|
||||
$this->argumentStartTokensSet = \array_flip(TokenStringMaps::KEYWORDS);
|
||||
unset($this->argumentStartTokensSet[TokenKind::YieldFromKeyword]);
|
||||
$this->argumentStartTokensSet[TokenKind::DotDotDotToken] = '...';
|
||||
$this->nameOrKeywordOrReservedWordTokens = \array_merge([TokenKind::Name], $this->keywordTokens, $this->reservedWordTokens);
|
||||
$this->nameOrReservedWordTokens = \array_merge([TokenKind::Name], $this->reservedWordTokens);
|
||||
$this->nameOrStaticOrReservedWordTokens = \array_merge([TokenKind::Name, TokenKind::StaticKeyword], $this->reservedWordTokens);
|
||||
$this->parameterTypeDeclarationTokens =
|
||||
[TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
|
||||
TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord,
|
||||
TokenKind::ObjectReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord]; // TODO update spec
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists so that it can be overridden in subclasses.
|
||||
* Any subclass must return a token stream that is equivalent to the contents in $fileContents for this to work properly.
|
||||
*
|
||||
* Possible reasons for applications to override the lexer:
|
||||
*
|
||||
* - Imitate token stream of a newer/older PHP version (e.g. T_FN is only available in php 7.4)
|
||||
* - Reuse the result of token_get_all to create a Node again.
|
||||
* - Reuse the result of token_get_all in a different library.
|
||||
*/
|
||||
protected function makeLexer(string $fileContents): TokenStreamProviderInterface
|
||||
{
|
||||
return TokenStreamProviderFactory::GetTokenStreamProvider($fileContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates AST from source file contents. Returns an instance of SourceFileNode, which is always the top-most
|
||||
* Node-type of the tree.
|
||||
@@ -143,7 +171,7 @@ class Parser {
|
||||
* @return SourceFileNode
|
||||
*/
|
||||
public function parseSourceFile(string $fileContents, string $uri = null) : SourceFileNode {
|
||||
$this->lexer = TokenStreamProviderFactory::GetTokenStreamProvider($fileContents);
|
||||
$this->lexer = $this->makeLexer($fileContents);
|
||||
|
||||
$this->reset();
|
||||
|
||||
@@ -151,7 +179,7 @@ class Parser {
|
||||
$this->sourceFile = $sourceFile;
|
||||
$sourceFile->fileContents = $fileContents;
|
||||
$sourceFile->uri = $uri;
|
||||
$sourceFile->statementList = array();
|
||||
$sourceFile->statementList = [];
|
||||
if ($this->getCurrentToken()->kind !== TokenKind::EndOfFileToken) {
|
||||
$inlineHTML = $this->parseInlineHtml($sourceFile);
|
||||
$sourceFile->statementList[] = $inlineHTML;
|
||||
@@ -193,7 +221,7 @@ class Parser {
|
||||
$this->currentParseContext |= 1 << $listParseContext;
|
||||
$parseListElementFn = $this->getParseListElementFn($listParseContext);
|
||||
|
||||
$nodeArray = array();
|
||||
$nodeArray = [];
|
||||
while (!$this->isListTerminator($listParseContext)) {
|
||||
if ($this->isValidListElement($listParseContext, $this->getCurrentToken())) {
|
||||
$element = $parseListElementFn($parentNode);
|
||||
@@ -266,6 +294,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;
|
||||
@@ -317,6 +346,9 @@ class Parser {
|
||||
case ParseContext::TraitMembers:
|
||||
return $this->isTraitMemberDeclarationStart($token);
|
||||
|
||||
case ParseContext::EnumMembers:
|
||||
return $this->isEnumMemberDeclarationStart($token);
|
||||
|
||||
case ParseContext::InterfaceMembers:
|
||||
return $this->isInterfaceMemberDeclarationStart($token);
|
||||
|
||||
@@ -348,6 +380,9 @@ class Parser {
|
||||
case ParseContext::InterfaceMembers:
|
||||
return $this->parseInterfaceElementFn();
|
||||
|
||||
case ParseContext::EnumMembers:
|
||||
return $this->parseEnumElementFn();
|
||||
|
||||
case ParseContext::SwitchStatementElements:
|
||||
return $this->parseCaseOrDefaultStatement();
|
||||
default:
|
||||
@@ -498,8 +533,6 @@ class Parser {
|
||||
return $this->parseBreakOrContinueStatement($parentNode);
|
||||
case TokenKind::ReturnKeyword: // return-statement
|
||||
return $this->parseReturnStatement($parentNode);
|
||||
case TokenKind::ThrowKeyword: // throw-statement
|
||||
return $this->parseThrowStatement($parentNode);
|
||||
|
||||
// try-statement
|
||||
case TokenKind::TryKeyword:
|
||||
@@ -509,6 +542,10 @@ class Parser {
|
||||
case TokenKind::DeclareKeyword:
|
||||
return $this->parseDeclareStatement($parentNode);
|
||||
|
||||
// attribute before statement or anonymous function
|
||||
case TokenKind::AttributeToken:
|
||||
return $this->parseAttributeStatement($parentNode);
|
||||
|
||||
// function-declaration
|
||||
case TokenKind::FunctionKeyword:
|
||||
// Check that this is not an anonymous-function-creation-expression
|
||||
@@ -553,6 +590,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);
|
||||
@@ -608,6 +648,9 @@ class Parser {
|
||||
case TokenKind::UseKeyword:
|
||||
return $this->parseTraitUseClause($parentNode);
|
||||
|
||||
case TokenKind::AttributeToken:
|
||||
return $this->parseAttributeStatement($parentNode);
|
||||
|
||||
default:
|
||||
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
|
||||
}
|
||||
@@ -643,6 +686,130 @@ class Parser {
|
||||
return $functionNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node
|
||||
*/
|
||||
private function parseAttributeExpression($parentNode) {
|
||||
$attributeGroups = $this->parseAttributeGroups(null);
|
||||
// Warn about invalid syntax for attributed declarations
|
||||
// Lookahead for static, function, or fn for the only type of expressions that can have attributes (anonymous functions)
|
||||
if (in_array($this->token->kind, [TokenKind::FunctionKeyword, TokenKind::FnKeyword], true) ||
|
||||
$this->token->kind === TokenKind::StaticKeyword && $this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword])) {
|
||||
$expression = $this->parsePrimaryExpression($parentNode);
|
||||
} else {
|
||||
// Create a MissingToken so that diagnostics indicate that the attributes did not match up with an expression/declaration.
|
||||
$expression = new MissingDeclaration();
|
||||
$expression->parent = $parentNode;
|
||||
$expression->declaration = new MissingToken(TokenKind::Expression, $this->token->fullStart);
|
||||
}
|
||||
if ($expression instanceof AnonymousFunctionCreationExpression ||
|
||||
$expression instanceof ArrowFunctionCreationExpression ||
|
||||
$expression instanceof MissingDeclaration) {
|
||||
$expression->attributes = $attributeGroups;
|
||||
foreach ($attributeGroups as $attributeGroup) {
|
||||
$attributeGroup->parent = $expression;
|
||||
}
|
||||
}
|
||||
return $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Precondition: The next token is an AttributeToken
|
||||
* @return Node
|
||||
*/
|
||||
private function parseAttributeStatement($parentNode) {
|
||||
$attributeGroups = $this->parseAttributeGroups(null);
|
||||
if ($parentNode instanceof ClassMembersNode) {
|
||||
// Create a class element or a MissingMemberDeclaration
|
||||
$statement = $this->parseClassElementFn()($parentNode);
|
||||
} 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, TokenKind::EnumKeyword], true) ||
|
||||
$this->token->kind === TokenKind::StaticKeyword && $this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword])) {
|
||||
$statement = $this->parseStatement($parentNode);
|
||||
} else {
|
||||
// Create a MissingToken so that diagnostics indicate that the attributes did not match up with an expression/declaration.
|
||||
$statement = new MissingDeclaration();
|
||||
$statement->parent = $parentNode;
|
||||
$statement->declaration = new MissingToken(TokenKind::Expression, $this->token->fullStart);
|
||||
}
|
||||
}
|
||||
|
||||
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 ||
|
||||
$statement instanceof MissingDeclaration ||
|
||||
$statement instanceof MissingMemberDeclaration) {
|
||||
|
||||
$statement->attributes = $attributeGroups;
|
||||
foreach ($attributeGroups as $attributeGroup) {
|
||||
$attributeGroup->parent = $statement;
|
||||
}
|
||||
}
|
||||
return $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node|null $parentNode
|
||||
* @return AttributeGroup[]
|
||||
*/
|
||||
private function parseAttributeGroups($parentNode): array
|
||||
{
|
||||
$attributeGroups = [];
|
||||
while ($attributeToken = $this->eatOptional1(TokenKind::AttributeToken)) {
|
||||
$attributeGroup = new AttributeGroup();
|
||||
$attributeGroup->startToken = $attributeToken;
|
||||
$attributeGroup->attributes = $this->parseAttributeElementList($attributeGroup)
|
||||
?: (new MissingToken(TokenKind::Name, $this->token->fullStart));
|
||||
$attributeGroup->endToken = $this->eat1(TokenKind::CloseBracketToken);
|
||||
$attributeGroup->parent = $parentNode;
|
||||
$attributeGroups[] = $attributeGroup;
|
||||
}
|
||||
return $attributeGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DelimitedList\AttributeElementList
|
||||
*/
|
||||
private function parseAttributeElementList(AttributeGroup $parentNode) {
|
||||
return $this->parseDelimitedList(
|
||||
DelimitedList\AttributeElementList::class,
|
||||
TokenKind::CommaToken,
|
||||
$this->isQualifiedNameStartFn(),
|
||||
$this->parseAttributeFn(),
|
||||
$parentNode,
|
||||
false);
|
||||
}
|
||||
|
||||
private function parseAttributeFn()
|
||||
{
|
||||
return function ($parentNode): Attribute {
|
||||
$attribute = new Attribute();
|
||||
$attribute->parent = $parentNode;
|
||||
$attribute->name = $this->parseQualifiedName($attribute);
|
||||
$attribute->openParen = $this->eatOptional1(TokenKind::OpenParenToken);
|
||||
if ($attribute->openParen) {
|
||||
$attribute->argumentExpressionList = $this->parseArgumentExpressionList($attribute);
|
||||
$attribute->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
}
|
||||
return $attribute;
|
||||
};
|
||||
}
|
||||
|
||||
private function parseMethodDeclaration($parentNode, $modifiers) {
|
||||
$methodDeclaration = new MethodDeclaration();
|
||||
$methodDeclaration->modifiers = $modifiers;
|
||||
@@ -655,16 +822,33 @@ class Parser {
|
||||
return function ($parentNode) {
|
||||
$parameter = new Parameter();
|
||||
$parameter->parent = $parentNode;
|
||||
if ($this->token->kind === TokenKind::AttributeToken) {
|
||||
$parameter->attributes = $this->parseAttributeGroups($parameter);
|
||||
}
|
||||
// Note that parameter modifiers are allowed to be repeated by the parser in php 8.1 (it is a compiler error)
|
||||
//
|
||||
// TODO: Remove the visibilityToken in a future backwards incompatible release
|
||||
$parameter->visibilityToken = $this->eatOptional([TokenKind::PublicKeyword, TokenKind::ProtectedKeyword, TokenKind::PrivateKeyword]);
|
||||
$parameter->modifiers = $this->parseParameterModifiers() ?: null;
|
||||
|
||||
$parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
|
||||
$typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
|
||||
if ($typeDeclarationList) {
|
||||
$parameter->typeDeclaration = array_shift($typeDeclarationList->children);
|
||||
$parameter->typeDeclaration->parent = $parameter;
|
||||
if ($typeDeclarationList->children) {
|
||||
$parameter->otherTypeDeclarations = $typeDeclarationList;
|
||||
$parameter->typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
|
||||
if ($parameter->typeDeclarationList) {
|
||||
$children = $parameter->typeDeclarationList->children;
|
||||
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?
|
||||
$parameter->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
|
||||
}
|
||||
if (!$parameter->byRefToken) {
|
||||
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
|
||||
}
|
||||
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
|
||||
// TODO add post-parse rule that prevents assignment
|
||||
// TODO add post-parse rule that requires only last parameter be variadic
|
||||
$parameter->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
|
||||
@@ -684,17 +868,17 @@ class Parser {
|
||||
private function parseAndSetReturnTypeDeclarationList($parentNode) {
|
||||
$returnTypeList = $this->parseReturnTypeDeclarationList($parentNode);
|
||||
if (!$returnTypeList) {
|
||||
$parentNode->returnType = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
|
||||
$parentNode->returnTypeList = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
|
||||
return;
|
||||
}
|
||||
$returnType = array_shift($returnTypeList->children);
|
||||
$parentNode->returnType = $returnType;
|
||||
$returnType->parent = $parentNode;
|
||||
if ($returnTypeList->children) {
|
||||
$parentNode->otherReturnTypes = $returnTypeList;
|
||||
}
|
||||
$parentNode->returnTypeList = $returnTypeList;
|
||||
}
|
||||
|
||||
const TYPE_DELIMITER_TOKENS = [
|
||||
TokenKind::BarToken,
|
||||
TokenKind::AmpersandToken,
|
||||
];
|
||||
|
||||
/**
|
||||
* Attempt to parse the return type after the `:` and optional `?` token.
|
||||
*
|
||||
@@ -703,7 +887,7 @@ class Parser {
|
||||
private function parseReturnTypeDeclarationList($parentNode) {
|
||||
$result = $this->parseDelimitedList(
|
||||
DelimitedList\QualifiedNameList::class,
|
||||
TokenKind::BarToken,
|
||||
self::TYPE_DELIMITER_TOKENS,
|
||||
function ($token) {
|
||||
return \in_array($token->kind, $this->returnTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
|
||||
},
|
||||
@@ -715,7 +899,7 @@ class Parser {
|
||||
|
||||
// Add a MissingToken so that this will warn about `function () : T| {}`
|
||||
// TODO: Make this a reusable abstraction?
|
||||
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
|
||||
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
|
||||
$result->children[] = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
|
||||
}
|
||||
return $result;
|
||||
@@ -739,7 +923,7 @@ class Parser {
|
||||
private function tryParseParameterTypeDeclarationList($parentNode) {
|
||||
$result = $this->parseDelimitedList(
|
||||
DelimitedList\QualifiedNameList::class,
|
||||
TokenKind::BarToken,
|
||||
self::TYPE_DELIMITER_TOKENS,
|
||||
function ($token) {
|
||||
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
|
||||
},
|
||||
@@ -751,7 +935,7 @@ class Parser {
|
||||
|
||||
// Add a MissingToken so that this will Warn about `function (T| $x) {}`
|
||||
// TODO: Make this a reusable abstraction?
|
||||
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
|
||||
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
|
||||
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
|
||||
}
|
||||
return $result;
|
||||
@@ -785,6 +969,9 @@ class Parser {
|
||||
// static-modifier
|
||||
case TokenKind::StaticKeyword:
|
||||
|
||||
// readonly-modifier
|
||||
case TokenKind::ReadonlyKeyword:
|
||||
|
||||
// class-modifier
|
||||
case TokenKind::AbstractKeyword:
|
||||
case TokenKind::FinalKeyword:
|
||||
@@ -794,6 +981,9 @@ class Parser {
|
||||
case TokenKind::FunctionKeyword:
|
||||
|
||||
case TokenKind::UseKeyword:
|
||||
|
||||
// attributes
|
||||
case TokenKind::AttributeToken:
|
||||
return true;
|
||||
|
||||
}
|
||||
@@ -853,6 +1043,9 @@ class Parser {
|
||||
// trait-declaration
|
||||
case TokenKind::TraitKeyword:
|
||||
|
||||
// enum-declaration
|
||||
case TokenKind::EnumKeyword:
|
||||
|
||||
// namespace-definition
|
||||
case TokenKind::NamespaceKeyword:
|
||||
|
||||
@@ -866,6 +1059,9 @@ class Parser {
|
||||
case TokenKind::StaticKeyword:
|
||||
|
||||
case TokenKind::ScriptSectionEndTag:
|
||||
|
||||
// attributes
|
||||
case TokenKind::AttributeToken:
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -967,11 +1163,13 @@ class Parser {
|
||||
case TokenKind::ObjectCastToken:
|
||||
case TokenKind::StringCastToken:
|
||||
case TokenKind::UnsetCastToken:
|
||||
case TokenKind::MatchKeyword:
|
||||
|
||||
// anonymous-function-creation-expression
|
||||
case TokenKind::StaticKeyword:
|
||||
case TokenKind::FunctionKeyword:
|
||||
case TokenKind::FnKeyword:
|
||||
case TokenKind::AttributeToken:
|
||||
return true;
|
||||
}
|
||||
return \in_array($token->kind, $this->reservedWordTokens, true);
|
||||
@@ -1066,6 +1264,9 @@ class Parser {
|
||||
return $this->parseParenthesizedExpression($parentNode);
|
||||
|
||||
// anonymous-function-creation-expression
|
||||
case TokenKind::AttributeToken:
|
||||
return $this->parseAttributeExpression($parentNode);
|
||||
|
||||
case TokenKind::StaticKeyword:
|
||||
// handle `static::`, `static(`, `new static;`, `instanceof static`
|
||||
if (!$this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword])) {
|
||||
@@ -1085,6 +1286,8 @@ class Parser {
|
||||
return $this->parseQualifiedName($parentNode);
|
||||
}
|
||||
return $this->parseReservedWordExpression($parentNode);
|
||||
case TokenKind::MatchKeyword:
|
||||
return $this->parseMatchExpression($parentNode);
|
||||
}
|
||||
if (\in_array($token->kind, TokenStringMaps::RESERVED_WORDS)) {
|
||||
return $this->parseQualifiedName($parentNode);
|
||||
@@ -1114,7 +1317,7 @@ class Parser {
|
||||
$expression = new StringLiteral();
|
||||
$expression->parent = $parentNode;
|
||||
$expression->startQuote = $this->eat(TokenKind::SingleQuoteToken, TokenKind::DoubleQuoteToken, TokenKind::HeredocStart, TokenKind::BacktickToken);
|
||||
$expression->children = array();
|
||||
$expression->children = [];
|
||||
|
||||
while (true) {
|
||||
switch ($this->getCurrentToken()->kind) {
|
||||
@@ -1177,7 +1380,7 @@ class Parser {
|
||||
$token = $this->getCurrentToken();
|
||||
if ($token->kind === TokenKind::OpenBracketToken) {
|
||||
return $this->parseTemplateStringSubscriptExpression($var);
|
||||
} else if ($token->kind === TokenKind::ArrowToken) {
|
||||
} else if ($token->kind === TokenKind::ArrowToken || $token->kind === TokenKind::QuestionArrowToken) {
|
||||
return $this->parseTemplateStringMemberAccessExpression($var);
|
||||
} else {
|
||||
return $var;
|
||||
@@ -1226,7 +1429,7 @@ class Parser {
|
||||
$expression->parent = $memberAccessExpression;
|
||||
|
||||
$memberAccessExpression->dereferencableExpression = $expression;
|
||||
$memberAccessExpression->arrowToken = $this->eat1(TokenKind::ArrowToken);
|
||||
$memberAccessExpression->arrowToken = $this->eat(TokenKind::ArrowToken, TokenKind::QuestionArrowToken);
|
||||
$memberAccessExpression->memberName = $this->eat1(TokenKind::Name);
|
||||
|
||||
return $memberAccessExpression;
|
||||
@@ -1248,7 +1451,7 @@ class Parser {
|
||||
return $reservedWord;
|
||||
}
|
||||
|
||||
private function isModifier($token) {
|
||||
private function isModifier($token): bool {
|
||||
switch ($token->kind) {
|
||||
// class-modifier
|
||||
case TokenKind::AbstractKeyword:
|
||||
@@ -1262,6 +1465,9 @@ class Parser {
|
||||
// static-modifier
|
||||
case TokenKind::StaticKeyword:
|
||||
|
||||
// readonly-modifier
|
||||
case TokenKind::ReadonlyKeyword:
|
||||
|
||||
// var
|
||||
case TokenKind::VarKeyword:
|
||||
return true;
|
||||
@@ -1269,8 +1475,36 @@ class Parser {
|
||||
return false;
|
||||
}
|
||||
|
||||
private function parseModifiers() {
|
||||
$modifiers = array();
|
||||
private function isParameterModifier($token): bool {
|
||||
switch ($token->kind) {
|
||||
// visibility-modifier
|
||||
case TokenKind::PublicKeyword:
|
||||
case TokenKind::ProtectedKeyword:
|
||||
case TokenKind::PrivateKeyword:
|
||||
|
||||
// readonly-modifier
|
||||
case TokenKind::ReadonlyKeyword:
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return Token[] */
|
||||
private function parseParameterModifiers(): array {
|
||||
$modifiers = [];
|
||||
$token = $this->getCurrentToken();
|
||||
while ($this->isParameterModifier($token)) {
|
||||
$modifiers[] = $token;
|
||||
$this->advanceToken();
|
||||
$token = $this->getCurrentToken();
|
||||
}
|
||||
return $modifiers;
|
||||
}
|
||||
|
||||
/** @return Token[] */
|
||||
private function parseModifiers(): array {
|
||||
$modifiers = [];
|
||||
$token = $this->getCurrentToken();
|
||||
while ($this->isModifier($token)) {
|
||||
$modifiers[] = $token;
|
||||
@@ -1293,10 +1527,15 @@ class Parser {
|
||||
case TokenKind::AmpersandToken:
|
||||
|
||||
case TokenKind::VariableName:
|
||||
return true;
|
||||
|
||||
// nullable-type
|
||||
case TokenKind::QuestionToken:
|
||||
|
||||
// parameter promotion
|
||||
case TokenKind::PublicKeyword:
|
||||
case TokenKind::ProtectedKeyword:
|
||||
case TokenKind::PrivateKeyword:
|
||||
case TokenKind::AttributeToken:
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1497,7 +1736,8 @@ class Parser {
|
||||
$namedLabelStatement->parent = $parentNode;
|
||||
$namedLabelStatement->name = $this->eat1(TokenKind::Name);
|
||||
$namedLabelStatement->colon = $this->eat1(TokenKind::ColonToken);
|
||||
$namedLabelStatement->statement = $this->parseStatement($namedLabelStatement);
|
||||
// A named label is a statement on its own. E.g. `while (false) label: echo "test";`
|
||||
// is parsed as `while (false) { label: } echo "test";
|
||||
return $namedLabelStatement;
|
||||
}
|
||||
|
||||
@@ -1762,6 +2002,8 @@ class Parser {
|
||||
case TokenKind::RequireKeyword:
|
||||
case TokenKind::RequireOnceKeyword:
|
||||
return $this->parseScriptInclusionExpression($parentNode);
|
||||
case TokenKind::ThrowKeyword: // throw-statement will become an expression in php 8.0
|
||||
return $this->parseThrowExpression($parentNode);
|
||||
}
|
||||
|
||||
$expression = $this->parsePrimaryExpression($parentNode);
|
||||
@@ -1776,12 +2018,12 @@ class Parser {
|
||||
private function parseBinaryExpressionOrHigher($precedence, $parentNode) {
|
||||
$leftOperand = $this->parseUnaryExpressionOrHigher($parentNode);
|
||||
|
||||
list($prevNewPrecedence, $prevAssociativity) = self::UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY;
|
||||
[$prevNewPrecedence, $prevAssociativity] = self::UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY;
|
||||
|
||||
while (true) {
|
||||
$token = $this->getCurrentToken();
|
||||
|
||||
list($newPrecedence, $associativity) = $this->getBinaryOperatorPrecedenceAndAssociativity($token);
|
||||
[$newPrecedence, $associativity] = $this->getBinaryOperatorPrecedenceAndAssociativity($token);
|
||||
|
||||
// Expressions using operators w/o associativity (equality, relational, instanceof)
|
||||
// cannot reference identical expression types within one of their operands.
|
||||
@@ -2212,15 +2454,15 @@ class Parser {
|
||||
return $returnStatement;
|
||||
}
|
||||
|
||||
private function parseThrowStatement($parentNode) {
|
||||
$throwStatement = new ThrowStatement();
|
||||
$throwStatement->parent = $parentNode;
|
||||
$throwStatement->throwKeyword = $this->eat1(TokenKind::ThrowKeyword);
|
||||
/** @return ThrowExpression */
|
||||
private function parseThrowExpression($parentNode) {
|
||||
$throwExpression = new ThrowExpression();
|
||||
$throwExpression->parent = $parentNode;
|
||||
$throwExpression->throwKeyword = $this->eat1(TokenKind::ThrowKeyword);
|
||||
// TODO error for failures to parse expressions when not optional
|
||||
$throwStatement->expression = $this->parseExpression($throwStatement);
|
||||
$throwStatement->semicolon = $this->eatSemicolonOrAbortStatement();
|
||||
$throwExpression->expression = $this->parseExpression($throwExpression);
|
||||
|
||||
return $throwStatement;
|
||||
return $throwExpression;
|
||||
}
|
||||
|
||||
private function parseTryStatement($parentNode) {
|
||||
@@ -2229,7 +2471,7 @@ class Parser {
|
||||
$tryStatement->tryKeyword = $this->eat1(TokenKind::TryKeyword);
|
||||
$tryStatement->compoundStatement = $this->parseCompoundStatement($tryStatement); // TODO verifiy this is only compound
|
||||
|
||||
$tryStatement->catchClauses = array(); // TODO - should be some standard for empty arrays vs. null?
|
||||
$tryStatement->catchClauses = []; // TODO - should be some standard for empty arrays vs. null?
|
||||
while ($this->checkToken(TokenKind::CatchKeyword)) {
|
||||
$tryStatement->catchClauses[] = $this->parseCatchClause($tryStatement);
|
||||
}
|
||||
@@ -2246,10 +2488,8 @@ class Parser {
|
||||
$catchClause->parent = $parentNode;
|
||||
$catchClause->catch = $this->eat1(TokenKind::CatchKeyword);
|
||||
$catchClause->openParen = $this->eat1(TokenKind::OpenParenToken);
|
||||
$qualifiedNameList = $this->parseQualifiedNameCatchList($catchClause)->children ?? [];
|
||||
$catchClause->qualifiedName = $qualifiedNameList[0] ?? null; // TODO generate missing token or error if null
|
||||
$catchClause->otherQualifiedNameList = array_slice($qualifiedNameList, 1); // TODO: Generate error if the name list has missing tokens
|
||||
$catchClause->variableName = $this->eat1(TokenKind::VariableName);
|
||||
$catchClause->qualifiedNameList = $this->parseQualifiedNameCatchList($catchClause) ?? new MissingToken(TokenKind::QualifiedName, $this->token->fullStart); // TODO generate missing token or error if null
|
||||
$catchClause->variableName = $this->eatOptional1(TokenKind::VariableName);
|
||||
$catchClause->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
$catchClause->compoundStatement = $this->parseCompoundStatement($catchClause);
|
||||
|
||||
@@ -2270,7 +2510,7 @@ class Parser {
|
||||
$declareStatement->parent = $parentNode;
|
||||
$declareStatement->declareKeyword = $this->eat1(TokenKind::DeclareKeyword);
|
||||
$declareStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
|
||||
$declareStatement->declareDirective = $this->parseDeclareDirective($declareStatement);
|
||||
$this->parseAndSetDeclareDirectiveList($declareStatement);
|
||||
$declareStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
|
||||
if ($this->checkToken(TokenKind::SemicolonToken)) {
|
||||
@@ -2287,26 +2527,56 @@ class Parser {
|
||||
return $declareStatement;
|
||||
}
|
||||
|
||||
private function parseDeclareDirective($parentNode) {
|
||||
$declareDirective = new DeclareDirective();
|
||||
$declareDirective->parent = $parentNode;
|
||||
$declareDirective->name = $this->eat1(TokenKind::Name);
|
||||
$declareDirective->equals = $this->eat1(TokenKind::EqualsToken);
|
||||
$declareDirective->literal =
|
||||
$this->eat(
|
||||
TokenKind::FloatingLiteralToken,
|
||||
TokenKind::IntegerLiteralToken,
|
||||
TokenKind::DecimalLiteralToken,
|
||||
TokenKind::OctalLiteralToken,
|
||||
TokenKind::HexadecimalLiteralToken,
|
||||
TokenKind::BinaryLiteralToken,
|
||||
TokenKind::InvalidOctalLiteralToken,
|
||||
TokenKind::InvalidHexadecimalLiteral,
|
||||
TokenKind::InvalidBinaryLiteral,
|
||||
TokenKind::StringLiteralToken
|
||||
); // TODO simplify
|
||||
/**
|
||||
* @param DeclareStatement $parentNode
|
||||
*/
|
||||
private function parseAndSetDeclareDirectiveList($parentNode) {
|
||||
$declareDirectiveList = $this->parseDeclareDirectiveList($parentNode);
|
||||
|
||||
return $declareDirective;
|
||||
$parentNode->declareDirectiveList = $declareDirectiveList ?? new MissingToken(TokenKind::Name, $this->token->fullStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DeclareStatement $parentNode
|
||||
* @return DelimitedList\DeclareDirectiveList|null
|
||||
*/
|
||||
private function parseDeclareDirectiveList($parentNode) {
|
||||
$declareDirectiveList = $this->parseDelimitedList(
|
||||
DelimitedList\DeclareDirectiveList::class,
|
||||
TokenKind::CommaToken,
|
||||
function ($token) {
|
||||
return $token->kind === TokenKind::Name;
|
||||
},
|
||||
$this->parseDeclareDirectiveFn(),
|
||||
$parentNode,
|
||||
false
|
||||
);
|
||||
|
||||
return $declareDirectiveList;
|
||||
}
|
||||
|
||||
private function parseDeclareDirectiveFn() {
|
||||
return function ($parentNode) {
|
||||
$declareDirective = new DeclareDirective();
|
||||
$declareDirective->parent = $parentNode;
|
||||
$declareDirective->name = $this->eat1(TokenKind::Name);
|
||||
$declareDirective->equals = $this->eat1(TokenKind::EqualsToken);
|
||||
$declareDirective->literal =
|
||||
$this->eat(
|
||||
TokenKind::FloatingLiteralToken,
|
||||
TokenKind::IntegerLiteralToken,
|
||||
TokenKind::DecimalLiteralToken,
|
||||
TokenKind::OctalLiteralToken,
|
||||
TokenKind::HexadecimalLiteralToken,
|
||||
TokenKind::BinaryLiteralToken,
|
||||
TokenKind::InvalidOctalLiteralToken,
|
||||
TokenKind::InvalidHexadecimalLiteral,
|
||||
TokenKind::InvalidBinaryLiteral,
|
||||
TokenKind::StringLiteralToken
|
||||
); // TODO simplify
|
||||
|
||||
return $declareDirective;
|
||||
};
|
||||
}
|
||||
|
||||
private function parseSimpleVariable($parentNode) {
|
||||
@@ -2387,34 +2657,15 @@ class Parser {
|
||||
return $scriptInclusionExpression;
|
||||
}
|
||||
|
||||
/** @return EchoStatement */
|
||||
private function parseEchoStatement($parentNode) {
|
||||
$expressionStatement = new ExpressionStatement();
|
||||
|
||||
// TODO: Could flatten into EchoStatement instead?
|
||||
$echoExpression = new EchoExpression();
|
||||
$echoExpression->parent = $expressionStatement;
|
||||
$echoExpression->echoKeyword = $this->eat1(TokenKind::EchoKeyword);
|
||||
$echoExpression->expressions =
|
||||
$this->parseExpressionList($echoExpression);
|
||||
|
||||
$expressionStatement->parent = $parentNode;
|
||||
$expressionStatement->expression = $echoExpression;
|
||||
$expressionStatement->semicolon = $this->eatSemicolonOrAbortStatement();
|
||||
|
||||
return $expressionStatement;
|
||||
}
|
||||
|
||||
private function parseUnsetStatement($parentNode) {
|
||||
$expressionStatement = new ExpressionStatement();
|
||||
|
||||
// TODO: Could flatten into UnsetStatement instead?
|
||||
$unsetExpression = $this->parseUnsetIntrinsicExpression($expressionStatement);
|
||||
|
||||
$expressionStatement->parent = $parentNode;
|
||||
$expressionStatement->expression = $unsetExpression;
|
||||
$expressionStatement->semicolon = $this->eatSemicolonOrAbortStatement();
|
||||
|
||||
return $expressionStatement;
|
||||
$echoStatement = new EchoStatement();
|
||||
$echoStatement->parent = $parentNode;
|
||||
$echoStatement->echoKeyword = $this->eat1(TokenKind::EchoKeyword);
|
||||
$echoStatement->expressions =
|
||||
$this->parseExpressionList($echoStatement);
|
||||
$echoStatement->semicolon = $this->eatSemicolonOrAbortStatement();
|
||||
return $echoStatement;
|
||||
}
|
||||
|
||||
private function parseListIntrinsicExpression($parentNode) {
|
||||
@@ -2481,16 +2732,16 @@ class Parser {
|
||||
);
|
||||
}
|
||||
|
||||
private function parseUnsetIntrinsicExpression($parentNode) {
|
||||
$unsetExpression = new UnsetIntrinsicExpression();
|
||||
$unsetExpression->parent = $parentNode;
|
||||
private function parseUnsetStatement($parentNode) {
|
||||
$unsetStatement = new UnsetStatement();
|
||||
$unsetStatement->parent = $parentNode;
|
||||
|
||||
$unsetExpression->unsetKeyword = $this->eat1(TokenKind::UnsetKeyword);
|
||||
$unsetExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
|
||||
$unsetExpression->expressions = $this->parseExpressionList($unsetExpression);
|
||||
$unsetExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
|
||||
return $unsetExpression;
|
||||
$unsetStatement->unsetKeyword = $this->eat1(TokenKind::UnsetKeyword);
|
||||
$unsetStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
|
||||
$unsetStatement->expressions = $this->parseExpressionList($unsetStatement);
|
||||
$unsetStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
$unsetStatement->semicolon = $this->eatSemicolonOrAbortStatement();
|
||||
return $unsetStatement;
|
||||
}
|
||||
|
||||
private function parseArrayCreationExpression($parentNode) {
|
||||
@@ -2673,7 +2924,7 @@ class Parser {
|
||||
return $expression;
|
||||
}
|
||||
|
||||
if ($tokenKind === TokenKind::ArrowToken) {
|
||||
if ($tokenKind === TokenKind::ArrowToken || $tokenKind === TokenKind::QuestionArrowToken) {
|
||||
$expression = $this->parseMemberAccessExpression($expression);
|
||||
return $this->parsePostfixExpressionRest($expression);
|
||||
}
|
||||
@@ -2724,7 +2975,7 @@ class Parser {
|
||||
private function isArgumentExpressionStartFn() {
|
||||
return function ($token) {
|
||||
return
|
||||
$token->kind === TokenKind::DotDotDotToken ? true : $this->isExpressionStart($token);
|
||||
isset($this->argumentStartTokensSet[$token->kind]) || $this->isExpressionStart($token);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2732,8 +2983,21 @@ class Parser {
|
||||
return function ($parentNode) {
|
||||
$argumentExpression = new ArgumentExpression();
|
||||
$argumentExpression->parent = $parentNode;
|
||||
$argumentExpression->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
|
||||
$argumentExpression->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
|
||||
|
||||
$nextToken = $this->lexer->getTokensArray()[$this->lexer->getCurrentPosition()] ?? null;
|
||||
if ($nextToken && $nextToken->kind === TokenKind::ColonToken) {
|
||||
$name = $this->token;
|
||||
$this->advanceToken();
|
||||
if ($name->kind === TokenKind::YieldFromKeyword || !\in_array($name->kind, $this->nameOrKeywordOrReservedWordTokens)) {
|
||||
$name = new SkippedToken($name);
|
||||
} else {
|
||||
$name->kind = TokenKind::Name;
|
||||
}
|
||||
$argumentExpression->name = $name;
|
||||
$argumentExpression->colonToken = $this->eat1(TokenKind::ColonToken);
|
||||
} else {
|
||||
$argumentExpression->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
|
||||
}
|
||||
$argumentExpression->expression = $this->parseExpression($argumentExpression);
|
||||
return $argumentExpression;
|
||||
};
|
||||
@@ -2798,7 +3062,7 @@ class Parser {
|
||||
$expression->parent = $memberAccessExpression;
|
||||
|
||||
$memberAccessExpression->dereferencableExpression = $expression;
|
||||
$memberAccessExpression->arrowToken = $this->eat1(TokenKind::ArrowToken);
|
||||
$memberAccessExpression->arrowToken = $this->eat(TokenKind::ArrowToken, TokenKind::QuestionArrowToken);
|
||||
$memberAccessExpression->memberName = $this->parseMemberName($memberAccessExpression);
|
||||
|
||||
return $memberAccessExpression;
|
||||
@@ -2848,6 +3112,12 @@ class Parser {
|
||||
// TODO - add tests for this scenario
|
||||
$oldIsParsingObjectCreationExpression = $this->isParsingObjectCreationExpression;
|
||||
$this->isParsingObjectCreationExpression = true;
|
||||
|
||||
if ($this->getCurrentToken()->kind === TokenKind::AttributeToken) {
|
||||
// Attributes such as `new #[MyAttr] class` can only be used with anonymous class declarations.
|
||||
// But handle this like $objectCreationExpression->classMembers and leave it up to the applications to detect the invalid combination.
|
||||
$objectCreationExpression->attributes = $this->parseAttributeGroups($objectCreationExpression);
|
||||
}
|
||||
$objectCreationExpression->classTypeDesignator =
|
||||
$this->eatOptional1(TokenKind::ClassKeyword) ??
|
||||
$this->parseExpression($objectCreationExpression);
|
||||
@@ -2870,14 +3140,27 @@ class Parser {
|
||||
return $objectCreationExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DelimitedList\ArgumentExpressionList|null
|
||||
*/
|
||||
private function parseArgumentExpressionList($parentNode) {
|
||||
return $this->parseDelimitedList(
|
||||
$list = $this->parseDelimitedList(
|
||||
DelimitedList\ArgumentExpressionList::class,
|
||||
TokenKind::CommaToken,
|
||||
$this->isArgumentExpressionStartFn(),
|
||||
$this->parseArgumentExpressionFn(),
|
||||
$parentNode
|
||||
);
|
||||
$children = $list->children ?? null;
|
||||
if (is_array($children) && \count($children) === 1) {
|
||||
$arg = $children[0];
|
||||
if ($arg instanceof ArgumentExpression) {
|
||||
if ($arg->dotDotDotToken && $arg->expression instanceof MissingToken && !$arg->colonToken && !$arg->name) {
|
||||
$arg->expression = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2941,6 +3224,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
|
||||
@@ -2968,18 +3266,10 @@ class Parser {
|
||||
$propertyDeclaration->modifiers = $modifiers;
|
||||
$propertyDeclaration->questionToken = $questionToken;
|
||||
if ($typeDeclarationList) {
|
||||
/** $typeDeclarationList is a Node or a Token (e.g. IntKeyword) */
|
||||
$typeDeclaration = \array_shift($typeDeclarationList->children);
|
||||
$propertyDeclaration->typeDeclaration = $typeDeclaration;
|
||||
if ($typeDeclaration instanceof Node) {
|
||||
$typeDeclaration->parent = $propertyDeclaration;
|
||||
}
|
||||
if ($typeDeclarationList->children) {
|
||||
$propertyDeclaration->otherTypeDeclarations = $typeDeclarationList;
|
||||
$typeDeclarationList->parent = $propertyDeclaration;
|
||||
}
|
||||
$propertyDeclaration->typeDeclarationList = $typeDeclarationList;
|
||||
$typeDeclarationList->parent = $propertyDeclaration;
|
||||
} elseif ($questionToken) {
|
||||
$propertyDeclaration->typeDeclaration = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
|
||||
$propertyDeclaration->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
|
||||
}
|
||||
$propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration);
|
||||
$propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
|
||||
@@ -3045,6 +3335,9 @@ class Parser {
|
||||
// static-modifier
|
||||
case TokenKind::StaticKeyword:
|
||||
|
||||
// readonly-modifier
|
||||
case TokenKind::ReadonlyKeyword:
|
||||
|
||||
// class-modifier
|
||||
case TokenKind::AbstractKeyword:
|
||||
case TokenKind::FinalKeyword:
|
||||
@@ -3052,6 +3345,8 @@ class Parser {
|
||||
case TokenKind::ConstKeyword:
|
||||
|
||||
case TokenKind::FunctionKeyword:
|
||||
|
||||
case TokenKind::AttributeToken:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -3069,6 +3364,9 @@ class Parser {
|
||||
case TokenKind::FunctionKeyword:
|
||||
return $this->parseMethodDeclaration($parentNode, $modifiers);
|
||||
|
||||
case TokenKind::AttributeToken:
|
||||
return $this->parseAttributeStatement($parentNode);
|
||||
|
||||
default:
|
||||
$missingInterfaceMemberDeclaration = new MissingMemberDeclaration();
|
||||
$missingInterfaceMemberDeclaration->parent = $parentNode;
|
||||
@@ -3214,12 +3512,16 @@ class Parser {
|
||||
case TokenKind::StaticKeyword:
|
||||
case TokenKind::AbstractKeyword:
|
||||
case TokenKind::FinalKeyword:
|
||||
case TokenKind::ReadonlyKeyword:
|
||||
|
||||
// method-declaration
|
||||
case TokenKind::FunctionKeyword:
|
||||
|
||||
// trait-use-clauses
|
||||
case TokenKind::UseKeyword:
|
||||
|
||||
// attributes
|
||||
case TokenKind::AttributeToken:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -3246,12 +3548,110 @@ class Parser {
|
||||
case TokenKind::UseKeyword:
|
||||
return $this->parseTraitUseClause($parentNode);
|
||||
|
||||
case TokenKind::AttributeToken:
|
||||
return $this->parseAttributeStatement($parentNode);
|
||||
|
||||
default:
|
||||
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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::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
|
||||
@@ -3264,14 +3664,10 @@ class Parser {
|
||||
$missingTraitMemberDeclaration->modifiers = $modifiers;
|
||||
$missingTraitMemberDeclaration->questionToken = $questionToken;
|
||||
if ($typeDeclarationList) {
|
||||
$missingTraitMemberDeclaration->typeDeclaration = \array_shift($typeDeclarationList->children);
|
||||
$missingTraitMemberDeclaration->typeDeclaration->parent = $missingTraitMemberDeclaration;
|
||||
if ($typeDeclarationList->children) {
|
||||
$missingTraitMemberDeclaration->otherTypeDeclarations = $typeDeclarationList;
|
||||
$typeDeclarationList->parent = $missingTraitMemberDeclaration;
|
||||
}
|
||||
$missingTraitMemberDeclaration->typeDeclarationList = $typeDeclarationList;
|
||||
$missingTraitMemberDeclaration->typeDeclarationList->parent = $missingTraitMemberDeclaration;
|
||||
} elseif ($questionToken) {
|
||||
$missingTraitMemberDeclaration->typeDeclaration = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
|
||||
$missingTraitMemberDeclaration->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
|
||||
}
|
||||
return $missingTraitMemberDeclaration;
|
||||
}
|
||||
@@ -3313,15 +3709,10 @@ class Parser {
|
||||
$traitSelectAndAliasClause->modifiers = $this->parseModifiers(); // TODO accept all modifiers, verify later
|
||||
|
||||
if ($traitSelectAndAliasClause->asOrInsteadOfKeyword->kind === TokenKind::InsteadOfKeyword) {
|
||||
// https://github.com/Microsoft/tolerant-php-parser/issues/190
|
||||
// TODO: In the next backwards incompatible release, convert targetName to a list?
|
||||
$interfaceNameList = $this->parseQualifiedNameList($traitSelectAndAliasClause)->children ?? [];
|
||||
$traitSelectAndAliasClause->targetName = $interfaceNameList[0] ?? new MissingToken(TokenKind::BarToken, $this->token->fullStart);
|
||||
$traitSelectAndAliasClause->remainingTargetNames = array_slice($interfaceNameList, 1);
|
||||
$traitSelectAndAliasClause->targetNameList = $this->parseQualifiedNameList($traitSelectAndAliasClause);
|
||||
} else {
|
||||
$traitSelectAndAliasClause->targetName =
|
||||
$traitSelectAndAliasClause->targetNameList =
|
||||
$this->parseQualifiedNameOrScopedPropertyAccessExpression($traitSelectAndAliasClause);
|
||||
$traitSelectAndAliasClause->remainingTargetNames = [];
|
||||
}
|
||||
|
||||
// TODO errors for insteadof/as
|
||||
@@ -3548,12 +3939,65 @@ class Parser {
|
||||
return $useVariableName;
|
||||
},
|
||||
$anonymousFunctionUseClause
|
||||
);
|
||||
) ?: (new MissingToken(TokenKind::VariableName, $this->token->fullStart));
|
||||
$anonymousFunctionUseClause->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
|
||||
return $anonymousFunctionUseClause;
|
||||
}
|
||||
|
||||
private function parseMatchExpression($parentNode) {
|
||||
$matchExpression = new MatchExpression();
|
||||
$matchExpression->parent = $parentNode;
|
||||
$matchExpression->matchToken = $this->eat1(TokenKind::MatchKeyword);
|
||||
$matchExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
|
||||
$matchExpression->expression = $this->parseExpression($matchExpression);
|
||||
$matchExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
$matchExpression->openBrace = $this->eat1(TokenKind::OpenBraceToken);
|
||||
$matchExpression->arms = $this->parseDelimitedList(
|
||||
DelimitedList\MatchExpressionArmList::class,
|
||||
TokenKind::CommaToken,
|
||||
$this->isMatchConditionStartFn(),
|
||||
$this->parseMatchArmFn(),
|
||||
$matchExpression);
|
||||
$matchExpression->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
|
||||
return $matchExpression;
|
||||
}
|
||||
|
||||
private function isMatchConditionStartFn() {
|
||||
return function ($token) {
|
||||
return $token->kind === TokenKind::DefaultKeyword ||
|
||||
$this->isExpressionStart($token);
|
||||
};
|
||||
}
|
||||
|
||||
private function parseMatchArmFn() {
|
||||
return function ($parentNode) {
|
||||
$matchArm = new MatchArm();
|
||||
$matchArm->parent = $parentNode;
|
||||
$matchArmConditionList = $this->parseDelimitedList(
|
||||
DelimitedList\MatchArmConditionList::class,
|
||||
TokenKind::CommaToken,
|
||||
$this->isMatchConditionStartFn(),
|
||||
$this->parseMatchConditionFn(),
|
||||
$matchArm
|
||||
);
|
||||
$matchArmConditionList->parent = $matchArm;
|
||||
$matchArm->conditionList = $matchArmConditionList;
|
||||
$matchArm->arrowToken = $this->eat1(TokenKind::DoubleArrowToken);
|
||||
$matchArm->body = $this->parseExpression($matchArm);
|
||||
return $matchArm;
|
||||
};
|
||||
}
|
||||
|
||||
private function parseMatchConditionFn() {
|
||||
return function ($parentNode) {
|
||||
if ($this->token->kind === TokenKind::DefaultKeyword) {
|
||||
return $this->eat1(TokenKind::DefaultKeyword);
|
||||
}
|
||||
return $this->parseExpression($parentNode);
|
||||
};
|
||||
}
|
||||
|
||||
private function parseCloneExpression($parentNode) {
|
||||
$cloneExpression = new CloneExpression();
|
||||
$cloneExpression->parent = $parentNode;
|
||||
@@ -3580,14 +4024,10 @@ class Parser {
|
||||
|
||||
// This is the easiest way to represent `<?= "expr", "other" `
|
||||
if (($inlineHtml->scriptSectionStartTag->kind ?? null) === TokenKind::ScriptSectionStartWithEchoTag) {
|
||||
$echoStatement = new ExpressionStatement();
|
||||
$echoStatement = new EchoStatement();
|
||||
$expressionList = $this->parseExpressionList($echoStatement) ?? (new MissingToken(TokenKind::Expression, $this->token->fullStart));
|
||||
$echoStatement->expressions = $expressionList;
|
||||
|
||||
$echoExpression = new EchoExpression();
|
||||
$expressionList = $this->parseExpressionList($echoExpression) ?? (new MissingToken(TokenKind::Expression, $this->token->fullStart));
|
||||
$echoExpression->expressions = $expressionList;
|
||||
$echoExpression->parent = $echoStatement;
|
||||
|
||||
$echoStatement->expression = $echoExpression;
|
||||
$echoStatement->semicolon = $this->eatSemicolonOrAbortStatement();
|
||||
$echoStatement->parent = $inlineHtml;
|
||||
// Deliberately leave echoKeyword as null instead of MissingToken
|
||||
|
||||
+85
-2
@@ -10,6 +10,15 @@ namespace Microsoft\PhpParser;
|
||||
// The replacement value is arbitrary - it just has to be different from other values of token constants.
|
||||
define(__NAMESPACE__ . '\T_COALESCE_EQUAL', defined('T_COALESCE_EQUAL') ? constant('T_COALESCE_EQUAL') : 'T_COALESCE_EQUAL');
|
||||
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_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');
|
||||
define(__NAMESPACE__ . '\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') ? constant('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') : 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG');
|
||||
define(__NAMESPACE__ . '\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') ? constant('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') : 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG');
|
||||
define(__NAMESPACE__ . '\T_READONLY', defined('T_READONLY') ? constant('T_READONLY') : 'T_READONLY');
|
||||
|
||||
/**
|
||||
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
|
||||
@@ -74,9 +83,9 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
$content = $prefix . $content;
|
||||
}
|
||||
|
||||
$tokens = @\token_get_all($content);
|
||||
$tokens = static::tokenGetAll($content, $parseContext);
|
||||
|
||||
$arr = array();
|
||||
$arr = [];
|
||||
$fullStart = $start = $pos = $initialPos;
|
||||
if ($parseContext !== null) {
|
||||
// If needed, skip over the prefix we added for token_get_all and remove those tokens.
|
||||
@@ -128,6 +137,57 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
$arr[] = new Token(TokenKind::ScriptSectionStartTag, $fullStart, $start, $pos-$fullStart);
|
||||
$start = $fullStart = $pos;
|
||||
break;
|
||||
case \PHP_VERSION_ID >= 80000 ? \T_NAME_QUALIFIED : -1000:
|
||||
case \PHP_VERSION_ID >= 80000 ? \T_NAME_FULLY_QUALIFIED : -1001:
|
||||
// NOTE: This switch is called on every token of every file being parsed, so this traded performance for readability.
|
||||
//
|
||||
// PHP's Opcache is able to optimize switches that are exclusively known longs,
|
||||
// but not switches that mix strings and longs or have unknown longs.
|
||||
// Longs are only known if they're declared within the same *class* or an internal constant (tokenizer).
|
||||
//
|
||||
// For some reason, the SWITCH_LONG opcode was not generated when the expression was part of a class constant.
|
||||
// (seen with php -d opcache.opt_debug_level=0x20000)
|
||||
//
|
||||
// Use negative values because that's not expected to overlap with token kinds that token_get_all() will return.
|
||||
//
|
||||
// T_NAME_* was added in php 8.0 to forbid whitespace between parts of names.
|
||||
// Here, emulate the tokenization of php 7 by splitting it up into 1 or more tokens.
|
||||
foreach (\explode('\\', $token[1]) as $i => $name) {
|
||||
if ($i) {
|
||||
$arr[] = new Token(TokenKind::BackslashToken, $fullStart, $start, 1 + $start - $fullStart);
|
||||
$start++;
|
||||
$fullStart = $start;
|
||||
}
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
// TODO: TokenStringMaps::RESERVED_WORDS[$name] ?? TokenKind::Name for compatibility?
|
||||
$len = \strlen($name);
|
||||
$arr[] = new Token(TokenKind::Name, $fullStart, $start, $len + $start - $fullStart);
|
||||
$start += $len;
|
||||
$fullStart = $start;
|
||||
}
|
||||
break;
|
||||
case \PHP_VERSION_ID >= 80000 ? \T_NAME_RELATIVE : -1002:
|
||||
// This is a namespace-relative name: namespace\...
|
||||
foreach (\explode('\\', $token[1]) as $i => $name) {
|
||||
$len = \strlen($name);
|
||||
if (!$i) {
|
||||
$arr[] = new Token(TokenKind::NamespaceKeyword, $fullStart, $start, $len + $start - $fullStart);
|
||||
$start += $len;
|
||||
$fullStart = $start;
|
||||
continue;
|
||||
}
|
||||
$arr[] = new Token(TokenKind::BackslashToken, $fullStart, $start, 1);
|
||||
$start++;
|
||||
|
||||
// TODO: TokenStringMaps::RESERVED_WORDS[$name] ?? TokenKind::Name for compatibility?
|
||||
$arr[] = new Token(TokenKind::Name, $start, $start, $len);
|
||||
|
||||
$start += $len;
|
||||
$fullStart = $start;
|
||||
}
|
||||
break;
|
||||
case \T_COMMENT:
|
||||
case \T_DOC_COMMENT:
|
||||
if ($treatCommentsAsTrivia) {
|
||||
@@ -147,6 +207,22 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content the raw php code
|
||||
* @param ?int $parseContext can be SourceElements when extracting doc comments.
|
||||
* Having this available may be useful for subclasses to decide whether or not to post-process results, cache results, etc.
|
||||
* @return array[]|string[] an array of tokens. When concatenated, these tokens must equal $content.
|
||||
*
|
||||
* This exists so that it can be overridden in subclasses, e.g. to cache the result of tokenizing entire files.
|
||||
* Applications using tolerant-php-parser may often end up needing to use the token stream for other reasons that are hard to do in the resulting AST,
|
||||
* such as iterating over T_COMMENTS, checking for inline html,
|
||||
* looking up all tokens (including skipped tokens) on a given line, etc.
|
||||
*/
|
||||
protected static function tokenGetAll(string $content, $parseContext): array
|
||||
{
|
||||
return @\token_get_all($content);
|
||||
}
|
||||
|
||||
const TOKEN_MAP = [
|
||||
T_CLASS_C => TokenKind::Name,
|
||||
T_DIR => TokenKind::Name,
|
||||
@@ -186,6 +262,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,
|
||||
@@ -206,6 +283,7 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
T_INTERFACE => TokenKind::InterfaceKeyword,
|
||||
T_ISSET => TokenKind::IsSetKeyword,
|
||||
T_LIST => TokenKind::ListKeyword,
|
||||
T_MATCH => TokenKind::MatchKeyword,
|
||||
T_NAMESPACE => TokenKind::NamespaceKeyword,
|
||||
T_NEW => TokenKind::NewKeyword,
|
||||
T_LOGICAL_OR => TokenKind::OrKeyword,
|
||||
@@ -213,6 +291,7 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
T_PRIVATE => TokenKind::PrivateKeyword,
|
||||
T_PROTECTED => TokenKind::ProtectedKeyword,
|
||||
T_PUBLIC => TokenKind::PublicKeyword,
|
||||
T_READONLY => TokenKind::ReadonlyKeyword,
|
||||
T_REQUIRE => TokenKind::RequireKeyword,
|
||||
T_REQUIRE_ONCE => TokenKind::RequireOnceKeyword,
|
||||
T_RETURN => TokenKind::ReturnKeyword,
|
||||
@@ -237,6 +316,8 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
"}" => TokenKind::CloseBraceToken,
|
||||
"." => TokenKind::DotToken,
|
||||
T_OBJECT_OPERATOR => TokenKind::ArrowToken,
|
||||
T_NULLSAFE_OBJECT_OPERATOR => TokenKind::QuestionArrowToken,
|
||||
T_ATTRIBUTE => TokenKind::AttributeToken,
|
||||
T_INC => TokenKind::PlusPlusToken,
|
||||
T_DEC => TokenKind::MinusMinusToken,
|
||||
T_POW => TokenKind::AsteriskAsteriskToken,
|
||||
@@ -261,6 +342,8 @@ class PhpTokenizer implements TokenStreamProviderInterface {
|
||||
"^" => TokenKind::CaretToken,
|
||||
"|" => TokenKind::BarToken,
|
||||
"&" => TokenKind::AmpersandToken,
|
||||
T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG => TokenKind::AmpersandToken,
|
||||
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG => TokenKind::AmpersandToken,
|
||||
T_BOOLEAN_AND => TokenKind::AmpersandAmpersandToken,
|
||||
T_BOOLEAN_OR => TokenKind::BarBarToken,
|
||||
":" => TokenKind::ColonToken,
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
namespace Microsoft\PhpParser;
|
||||
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class SkippedToken extends Token {
|
||||
public function __construct(Token $token) {
|
||||
parent::__construct($token->kind, $token->fullStart, $token->start, $token->length);
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return array_merge(
|
||||
["error" => $this->getTokenKindNameFromValue(TokenKind::SkippedToken)],
|
||||
|
||||
+4
-1
@@ -6,6 +6,8 @@
|
||||
|
||||
namespace Microsoft\PhpParser;
|
||||
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
use function substr;
|
||||
|
||||
class Token implements \JsonSerializable {
|
||||
@@ -61,7 +63,7 @@ class Token implements \JsonSerializable {
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFullStart() {
|
||||
public function getFullStartPosition() {
|
||||
return $this->fullStart;
|
||||
}
|
||||
|
||||
@@ -110,6 +112,7 @@ class Token implements \JsonSerializable {
|
||||
return $mapToKindName[$kind] ?? $kind;
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
$kindName = $this->getTokenKindNameFromValue($this->kind);
|
||||
|
||||
|
||||
@@ -86,6 +86,11 @@ class TokenKind {
|
||||
const YieldKeyword = 166;
|
||||
const YieldFromKeyword = 167;
|
||||
const FnKeyword = 168;
|
||||
const MatchKeyword = 169;
|
||||
/** @deprecated use IterableReservedWord */
|
||||
const IterableKeyword = self::IterableReservedWord;
|
||||
const EnumKeyword = 171;
|
||||
const ReadonlyKeyword = 172;
|
||||
|
||||
const OpenBracketToken = 201;
|
||||
const CloseBracketToken = 202;
|
||||
@@ -148,6 +153,8 @@ class TokenKind {
|
||||
const BacktickToken = 260;
|
||||
const QuestionToken = 261;
|
||||
const QuestionQuestionEqualsToken = 262;
|
||||
const QuestionArrowToken = 263;
|
||||
const AttributeToken = 264;
|
||||
|
||||
const DecimalLiteralToken = 301;
|
||||
const OctalLiteralToken = 302;
|
||||
@@ -166,6 +173,8 @@ class TokenKind {
|
||||
const StringReservedWord = 320;
|
||||
const BoolReservedWord = 321;
|
||||
const NullReservedWord = 322;
|
||||
const MixedReservedWord = 340;
|
||||
const IterableReservedWord = 170;
|
||||
|
||||
const ScriptSectionStartTag = 323;
|
||||
const ScriptSectionEndTag = 324;
|
||||
@@ -188,6 +197,7 @@ class TokenKind {
|
||||
const ReturnType = 336;
|
||||
const InlineHtml = 337;
|
||||
const PropertyType = 338;
|
||||
const EnumType = 339;
|
||||
|
||||
// const DollarOpenCurly = 339;
|
||||
const EncapsedAndWhitespace = 400;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Microsoft\PhpParser;
|
||||
use Microsoft\PhpParser\TokenKind;
|
||||
|
||||
class TokenStringMaps {
|
||||
const KEYWORDS = array(
|
||||
const KEYWORDS = [
|
||||
"abstract" => TokenKind::AbstractKeyword,
|
||||
"and" => TokenKind::AndKeyword,
|
||||
"array" => TokenKind::ArrayKeyword,
|
||||
@@ -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,
|
||||
@@ -63,6 +64,7 @@ class TokenStringMaps {
|
||||
"private" => TokenKind::PrivateKeyword,
|
||||
"protected" => TokenKind::ProtectedKeyword,
|
||||
"public" => TokenKind::PublicKeyword,
|
||||
"readonly" => TokenKind::ReadonlyKeyword,
|
||||
"require" => TokenKind::RequireKeyword,
|
||||
"require_once" => TokenKind::RequireOnceKeyword,
|
||||
"return" => TokenKind::ReturnKeyword,
|
||||
@@ -81,7 +83,7 @@ class TokenStringMaps {
|
||||
|
||||
|
||||
// TODO soft reserved words?
|
||||
);
|
||||
];
|
||||
|
||||
const RESERVED_WORDS = [
|
||||
// http://php.net/manual/en/reserved.constants.php
|
||||
@@ -103,10 +105,12 @@ class TokenStringMaps {
|
||||
"integer" => TokenKind::IntegerReservedWord,
|
||||
"object" => TokenKind::ObjectReservedWord,
|
||||
"real" => TokenKind::RealReservedWord,
|
||||
"void" => TokenKind::VoidReservedWord
|
||||
"void" => TokenKind::VoidReservedWord,
|
||||
"iterable" => TokenKind::IterableReservedWord,
|
||||
"mixed" => TokenKind::MixedReservedWord,
|
||||
];
|
||||
|
||||
const OPERATORS_AND_PUNCTUATORS = array(
|
||||
const OPERATORS_AND_PUNCTUATORS = [
|
||||
"[" => TokenKind::OpenBracketToken,
|
||||
"]" => TokenKind::CloseBracketToken,
|
||||
"(" => TokenKind::OpenParenToken,
|
||||
@@ -160,6 +164,7 @@ class TokenStringMaps {
|
||||
"^=" => TokenKind::CaretEqualsToken,
|
||||
"|=" => TokenKind::BarEqualsToken,
|
||||
"," => TokenKind::CommaToken,
|
||||
"?->" => TokenKind::QuestionArrowToken,
|
||||
"??" => TokenKind::QuestionQuestionToken,
|
||||
"??=" => TokenKind::QuestionQuestionEqualsToken,
|
||||
"<=>" => TokenKind::LessThanEqualsGreaterThanToken,
|
||||
@@ -178,7 +183,7 @@ class TokenStringMaps {
|
||||
"?>\r" => TokenKind::ScriptSectionEndTag, // TODO, technically not an operator
|
||||
"@" => TokenKind::AtSymbolToken, // TODO not in spec
|
||||
"`" => TokenKind::BacktickToken
|
||||
);
|
||||
];
|
||||
|
||||
// TODO add new tokens
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user