UNPKG

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