123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- const {parsingErrorCode, SQLParsingError} = require('./error');
- const {getIndexPos} = require('./utils');
- // symbols that need no spaces around them:
- const compressors = '.,;:()[]=<>+-*/|!?@#';
- ////////////////////////////////////////////
- // Parses and minimizes a PostgreSQL script.
- function minify(sql, options) {
- if (typeof sql !== 'string') {
- throw new TypeError('Input SQL must be a text string.');
- }
- if (!sql.length) {
- return '';
- }
- sql = sql.replace(/\r\n/g, '\n');
- options = options || {};
- let idx = 0, // current index
- result = '', // resulting sql
- space = false; // add a space on the next step
- const len = sql.length;
- do {
- const s = sql[idx], // current symbol;
- s1 = sql[idx + 1]; // next symbol;
- if (isGap(s)) {
- while (++idx < len && isGap(sql[idx])) ;
- if (idx < len) {
- space = true;
- }
- idx--;
- continue;
- }
- if (s === '-' && s1 === '-') {
- const lb = sql.indexOf('\n', idx + 2);
- if (lb < 0) {
- break;
- }
- idx = lb - 1;
- skipGaps();
- continue;
- }
- if (s === '/' && s1 === '*') {
- let c = idx + 1, open = 0, close = 0, lastOpen, lastClose;
- while (++c < len - 1 && close <= open) {
- if (sql[c] === '/' && sql[c + 1] === '*') {
- lastOpen = c;
- open++;
- c++;
- } else {
- if (sql[c] === '*' && sql[c + 1] === '/') {
- lastClose = c;
- close++;
- c++;
- }
- }
- }
- if (close <= open) {
- idx = lastOpen;
- throwError(parsingErrorCode.unclosedMLC);
- }
- if (sql[idx + 2] === '!' && !options.removeAll) {
- if (options.compress) {
- space = false;
- }
- addSpace();
- result += sql.substring(idx, lastClose + 2)
- .replace(/\n/g, '\r\n');
- }
- idx = lastClose + 1;
- skipGaps();
- continue;
- }
- let closeIdx, text;
- if (s === '"') {
- closeIdx = sql.indexOf('"', idx + 1);
- if (closeIdx < 0) {
- throwError(parsingErrorCode.unclosedQI);
- }
- text = sql.substring(idx, closeIdx + 1);
- if (text.indexOf('\n') > 0) {
- throwError(parsingErrorCode.multiLineQI);
- }
- if (options.compress) {
- space = false;
- }
- addSpace();
- result += text;
- idx = closeIdx;
- skipGaps();
- continue;
- }
- if (s === `'`) {
- closeIdx = idx;
- do {
- closeIdx = sql.indexOf(`'`, closeIdx + 1);
- if (closeIdx > 0) {
- let i = closeIdx;
- while (sql[--i] === '\\') ;
- if ((closeIdx - i) % 2) {
- let step = closeIdx;
- while (++step < len && sql[step] === `'`) ;
- if ((step - closeIdx) % 2) {
- closeIdx = step - 1;
- break;
- }
- closeIdx = step === len ? -1 : step;
- }
- }
- } while (closeIdx > 0);
- if (closeIdx < 0) {
- throwError(parsingErrorCode.unclosedText);
- }
- if (options.compress) {
- space = false;
- }
- addSpace();
- text = sql.substring(idx, closeIdx + 1);
- const hasLB = text.indexOf('\n') > 0;
- if (hasLB) {
- text = text.split('\n').map(m => {
- return m.replace(/^\s+|\s+$/g, '');
- }).join('\\n');
- }
- const hasTabs = text.indexOf('\t') > 0;
- if (hasLB || hasTabs) {
- const prev = idx ? sql[idx - 1] : '';
- if (prev !== 'E' && prev !== 'e') {
- const r = result ? result[result.length - 1] : '';
- if (r && r !== ' ' && compressors.indexOf(r) < 0) {
- result += ' ';
- }
- result += 'E';
- }
- if (hasTabs) {
- text = text.replace(/\t/g, '\\t');
- }
- }
- result += text;
- idx = closeIdx;
- skipGaps();
- continue;
- }
- if (options.compress && compressors.indexOf(s) >= 0) {
- space = false;
- skipGaps();
- }
- addSpace();
- result += s;
- } while (++idx < len);
- return result;
- function skipGaps() {
- if (options.compress) {
- while (idx < len - 1 && isGap(sql[idx + 1]) && idx++) ;
- }
- }
- function addSpace() {
- if (space) {
- if (result.length) {
- result += ' ';
- }
- space = false;
- }
- }
- function throwError(code) {
- const position = getIndexPos(sql, idx);
- throw new SQLParsingError(code, position);
- }
- }
- ////////////////////////////////////
- // Identifies a gap / empty symbol.
- function isGap(s) {
- return s === ' ' || s === '\t' || s === '\r' || s === '\n';
- }
- module.exports = minify;
|