diff --git a/.travis.yml b/.travis.yml index 6d15865e..030b50a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,10 @@ language: php php: - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - 7.4 - # Should be changed to 8.0 once travis officially provides that label. - - nightly + - '7.2' + - '7.3' + - '7.4' + - '8.0' env: - VALIDATION=false @@ -30,12 +27,7 @@ cache: before_script: - if [[ $STATIC_ANALYSIS = true ]]; then composer require phpstan/phpstan --no-update; fi - - | - if php -r 'exit(PHP_MAJOR_VERSION < 8 ? 0 : 1);'; - then composer install - else - composer install --ignore-platform-reqs - fi + - composer install - set -e # Stop on first error. - phpenv config-rm xdebug.ini || true - if find . -name "*.php" -path "./src/*" -path "./experiments/*" -path "./tools/*" -path "./syntax-visualizer/server/src/*" -exec php -l {} 2>&1 \; | grep "syntax error, unexpected"; then exit 1; fi diff --git a/README.md b/README.md index cf658853..60a18364 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,15 @@ [![Build Status](https://travis-ci.org/Microsoft/tolerant-php-parser.svg?branch=master)](https://travis-ci.org/Microsoft/tolerant-php-parser) This is an early-stage PHP parser designed, from the beginning, for IDE usage scenarios (see [Design Goals](#design-goals) for more details). There is -still a ton of work to be done, so at this point, this repo mostly serves as +still a ton of work to be done, so at this point, this repo mostly serves as an experiment and the start of a conversation. ![image](https://cloud.githubusercontent.com/assets/762848/19023070/4ab01c92-889a-11e6-9bb5-ec1a6816aba2.png) +This is the v0.1 branch, which changes data structures to support syntax added after the initial 0.0.x release line. + ## Get Started -After you've [configured your machine](docs/GettingStarted.md), you can use the parser to generate and work +After you've [configured your machine](docs/GettingStarted.md), you can use the parser to generate and work with the Abstract Syntax Tree (AST) via a friendly API. ```php getDescendantNodes() as $descendant) { // All Nodes link back to their parents, so it's easy to navigate the tree. $grandParent = $descendant->getParent()->getParent(); var_dump($grandParent->getNodeKindName()); - + // The AST is fully-representative, and round-trippable to the original source. // This enables consumers to build reliable formatting and refactoring tools. var_dump($grandParent->getLeadingCommentAndWhitespaceText()); } - + // In addition to retrieving all children or descendants of a Node, // Nodes expose properties specific to the Node type. if ($descendant instanceof Node\Expression\EchoExpression) { $echoKeywordStartPosition = $descendant->echoKeyword->getStartPosition(); - // To cut down on memory consumption, positions are represented as a single integer + // To cut down on memory consumption, positions are represented as a single integer // index into the document, but their line and character positions are easily retrieved. $lineCharacterPosition = PositionUtilities::getLineCharacterPositionFromPosition( $echoKeywordStartPosition, @@ -59,15 +61,15 @@ foreach ($astNode->getDescendantNodes() as $descendant) { } ``` -> Note: [the API](docs/ApiDocumentation.md) is not yet finalized, so please file issues let us know what functionality you want exposed, +> Note: [the API](docs/ApiDocumentation.md) is not yet finalized, so please file issues let us know what functionality you want exposed, and we'll see what we can do! Also please file any bugs with unexpected behavior in the parse tree. We're still in our early stages, and any feedback you have is much appreciated :smiley:. ## Design Goals * Error tolerant design - in IDE scenarios, code is, by definition, incomplete. In the case that invalid code is entered, the -parser should still be able to recover and produce a valid + complete tree, as well as relevant diagnostics. +parser should still be able to recover and produce a valid + complete tree, as well as relevant diagnostics. * Fast and lightweight (should be able to parse several MB of source code per second, - to leave room for other features). + to leave room for other features). * Memory-efficient data structures * Allow for incremental parsing in the future * Adheres to [PHP language spec](https://github.com/php/php-langspec), @@ -83,34 +85,34 @@ so each language server operation should be < 50 ms to leave room for all the confusing, really fast, so readability and debug-ability is high priority. * Testable - the parser should produce provably valid parse trees. We achieve this by defining and continuously testing a set of invariants about the tree. -* Friendly and descriptive API to make it easy for others to build on. +* Friendly and descriptive API to make it easy for others to build on. * Written in PHP - make it as easy as possible for the PHP community to consume and contribute. ## Current Status and Approach To ensure a sufficient level of correctness at every step of the way, the parser is being developed using the following incremental approach: -* [x] **Phase 1:** Write lexer that does not support PHP grammar, but supports EOF +* [x] **Phase 1:** Write lexer that does not support PHP grammar, but supports EOF and Unknown tokens. Write tests for all invariants. * [x] **Phase 2:** Support PHP lexical grammar, lots of tests -* [x] **Phase 3:** Write a parser that does not support PHP grammar, but produces tree of +* [x] **Phase 3:** Write a parser that does not support PHP grammar, but produces tree of Error Nodes. Write tests for all invariants. * [x] **Phase 4:** Support PHP syntactic grammar, lots of tests * [ ] **Phase 5 (in progress :running:):** Real-world validation and optimization * [ ] _**Correctness:**_ validate that there are no errors produced on sample codebases, benchmark against other parsers (investigate any instance of disagreement), fuzz-testing * [ ] _**Performance:**_ profile, benchmark against large PHP applications -* [ ] **Phase 6:** Finalize API to make it as easy as possible for people to consume. +* [ ] **Phase 6:** Finalize API to make it as easy as possible for people to consume. ### Additional notes A few of the PHP grammatical constructs (namely yield-expression, and template strings) are not yet supported and there are also other miscellaneous bugs. However, because the parser is error-tolerant, these errors are handled gracefully, and the resulting tree is otherwise complete. To get a more holistic sense for -where we are, you can run the "validation" test suite (see [Contributing Guidelines](Contributing.md) for more info +where we are, you can run the "validation" test suite (see [Contributing Guidelines](Contributing.md) for more info on running tests). Or simply, take a look at the current [validation test results](https://travis-ci.org/Microsoft/tolerant-php-parser). -Even though we haven't yet begun the performance optimization stage, we have seen promising results so far, -and have plenty more room for improvement. See [How It Works](docs/HowItWorks.md) for details on our current -approach, and run the [Performance Tests](Contributing.md#running-performance-tests) on your +Even though we haven't yet begun the performance optimization stage, we have seen promising results so far, +and have plenty more room for improvement. See [How It Works](docs/HowItWorks.md) for details on our current +approach, and run the [Performance Tests](Contributing.md#running-performance-tests) on your own machine to see for yourself. ## Learn more @@ -119,7 +121,7 @@ own machine to see for yourself. **:book: [Documentation](docs/GettingStarted.md#getting-started)** - learn how to reference the parser from your project, and how to perform operations on the AST to answer questions about your code. -**:eyes: [Syntax Visualizer Tool](syntax-visualizer/client#php-parser-syntax-visualizer-tool)** - get a more tangible feel for the AST. Get creative - see if you can break it! +**:eyes: [Syntax Visualizer Tool](syntax-visualizer/client#php-parser-syntax-visualizer-tool)** - get a more tangible feel for the AST. Get creative - see if you can break it! **:chart_with_upwards_trend: [Current Status and Approach](#current-status-and-approach)** - how much of the grammar is supported? Performance? Memory? API stability? @@ -131,10 +133,10 @@ operations on the AST to answer questions about your code. * [Validation Strategy](docs/HowItWorks.md#validation-strategy) **:sparkling_heart: [Contribute!](Contributing.md)** - learn how to get involved, check out some pointers to educational commits that'll -help you ramp up on the codebase (even if you've never worked on a parser before), +help you ramp up on the codebase (even if you've never worked on a parser before), and recommended workflows that make it easier to iterate. --- -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/composer.json b/composer.json index 471f798e..91df816a 100644 --- a/composer.json +++ b/composer.json @@ -3,10 +3,10 @@ "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios", "type": "library", "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "phpunit/phpunit": "^6.4|^7.5.20" + "phpunit/phpunit": "^8.5.15" }, "license": "MIT", "authors": [ diff --git a/docs/ApiDocumentation.md b/docs/ApiDocumentation.md index 58ce4604..adbee29f 100644 --- a/docs/ApiDocumentation.md +++ b/docs/ApiDocumentation.md @@ -10,15 +10,15 @@ ```php public function getNodeKindName ( ) : string ``` -### Node::getStart +### Node::getStartPosition Gets start position of Node, not including leading comments and whitespace. ```php -public function getStart ( ) : int +public function getStartPosition ( ) : int ``` -### Node::getFullStart +### Node::getFullStartPosition Gets start position of Node, including leading comments and whitespace ```php -public function getFullStart ( ) : int +public function getFullStartPosition ( ) : int ``` ### Node::getParent Gets parent of current node (returns null if has no parent) @@ -198,11 +198,11 @@ public function getFullText ( string & $document ) : string ```php public function getStartPosition ( ) ``` -### Token::getFullStart +### Token::getFullStartPosition > TODO: add doc comment ```php -public function getFullStart ( ) +public function getFullStartPosition ( ) ``` ### Token::getWidth > TODO: add doc comment diff --git a/src/FilePositionMap.php b/src/FilePositionMap.php index 1cb056bc..c6c9f479 100644 --- a/src/FilePositionMap.php +++ b/src/FilePositionMap.php @@ -34,33 +34,11 @@ public function __construct(string $file_contents) { $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 @@ public function getStartLine($node) : int { * 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 */ diff --git a/src/MissingToken.php b/src/MissingToken.php index be3e6e04..177b46be 100644 --- a/src/MissingToken.php +++ b/src/MissingToken.php @@ -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)], diff --git a/src/Node.php b/src/Node.php index 4b87d417..2eb6ebdc 100644 --- a/src/Node.php +++ b/src/Node.php @@ -11,6 +11,7 @@ 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 @@ public function getNodeKindName() : string { * @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 @@ public function getStart() : int { * @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 @@ public function getFullStart() : int { $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 @@ public function getChildNames() { * @return int */ public function getWidth() : int { - $first = $this->getStart(); + $first = $this->getStartPosition(); $last = $this->getEndPosition(); return $last - $first; @@ -342,7 +329,7 @@ public function getWidth() : int { * @return int */ public function getFullWidth() : int { - $first = $this->getFullStart(); + $first = $this->getFullStartPosition(); $last = $this->getEndPosition(); return $last - $first; @@ -353,7 +340,7 @@ public function getFullWidth() : int { * @return string */ public function getText() : string { - $start = $this->getStart(); + $start = $this->getStartPosition(); $end = $this->getEndPosition(); $fileContents = $this->getFileContents(); @@ -365,7 +352,7 @@ public function getText() : string { * @return string */ public function getFullText() : string { - $start = $this->getFullStart(); + $start = $this->getFullStartPosition(); $end = $this->getEndPosition(); $fileContents = $this->getFileContents(); @@ -387,13 +374,14 @@ public function getLeadingCommentAndWhitespaceText() : string { } 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 @@ public function getDescendantNodeAtPosition(int $pos) { * @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 @@ private function containsPosition(int $pos): bool { 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 @@ public function getImportTablesForCurrentScope() { $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 @@ public function getImportTablesForCurrentScope() { $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; @@ -609,11 +597,11 @@ public function getNamespaceDefinition() { 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 @@ public function getPreviousSibling() { * 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 @@ private function addToImportTable($alias, $functionOrConst, $namespaceNameParts, // 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]; } /** diff --git a/src/Node/CatchClause.php b/src/Node/CatchClause.php index 2784d116..33a6e9e8 100644 --- a/src/Node/CatchClause.php +++ b/src/Node/CatchClause.php @@ -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,16 +16,8 @@ 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 QualifiedNameList[]|MissingToken */ + public $qualifiedNameList; /** @var Token|null */ public $variableName; /** @var Token */ @@ -34,8 +28,7 @@ class CatchClause extends Node { const CHILD_NAMES = [ 'catch', 'openParen', - 'qualifiedName', - 'otherQualifiedNameList', + 'qualifiedNameList', 'variableName', 'closeParen', 'compoundStatement' diff --git a/src/Node/Expression/AnonymousFunctionCreationExpression.php b/src/Node/Expression/AnonymousFunctionCreationExpression.php index a723d116..40abdb5b 100644 --- a/src/Node/Expression/AnonymousFunctionCreationExpression.php +++ b/src/Node/Expression/AnonymousFunctionCreationExpression.php @@ -38,8 +38,7 @@ class AnonymousFunctionCreationExpression extends Expression implements Function // FunctionReturnType 'colonToken', 'questionToken', - 'returnType', - 'otherReturnTypes', + 'returnTypeList', // FunctionBody 'compoundStatementOrSemicolon' diff --git a/src/Node/Expression/ArgumentExpression.php b/src/Node/Expression/ArgumentExpression.php index ac1596ac..881a5c0d 100644 --- a/src/Node/Expression/ArgumentExpression.php +++ b/src/Node/Expression/ArgumentExpression.php @@ -10,15 +10,12 @@ use Microsoft\PhpParser\Token; class ArgumentExpression extends Expression { - /** @var Token|null for php named arguments. If this is set, byRefToken and dotDotDotToken will not be set. */ + /** @var Token|null for php named arguments. If this is set, dotDotDotToken will not be set. */ public $name; /** @var Token|null */ public $colonToken; - /** @var Token|null */ - public $byRefToken; // TODO removed in newer versions of PHP. Also only accept variable, not expression if byRef - /** @var Token|null */ public $dotDotDotToken; @@ -28,7 +25,6 @@ class ArgumentExpression extends Expression { const CHILD_NAMES = [ 'name', 'colonToken', - 'byRefToken', 'dotDotDotToken', 'expression' ]; diff --git a/src/Node/Expression/ArrowFunctionCreationExpression.php b/src/Node/Expression/ArrowFunctionCreationExpression.php index 8f93de00..96673692 100644 --- a/src/Node/Expression/ArrowFunctionCreationExpression.php +++ b/src/Node/Expression/ArrowFunctionCreationExpression.php @@ -40,8 +40,7 @@ class ArrowFunctionCreationExpression extends Expression implements FunctionLike // FunctionReturnType 'colonToken', 'questionToken', - 'returnType', - 'otherReturnTypes', + 'returnTypeList', // body 'arrowToken', diff --git a/src/Node/FunctionReturnType.php b/src/Node/FunctionReturnType.php index 8f836773..1429234e 100644 --- a/src/Node/FunctionReturnType.php +++ b/src/Node/FunctionReturnType.php @@ -11,10 +11,9 @@ 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; } diff --git a/src/Node/MethodDeclaration.php b/src/Node/MethodDeclaration.php index a674e930..49cf1822 100644 --- a/src/Node/MethodDeclaration.php +++ b/src/Node/MethodDeclaration.php @@ -34,8 +34,7 @@ class MethodDeclaration extends Node implements FunctionLike, ModifiedTypeInterf // FunctionReturnType 'colonToken', 'questionToken', - 'returnType', - 'otherReturnTypes', + 'returnTypeList', // FunctionBody 'compoundStatementOrSemicolon' diff --git a/src/Node/MissingMemberDeclaration.php b/src/Node/MissingMemberDeclaration.php index 6eca7333..c3043ca5 100644 --- a/src/Node/MissingMemberDeclaration.php +++ b/src/Node/MissingMemberDeclaration.php @@ -20,17 +20,13 @@ class MissingMemberDeclaration extends Node implements ModifiedTypeInterface { /** @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', ]; } diff --git a/src/Node/Parameter.php b/src/Node/Parameter.php index 24571638..f8294b31 100644 --- a/src/Node/Parameter.php +++ b/src/Node/Parameter.php @@ -6,6 +6,7 @@ namespace Microsoft\PhpParser\Node; +use Microsoft\PhpParser\MissingToken; use Microsoft\PhpParser\Node; use Microsoft\PhpParser\Token; @@ -16,13 +17,8 @@ class Parameter extends Node { public $visibilityToken; /** @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 */ @@ -38,8 +34,7 @@ class Parameter extends Node { 'attributes', 'visibilityToken', 'questionToken', - 'typeDeclaration', - 'otherTypeDeclarations', + 'typeDeclarationList', 'byRefToken', 'dotDotDotToken', 'variableName', diff --git a/src/Node/PropertyDeclaration.php b/src/Node/PropertyDeclaration.php index 603d4955..1995aed5 100644 --- a/src/Node/PropertyDeclaration.php +++ b/src/Node/PropertyDeclaration.php @@ -6,9 +6,11 @@ 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; class PropertyDeclaration extends Node implements ModifiedTypeInterface { @@ -20,14 +22,8 @@ class PropertyDeclaration extends Node implements ModifiedTypeInterface { /** @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; @@ -39,8 +35,7 @@ class PropertyDeclaration extends Node implements ModifiedTypeInterface { 'attributes', 'modifiers', 'questionToken', - 'typeDeclaration', - 'otherTypeDeclarations', + 'typeDeclarationList', 'propertyElements', 'semicolon' ]; diff --git a/src/Node/QualifiedName.php b/src/Node/QualifiedName.php index 47663037..6955dc0e 100644 --- a/src/Node/QualifiedName.php +++ b/src/Node/QualifiedName.php @@ -81,7 +81,7 @@ public function isUnqualifiedName() : bool { */ 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 @@ public function getResolvedName($namespaceDefinition = null) { 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. diff --git a/src/Node/Statement/BreakOrContinueStatement.php b/src/Node/Statement/BreakOrContinueStatement.php index e0ed83e1..442a2a87 100644 --- a/src/Node/Statement/BreakOrContinueStatement.php +++ b/src/Node/Statement/BreakOrContinueStatement.php @@ -60,12 +60,7 @@ public function getDiagnosticForNode() { } } - if ($breakoutLevel instanceof Token) { - $start = $breakoutLevel->getStartPosition(); - } - else { - $start = $breakoutLevel->getStart(); - } + $start = $breakoutLevel->getStartPosition(); $end = $breakoutLevel->getEndPosition(); return new Diagnostic( diff --git a/src/Node/Statement/DeclareStatement.php b/src/Node/Statement/DeclareStatement.php index fcf39715..d9b6155e 100644 --- a/src/Node/Statement/DeclareStatement.php +++ b/src/Node/Statement/DeclareStatement.php @@ -6,7 +6,7 @@ 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; @@ -16,10 +16,9 @@ class DeclareStatement extends StatementNode { public $declareKeyword; /** @var Token */ public $openParen; - /** @var Node */ - public $declareDirective; - /** @var DelimitedList\DeclareDirectiveList|null TODO: Merge with $declareDirective in a future backwards incompatible release. */ - public $otherDeclareDirectives; + // 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 */ @@ -34,8 +33,7 @@ class DeclareStatement extends StatementNode { const CHILD_NAMES = [ 'declareKeyword', 'openParen', - 'declareDirective', - 'otherDeclareDirectives', + 'declareDirectiveList', 'closeParen', 'colon', 'statements', diff --git a/src/Node/Expression/EchoExpression.php b/src/Node/Statement/EchoStatement.php similarity index 65% rename from src/Node/Expression/EchoExpression.php rename to src/Node/Statement/EchoStatement.php index d8ea286e..5cfc1067 100644 --- a/src/Node/Expression/EchoExpression.php +++ b/src/Node/Statement/EchoStatement.php @@ -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 (`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; @@ -222,7 +221,7 @@ private function parseList($parentNode, int $listParseContext) { $this->currentParseContext |= 1 << $listParseContext; $parseListElementFn = $this->getParseListElementFn($listParseContext); - $nodeArray = array(); + $nodeArray = []; while (!$this->isListTerminator($listParseContext)) { if ($this->isValidListElement($listParseContext, $this->getCurrentToken())) { $element = $parseListElementFn($parentNode); @@ -534,8 +533,6 @@ private function parseStatementFn() { 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: @@ -830,13 +827,10 @@ private function parseParameterFn() { } $parameter->visibilityToken = $this->eatOptional([TokenKind::PublicKeyword, TokenKind::ProtectedKeyword, TokenKind::PrivateKeyword]); $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->questionToken && !$parameter->typeDeclarationList) { + // TODO ParameterType? + $parameter->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart); } $parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); // TODO add post-parse rule that prevents assignment @@ -858,15 +852,10 @@ private function parseParameterFn() { 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; } /** @@ -1304,7 +1293,7 @@ private function parseStringLiteralExpression2($parentNode) { $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) { @@ -1460,7 +1449,7 @@ private function isModifier($token) { } private function parseModifiers() { - $modifiers = array(); + $modifiers = []; $token = $this->getCurrentToken(); while ($this->isModifier($token)) { $modifiers[] = $token; @@ -1974,12 +1963,12 @@ private function parseUnaryExpressionOrHigher($parentNode) { 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. @@ -2410,17 +2399,7 @@ private function parseReturnStatement($parentNode) { return $returnStatement; } - private function parseThrowStatement($parentNode) { - $throwStatement = new ThrowStatement(); - $throwStatement->parent = $parentNode; - $throwStatement->throwKeyword = $this->eat1(TokenKind::ThrowKeyword); - // TODO error for failures to parse expressions when not optional - $throwStatement->expression = $this->parseExpression($throwStatement); - $throwStatement->semicolon = $this->eatSemicolonOrAbortStatement(); - - return $throwStatement; - } - + /** @return ThrowExpression */ private function parseThrowExpression($parentNode) { $throwExpression = new ThrowExpression(); $throwExpression->parent = $parentNode; @@ -2437,7 +2416,7 @@ private function parseTryStatement($parentNode) { $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); } @@ -2454,9 +2433,7 @@ private function parseCatchClause($parentNode) { $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->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); @@ -2501,27 +2478,7 @@ private function parseDeclareStatement($parentNode) { private function parseAndSetDeclareDirectiveList($parentNode) { $declareDirectiveList = $this->parseDeclareDirectiveList($parentNode); - if (!$declareDirectiveList) { - $declareDirective = new DeclareDirective(); - $declareDirective->parent = $parentNode; - - $declareDirective->name = new MissingToken(TokenKind::Name, $this->token->fullStart); - $declareDirective->equals = new MissingToken(TokenKind::EqualsToken, $this->token->fullStart); - // TODO: This is matching the first token in $this::parseDeclareDirectiveFn. - // Probably best to emit a more general "literal error". - $declareDirective->literal = new MissingToken(TokenKind::FloatingLiteralToken, $this->token->fullStart); - - $parentNode->declareDirective = $declareDirective; - return; - } - - $declareDirective = array_shift($declareDirectiveList->children); - $parentNode->declareDirective = $declareDirective; - $declareDirective->parent = $parentNode; - - if ($declareDirectiveList->children) { - $parentNode->otherDeclareDirectives = $declareDirectiveList; - } + $parentNode->declareDirectiveList = $declareDirectiveList ?? new MissingToken(TokenKind::Name, $this->token->fullStart); } /** @@ -2645,34 +2602,15 @@ private function parseScriptInclusionExpression($parentNode) { 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) { @@ -2739,16 +2677,16 @@ private function parseExpressionList($parentExpression) { ); } - private function parseUnsetIntrinsicExpression($parentNode) { - $unsetExpression = new UnsetIntrinsicExpression(); - $unsetExpression->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); + private function parseUnsetStatement($parentNode) { + $unsetStatement = new UnsetStatement(); + $unsetStatement->parent = $parentNode; - 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) { @@ -3003,7 +2941,6 @@ private function parseArgumentExpressionFn() { $argumentExpression->name = $name; $argumentExpression->colonToken = $this->eat1(TokenKind::ColonToken); } else { - $argumentExpression->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); $argumentExpression->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken); } $argumentExpression->expression = $this->parseExpression($argumentExpression); @@ -3261,18 +3198,10 @@ private function parsePropertyDeclaration($parentNode, $modifiers, $questionToke $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); @@ -3664,14 +3593,10 @@ private function makeMissingMemberDeclaration($parentNode, $modifiers, $question $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; } @@ -3713,15 +3638,10 @@ private function parseTraitSelectOrAliasClauseFn() { $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 @@ -4033,14 +3953,10 @@ private function parseInlineHtml($parentNode) { // This is the easiest way to represent `scriptSectionStartTag->kind ?? null) === TokenKind::ScriptSectionStartWithEchoTag) { - $echoStatement = new ExpressionStatement(); - - $echoExpression = new EchoExpression(); - $expressionList = $this->parseExpressionList($echoExpression) ?? (new MissingToken(TokenKind::Expression, $this->token->fullStart)); - $echoExpression->expressions = $expressionList; - $echoExpression->parent = $echoStatement; + $echoStatement = new EchoStatement(); + $expressionList = $this->parseExpressionList($echoStatement) ?? (new MissingToken(TokenKind::Expression, $this->token->fullStart)); + $echoStatement->expressions = $expressionList; - $echoStatement->expression = $echoExpression; $echoStatement->semicolon = $this->eatSemicolonOrAbortStatement(); $echoStatement->parent = $inlineHtml; // Deliberately leave echoKeyword as null instead of MissingToken diff --git a/src/PhpTokenizer.php b/src/PhpTokenizer.php index e951bad6..95faed3e 100644 --- a/src/PhpTokenizer.php +++ b/src/PhpTokenizer.php @@ -82,7 +82,7 @@ public static function getTokensArrayFromContent( $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. diff --git a/src/SkippedToken.php b/src/SkippedToken.php index 4c3174d2..34594db4 100644 --- a/src/SkippedToken.php +++ b/src/SkippedToken.php @@ -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)], diff --git a/src/Token.php b/src/Token.php index f746e8a2..6c9daf74 100644 --- a/src/Token.php +++ b/src/Token.php @@ -6,6 +6,8 @@ namespace Microsoft\PhpParser; +use ReturnTypeWillChange; + use function substr; class Token implements \JsonSerializable { @@ -61,7 +63,7 @@ public function getStartPosition() { /** * @return int */ - public function getFullStart() { + public function getFullStartPosition() { return $this->fullStart; } @@ -110,6 +112,7 @@ public static function getTokenKindNameFromValue($kind) { return $mapToKindName[$kind] ?? $kind; } + #[ReturnTypeWillChange] public function jsonSerialize() { $kindName = $this->getTokenKindNameFromValue($this->kind); diff --git a/src/TokenStringMaps.php b/src/TokenStringMaps.php index 441b33c3..6c1ab835 100644 --- a/src/TokenStringMaps.php +++ b/src/TokenStringMaps.php @@ -9,7 +9,7 @@ use Microsoft\PhpParser\TokenKind; class TokenStringMaps { - const KEYWORDS = array( + const KEYWORDS = [ "abstract" => TokenKind::AbstractKeyword, "and" => TokenKind::AndKeyword, "array" => TokenKind::ArrayKeyword, @@ -82,7 +82,7 @@ class TokenStringMaps { // TODO soft reserved words? - ); + ]; const RESERVED_WORDS = [ // http://php.net/manual/en/reserved.constants.php @@ -109,7 +109,7 @@ class TokenStringMaps { "mixed" => TokenKind::MixedReservedWord, ]; - const OPERATORS_AND_PUNCTUATORS = array( + const OPERATORS_AND_PUNCTUATORS = [ "[" => TokenKind::OpenBracketToken, "]" => TokenKind::CloseBracketToken, "(" => TokenKind::OpenParenToken, @@ -182,7 +182,7 @@ class TokenStringMaps { "?>\r" => TokenKind::ScriptSectionEndTag, // TODO, technically not an operator "@" => TokenKind::AtSymbolToken, // TODO not in spec "`" => TokenKind::BacktickToken - ); + ]; // TODO add new tokens } diff --git a/tests/CallbackTestListener.php b/tests/CallbackTestListener.php index a980fc8a..fb585749 100644 --- a/tests/CallbackTestListener.php +++ b/tests/CallbackTestListener.php @@ -9,28 +9,13 @@ use PHPUnit\Framework\TestListenerDefaultImplementation; use PHPUnit\Framework\AssertionFailedError; -if (PHP_VERSION_ID >= 70100) { - // PHPUnit 7 requires a return type of void, which is impossible in php 7.0 - class CallbackTestListener implements TestListener { - private $cb; - public function __construct(Closure $cb) { - $this->cb = $cb; - } - use TestListenerDefaultImplementation; - // php 7.1 does not support param type widening. - function addFailure(Test $test, AssertionFailedError $e, float $time): void { - ($this->cb)($test); - } +class CallbackTestListener implements TestListener { + private $cb; + public function __construct(Closure $cb) { + $this->cb = $cb; } -} else { - class CallbackTestListener implements TestListener { - private $cb; - public function __construct(Closure $cb) { - $this->cb = $cb; - } - use TestListenerDefaultImplementation; - function addFailure(Test $test, AssertionFailedError $e, $time) { - ($this->cb)($test); - } + use TestListenerDefaultImplementation; + function addFailure(Test $test, AssertionFailedError $e, float $time): void { + ($this->cb)($test); } } diff --git a/tests/LexerInvariantsTest.php b/tests/LexerInvariantsTest.php index ffd6bc43..1d721c93 100644 --- a/tests/LexerInvariantsTest.php +++ b/tests/LexerInvariantsTest.php @@ -10,13 +10,13 @@ use Microsoft\PhpParser\TokenKind; class LexerInvariantsTest extends TestCase { - const FILENAMES = array( + const FILENAMES = [ __dir__ . "/cases/testfile.php", __dir__ . "/cases/commentsFile.php" - ); + ]; public static function tokensArrayProvider() { - $fileToTokensMap = array(); + $fileToTokensMap = []; foreach (self::FILENAMES as $filename) { $lexer = \Microsoft\PhpParser\TokenStreamProviderFactory::GetTokenStreamProvider(file_get_contents($filename)); $fileToTokensMap[basename($filename)] = [$filename, $lexer->getTokensArray()]; diff --git a/tests/LexicalGrammarTest.php b/tests/LexicalGrammarTest.php index 0f70ddb6..6598dce0 100644 --- a/tests/LexicalGrammarTest.php +++ b/tests/LexicalGrammarTest.php @@ -57,7 +57,7 @@ public function lexicalProvider() { $skipped = json_decode(file_get_contents(__DIR__ . "/skipped.json")); - $testProviderArray = array(); + $testProviderArray = []; foreach ($testCases as $testCase) { if (in_array(basename($testCase), $skipped)) { continue; @@ -90,7 +90,7 @@ public function testSpecTokenClassificationAndLength($testCaseFile, $expectedTok public function lexicalSpecProvider() { $testCases = glob(__dir__ . "/cases/php-langspec/**/*.php"); - $testProviderArray = array(); + $testProviderArray = []; foreach ($testCases as $testCase) { $testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree"]; } diff --git a/tests/ParserFrameworkValidationTests.php b/tests/ParserFrameworkValidationTests.php index d73efc84..74ca07a2 100644 --- a/tests/ParserFrameworkValidationTests.php +++ b/tests/ParserFrameworkValidationTests.php @@ -12,7 +12,7 @@ public function frameworkErrorProvider() { $totalSize = 0; $frameworks = glob(__DIR__ . "/../validation/frameworks/*", GLOB_ONLYDIR); - $testProviderArray = array(); + $testProviderArray = []; foreach ($frameworks as $frameworkDir) { $frameworkName = basename($frameworkDir); $iterator = new RecursiveDirectoryIterator(__DIR__ . "/../validation/frameworks/" . $frameworkName); diff --git a/tests/ParserGrammarTest.php b/tests/ParserGrammarTest.php index a514ae1e..1bdd9645 100644 --- a/tests/ParserGrammarTest.php +++ b/tests/ParserGrammarTest.php @@ -88,7 +88,7 @@ public function treeProvider() { $testCases = glob(self::FILE_PATTERN . ".php"); $skipped = json_decode(file_get_contents(__DIR__ . "/skipped.json")); - $testProviderArray = array(); + $testProviderArray = []; foreach ($testCases as $testCase) { if (in_array(basename($testCase), $skipped)) { continue; @@ -124,7 +124,7 @@ public function outTreeProvider() { $testCases = glob(__DIR__ . "/cases/php-langspec/**/*.php"); $skipped = json_decode(file_get_contents(__DIR__ . "/skipped.json")); - $testProviderArray = array(); + $testProviderArray = []; foreach ($testCases as $case) { if (in_array(basename($case), $skipped)) { continue; diff --git a/tests/ParserInvariantsTest.php b/tests/ParserInvariantsTest.php index e66b1a9f..54b49e0d 100644 --- a/tests/ParserInvariantsTest.php +++ b/tests/ParserInvariantsTest.php @@ -13,7 +13,7 @@ class ParserInvariantsTest extends LexerInvariantsTest { const FILENAME_PATTERN = __dir__ . "/cases/{parser,parser74,}/*.php"; public static function sourceFileNodeProvider() { - $testFiles = array(); + $testFiles = []; $testCases = glob(self::FILENAME_PATTERN, GLOB_BRACE); foreach ($testCases as $filename) { @@ -24,13 +24,13 @@ public static function sourceFileNodeProvider() { } public static function tokensArrayProvider() { - $testFiles = array(); + $testFiles = []; $testCases = glob(self::FILENAME_PATTERN, GLOB_BRACE); foreach ($testCases as $filename) { $parser = new \Microsoft\PhpParser\Parser(); $sourceFileNode = $parser->parseSourceFile(file_get_contents($filename)); - $tokensArray = array(); + $tokensArray = []; foreach ($sourceFileNode->getDescendantNodesAndTokens() as $child) { if ($child instanceof \Microsoft\PhpParser\Token) { $tokensArray[] = $child; diff --git a/tests/api/NodeApiTest.php b/tests/api/NodeApiTest.php index 426005f3..67436a59 100644 --- a/tests/api/NodeApiTest.php +++ b/tests/api/NodeApiTest.php @@ -25,7 +25,7 @@ function a () { public static $sourceFileNode; - public static function setUpBeforeClass() { + public static function setUpBeforeClass(): void { $parser = new Parser(); self::$sourceFileNode = $parser->parseSourceFile(self::FILE_CONTENTS); parent::setUpBeforeClass(); diff --git a/tests/cases/parser/InlineHtmlInControlStructures.php.tree b/tests/cases/parser/InlineHtmlInControlStructures.php.tree index 0d102096..33cd49bf 100644 --- a/tests/cases/parser/InlineHtmlInControlStructures.php.tree +++ b/tests/cases/parser/InlineHtmlInControlStructures.php.tree @@ -52,26 +52,22 @@ } }, { - "ExpressionStatement": { - "expression": { - "EchoExpression": { - "echoKeyword": null, - "expressions": { - "ExpressionList": { - "children": [ - { - "StringLiteral": { - "startQuote": null, - "children": { - "kind": "StringLiteralToken", - "textLength": 3 - }, - "endQuote": null - } - } - ] + "EchoStatement": { + "echoKeyword": null, + "expressions": { + "ExpressionList": { + "children": [ + { + "StringLiteral": { + "startQuote": null, + "children": { + "kind": "StringLiteralToken", + "textLength": 3 + }, + "endQuote": null + } } - } + ] } }, "semicolon": null diff --git a/tests/cases/parser/abstractMethodDeclaration1.php.tree b/tests/cases/parser/abstractMethodDeclaration1.php.tree index 1a03253c..2313ff32 100644 --- a/tests/cases/parser/abstractMethodDeclaration1.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration1.php.tree @@ -68,8 +68,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "kind": "SemicolonToken", "textLength": 1 diff --git a/tests/cases/parser/abstractMethodDeclaration2.php.tree b/tests/cases/parser/abstractMethodDeclaration2.php.tree index 42f78cdd..47b5dd25 100644 --- a/tests/cases/parser/abstractMethodDeclaration2.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration2.php.tree @@ -65,8 +65,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "kind": "SemicolonToken", "textLength": 1 diff --git a/tests/cases/parser/abstractMethodDeclaration3.php.tree b/tests/cases/parser/abstractMethodDeclaration3.php.tree index 4e988a77..02f623cf 100644 --- a/tests/cases/parser/abstractMethodDeclaration3.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration3.php.tree @@ -68,8 +68,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/abstractMethodDeclaration4.php.tree b/tests/cases/parser/abstractMethodDeclaration4.php.tree index ad401f6f..376655fd 100644 --- a/tests/cases/parser/abstractMethodDeclaration4.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration4.php.tree @@ -34,8 +34,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/abstractMethodDeclaration5.php.tree b/tests/cases/parser/abstractMethodDeclaration5.php.tree index 651b6ab7..b1b87ed6 100644 --- a/tests/cases/parser/abstractMethodDeclaration5.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration5.php.tree @@ -67,11 +67,16 @@ "textLength": 1 }, "questionToken": null, - "returnType": { - "kind": "BoolReservedWord", - "textLength": 4 + "returnTypeList": { + "QualifiedNameList": { + "children": [ + { + "kind": "BoolReservedWord", + "textLength": 4 + } + ] + } }, - "otherReturnTypes": null, "compoundStatementOrSemicolon": { "kind": "SemicolonToken", "textLength": 1 diff --git a/tests/cases/parser/abstractMethodDeclaration6.php.tree b/tests/cases/parser/abstractMethodDeclaration6.php.tree index ea1a6962..01703411 100644 --- a/tests/cases/parser/abstractMethodDeclaration6.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration6.php.tree @@ -72,11 +72,16 @@ "textLength": 1 }, "questionToken": null, - "returnType": { - "kind": "BoolReservedWord", - "textLength": 4 + "returnTypeList": { + "QualifiedNameList": { + "children": [ + { + "kind": "BoolReservedWord", + "textLength": 4 + } + ] + } }, - "otherReturnTypes": null, "compoundStatementOrSemicolon": { "kind": "SemicolonToken", "textLength": 1 diff --git a/tests/cases/parser/abstractMethodDeclaration7.php.tree b/tests/cases/parser/abstractMethodDeclaration7.php.tree index 3f8d4186..ba3509c2 100644 --- a/tests/cases/parser/abstractMethodDeclaration7.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration7.php.tree @@ -64,8 +64,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree index aa2fea01..88e7e76d 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree @@ -41,8 +41,7 @@ "anonymousFunctionUseClause": null, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { @@ -51,29 +50,25 @@ }, "statements": [ { - "ExpressionStatement": { - "expression": { - "EchoExpression": { - "echoKeyword": { - "kind": "EchoKeyword", - "textLength": 4 - }, - "expressions": { - "ExpressionList": { - "children": [ - { - "StringLiteral": { - "startQuote": null, - "children": { - "kind": "StringLiteralToken", - "textLength": 7 - }, - "endQuote": null - } - } - ] + "EchoStatement": { + "echoKeyword": { + "kind": "EchoKeyword", + "textLength": 4 + }, + "expressions": { + "ExpressionList": { + "children": [ + { + "StringLiteral": { + "startQuote": null, + "children": { + "kind": "StringLiteralToken", + "textLength": 7 + }, + "endQuote": null + } } - } + ] } }, "semicolon": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree index 126a2a88..7c19f03e 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree @@ -84,11 +84,16 @@ "textLength": 1 }, "questionToken": null, - "returnType": { - "kind": "VoidReservedWord", - "textLength": 4 + "returnTypeList": { + "QualifiedNameList": { + "children": [ + { + "kind": "VoidReservedWord", + "textLength": 4 + } + ] + } }, - "otherReturnTypes": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression11.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression11.php.tree index 08021e1b..fa8e19ca 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression11.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression11.php.tree @@ -55,8 +55,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree index e69b922f..3a8cb5ec 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree @@ -38,8 +38,7 @@ "anonymousFunctionUseClause": null, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree index c67ae972..189ced83 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree @@ -35,8 +35,7 @@ "anonymousFunctionUseClause": null, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree index 24f67a7b..e66ea4b0 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree @@ -42,8 +42,7 @@ "anonymousFunctionUseClause": null, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree index e08e3c56..8a8e6f41 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree @@ -55,8 +55,7 @@ "anonymousFunctionUseClause": null, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree index 6dd40366..71d50f86 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree @@ -55,8 +55,7 @@ "anonymousFunctionUseClause": null, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree index 136ee3e9..35a56bfd 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree @@ -94,8 +94,7 @@ }, "colonToken": null, "questionToken": null, - "returnType": null, - "otherReturnTypes": null, + "returnTypeList": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { "openBrace": { diff --git a/tests/cases/parser/binaryAssignmentExpressions6.php.tree b/tests/cases/parser/binaryAssignmentExpressions6.php.tree index 7a7a4a10..e65aedd7 100644 --- a/tests/cases/parser/binaryAssignmentExpressions6.php.tree +++ b/tests/cases/parser/binaryAssignmentExpressions6.php.tree @@ -38,7 +38,6 @@ "ArgumentExpression": { "name": null, "colonToken": null, - "byRefToken": null, "dotDotDotToken": null, "expression": { "UnaryOpExpression": { diff --git a/tests/cases/parser/callExpression10.php.tree b/tests/cases/parser/callExpression10.php.tree index c77c7d88..196ea2a2 100644 --- a/tests/cases/parser/callExpression10.php.tree +++ b/tests/cases/parser/callExpression10.php.tree @@ -38,7 +38,6 @@ "ArgumentExpression": { "name": null, "colonToken": null, - "byRefToken": null, "dotDotDotToken": null, "expression": { "ReservedWord": { @@ -58,7 +57,6 @@ "ArgumentExpression": { "name": null, "colonToken": null, - "byRefToken": null, "dotDotDotToken": null, "expression": { "ReservedWord": { @@ -78,7 +76,6 @@ "ArgumentExpression": { "name": null, "colonToken": null, - "byRefToken": null, "dotDotDotToken": null, "expression": { "ReservedWord": { diff --git a/tests/cases/parser/callExpression12.php.tree b/tests/cases/parser/callExpression12.php.tree index 3ea4b2eb..245292a1 100644 --- a/tests/cases/parser/callExpression12.php.tree +++ b/tests/cases/parser/callExpression12.php.tree @@ -38,7 +38,6 @@ "ArgumentExpression": { "name": null, "colonToken": null, - "byRefToken": null, "dotDotDotToken": null, "expression": { "MemberAccessExpression": { diff --git a/tests/cases/parser/callExpression15.php.tree b/tests/cases/parser/callExpression15.php.tree index 4d83e934..288fd690 100644 --- a/tests/cases/parser/callExpression15.php.tree +++ b/tests/cases/parser/callExpression15.php.tree @@ -73,7 +73,6 @@ "ArgumentExpression": { "name": null, "colonToken": null, - "byRefToken": null, "dotDotDotToken": null, "expression": { "NumericLiteral": { diff --git a/tests/cases/parser/callExpression16.php b/tests/cases/parser/callExpression16.php new file mode 100644 index 00000000..97c1677b --- /dev/null +++ b/tests/cases/parser/callExpression16.php @@ -0,0 +1,4 @@ +