mirror of
https://github.com/phabelio/PHP-Parser.git
synced 2024-11-26 20:14:46 +01:00
Introduce ErrorHandler
Add ErrorHandler interface, as well as ErrorHandler\Throwing and ErrorHandler\Collecting. The error handler is passed to Parser::parse(). This supersedes the throwOnError option. NameResolver now accepts an ErrorHandler in the ctor.
This commit is contained in:
parent
90834bff8e
commit
f99a96e0a2
13
lib/PhpParser/ErrorHandler.php
Normal file
13
lib/PhpParser/ErrorHandler.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpParser;
|
||||||
|
|
||||||
|
interface ErrorHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an error generated during lexing, parsing or some other operation.
|
||||||
|
*
|
||||||
|
* @param Error $error The error that needs to be handled
|
||||||
|
*/
|
||||||
|
public function handleError(Error $error);
|
||||||
|
}
|
46
lib/PhpParser/ErrorHandler/Collecting.php
Normal file
46
lib/PhpParser/ErrorHandler/Collecting.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpParser\ErrorHandler;
|
||||||
|
|
||||||
|
use PhpParser\Error;
|
||||||
|
use PhpParser\ErrorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error handler that collects all errors into an array.
|
||||||
|
*
|
||||||
|
* This allows graceful handling of errors.
|
||||||
|
*/
|
||||||
|
class Collecting implements ErrorHandler
|
||||||
|
{
|
||||||
|
/** @var Error[] Collected errors */
|
||||||
|
private $errors = [];
|
||||||
|
|
||||||
|
public function handleError(Error $error) {
|
||||||
|
$this->errors[] = $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get collected errors.
|
||||||
|
*
|
||||||
|
* @return Error[]
|
||||||
|
*/
|
||||||
|
public function getErrors() {
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether there are any errors.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasErrors() {
|
||||||
|
return !empty($this->errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset/clear collected errors.
|
||||||
|
*/
|
||||||
|
public function clearErrors() {
|
||||||
|
$this->errors = [];
|
||||||
|
}
|
||||||
|
}
|
18
lib/PhpParser/ErrorHandler/Throwing.php
Normal file
18
lib/PhpParser/ErrorHandler/Throwing.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpParser\ErrorHandler;
|
||||||
|
|
||||||
|
use PhpParser\Error;
|
||||||
|
use PhpParser\ErrorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error handler that handles all errors by throwing them.
|
||||||
|
*
|
||||||
|
* This is the default strategy used by all components.
|
||||||
|
*/
|
||||||
|
class Throwing implements ErrorHandler
|
||||||
|
{
|
||||||
|
public function handleError(Error $error) {
|
||||||
|
throw $error;
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,6 @@ class Lexer
|
|||||||
{
|
{
|
||||||
protected $code;
|
protected $code;
|
||||||
protected $tokens;
|
protected $tokens;
|
||||||
protected $errors;
|
|
||||||
protected $pos;
|
protected $pos;
|
||||||
protected $line;
|
protected $line;
|
||||||
protected $filePos;
|
protected $filePos;
|
||||||
@ -54,13 +53,18 @@ class Lexer
|
|||||||
* the getErrors() method.
|
* the getErrors() method.
|
||||||
*
|
*
|
||||||
* @param string $code The source code to lex
|
* @param string $code The source code to lex
|
||||||
|
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
|
||||||
|
* ErrorHandler\Throwing
|
||||||
*/
|
*/
|
||||||
public function startLexing($code) {
|
public function startLexing($code, ErrorHandler $errorHandler = null) {
|
||||||
|
if (null === $errorHandler) {
|
||||||
|
$errorHandler = new ErrorHandler\Throwing();
|
||||||
|
}
|
||||||
|
|
||||||
$this->code = $code; // keep the code around for __halt_compiler() handling
|
$this->code = $code; // keep the code around for __halt_compiler() handling
|
||||||
$this->pos = -1;
|
$this->pos = -1;
|
||||||
$this->line = 1;
|
$this->line = 1;
|
||||||
$this->filePos = 0;
|
$this->filePos = 0;
|
||||||
$this->errors = [];
|
|
||||||
|
|
||||||
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
|
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
|
||||||
// This ensures proper composability, because having a newline is the "safe" assumption.
|
// This ensures proper composability, because having a newline is the "safe" assumption.
|
||||||
@ -70,7 +74,7 @@ class Lexer
|
|||||||
|
|
||||||
$this->resetErrors();
|
$this->resetErrors();
|
||||||
$this->tokens = @token_get_all($code);
|
$this->tokens = @token_get_all($code);
|
||||||
$this->handleErrors();
|
$this->handleErrors($errorHandler);
|
||||||
|
|
||||||
if (false !== $scream) {
|
if (false !== $scream) {
|
||||||
ini_set('xdebug.scream', $scream);
|
ini_set('xdebug.scream', $scream);
|
||||||
@ -88,7 +92,7 @@ class Lexer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleInvalidCharacterRange($start, $end, $line) {
|
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
|
||||||
for ($i = $start; $i < $end; $i++) {
|
for ($i = $start; $i < $end; $i++) {
|
||||||
$chr = $this->code[$i];
|
$chr = $this->code[$i];
|
||||||
if ($chr === 'b' || $chr === 'B') {
|
if ($chr === 'b' || $chr === 'B') {
|
||||||
@ -105,12 +109,12 @@ class Lexer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->errors[] = new Error($errorMsg, [
|
$errorHandler->handleError(new Error($errorMsg, [
|
||||||
'startLine' => $line,
|
'startLine' => $line,
|
||||||
'endLine' => $line,
|
'endLine' => $line,
|
||||||
'startFilePos' => $i,
|
'startFilePos' => $i,
|
||||||
'endFilePos' => $i,
|
'endFilePos' => $i,
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +136,7 @@ class Lexer
|
|||||||
&& false === strpos($error['message'], 'Undefined variable');
|
&& false === strpos($error['message'], 'Undefined variable');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleErrors() {
|
protected function handleErrors(ErrorHandler $errorHandler) {
|
||||||
if (!$this->errorMayHaveOccurred()) {
|
if (!$this->errorMayHaveOccurred()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -151,7 +155,8 @@ class Lexer
|
|||||||
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
|
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
|
||||||
// Something is missing, must be an invalid character
|
// Something is missing, must be an invalid character
|
||||||
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
|
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
|
||||||
$this->handleInvalidCharacterRange($filePos, $nextFilePos, $line);
|
$this->handleInvalidCharacterRange(
|
||||||
|
$filePos, $nextFilePos, $line, $errorHandler);
|
||||||
$filePos = $nextFilePos;
|
$filePos = $nextFilePos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,19 +168,20 @@ class Lexer
|
|||||||
if (substr($this->code, $filePos, 2) === '/*') {
|
if (substr($this->code, $filePos, 2) === '/*') {
|
||||||
// Unlike PHP, HHVM will drop unterminated comments entirely
|
// Unlike PHP, HHVM will drop unterminated comments entirely
|
||||||
$comment = substr($this->code, $filePos);
|
$comment = substr($this->code, $filePos);
|
||||||
$this->errors[] = new Error('Unterminated comment', [
|
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||||
'startLine' => $line,
|
'startLine' => $line,
|
||||||
'endLine' => $line + substr_count($comment, "\n"),
|
'endLine' => $line + substr_count($comment, "\n"),
|
||||||
'startFilePos' => $filePos,
|
'startFilePos' => $filePos,
|
||||||
'endFilePos' => $filePos + \strlen($comment),
|
'endFilePos' => $filePos + \strlen($comment),
|
||||||
]);
|
]));
|
||||||
|
|
||||||
// Emulate the PHP behavior
|
// Emulate the PHP behavior
|
||||||
$isDocComment = isset($comment[3]) && $comment[3] === '*';
|
$isDocComment = isset($comment[3]) && $comment[3] === '*';
|
||||||
$this->tokens[] = [$isDocComment ? T_DOC_COMMENT : T_COMMENT, $comment, $line];
|
$this->tokens[] = [$isDocComment ? T_DOC_COMMENT : T_COMMENT, $comment, $line];
|
||||||
} else {
|
} else {
|
||||||
// Invalid characters at the end of the input
|
// Invalid characters at the end of the input
|
||||||
$this->handleInvalidCharacterRange($filePos, \strlen($this->code), $line);
|
$this->handleInvalidCharacterRange(
|
||||||
|
$filePos, \strlen($this->code), $line, $errorHandler);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -183,12 +189,12 @@ class Lexer
|
|||||||
// Check for unterminated comment
|
// Check for unterminated comment
|
||||||
$lastToken = $this->tokens[count($this->tokens) - 1];
|
$lastToken = $this->tokens[count($this->tokens) - 1];
|
||||||
if ($this->isUnterminatedComment($lastToken)) {
|
if ($this->isUnterminatedComment($lastToken)) {
|
||||||
$this->errors[] = new Error('Unterminated comment', [
|
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||||
'startLine' => $line - substr_count($lastToken[1], "\n"),
|
'startLine' => $line - substr_count($lastToken[1], "\n"),
|
||||||
'endLine' => $line,
|
'endLine' => $line,
|
||||||
'startFilePos' => $filePos - \strlen($lastToken[1]),
|
'startFilePos' => $filePos - \strlen($lastToken[1]),
|
||||||
'endFilePos' => $filePos,
|
'endFilePos' => $filePos,
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,15 +308,6 @@ class Lexer
|
|||||||
return $this->tokens;
|
return $this->tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns errors that occurred during lexing.
|
|
||||||
*
|
|
||||||
* @return Error[] Array of lexer errors
|
|
||||||
*/
|
|
||||||
public function getErrors() {
|
|
||||||
return $this->errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles __halt_compiler() by returning the text after it.
|
* Handles __halt_compiler() by returning the text after it.
|
||||||
*
|
*
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace PhpParser\Lexer;
|
namespace PhpParser\Lexer;
|
||||||
|
|
||||||
|
use PhpParser\ErrorHandler;
|
||||||
use PhpParser\Parser\Tokens;
|
use PhpParser\Parser\Tokens;
|
||||||
|
|
||||||
class Emulative extends \PhpParser\Lexer
|
class Emulative extends \PhpParser\Lexer
|
||||||
@ -50,10 +51,10 @@ class Emulative extends \PhpParser\Lexer
|
|||||||
$this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
|
$this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function startLexing($code) {
|
public function startLexing($code, ErrorHandler $errorHandler = null) {
|
||||||
$this->inObjectAccess = false;
|
$this->inObjectAccess = false;
|
||||||
|
|
||||||
parent::startLexing($code);
|
parent::startLexing($code, $errorHandler);
|
||||||
if ($this->requiresEmulation($code)) {
|
if ($this->requiresEmulation($code)) {
|
||||||
$this->emulateTokens();
|
$this->emulateTokens();
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ namespace PhpParser;
|
|||||||
|
|
||||||
class NodeTraverser implements NodeTraverserInterface
|
class NodeTraverser implements NodeTraverserInterface
|
||||||
{
|
{
|
||||||
/**
|
/** @var NodeVisitor[] Visitors */
|
||||||
* @var NodeVisitor[] Visitors
|
|
||||||
*/
|
|
||||||
protected $visitors;
|
protected $visitors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace PhpParser\NodeVisitor;
|
namespace PhpParser\NodeVisitor;
|
||||||
|
|
||||||
|
use PhpParser\ErrorHandler;
|
||||||
use PhpParser\NodeVisitorAbstract;
|
use PhpParser\NodeVisitorAbstract;
|
||||||
use PhpParser\Error;
|
use PhpParser\Error;
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
@ -18,6 +19,18 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
/** @var array Map of format [aliasType => [aliasName => originalName]] */
|
/** @var array Map of format [aliasType => [aliasName => originalName]] */
|
||||||
protected $aliases;
|
protected $aliases;
|
||||||
|
|
||||||
|
/** @var ErrorHandler Error handler */
|
||||||
|
protected $errorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a name resolution visitor.
|
||||||
|
*
|
||||||
|
* @param ErrorHandler|null $errorHandler Error handler
|
||||||
|
*/
|
||||||
|
public function __construct(ErrorHandler $errorHandler = null) {
|
||||||
|
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
|
||||||
|
}
|
||||||
|
|
||||||
public function beforeTraverse(array $nodes) {
|
public function beforeTraverse(array $nodes) {
|
||||||
$this->resetState();
|
$this->resetState();
|
||||||
}
|
}
|
||||||
@ -132,13 +145,14 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
Stmt\Use_::TYPE_CONSTANT => 'const ',
|
Stmt\Use_::TYPE_CONSTANT => 'const ',
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new Error(
|
$this->errorHandler->handleError(new Error(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Cannot use %s%s as %s because the name is already in use',
|
'Cannot use %s%s as %s because the name is already in use',
|
||||||
$typeStringMap[$type], $name, $use->alias
|
$typeStringMap[$type], $name, $use->alias
|
||||||
),
|
),
|
||||||
$use->getLine()
|
$use->getAttributes()
|
||||||
);
|
));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->aliases[$type][$aliasName] = $name;
|
$this->aliases[$type][$aliasName] = $name;
|
||||||
@ -160,10 +174,10 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
// don't resolve special class names
|
// don't resolve special class names
|
||||||
if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
|
if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
|
||||||
if (!$name->isUnqualified()) {
|
if (!$name->isUnqualified()) {
|
||||||
throw new Error(
|
$this->errorHandler->handleError(new Error(
|
||||||
sprintf("'\\%s' is an invalid class name", $name->toString()),
|
sprintf("'\\%s' is an invalid class name", $name->toString()),
|
||||||
$name->getLine()
|
$name->getAttributes()
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $name;
|
return $name;
|
||||||
|
@ -7,18 +7,11 @@ interface Parser {
|
|||||||
* Parses PHP code into a node tree.
|
* Parses PHP code into a node tree.
|
||||||
*
|
*
|
||||||
* @param string $code The source code to parse
|
* @param string $code The source code to parse
|
||||||
|
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
|
||||||
|
* to ErrorHandler\Throwing.
|
||||||
*
|
*
|
||||||
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
|
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
|
||||||
* unable to recover from an error).
|
* unable to recover from an error).
|
||||||
*/
|
*/
|
||||||
public function parse($code);
|
public function parse($code, ErrorHandler $errorHandler = null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Get array of errors that occurred during the last parse.
|
|
||||||
*
|
|
||||||
* This method may only return multiple errors if the 'throwOnError' option is disabled.
|
|
||||||
*
|
|
||||||
* @return Error[]
|
|
||||||
*/
|
|
||||||
public function getErrors();
|
|
||||||
}
|
}
|
||||||
|
@ -3,61 +3,52 @@
|
|||||||
namespace PhpParser\Parser;
|
namespace PhpParser\Parser;
|
||||||
|
|
||||||
use PhpParser\Error;
|
use PhpParser\Error;
|
||||||
|
use PhpParser\ErrorHandler;
|
||||||
use PhpParser\Parser;
|
use PhpParser\Parser;
|
||||||
|
|
||||||
class Multiple implements Parser {
|
class Multiple implements Parser {
|
||||||
/** @var Parser[] List of parsers to try, in order of preference */
|
/** @var Parser[] List of parsers to try, in order of preference */
|
||||||
private $parsers;
|
private $parsers;
|
||||||
/** @var Error[] Errors collected during last parse */
|
|
||||||
private $errors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a parser which will try multiple parsers in an order of preference.
|
* Create a parser which will try multiple parsers in an order of preference.
|
||||||
*
|
*
|
||||||
* Parsers will be invoked in the order they're provided to the constructor. If one of the
|
* Parsers will be invoked in the order they're provided to the constructor. If one of the
|
||||||
* parsers runs without errors, it's output is returned. Otherwise the errors (and
|
* parsers runs without throwing, it's output is returned. Otherwise the exception that the
|
||||||
* PhpParser\Error exception) of the first parser are used.
|
* first parser generated is thrown.
|
||||||
*
|
*
|
||||||
* @param Parser[] $parsers
|
* @param Parser[] $parsers
|
||||||
*/
|
*/
|
||||||
public function __construct(array $parsers) {
|
public function __construct(array $parsers) {
|
||||||
$this->parsers = $parsers;
|
$this->parsers = $parsers;
|
||||||
$this->errors = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parse($code) {
|
public function parse($code, ErrorHandler $errorHandler = null) {
|
||||||
list($firstStmts, $firstErrors, $firstError) = $this->tryParse($this->parsers[0], $code);
|
if (null === $errorHandler) {
|
||||||
if ($firstErrors === []) {
|
$errorHandler = new ErrorHandler\Throwing;
|
||||||
$this->errors = [];
|
}
|
||||||
|
|
||||||
|
list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code);
|
||||||
|
if ($firstError === null) {
|
||||||
return $firstStmts;
|
return $firstStmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
|
for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
|
||||||
list($stmts, $errors) = $this->tryParse($this->parsers[$i], $code);
|
list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code);
|
||||||
if ($errors === []) {
|
if ($error === null) {
|
||||||
$this->errors = [];
|
|
||||||
return $stmts;
|
return $stmts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->errors = $firstErrors;
|
throw $firstError;
|
||||||
if ($firstError) {
|
|
||||||
throw $firstError;
|
|
||||||
}
|
|
||||||
return $firstStmts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getErrors() {
|
private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) {
|
||||||
return $this->errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function tryParse(Parser $parser, $code) {
|
|
||||||
$stmts = null;
|
$stmts = null;
|
||||||
$error = null;
|
$error = null;
|
||||||
try {
|
try {
|
||||||
$stmts = $parser->parse($code);
|
$stmts = $parser->parse($code, $errorHandler);
|
||||||
} catch (Error $error) {}
|
} catch (Error $error) {}
|
||||||
$errors = $parser->getErrors();
|
return [$stmts, $error];
|
||||||
return [$stmts, $errors, $error];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,8 +103,8 @@ abstract class ParserAbstract implements Parser
|
|||||||
/** @var array Start attributes of last *read* token */
|
/** @var array Start attributes of last *read* token */
|
||||||
protected $lookaheadStartAttributes;
|
protected $lookaheadStartAttributes;
|
||||||
|
|
||||||
/** @var bool Whether to throw on first error */
|
/** @var ErrorHandler Error handler */
|
||||||
protected $throwOnError;
|
protected $errorHandler;
|
||||||
/** @var Error[] Errors collected during last parse */
|
/** @var Error[] Errors collected during last parse */
|
||||||
protected $errors;
|
protected $errors;
|
||||||
/** @var int Error state, used to avoid error floods */
|
/** @var int Error state, used to avoid error floods */
|
||||||
@ -114,42 +114,36 @@ abstract class ParserAbstract implements Parser
|
|||||||
* Creates a parser instance.
|
* Creates a parser instance.
|
||||||
*
|
*
|
||||||
* @param Lexer $lexer A lexer
|
* @param Lexer $lexer A lexer
|
||||||
* @param array $options Options array. The boolean 'throwOnError' option determines whether an exception should be
|
* @param array $options Options array. Currently no options are supported.
|
||||||
* thrown on first error, or if the parser should try to continue parsing the remaining code
|
|
||||||
* and build a partial AST.
|
|
||||||
*/
|
*/
|
||||||
public function __construct(Lexer $lexer, array $options = array()) {
|
public function __construct(Lexer $lexer, array $options = array()) {
|
||||||
$this->lexer = $lexer;
|
$this->lexer = $lexer;
|
||||||
$this->errors = array();
|
$this->errors = array();
|
||||||
$this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (isset($options['throwOnError'])) {
|
||||||
* Get array of errors that occurred during the last parse.
|
throw new \LogicException(
|
||||||
*
|
'"throwOnError" is no longer supported, use "errorHandler" instead');
|
||||||
* This method may only return multiple errors if the 'throwOnError' option is disabled.
|
}
|
||||||
*
|
|
||||||
* @return Error[]
|
|
||||||
*/
|
|
||||||
public function getErrors() {
|
|
||||||
return $this->errors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses PHP code into a node tree.
|
* Parses PHP code into a node tree.
|
||||||
*
|
*
|
||||||
|
* If a non-throwing error handler is used, the parser will continue parsing after an error
|
||||||
|
* occurred and attempt to build a partial AST.
|
||||||
|
*
|
||||||
* @param string $code The source code to parse
|
* @param string $code The source code to parse
|
||||||
|
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
|
||||||
|
* to ErrorHandler\Throwing.
|
||||||
*
|
*
|
||||||
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
|
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
|
||||||
* unable to recover from an error).
|
* unable to recover from an error).
|
||||||
*/
|
*/
|
||||||
public function parse($code) {
|
public function parse($code, ErrorHandler $errorHandler = null) {
|
||||||
// Initialize the lexer and inherit lexing errors
|
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
|
||||||
$this->lexer->startLexing($code);
|
|
||||||
$this->errors = $this->lexer->getErrors();
|
// Initialize the lexer
|
||||||
if ($this->throwOnError && !empty($this->errors)) {
|
$this->lexer->startLexing($code, $this->errorHandler);
|
||||||
throw $this->errors[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// We start off with no lookahead-token
|
// We start off with no lookahead-token
|
||||||
$symbol = self::SYMBOL_NONE;
|
$symbol = self::SYMBOL_NONE;
|
||||||
@ -346,10 +340,7 @@ abstract class ParserAbstract implements Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function emitError(Error $error) {
|
protected function emitError(Error $error) {
|
||||||
$this->errors[] = $error;
|
$this->errorHandler->handleError($error);
|
||||||
if ($this->throwOnError) {
|
|
||||||
throw $error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getErrorMessage($symbol, $state) {
|
protected function getErrorMessage($symbol, $state) {
|
||||||
|
@ -15,12 +15,8 @@ class CodeParsingTest extends CodeTestAbstract
|
|||||||
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
|
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
|
||||||
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
||||||
)));
|
)));
|
||||||
$parser5 = new Parser\Php5($lexer, array(
|
$parser5 = new Parser\Php5($lexer);
|
||||||
'throwOnError' => false,
|
$parser7 = new Parser\Php7($lexer);
|
||||||
));
|
|
||||||
$parser7 = new Parser\Php7($lexer, array(
|
|
||||||
'throwOnError' => false,
|
|
||||||
));
|
|
||||||
|
|
||||||
$output5 = $this->getParseOutput($parser5, $code);
|
$output5 = $this->getParseOutput($parser5, $code);
|
||||||
$output7 = $this->getParseOutput($parser7, $code);
|
$output7 = $this->getParseOutput($parser7, $code);
|
||||||
@ -38,11 +34,11 @@ class CodeParsingTest extends CodeTestAbstract
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getParseOutput(Parser $parser, $code) {
|
private function getParseOutput(Parser $parser, $code) {
|
||||||
$stmts = $parser->parse($code);
|
$errors = new ErrorHandler\Collecting;
|
||||||
$errors = $parser->getErrors();
|
$stmts = $parser->parse($code, $errors);
|
||||||
|
|
||||||
$output = '';
|
$output = '';
|
||||||
foreach ($errors as $error) {
|
foreach ($errors->getErrors() as $error) {
|
||||||
$output .= $this->formatErrorMessage($error, $code) . "\n";
|
$output .= $this->formatErrorMessage($error, $code) . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
test/PhpParser/ErrorHandler/CollectingTest.php
Normal file
22
test/PhpParser/ErrorHandler/CollectingTest.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpParser\ErrorHandler;
|
||||||
|
|
||||||
|
use PhpParser\Error;
|
||||||
|
|
||||||
|
class CollectingTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
public function testHandleError() {
|
||||||
|
$errorHandler = new Collecting();
|
||||||
|
$this->assertFalse($errorHandler->hasErrors());
|
||||||
|
$this->assertEmpty($errorHandler->getErrors());
|
||||||
|
|
||||||
|
$errorHandler->handleError($e1 = new Error('Test 1'));
|
||||||
|
$errorHandler->handleError($e2 = new Error('Test 2'));
|
||||||
|
$this->assertTrue($errorHandler->hasErrors());
|
||||||
|
$this->assertSame([$e1, $e2], $errorHandler->getErrors());
|
||||||
|
|
||||||
|
$errorHandler->clearErrors();
|
||||||
|
$this->assertFalse($errorHandler->hasErrors());
|
||||||
|
$this->assertEmpty($errorHandler->getErrors());
|
||||||
|
}
|
||||||
|
}
|
16
test/PhpParser/ErrorHandler/ThrowingTest.php
Normal file
16
test/PhpParser/ErrorHandler/ThrowingTest.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpParser\ErrorHandler;
|
||||||
|
|
||||||
|
use PhpParser\Error;
|
||||||
|
|
||||||
|
class ThrowingTest extends \PHPUnit_Framework_TestCase {
|
||||||
|
/**
|
||||||
|
* @expectedException \PhpParser\Error
|
||||||
|
* @expectedExceptionMessage Test
|
||||||
|
*/
|
||||||
|
public function testHandleError() {
|
||||||
|
$errorHandler = new Throwing();
|
||||||
|
$errorHandler->handleError(new Error('Test'));
|
||||||
|
}
|
||||||
|
}
|
@ -19,11 +19,12 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->markTestSkipped('HHVM does not throw warnings from token_get_all()');
|
$this->markTestSkipped('HHVM does not throw warnings from token_get_all()');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$errorHandler = new ErrorHandler\Collecting();
|
||||||
$lexer = $this->getLexer(['usedAttributes' => [
|
$lexer = $this->getLexer(['usedAttributes' => [
|
||||||
'comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'
|
'comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'
|
||||||
]]);
|
]]);
|
||||||
$lexer->startLexing($code);
|
$lexer->startLexing($code, $errorHandler);
|
||||||
$errors = $lexer->getErrors();
|
$errors = $errorHandler->getErrors();
|
||||||
|
|
||||||
$this->assertSame(count($messages), count($errors));
|
$this->assertSame(count($messages), count($errors));
|
||||||
for ($i = 0; $i < count($messages); $i++) {
|
for ($i = 0; $i < count($messages); $i++) {
|
||||||
|
@ -30,7 +30,6 @@ class MultipleTest extends ParserTest {
|
|||||||
/** @dataProvider provideTestParse */
|
/** @dataProvider provideTestParse */
|
||||||
public function testParse($code, Multiple $parser, $expected) {
|
public function testParse($code, Multiple $parser, $expected) {
|
||||||
$this->assertEquals($expected, $parser->parse($code));
|
$this->assertEquals($expected, $parser->parse($code));
|
||||||
$this->assertSame([], $parser->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideTestParse() {
|
public function provideTestParse() {
|
||||||
@ -92,22 +91,4 @@ class MultipleTest extends ParserTest {
|
|||||||
$parser = new Multiple([$parserA, $parserB]);
|
$parser = new Multiple([$parserA, $parserB]);
|
||||||
$parser->parse('dummy');
|
$parser->parse('dummy');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetErrors() {
|
|
||||||
$errorsA = [new Error('A1'), new Error('A2')];
|
|
||||||
$parserA = $this->getMockBuilder('PhpParser\Parser')->getMock();
|
|
||||||
$parserA->expects($this->at(0))->method('parse');
|
|
||||||
$parserA->expects($this->at(1))
|
|
||||||
->method('getErrors')->will($this->returnValue($errorsA));
|
|
||||||
|
|
||||||
$errorsB = [new Error('B1'), new Error('B2')];
|
|
||||||
$parserB = $this->getMockBuilder('PhpParser\Parser')->getMock();
|
|
||||||
$parserB->expects($this->at(0))->method('parse');
|
|
||||||
$parserB->expects($this->at(1))
|
|
||||||
->method('getErrors')->will($this->returnValue($errorsB));
|
|
||||||
|
|
||||||
$parser = new Multiple([$parserA, $parserB]);
|
|
||||||
$parser->parse('dummy');
|
|
||||||
$this->assertSame($errorsA, $parser->getErrors());
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user