parser.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. const {parsingErrorCode, SQLParsingError} = require('./error');
  2. const {getIndexPos} = require('./utils');
  3. // symbols that need no spaces around them:
  4. const compressors = '.,;:()[]=<>+-*/|!?@#';
  5. ////////////////////////////////////////////
  6. // Parses and minimizes a PostgreSQL script.
  7. function minify(sql, options) {
  8. if (typeof sql !== 'string') {
  9. throw new TypeError('Input SQL must be a text string.');
  10. }
  11. if (!sql.length) {
  12. return '';
  13. }
  14. sql = sql.replace(/\r\n/g, '\n');
  15. options = options || {};
  16. let idx = 0, // current index
  17. result = '', // resulting sql
  18. space = false; // add a space on the next step
  19. const len = sql.length;
  20. do {
  21. const s = sql[idx], // current symbol;
  22. s1 = sql[idx + 1]; // next symbol;
  23. if (isGap(s)) {
  24. while (++idx < len && isGap(sql[idx])) ;
  25. if (idx < len) {
  26. space = true;
  27. }
  28. idx--;
  29. continue;
  30. }
  31. if (s === '-' && s1 === '-') {
  32. const lb = sql.indexOf('\n', idx + 2);
  33. if (lb < 0) {
  34. break;
  35. }
  36. idx = lb - 1;
  37. skipGaps();
  38. continue;
  39. }
  40. if (s === '/' && s1 === '*') {
  41. let c = idx + 1, open = 0, close = 0, lastOpen, lastClose;
  42. while (++c < len - 1 && close <= open) {
  43. if (sql[c] === '/' && sql[c + 1] === '*') {
  44. lastOpen = c;
  45. open++;
  46. c++;
  47. } else {
  48. if (sql[c] === '*' && sql[c + 1] === '/') {
  49. lastClose = c;
  50. close++;
  51. c++;
  52. }
  53. }
  54. }
  55. if (close <= open) {
  56. idx = lastOpen;
  57. throwError(parsingErrorCode.unclosedMLC);
  58. }
  59. if (sql[idx + 2] === '!' && !options.removeAll) {
  60. if (options.compress) {
  61. space = false;
  62. }
  63. addSpace();
  64. result += sql.substring(idx, lastClose + 2)
  65. .replace(/\n/g, '\r\n');
  66. }
  67. idx = lastClose + 1;
  68. skipGaps();
  69. continue;
  70. }
  71. let closeIdx, text;
  72. if (s === '"') {
  73. closeIdx = sql.indexOf('"', idx + 1);
  74. if (closeIdx < 0) {
  75. throwError(parsingErrorCode.unclosedQI);
  76. }
  77. text = sql.substring(idx, closeIdx + 1);
  78. if (text.indexOf('\n') > 0) {
  79. throwError(parsingErrorCode.multiLineQI);
  80. }
  81. if (options.compress) {
  82. space = false;
  83. }
  84. addSpace();
  85. result += text;
  86. idx = closeIdx;
  87. skipGaps();
  88. continue;
  89. }
  90. if (s === `'`) {
  91. closeIdx = idx;
  92. do {
  93. closeIdx = sql.indexOf(`'`, closeIdx + 1);
  94. if (closeIdx > 0) {
  95. let i = closeIdx;
  96. while (sql[--i] === '\\') ;
  97. if ((closeIdx - i) % 2) {
  98. let step = closeIdx;
  99. while (++step < len && sql[step] === `'`) ;
  100. if ((step - closeIdx) % 2) {
  101. closeIdx = step - 1;
  102. break;
  103. }
  104. closeIdx = step === len ? -1 : step;
  105. }
  106. }
  107. } while (closeIdx > 0);
  108. if (closeIdx < 0) {
  109. throwError(parsingErrorCode.unclosedText);
  110. }
  111. if (options.compress) {
  112. space = false;
  113. }
  114. addSpace();
  115. text = sql.substring(idx, closeIdx + 1);
  116. const hasLB = text.indexOf('\n') > 0;
  117. if (hasLB) {
  118. text = text.split('\n').map(m => {
  119. return m.replace(/^\s+|\s+$/g, '');
  120. }).join('\\n');
  121. }
  122. const hasTabs = text.indexOf('\t') > 0;
  123. if (hasLB || hasTabs) {
  124. const prev = idx ? sql[idx - 1] : '';
  125. if (prev !== 'E' && prev !== 'e') {
  126. const r = result ? result[result.length - 1] : '';
  127. if (r && r !== ' ' && compressors.indexOf(r) < 0) {
  128. result += ' ';
  129. }
  130. result += 'E';
  131. }
  132. if (hasTabs) {
  133. text = text.replace(/\t/g, '\\t');
  134. }
  135. }
  136. result += text;
  137. idx = closeIdx;
  138. skipGaps();
  139. continue;
  140. }
  141. if (options.compress && compressors.indexOf(s) >= 0) {
  142. space = false;
  143. skipGaps();
  144. }
  145. addSpace();
  146. result += s;
  147. } while (++idx < len);
  148. return result;
  149. function skipGaps() {
  150. if (options.compress) {
  151. while (idx < len - 1 && isGap(sql[idx + 1]) && idx++) ;
  152. }
  153. }
  154. function addSpace() {
  155. if (space) {
  156. if (result.length) {
  157. result += ' ';
  158. }
  159. space = false;
  160. }
  161. }
  162. function throwError(code) {
  163. const position = getIndexPos(sql, idx);
  164. throw new SQLParsingError(code, position);
  165. }
  166. }
  167. ////////////////////////////////////
  168. // Identifies a gap / empty symbol.
  169. function isGap(s) {
  170. return s === ' ' || s === '\t' || s === '\r' || s === '\n';
  171. }
  172. module.exports = minify;