From 4defbc2174605f5ab5f55293eccd6566b9600533 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 18 Apr 2015 12:51:26 +0200 Subject: [PATCH] Make Error column info attribute based --- lib/PhpParser/Error.php | 184 ++++++++++++++----------------- lib/PhpParser/ParserAbstract.php | 11 +- test/PhpParser/ErrorTest.php | 63 +++++++---- 3 files changed, 128 insertions(+), 130 deletions(-) diff --git a/lib/PhpParser/Error.php b/lib/PhpParser/Error.php index 9f3cba6..4cdf108 100644 --- a/lib/PhpParser/Error.php +++ b/lib/PhpParser/Error.php @@ -5,24 +5,22 @@ namespace PhpParser; class Error extends \RuntimeException { protected $rawMessage; - protected $rawLine; - protected $tokens = array(); - protected $tokenIndex; - protected $beginColumnCache; + protected $attributes; /** * Creates an Exception signifying a parse error. * - * @param string $message Error message - * @param int $line Error line in PHP file - * @param array $tokens Array of all tokens in the file that caused the error. - * @param int $tokenIndex Index in $tokens of token where error happened. + * @param string $message Error message + * @param array|int $attributes Attributes of node/token where error occurred + * (or start line of error -- deprecated) */ - public function __construct($message, $line = -1, array $tokens=array(), $tokenIndex=null) { + public function __construct($message, $attributes = array()) { $this->rawMessage = (string) $message; - $this->rawLine = (int) $line; - $this->tokens = $tokens; - $this->tokenIndex = $tokenIndex; + if (is_array($attributes)) { + $this->attributes = $attributes; + } else { + $this->attributes = array('startLine' => $attributes); + } $this->updateMessage(); } @@ -35,6 +33,34 @@ class Error extends \RuntimeException return $this->rawMessage; } + /** + * Gets the line the error starts in. + * + * @return int Error start line + */ + public function getStartLine() { + return isset($this->attributes['startLine']) ? $this->attributes['startLine'] : -1; + } + + /** + * Gets the line the error ends in. + * + * @return int Error end line + */ + public function getEndLine() { + return isset($this->attributes['endLine']) ? $this->attributes['endLine'] : -1; + } + + + /** + * Gets the attributes of the node/token the error occurred at. + * + * @return array + */ + public function getAttributes() { + return $this->attributes; + } + /** * Sets the line of the PHP file the error occurred in. * @@ -46,120 +72,65 @@ class Error extends \RuntimeException } /** - * Gets the error line in the PHP file. + * Sets the line the error starts in. * - * @return int Error line in the PHP file + * @param int $line Error start line */ - public function getRawLine() { - return $this->rawLine; - } - - /** - * Sets the line of the PHP file the error occurred in. - * - * @param int $line Error line in the PHP file - */ - public function setRawLine($line) { - $this->rawLine = (int) $line; + public function setStartLine($line) { + $this->attributes['startLine'] = (int) $line; $this->updateMessage(); } /** - * Checks if valid token-information is available for this error. - * + * Returns whether the error has start and end column information. + * + * For column information enable the startFilePos and endFilePos in the lexer options. + * * @return bool */ - public function hasTokenAttributes(){ - return is_numeric($this->tokenIndex) && isset($this->tokens[(int)$this->tokenIndex]); + public function hasColumnInfo() { + return isset($this->attributes['startFilePos']) && isset($this->attributes['endFilePos']); } /** - * Gets the tokens for the php-file in which this error happened. - * Only works if token-information was provided. - * - * @return array - */ - public function getTokens(){ - return $this->tokens; - } - - /** - * Get sthe index in tokens in which this error happened. - * Only works if token-information was provided. - * + * Gets the start column (0-based) into the line where the error started. + * + * @param string $code Source code of the file * @return int */ - public function getTokenIndex(){ - return $this->tokenIndex; + public function getStartColumn($code) { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); + } + + return $this->toColumn($code, $this->attributes['startFilePos']); } /** - * Gets the first column number in which the error happened. - * Only works if token-information was provided. - * + * Gets the end column (0-based) into the line where the error ended. + * + * @param string $code Source code of the file * @return int */ - public function getBeginColumn() { - $beginColumn = null; - - if($this->hasTokenAttributes()) { - - if(!is_null($this->beginColumnCache)) { - $beginColumn = $this->beginColumnCache; - - } else { - $beginColumn = 0; - $tokenIndex = $this->tokenIndex; - for($i=$tokenIndex-1;$i>=0;$i--) { - $tokenText = $this->getTextFromToken($this->tokens[$i]); - - $beginColumn += strlen($tokenText); - - $newlinePosition = strrpos($tokenText, "\n"); - if($newlinePosition !== false){ - $beginColumn -= $newlinePosition; - break; - } - } - } - + public function getEndColumn($code) { + if (!$this->hasColumnInfo()) { + throw new \RuntimeException('Error does not have column information'); } - return $beginColumn; + return $this->toColumn($code, $this->attributes['endFilePos']); } - /** - * Gets the last column number in which the error happened. - * Only works if token-information was provided. - * - * @return int - */ - public function getEndColumn(){ - $endColumn = null; - - if($this->hasTokenAttributes()){ - $beginColumn = $this->getBeginColumn(); - $token = $this->tokens[(int)$this->tokenIndex]; - $tokenText = $this->getTextFromToken($token); - $endColumn = $beginColumn + strlen($tokenText); + private function toColumn($code, $pos) { + if ($pos >= strlen($code)) { + throw new \RuntimeException('Invalid position information'); } - return $endColumn; - } - - private function getTextFromToken($token){ - - $tokenText = $token; - if(is_array($tokenText)){ - if(is_int($tokenText[0])){ - $tokenText = $tokenText[1]; - - }else{ - $tokenText = $tokenText[0]; - } + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); + if (false === $lineStartPos) { + $lineStartPos = -1; } - return $tokenText; + return $pos - $lineStartPos - 1; } /** @@ -168,11 +139,20 @@ class Error extends \RuntimeException protected function updateMessage() { $this->message = $this->rawMessage; - if (-1 === $this->rawLine) { + if (-1 === $this->getStartLine()) { $this->message .= ' on unknown line'; } else { - $this->message .= ' on line ' . $this->rawLine; + $this->message .= ' on line ' . $this->getStartLine(); } + } + /** @deprecated Use getStartLine() instead */ + public function getRawLine() { + return $this->getStartLine(); + } + + /** @deprecated Use setStartLine() instead */ + public function setRawLine($line) { + $this->setStartLine($line); } } diff --git a/lib/PhpParser/ParserAbstract.php b/lib/PhpParser/ParserAbstract.php index f2adce9..2b3f5e4 100644 --- a/lib/PhpParser/ParserAbstract.php +++ b/lib/PhpParser/ParserAbstract.php @@ -205,8 +205,8 @@ abstract class ParserAbstract + $endAttributes ); } catch (Error $e) { - if (-1 === $e->getRawLine() && isset($startAttributes['startLine'])) { - $e->setRawLine($startAttributes['startLine']); + if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) { + $e->setStartLine($startAttributes['startLine']); } throw $e; @@ -239,14 +239,9 @@ abstract class ParserAbstract $expectedString = ''; } - $tokens = $this->lexer->getTokens(); - $errorPosition = $this->lexer->getPosition(); - throw new Error( 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString, - $startAttributes['startLine'], - $tokens, - $errorPosition + $startAttributes + $endAttributes ); } } diff --git a/test/PhpParser/ErrorTest.php b/test/PhpParser/ErrorTest.php index 6f30909..a94e542 100644 --- a/test/PhpParser/ErrorTest.php +++ b/test/PhpParser/ErrorTest.php @@ -33,32 +33,55 @@ class ErrorTest extends \PHPUnit_Framework_TestCase $this->assertSame('Some error on unknown line', $error->getMessage()); } - /** - * @depends testConstruct - */ - public function testColumnNumbers() { + /** @dataProvider provideTestColumnInfo */ + public function testColumnInfo($code, $startPos, $endPos, $startColumn, $endColumn) { + $error = new Error('Some error', array( + 'startFilePos' => $startPos, + 'endFilePos' => $endPos, + )); - $faultyCode = ""; - - $tokens = token_get_all($faultyCode); - - $error = new Error('Some error', 1, $tokens, 5); - - $this->assertSame(true, $error->hasTokenAttributes()); - $this->assertSame(13, $error->getBeginColumn()); - $this->assertSame(16, $error->getEndColumn()); + $this->assertSame(true, $error->hasColumnInfo()); + $this->assertSame($startColumn, $error->getStartColumn($code)); + $this->assertSame($endColumn, $error->getEndColumn($code)); } - /** - * @depends testConstruct - */ - public function testTokenInformationMissing(){ + public function provideTestColumnInfo() { + return array( + // Error at "bar" + array("assertSame(false, $error->hasTokenAttributes()); - $this->assertSame(null, $error->getBeginColumn()); - $this->assertSame(null, $error->getEndColumn()); + $this->assertSame(false, $error->hasColumnInfo()); + try { + $error->getStartColumn(''); + $this->fail('Expected RuntimeException'); + } catch (\RuntimeException $e) { + $this->assertEquals('Error does not have column information', $e->getMessage()); + } + try { + $error->getEndColumn(''); + $this->fail('Expected RuntimeException'); + } catch (\RuntimeException $e) { + $this->assertEquals('Error does not have column information', $e->getMessage()); + } } }