source: frontend/node_modules/postcss-normalize-string/src/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.2 KB
RevLine 
[9af201e]1'use strict';
2const valueParser = require('postcss-value-parser');
3
4/*
5 * Constants (parser usage)
6 */
7
8const SINGLE_QUOTE = "'".charCodeAt(0);
9const DOUBLE_QUOTE = '"'.charCodeAt(0);
10const BACKSLASH = '\\'.charCodeAt(0);
11const NEWLINE = '\n'.charCodeAt(0);
12const SPACE = ' '.charCodeAt(0);
13const FEED = '\f'.charCodeAt(0);
14const TAB = '\t'.charCodeAt(0);
15const CR = '\r'.charCodeAt(0);
16
17const WORD_END = /[ \n\t\r\f'"\\]/g;
18
19/*
20 * Constants (node type strings)
21 */
22
23const C_STRING = 'string';
24const C_ESCAPED_SINGLE_QUOTE = 'escapedSingleQuote';
25const C_ESCAPED_DOUBLE_QUOTE = 'escapedDoubleQuote';
26const C_SINGLE_QUOTE = 'singleQuote';
27const C_DOUBLE_QUOTE = 'doubleQuote';
28const C_NEWLINE = 'newline';
29const C_SINGLE = 'single';
30
31/*
32 * Literals
33 */
34
35const L_SINGLE_QUOTE = `'`;
36const L_DOUBLE_QUOTE = `"`;
37const L_NEWLINE = `\\\n`;
38
39/*
40 * Parser nodes
41 */
42
43const T_ESCAPED_SINGLE_QUOTE = { type: C_ESCAPED_SINGLE_QUOTE, value: `\\'` };
44const T_ESCAPED_DOUBLE_QUOTE = { type: C_ESCAPED_DOUBLE_QUOTE, value: `\\"` };
45const T_SINGLE_QUOTE = { type: C_SINGLE_QUOTE, value: L_SINGLE_QUOTE };
46const T_DOUBLE_QUOTE = { type: C_DOUBLE_QUOTE, value: L_DOUBLE_QUOTE };
47const T_NEWLINE = { type: C_NEWLINE, value: L_NEWLINE };
48
49/** @typedef {T_ESCAPED_SINGLE_QUOTE | T_ESCAPED_DOUBLE_QUOTE | T_SINGLE_QUOTE | T_NEWLINE} StringAstNode */
50/**
51 * @typedef {{nodes: StringAstNode[],
52 * types: {escapedSingleQuote: number, escapedDoubleQuote: number, singleQuote: number, doubleQuote: number},
53 * quotes: boolean}} StringAst
54 */
55
56/**
57 * @param {StringAst} ast
58 * @return {string}
59 */
60function stringify(ast) {
61 return ast.nodes.reduce((str, { value }) => {
62 // Collapse multiple line strings automatically
63 if (value === L_NEWLINE) {
64 return str;
65 }
66
67 return str + value;
68 }, '');
69}
70
71/**
72 * @param {string} str
73 * @return {StringAst}
74 */
75function parse(str) {
76 let code, next, value;
77 let pos = 0;
78 let len = str.length;
79
80 /** @type StringAst */
81 const ast = {
82 nodes: [],
83 types: {
84 escapedSingleQuote: 0,
85 escapedDoubleQuote: 0,
86 singleQuote: 0,
87 doubleQuote: 0,
88 },
89 quotes: false,
90 };
91
92 while (pos < len) {
93 code = str.charCodeAt(pos);
94
95 switch (code) {
96 case SPACE:
97 case TAB:
98 case CR:
99 case FEED:
100 next = pos;
101
102 do {
103 next += 1;
104 code = str.charCodeAt(next);
105 } while (
106 code === SPACE ||
107 code === NEWLINE ||
108 code === TAB ||
109 code === CR ||
110 code === FEED
111 );
112
113 ast.nodes.push({
114 type: 'space',
115 value: str.slice(pos, next),
116 });
117 pos = next - 1;
118 break;
119 case SINGLE_QUOTE:
120 ast.nodes.push(T_SINGLE_QUOTE);
121 ast.types[C_SINGLE_QUOTE]++;
122 ast.quotes = true;
123 break;
124 case DOUBLE_QUOTE:
125 ast.nodes.push(T_DOUBLE_QUOTE);
126 ast.types[C_DOUBLE_QUOTE]++;
127 ast.quotes = true;
128 break;
129 case BACKSLASH:
130 next = pos + 1;
131
132 if (str.charCodeAt(next) === SINGLE_QUOTE) {
133 ast.nodes.push(T_ESCAPED_SINGLE_QUOTE);
134 ast.types[C_ESCAPED_SINGLE_QUOTE]++;
135 ast.quotes = true;
136 pos = next;
137 break;
138 } else if (str.charCodeAt(next) === DOUBLE_QUOTE) {
139 ast.nodes.push(T_ESCAPED_DOUBLE_QUOTE);
140 ast.types[C_ESCAPED_DOUBLE_QUOTE]++;
141 ast.quotes = true;
142 pos = next;
143 break;
144 } else if (str.charCodeAt(next) === NEWLINE) {
145 ast.nodes.push(T_NEWLINE);
146 pos = next;
147 break;
148 }
149 /*
150 * We need to fall through here to handle the token as
151 * a whole word. The missing 'break' is intentional.
152 */
153 default:
154 WORD_END.lastIndex = pos + 1;
155 WORD_END.test(str);
156
157 if (WORD_END.lastIndex === 0) {
158 next = len - 1;
159 } else {
160 next = WORD_END.lastIndex - 2;
161 }
162
163 value = str.slice(pos, next + 1);
164
165 ast.nodes.push({
166 type: C_STRING,
167 value,
168 });
169
170 pos = next;
171 }
172 pos++;
173 }
174
175 return ast;
176}
177
178/**
179 * @param {valueParser.StringNode} node
180 * @param {StringAst} ast
181 * @return {void}
182 */
183function changeWrappingQuotes(node, ast) {
184 const { types } = ast;
185
186 if (types[C_SINGLE_QUOTE] || types[C_DOUBLE_QUOTE]) {
187 return;
188 }
189
190 if (
191 node.quote === L_SINGLE_QUOTE &&
192 types[C_ESCAPED_SINGLE_QUOTE] > 0 &&
193 !types[C_ESCAPED_DOUBLE_QUOTE]
194 ) {
195 node.quote = L_DOUBLE_QUOTE;
196 }
197
198 if (
199 node.quote === L_DOUBLE_QUOTE &&
200 types[C_ESCAPED_DOUBLE_QUOTE] > 0 &&
201 !types[C_ESCAPED_SINGLE_QUOTE]
202 ) {
203 node.quote = L_SINGLE_QUOTE;
204 }
205
206 ast.nodes = changeChildQuotes(ast.nodes, node.quote);
207}
208/**
209 * @param {StringAstNode[]} childNodes
210 * @param {string} parentQuote
211 * @return {StringAstNode[]}
212 */
213function changeChildQuotes(childNodes, parentQuote) {
214 const updatedChildren = [];
215 for (const child of childNodes) {
216 if (
217 child.type === C_ESCAPED_DOUBLE_QUOTE &&
218 parentQuote === L_SINGLE_QUOTE
219 ) {
220 updatedChildren.push(T_DOUBLE_QUOTE);
221 } else if (
222 child.type === C_ESCAPED_SINGLE_QUOTE &&
223 parentQuote === L_DOUBLE_QUOTE
224 ) {
225 updatedChildren.push(T_SINGLE_QUOTE);
226 } else {
227 updatedChildren.push(child);
228 }
229 }
230 return updatedChildren;
231}
232
233/**
234 * @param {string} value
235 * @param {'single' | 'double'} preferredQuote
236 * @return {string}
237 */
238function normalize(value, preferredQuote) {
239 if (!value || !value.length) {
240 return value;
241 }
242
243 return valueParser(value)
244 .walk((child) => {
245 if (child.type !== C_STRING) {
246 return;
247 }
248
249 const ast = parse(child.value);
250
251 if (ast.quotes) {
252 changeWrappingQuotes(child, ast);
253 } else if (preferredQuote === C_SINGLE) {
254 child.quote = L_SINGLE_QUOTE;
255 } else {
256 child.quote = L_DOUBLE_QUOTE;
257 }
258
259 child.value = stringify(ast);
260 })
261 .toString();
262}
263
264/**
265 * @param {string} original
266 * @param {Map<string, string>} cache
267 * @param {'single' | 'double'} preferredQuote
268 * @return {string}
269 */
270function minify(original, cache, preferredQuote) {
271 const key = original + '|' + preferredQuote;
272 if (cache.has(key)) {
273 return /** @type {string} */ (cache.get(key));
274 }
275 const newValue = normalize(original, preferredQuote);
276 cache.set(key, newValue);
277 return newValue;
278}
279
280/** @typedef {{preferredQuote?: 'double' | 'single'}} Options */
281/**
282 * @type {import('postcss').PluginCreator<Options>}
283 * @param {Options} opts
284 * @return {import('postcss').Plugin}
285 */
286function pluginCreator(opts) {
287 const { preferredQuote } = Object.assign(
288 {},
289 {
290 preferredQuote: 'double',
291 },
292 opts
293 );
294
295 return {
296 postcssPlugin: 'postcss-normalize-string',
297
298 OnceExit(css) {
299 const cache = new Map();
300
301 css.walk((node) => {
302 switch (node.type) {
303 case 'rule':
304 node.selector = minify(node.selector, cache, preferredQuote);
305 break;
306 case 'decl':
307 node.value = minify(node.value, cache, preferredQuote);
308 break;
309 case 'atrule':
310 node.params = minify(node.params, cache, preferredQuote);
311 break;
312 }
313 });
314 },
315 };
316}
317
318pluginCreator.postcss = true;
319module.exports = pluginCreator;
Note: See TracBrowser for help on using the repository browser.