Dep: update

주로 PHAN
This commit is contained in:
2021-08-06 22:39:09 +09:00
parent 5310c2e7f6
commit b306601c72
1098 changed files with 92137 additions and 33228 deletions
@@ -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
View File
@@ -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];
}
/**
@@ -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',
@@ -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 {
}
@@ -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 {
}
@@ -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 {
}
@@ -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',
];
}
@@ -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'
@@ -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'
];
@@ -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,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',
@@ -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
View File
@@ -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.
@@ -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,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',
@@ -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];
}
}
@@ -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'
@@ -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',
@@ -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'
@@ -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',
];
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+10
View File
@@ -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
}