diff options
author | Dan Ungureanu <udan1107@gmail.com> | 2015-07-15 18:08:18 +0300 |
---|---|---|
committer | Dan Ungureanu <udan1107@gmail.com> | 2015-07-15 18:08:18 +0300 |
commit | 024d96700d7aa8601687ebbc5a14bfbd103d7d56 (patch) | |
tree | cb349f92e762c0f38c9b2aa329e7ba1e322c3a01 | |
parent | 4c24db081295ad034c0a7c52694d7612d9893761 (diff) |
Reorganized code.
Fixed a bug that miscalculated the position of the tokens.
Added tests.
Signed-off-by: Dan Ungureanu <udan1107@gmail.com>
-rw-r--r-- | libraries/Linter.class.php | 149 | ||||
-rw-r--r-- | lint.php | 136 | ||||
-rw-r--r-- | test/libraries/PMA_Linter_Test.php | 146 |
3 files changed, 305 insertions, 126 deletions
diff --git a/libraries/Linter.class.php b/libraries/Linter.class.php new file mode 100644 index 0000000000..795eb36c58 --- /dev/null +++ b/libraries/Linter.class.php @@ -0,0 +1,149 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Analyzes a query and gives user feedback. + * + * @package PhpMyAdmin + */ +if (! defined('PHPMYADMIN')) { + exit; +} + +/** + * The linter itself. + * + * @package PhpMyAdmin + */ +class PMA_Linter { + + /** + * Gets the starting position of each line. + * + * @param string $str String to be analyzed. + * + * @return array + */ + public static function getLines($str) + { + $lines = array(0); + for ($i = 0, $len = strlen($str); $i < $len; ++$i) { + if ($str[$i] === "\n") { + $lines[] = $i + 1; + } + } + return $lines; + } + + /** + * Computes the number of the line and column given an absolute position. + * + * @param array $lines The starting position of each line. + * @param int $pos The absolute position + * + * @return void + */ + public static function findLineNumberAndColumn($lines, $pos) + { + $line = 0; + foreach ($lines as $lineNo => $lineStart) { + if ($lineStart > $pos) { + break; + } + $line = $lineNo; + } + return array($line, $pos - $lines[$line]); + } + + /** + * Runs the linting process. + * + * @param string $query The query to be checked. + * + * @return void + */ + public static function lint($query) + { + // Disabling lint for huge queries to save some resources. + if (strlen($query) > 10000) { + echo json_encode( + array( + array( + 'message' => 'The linting is disabled for this query because it exceededs the maxmimum length.', + 'fromLine' => 0, + 'fromColumn' => 0, + 'toLine' => 0, + 'toColumn' => 0, + 'severity' => 'warning', + ) + ) + ); + return; + } + + /** + * Lexer used for tokenizing the query. + * + * @var SqlParser\Lexer + */ + $lexer = new SqlParser\Lexer($query); + + /** + * Parsed used for analysing the query. + * + * @var SqlParser\Parser + */ + $parser = new SqlParser\Parser($lexer->list); + + /** + * Array containing all errors. + * + * @var array + */ + $errors = SqlParser\Utils\Error::get(array($lexer, $parser)); + + /** + * The response containing of all errors. + * + * @var array + */ + $response = array(); + + /** + * The starting position for each line. + * + * CodeMirror requires relative position to line, but the parser stores + * only the absolute position of the character in string. + * + * @var array + */ + $lines = static::getLines($query); + + // Building the response. + foreach ($errors as $idx => $error) { + + // Starting position of the string that caused the error. + list($fromLine, $fromColumn) = static::findLineNumberAndColumn( + $lines, $error[3] + ); + + // Ending position of the string that caused the error. + list($toLine, $toColumn) = static::findLineNumberAndColumn( + $lines, $error[3] + strlen($error[2]) + ); + + // Building the response. + $response[] = array( + 'message' => $error[0] . ' (near <code>' . $error[2] . '</code>)', + 'fromLine' => $fromLine, + 'fromColumn' => $fromColumn, + 'toLine' => $toLine, + 'toColumn' => $toColumn, + 'severity' => 'error', + ); + } + + // Sending back the answer. + echo json_encode($response); + } + +}
\ No newline at end of file @@ -1,137 +1,21 @@ <?php - -// Loads the SQL lexer and parser, which are used to parse this error to detect -// any errors. -require_once 'libraries/sql-parser/autoload.php'; - +/* vim: set expandtab sw=4 ts=4 sts=4: */ /** - * Gets the starting position of each line. + * Represents the interface between the linter and the query editor. * - * @param string $str String to be analyzed. - * - * @return array + * @package PhpMyAdmin */ -function getLines($str) -{ - $lines = array(0); - for ($i = 0, $len = strlen($str); $i < $len; ++$i) { - if ($str[$i] === "\n") { - $lines[] = $i; - } - } - return $lines; -} + +define('PHPMYADMIN', true); /** - * Computes the number of the line and column given an absolute position. - * - * @param array $lines The starting position of each line. - * @param int $pos The absolute position - * - * @return void + * Loads the SQL lexer and parser, which are used to detect errors. */ -function findLineNumberAndColumn($lines, $pos) -{ - $line = 0; - foreach ($lines as $lineNo => $lineStart) { - if ($lineStart >= $pos) { - break; - } - $line = $lineNo; - } - return array($line, $pos - $lines[$line]); -} +require_once 'libraries/sql-parser/autoload.php'; /** - * Runs the linting process. - * - * @param string $query The query to be checked. - * - * @return void + * Loads the linter. */ -function linter($query) -{ - // Disabling lint for huge queries to save some resources. - if (strlen($query) > 10000) { - echo json_encode( - array( - array( - 'message' => 'The linting is disabled for this query because it exceededs the maxmimum length', - 'fromLine' => 0, - 'fromColumn' => 0, - 'toLine' => 0, - 'toColumn' => 0, - 'severity' => 'warning', - ) - ) - ); - return; - } - - /** - * Lexer used for tokenizing the query. - * - * @var SqlParser\Lexer - */ - $lexer = new SqlParser\Lexer($query); - - /** - * Parsed used for analysing the query. - * - * @var SqlParser\Parser - */ - $parser = new SqlParser\Parser($lexer->list); - - /** - * Array containing all errors. - * - * @var array - */ - $errors = SqlParser\Utils\Error::get(array($lexer, $parser)); - - /** - * The response containing of all errors. - * - * @var array - */ - $response = array(); - - /** - * The starting position for each line. - * - * CodeMirror requires relative position to line, but the parser stores - * only the absolute position of the character in string. - * - * @var array - */ - $lines = getLines($query); - - // Building the response. - foreach ($errors as $idx => $error) { - - // Starting position of the string that caused the error. - list($fromLine, $fromColumn) = findLineNumberAndColumn( - $lines, $error[3] - ); - - // Ending position of the string that caused the error. - list($toLine, $toColumn) = findLineNumberAndColumn( - $lines, $error[3] + strlen($error[2]) - ); - - // Building the response. - $response[] = array( - 'message' => $error[0] . ' (near <code>' . $error[2] . '</code>)', - 'fromLine' => $fromLine, - 'fromColumn' => $fromColumn, - 'toLine' => $toLine, - 'toColumn' => $toColumn, - 'severity' => 'error', - ); - } - - // Sending back the answer. - echo json_encode($response); -} +require_once 'libraries/Linter.class.php'; -linter($_REQUEST['sql_query']); +PMA_Linter::lint($_REQUEST['sql_query']); diff --git a/test/libraries/PMA_Linter_Test.php b/test/libraries/PMA_Linter_Test.php new file mode 100644 index 0000000000..7a5b60df2e --- /dev/null +++ b/test/libraries/PMA_Linter_Test.php @@ -0,0 +1,146 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Tests for Linter.class.php. + * + * @package PhpMyAdmin-test + */ + +/* + * Include to test. + */ +require_once 'libraries/Linter.class.php'; + +/** + * Tests for Linter.class.php. + * + * @package PhpMyAdmin-test + */ +class PMA_Linter_Test extends PHPUnit_Framework_TestCase +{ + + /** + * Test for PMA_Linter::getLines + * + * @return void + */ + public function testGetLines() + { + $this->assertEquals(array(0), PMA_Linter::getLines('')); + $this->assertEquals(array(0, 2), PMA_Linter::getLines("a\nb")); + $this->assertEquals(array(0, 4, 7), PMA_Linter::getLines("abc\nde\n")); + } + + /** + * Test for PMA_Linter::findLineNumberAndColumn + * + * @return void + */ + public function testFindLineNumberAndColumn() + { + // Let the analyzed string be: + // ^abc$ + // ^de$ + // ^$ + // + // Where `^` is the beginning of the line and `$` the end of the line. + // + // Positions of each character (by line): + // ( a, 0), ( b, 1), ( c, 2), (\n, 3), + // ( d, 4), ( e, 5), (\n, 6), + // (\n, 7). + $this->assertEquals( + array(1, 0), + PMA_Linter::findLineNumberAndColumn(array(0, 4, 7), 4) + ); + $this->assertEquals( + array(1, 1), + PMA_Linter::findLineNumberAndColumn(array(0, 4, 7), 5) + ); + $this->assertEquals( + array(1, 2), + PMA_Linter::findLineNumberAndColumn(array(0, 4, 7), 6) + ); + $this->assertEquals( + array(2, 0), + PMA_Linter::findLineNumberAndColumn(array(0, 4, 7), 7) + ); + } + + /** + * Test for PMA_Linter::lint + * + * @return void + */ + public function testLintEmpty() + { + $this->expectOutputString('[]'); + PMA_Linter::lint(''); + } + + /** + * Test for PMA_Linter::lint + * + * @return void + */ + public function testLintNoErrors() + { + $this->expectOutputString('[]'); + PMA_Linter::lint('SELECT * FROM tbl'); + } + + /** + * Test for PMA_Linter::lint + * + * @return void + */ + public function testLintErrors() + { + $this->expectOutputString( + json_encode( + array( + array( + 'message' => 'Unrecognized data type. (near <code>IN</code>)', + 'fromLine' => 0, + 'fromColumn' => 22, + 'toLine' => 0, + 'toColumn' => 24, + 'severity' => 'error', + ), + array( + 'message' => 'A closing bracket was expected. (near <code>IN</code>)', + 'fromLine' => 0, + 'fromColumn' => 22, + 'toLine' => 0, + 'toColumn' => 24, + 'severity' => 'error', + ) + ) + ) + ); + PMA_Linter::lint('CREATE TABLE tbl ( id IN'); + } + + /** + * Test for PMA_Linter::lint + * + * @return void + */ + public function testLongQuery() { + $this->expectOutputString( + json_encode( + array( + array( + 'message' => 'The linting is disabled for this query because it exceededs the maxmimum length.', + 'fromLine' => 0, + 'fromColumn' => 0, + 'toLine' => 0, + 'toColumn' => 0, + 'severity' => 'warning', + ) + ) + ) + ); + PMA_Linter::lint(str_repeat(";", 10001)); + } +} |