You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
5.7 KiB
196 lines
5.7 KiB
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;
|
|
|