From 024d96700d7aa8601687ebbc5a14bfbd103d7d56 Mon Sep 17 00:00:00 2001 From: Dan Ungureanu Date: Wed, 15 Jul 2015 18:08:18 +0300 Subject: [PATCH] Reorganized code. Fixed a bug that miscalculated the position of the tokens. Added tests. Signed-off-by: Dan Ungureanu --- libraries/Linter.class.php | 149 +++++++++++++++++++++++++++++ lint.php | 144 +++------------------------- test/libraries/PMA_Linter_Test.php | 146 ++++++++++++++++++++++++++++ 3 files changed, 309 insertions(+), 130 deletions(-) create mode 100644 libraries/Linter.class.php create mode 100644 test/libraries/PMA_Linter_Test.php 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 @@ + $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 ' . $error[2] . ')', + 'fromLine' => $fromLine, + 'fromColumn' => $fromColumn, + 'toLine' => $toLine, + 'toColumn' => $toColumn, + 'severity' => 'error', + ); + } + + // Sending back the answer. + echo json_encode($response); + } + +} \ No newline at end of file diff --git a/lint.php b/lint.php index 8a5a7cfb4a..138156c806 100644 --- a/lint.php +++ b/lint.php @@ -1,137 +1,21 @@ $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 - */ -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 ' . $error[2] . ')', - 'fromLine' => $fromLine, - 'fromColumn' => $fromColumn, - 'toLine' => $toLine, - 'toColumn' => $toColumn, - 'severity' => 'error', - ); - } - - // Sending back the answer. - echo json_encode($response); -} - -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 @@ +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 IN)', + 'fromLine' => 0, + 'fromColumn' => 22, + 'toLine' => 0, + 'toColumn' => 24, + 'severity' => 'error', + ), + array( + 'message' => 'A closing bracket was expected. (near IN)', + '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)); + } +}