Merge pull request #244 from TysonAndre/multi-class-catch
Fixes #103: Support parsing multiple exception types
This commit is contained in:
Коммит
01479d7e0a
|
@ -16,6 +16,14 @@ class CatchClause extends Node {
|
|||
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 */
|
||||
public $variableName;
|
||||
/** @var Token */
|
||||
|
@ -27,6 +35,7 @@ class CatchClause extends Node {
|
|||
'catch',
|
||||
'openParen',
|
||||
'qualifiedName',
|
||||
'otherQualifiedNameList',
|
||||
'variableName',
|
||||
'closeParen',
|
||||
'compoundStatement'
|
||||
|
|
|
@ -881,7 +881,7 @@ class Parser {
|
|||
case TokenKind::FunctionKeyword:
|
||||
return true;
|
||||
}
|
||||
return \in_array($token->kind, $this->reservedWordTokens);
|
||||
return \in_array($token->kind, $this->reservedWordTokens, true);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1250,6 +1250,20 @@ class Parser {
|
|||
};
|
||||
}
|
||||
|
||||
private function isQualifiedNameStartForCatchFn() {
|
||||
return function ($token) {
|
||||
switch ($token->kind) {
|
||||
case TokenKind::BackslashToken:
|
||||
case TokenKind::NamespaceKeyword:
|
||||
case TokenKind::Name:
|
||||
return true;
|
||||
}
|
||||
// Unfortunately, catch(int $x) is *syntactically valid* php which `php --syntax-check` would accept.
|
||||
// (tolerant-php-parser is concerned with syntax, not semantics)
|
||||
return in_array($token->kind, $this->reservedWordTokens, true);
|
||||
};
|
||||
}
|
||||
|
||||
private function parseQualifiedName($parentNode) {
|
||||
return ($this->parseQualifiedNameFn())($parentNode);
|
||||
}
|
||||
|
@ -2017,7 +2031,9 @@ class Parser {
|
|||
$catchClause->parent = $parentNode;
|
||||
$catchClause->catch = $this->eat1(TokenKind::CatchKeyword);
|
||||
$catchClause->openParen = $this->eat1(TokenKind::OpenParenToken);
|
||||
$catchClause->qualifiedName = $this->parseQualifiedName($catchClause); // TODO generate missing token or error if null
|
||||
$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->closeParen = $this->eat1(TokenKind::CloseParenToken);
|
||||
$catchClause->compoundStatement = $this->parseCompoundStatement($catchClause);
|
||||
|
@ -2678,6 +2694,22 @@ class Parser {
|
|||
$parentNode);
|
||||
}
|
||||
|
||||
private function parseQualifiedNameCatchList($parentNode) {
|
||||
$result = $this->parseDelimitedList(
|
||||
DelimitedList\QualifiedNameList::class,
|
||||
TokenKind::BarToken,
|
||||
$this->isQualifiedNameStartForCatchFn(),
|
||||
$this->parseQualifiedNameFn(),
|
||||
$parentNode);
|
||||
|
||||
// Add a MissingToken so that this will Warn about `catch (T| $x) {}`
|
||||
// TODO: Make this a reusable abstraction?
|
||||
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
|
||||
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function parseInterfaceDeclaration($parentNode) {
|
||||
$interfaceDeclaration = new InterfaceDeclaration(); // TODO verify not nested
|
||||
$interfaceDeclaration->parent = $parentNode;
|
||||
|
|
|
@ -100,7 +100,7 @@ class ParserGrammarTest extends TestCase {
|
|||
$tokens = str_replace("\r\n", "\n", json_encode($sourceFile, JSON_PRETTY_PRINT));
|
||||
file_put_contents($expectedTreeFile, $tokens);
|
||||
|
||||
$this->assertCount(0, DiagnosticsProvider::getDiagnostics($sourceFile));
|
||||
$this->assertSame([], DiagnosticsProvider::getDiagnostics($sourceFile));
|
||||
}
|
||||
|
||||
public function outTreeProvider() {
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": null,
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"error": "MissingToken",
|
||||
"kind": "VariableName",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
try {
|
||||
} catch (Hello| $e) {
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'Name' expected.",
|
||||
"start": 28,
|
||||
"length": 0
|
||||
}
|
||||
]
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TryStatement": {
|
||||
"tryKeyword": {
|
||||
"kind": "TryKeyword",
|
||||
"textLength": 3
|
||||
},
|
||||
"compoundStatement": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"catchClauses": [
|
||||
{
|
||||
"CatchClause": {
|
||||
"catch": {
|
||||
"kind": "CatchKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"openParen": {
|
||||
"kind": "OpenParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": {
|
||||
"QualifiedName": {
|
||||
"globalSpecifier": null,
|
||||
"relativeSpecifier": null,
|
||||
"nameParts": [
|
||||
{
|
||||
"kind": "Name",
|
||||
"textLength": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"otherQualifiedNameList": [
|
||||
{
|
||||
"kind": "BarToken",
|
||||
"textLength": 1
|
||||
},
|
||||
{
|
||||
"error": "MissingToken",
|
||||
"kind": "Name",
|
||||
"textLength": 0
|
||||
}
|
||||
],
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
},
|
||||
"closeParen": {
|
||||
"kind": "CloseParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"compoundStatement": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"finallyClause": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
try {
|
||||
} catch (Hello|NS\World $e) {
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TryStatement": {
|
||||
"tryKeyword": {
|
||||
"kind": "TryKeyword",
|
||||
"textLength": 3
|
||||
},
|
||||
"compoundStatement": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"catchClauses": [
|
||||
{
|
||||
"CatchClause": {
|
||||
"catch": {
|
||||
"kind": "CatchKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"openParen": {
|
||||
"kind": "OpenParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": {
|
||||
"QualifiedName": {
|
||||
"globalSpecifier": null,
|
||||
"relativeSpecifier": null,
|
||||
"nameParts": [
|
||||
{
|
||||
"kind": "Name",
|
||||
"textLength": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"otherQualifiedNameList": [
|
||||
{
|
||||
"kind": "BarToken",
|
||||
"textLength": 1
|
||||
},
|
||||
{
|
||||
"QualifiedName": {
|
||||
"globalSpecifier": null,
|
||||
"relativeSpecifier": null,
|
||||
"nameParts": [
|
||||
{
|
||||
"kind": "Name",
|
||||
"textLength": 2
|
||||
},
|
||||
{
|
||||
"kind": "BackslashToken",
|
||||
"textLength": 1
|
||||
},
|
||||
{
|
||||
"kind": "Name",
|
||||
"textLength": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
},
|
||||
"closeParen": {
|
||||
"kind": "CloseParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"compoundStatement": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"finallyClause": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
try {
|
||||
// The behavior this parse error may change in the future.
|
||||
// This unit test is just a sanity check that tolerant-php-parser doesn't crash.
|
||||
} catch(| $x) {
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'VariableName' expected.",
|
||||
"start": 169,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "')' expected.",
|
||||
"start": 169,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'{' expected.",
|
||||
"start": 169,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "Unexpected '|'",
|
||||
"start": 169,
|
||||
"length": 1
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "';' expected.",
|
||||
"start": 173,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "Unexpected ')'",
|
||||
"start": 173,
|
||||
"length": 1
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'}' expected.",
|
||||
"start": 179,
|
||||
"length": 0
|
||||
}
|
||||
]
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TryStatement": {
|
||||
"tryKeyword": {
|
||||
"kind": "TryKeyword",
|
||||
"textLength": 3
|
||||
},
|
||||
"compoundStatement": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"catchClauses": [
|
||||
{
|
||||
"CatchClause": {
|
||||
"catch": {
|
||||
"kind": "CatchKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"openParen": {
|
||||
"kind": "OpenParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": null,
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"error": "MissingToken",
|
||||
"kind": "VariableName",
|
||||
"textLength": 0
|
||||
},
|
||||
"closeParen": {
|
||||
"error": "MissingToken",
|
||||
"kind": "CloseParenToken",
|
||||
"textLength": 0
|
||||
},
|
||||
"compoundStatement": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"error": "MissingToken",
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 0
|
||||
},
|
||||
"statements": [
|
||||
{
|
||||
"error": "SkippedToken",
|
||||
"kind": "BarToken",
|
||||
"textLength": 1
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"error": "MissingToken",
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": "SkippedToken",
|
||||
"kind": "CloseParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
{
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"closeBrace": {
|
||||
"error": "MissingToken",
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"finallyClause": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@
|
|||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": null,
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": null,
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"textLength": 1
|
||||
},
|
||||
"qualifiedName": null,
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"textLength": 0
|
||||
},
|
||||
"qualifiedName": null,
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"error": "MissingToken",
|
||||
"kind": "VariableName",
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"otherQualifiedNameList": [],
|
||||
"variableName": {
|
||||
"error": "MissingToken",
|
||||
"kind": "VariableName",
|
||||
|
|
Загрузка…
Ссылка в новой задаче