Linter.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Analyzes a query and gives user feedback.
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. namespace PhpMyAdmin;
  9. use PhpMyAdmin\SqlParser\Lexer;
  10. use PhpMyAdmin\SqlParser\Parser;
  11. use PhpMyAdmin\SqlParser\UtfString;
  12. use PhpMyAdmin\SqlParser\Utils\Error as ParserError;
  13. /**
  14. * The linter itself.
  15. *
  16. * @package PhpMyAdmin
  17. */
  18. class Linter
  19. {
  20. /**
  21. * Gets the starting position of each line.
  22. *
  23. * @param string $str String to be analyzed.
  24. *
  25. * @return array
  26. */
  27. public static function getLines($str)
  28. {
  29. if ((!($str instanceof UtfString))
  30. && (defined('USE_UTF_STRINGS')) && (USE_UTF_STRINGS)
  31. ) {
  32. // If the lexer uses UtfString for processing then the position will
  33. // represent the position of the character and not the position of
  34. // the byte.
  35. $str = new UtfString($str);
  36. }
  37. // The reason for using the strlen is that the length
  38. // required is the length in bytes, not characters.
  39. //
  40. // Given the following string: `????+`, where `?` represents a
  41. // multi-byte character (lets assume that every `?` is a 2-byte
  42. // character) and `+` is a newline, the first value of `$i` is `0`
  43. // and the last one is `4` (because there are 5 characters). Bytes
  44. // `$str[0]` and `$str[1]` are the first character, `$str[2]` and
  45. // `$str[3]` are the second one and `$str[4]` is going to be the
  46. // first byte of the third character. The fourth and the last one
  47. // (which is actually a new line) aren't going to be processed at
  48. // all.
  49. $len = ($str instanceof UtfString) ?
  50. $str->length() : strlen($str);
  51. $lines = array(0);
  52. for ($i = 0; $i < $len; ++$i) {
  53. if ($str[$i] === "\n") {
  54. $lines[] = $i + 1;
  55. }
  56. }
  57. return $lines;
  58. }
  59. /**
  60. * Computes the number of the line and column given an absolute position.
  61. *
  62. * @param array $lines The starting position of each line.
  63. * @param int $pos The absolute position
  64. *
  65. * @return array
  66. */
  67. public static function findLineNumberAndColumn(array $lines, $pos)
  68. {
  69. $line = 0;
  70. foreach ($lines as $lineNo => $lineStart) {
  71. if ($lineStart > $pos) {
  72. break;
  73. }
  74. $line = $lineNo;
  75. }
  76. return array($line, $pos - $lines[$line]);
  77. }
  78. /**
  79. * Runs the linting process.
  80. *
  81. * @param string $query The query to be checked.
  82. *
  83. * @return array
  84. */
  85. public static function lint($query)
  86. {
  87. // Disabling lint for huge queries to save some resources.
  88. if (mb_strlen($query) > 10000) {
  89. return array(
  90. array(
  91. 'message' => __(
  92. 'Linting is disabled for this query because it exceeds the '
  93. . 'maximum length.'
  94. ),
  95. 'fromLine' => 0,
  96. 'fromColumn' => 0,
  97. 'toLine' => 0,
  98. 'toColumn' => 0,
  99. 'severity' => 'warning',
  100. )
  101. );
  102. }
  103. /**
  104. * Lexer used for tokenizing the query.
  105. *
  106. * @var Lexer
  107. */
  108. $lexer = new Lexer($query);
  109. /**
  110. * Parsed used for analysing the query.
  111. *
  112. * @var Parser
  113. */
  114. $parser = new Parser($lexer->list);
  115. /**
  116. * Array containing all errors.
  117. *
  118. * @var array
  119. */
  120. $errors = ParserError::get(array($lexer, $parser));
  121. /**
  122. * The response containing of all errors.
  123. *
  124. * @var array
  125. */
  126. $response = array();
  127. /**
  128. * The starting position for each line.
  129. *
  130. * CodeMirror requires relative position to line, but the parser stores
  131. * only the absolute position of the character in string.
  132. *
  133. * @var array
  134. */
  135. $lines = static::getLines($query);
  136. // Building the response.
  137. foreach ($errors as $idx => $error) {
  138. // Starting position of the string that caused the error.
  139. list($fromLine, $fromColumn) = static::findLineNumberAndColumn(
  140. $lines, $error[3]
  141. );
  142. // Ending position of the string that caused the error.
  143. list($toLine, $toColumn) = static::findLineNumberAndColumn(
  144. $lines, $error[3] + mb_strlen($error[2])
  145. );
  146. // Building the response.
  147. $response[] = array(
  148. 'message' => sprintf(
  149. __('%1$s (near <code>%2$s</code>)'),
  150. htmlspecialchars($error[0]), htmlspecialchars($error[2])
  151. ),
  152. 'fromLine' => $fromLine,
  153. 'fromColumn' => $fromColumn,
  154. 'toLine' => $toLine,
  155. 'toColumn' => $toColumn,
  156. 'severity' => 'error',
  157. );
  158. }
  159. // Sending back the answer.
  160. return $response;
  161. }
  162. }