From 33a39fae06dec77ba262fae61b2727fe960d5215 Mon Sep 17 00:00:00 2001 From: Gerrit Addiks Date: Tue, 14 Apr 2015 20:31:06 +0200 Subject: [PATCH] added column-numbers to syntax errors --- bin/php-parse.php | 33 +++-- lib/PhpParser/Error.php | 114 +++++++++++++++++- lib/PhpParser/Lexer.php | 9 ++ lib/PhpParser/ParserAbstract.php | 7 +- test/PhpParser/ErrorTest.php | 31 ++++- .../parser/expr/newWithoutClass.test-fail | 2 +- 6 files changed, 181 insertions(+), 15 deletions(-) diff --git a/bin/php-parse.php b/bin/php-parse.php index 5558c5a..6f98974 100755 --- a/bin/php-parse.php +++ b/bin/php-parse.php @@ -10,7 +10,7 @@ ini_set('xdebug.var_display_max_children', -1); ini_set('xdebug.var_display_max_data', -1); ini_set('xdebug.var_display_max_depth', -1); -list($operations, $files) = parseArgs($argv); +list($operations, $files, $attributes) = parseArgs($argv); /* Dump nodes by default */ if (empty($operations)) { @@ -45,7 +45,16 @@ foreach ($files as $file) { try { $stmts = $parser->parse($code); } catch (PhpParser\Error $e) { - die("==> Parse Error: {$e->getMessage()}\n"); + + $message = $e->getMessage(); + + if($attributes['with-column-info'] && $e->hasTokenAttributes()){ + $beginColumn = $e->getBeginColumn(); + $endColumn = $e->getEndColumn(); + $message .= ", column {$beginColumn} to {$endColumn}"; + } + + die($message . "\n"); } foreach ($operations as $operation) { @@ -81,11 +90,12 @@ The file arguments can also be replaced with a code string: Operations is a list of the following options (--dump by default): - --dump -d Dump nodes using NodeDumper - --pretty-print -p Pretty print file using PrettyPrinter\Standard - --serialize-xml Serialize nodes using Serializer\XML - --var-dump var_dump() nodes (for exact structure) - --resolve-names -N Resolve names using NodeVisitor\NameResolver + --dump -d Dump nodes using NodeDumper + --pretty-print -p Pretty print file using PrettyPrinter\Standard + --serialize-xml Serialize nodes using Serializer\XML + --var-dump var_dump() nodes (for exact structure) + --resolve-names -N Resolve names using NodeVisitor\NameResolver + --with-column-info -c Show column-numbers for occoured errors. (if any) Example: @@ -101,6 +111,9 @@ OUTPUT function parseArgs($args) { $operations = array(); $files = array(); + $attributes = array( + 'with-column-info' => false, + ); array_shift($args); $parseOptions = true; @@ -129,6 +142,10 @@ function parseArgs($args) { case '-N'; $operations[] = 'resolve-names'; break; + case '--with-column-info': + case '-c'; + $attributes['with-column-info'] = true; + break; case '--': $parseOptions = false; break; @@ -141,5 +158,5 @@ function parseArgs($args) { } } - return array($operations, $files); + return array($operations, $files, $attributes); } diff --git a/lib/PhpParser/Error.php b/lib/PhpParser/Error.php index 619377d..9f3cba6 100644 --- a/lib/PhpParser/Error.php +++ b/lib/PhpParser/Error.php @@ -6,16 +6,23 @@ class Error extends \RuntimeException { protected $rawMessage; protected $rawLine; + protected $tokens = array(); + protected $tokenIndex; + protected $beginColumnCache; /** * Creates an Exception signifying a parse error. * - * @param string $message Error message - * @param int $line Error line in PHP file + * @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. */ - public function __construct($message, $line = -1) { + public function __construct($message, $line = -1, array $tokens=array(), $tokenIndex=null) { $this->rawMessage = (string) $message; $this->rawLine = (int) $line; + $this->tokens = $tokens; + $this->tokenIndex = $tokenIndex; $this->updateMessage(); } @@ -57,6 +64,104 @@ class Error extends \RuntimeException $this->updateMessage(); } + /** + * Checks if valid token-information is available for this error. + * + * @return bool + */ + public function hasTokenAttributes(){ + return is_numeric($this->tokenIndex) && isset($this->tokens[(int)$this->tokenIndex]); + } + + /** + * 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. + * + * @return int + */ + public function getTokenIndex(){ + return $this->tokenIndex; + } + + /** + * Gets the first column number in which the error happened. + * Only works if token-information was provided. + * + * @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; + } + } + } + + } + + return $beginColumn; + } + + /** + * 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); + } + + return $endColumn; + } + + private function getTextFromToken($token){ + + $tokenText = $token; + if(is_array($tokenText)){ + if(is_int($tokenText[0])){ + $tokenText = $tokenText[1]; + + }else{ + $tokenText = $tokenText[0]; + } + } + + return $tokenText; + } + /** * Updates the exception message after a change to rawMessage or rawLine. */ @@ -68,5 +173,6 @@ class Error extends \RuntimeException } else { $this->message .= ' on line ' . $this->rawLine; } + } -} \ No newline at end of file +} diff --git a/lib/PhpParser/Lexer.php b/lib/PhpParser/Lexer.php index 2a99a3c..4c66562 100644 --- a/lib/PhpParser/Lexer.php +++ b/lib/PhpParser/Lexer.php @@ -212,6 +212,15 @@ class Lexer return $this->tokens; } + /** + * Get's the current token-index (position in tokens). + * + * @return int + */ + public function getPosition() { + return $this->pos; + } + /** * Handles __halt_compiler() by returning the text after it. * diff --git a/lib/PhpParser/ParserAbstract.php b/lib/PhpParser/ParserAbstract.php index 31f266f..f2adce9 100644 --- a/lib/PhpParser/ParserAbstract.php +++ b/lib/PhpParser/ParserAbstract.php @@ -239,9 +239,14 @@ abstract class ParserAbstract $expectedString = ''; } + $tokens = $this->lexer->getTokens(); + $errorPosition = $this->lexer->getPosition(); + throw new Error( 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString, - $startAttributes['startLine'] + $startAttributes['startLine'], + $tokens, + $errorPosition ); } } diff --git a/test/PhpParser/ErrorTest.php b/test/PhpParser/ErrorTest.php index 62c03dc..6f30909 100644 --- a/test/PhpParser/ErrorTest.php +++ b/test/PhpParser/ErrorTest.php @@ -32,4 +32,33 @@ class ErrorTest extends \PHPUnit_Framework_TestCase $this->assertSame(-1, $error->getRawLine()); $this->assertSame('Some error on unknown line', $error->getMessage()); } -} \ No newline at end of file + + /** + * @depends testConstruct + */ + public function testColumnNumbers() { + + $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()); + + } + + /** + * @depends testConstruct + */ + public function testTokenInformationMissing(){ + + $error = new Error('Some error', 3); + + $this->assertSame(false, $error->hasTokenAttributes()); + $this->assertSame(null, $error->getBeginColumn()); + $this->assertSame(null, $error->getEndColumn()); + } +} diff --git a/test/code/parser/expr/newWithoutClass.test-fail b/test/code/parser/expr/newWithoutClass.test-fail index 2594cb3..0708934 100644 --- a/test/code/parser/expr/newWithoutClass.test-fail +++ b/test/code/parser/expr/newWithoutClass.test-fail @@ -3,4 +3,4 @@ New without a class