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));
+ }
+}