Linter.class.php 5.1 KB

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