Merge pull request #244 from TysonAndre/multi-class-catch

Fixes #103: Support parsing multiple exception types
This commit is contained in:
Rob Lourens 2018-05-26 12:42:09 -07:00 коммит произвёл GitHub
Родитель 7647b60b13 60d3fc5d25
Коммит 01479d7e0a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 462 добавлений и 3 удалений

Просмотреть файл

@ -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",