postcss-normalize-string 9.0.4 → 9.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +152 -85
- package/types/index.d.ts.map +1 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -6,6 +6,69 @@ const BACKSLASH = '\\'.charCodeAt(0);
|
|
|
6
6
|
const NEWLINE = '\n'.charCodeAt(0);
|
|
7
7
|
const FEED = '\f'.charCodeAt(0);
|
|
8
8
|
const CR = '\r'.charCodeAt(0);
|
|
9
|
+
const SPACE = ' '.charCodeAt(0);
|
|
10
|
+
const TAB = '\t'.charCodeAt(0);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {number} code
|
|
14
|
+
* @return {boolean}
|
|
15
|
+
*/
|
|
16
|
+
function isHexDigit(code) {
|
|
17
|
+
return (
|
|
18
|
+
(code >= 0x30 && code <= 0x39) ||
|
|
19
|
+
(code >= 0x41 && code <= 0x46) ||
|
|
20
|
+
(code >= 0x61 && code <= 0x66)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return the index just past the hex digits of the escape whose backslash is at `pos`.
|
|
26
|
+
* CSS Syntax 3 allows one to six hex digits.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} inner
|
|
29
|
+
* @param {number} pos
|
|
30
|
+
* @return {number}
|
|
31
|
+
*/
|
|
32
|
+
function getHexDigitsEnd(inner, pos) {
|
|
33
|
+
const limit = Math.min(pos + 7, inner.length);
|
|
34
|
+
let end = pos + 2;
|
|
35
|
+
while (end < limit && isHexDigit(inner.charCodeAt(end))) {
|
|
36
|
+
end++;
|
|
37
|
+
}
|
|
38
|
+
return end;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Return the length of the whitespace at `pos` (CRLF counts as one newline), or 0.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} inner
|
|
45
|
+
* @param {number} pos
|
|
46
|
+
* @return {number}
|
|
47
|
+
*/
|
|
48
|
+
function getWhitespaceLength(inner, pos) {
|
|
49
|
+
const code = inner.charCodeAt(pos);
|
|
50
|
+
if (code === CR) {
|
|
51
|
+
return inner.charCodeAt(pos + 1) === NEWLINE ? 2 : 1;
|
|
52
|
+
}
|
|
53
|
+
return code === SPACE || code === TAB || code === NEWLINE || code === FEED
|
|
54
|
+
? 1
|
|
55
|
+
: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Return the space that keeps an open hex escape from absorbing the code point at `pos`.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} inner
|
|
62
|
+
* @param {number} pos
|
|
63
|
+
* @param {boolean} acceptsMoreDigits
|
|
64
|
+
* @return {string}
|
|
65
|
+
*/
|
|
66
|
+
function getHexEscapeDelimiter(inner, pos, acceptsMoreDigits) {
|
|
67
|
+
return getWhitespaceLength(inner, pos) > 0 ||
|
|
68
|
+
(acceptsMoreDigits && isHexDigit(inner.charCodeAt(pos)))
|
|
69
|
+
? ' '
|
|
70
|
+
: '';
|
|
71
|
+
}
|
|
9
72
|
|
|
10
73
|
/**
|
|
11
74
|
* Verify whether a string token has matching delimiters and balanced trailing escapes.
|
|
@@ -14,20 +77,21 @@ const CR = '\r'.charCodeAt(0);
|
|
|
14
77
|
* @return {boolean}
|
|
15
78
|
*/
|
|
16
79
|
function isClosedString(value) {
|
|
17
|
-
|
|
80
|
+
const len = value.length;
|
|
81
|
+
if (len < 2 || value.charCodeAt(len - 1) !== value.charCodeAt(0)) {
|
|
18
82
|
return false;
|
|
19
83
|
}
|
|
20
84
|
|
|
21
85
|
let backslashes = 0;
|
|
22
86
|
for (
|
|
23
|
-
let index =
|
|
24
|
-
index >= 0 && value
|
|
87
|
+
let index = len - 2;
|
|
88
|
+
index >= 0 && value.charCodeAt(index) === BACKSLASH;
|
|
25
89
|
index--
|
|
26
90
|
) {
|
|
27
91
|
backslashes++;
|
|
28
92
|
}
|
|
29
93
|
|
|
30
|
-
return
|
|
94
|
+
return backslashes % 2 === 0;
|
|
31
95
|
}
|
|
32
96
|
|
|
33
97
|
/**
|
|
@@ -66,43 +130,30 @@ function scanQuotes(inner, currentQuote) {
|
|
|
66
130
|
|
|
67
131
|
while (pos < len) {
|
|
68
132
|
const code = inner.charCodeAt(pos);
|
|
69
|
-
if (code
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
pos += newlineLen;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const next = inner.charCodeAt(pos + 1);
|
|
78
|
-
if (next === SINGLE_QUOTE) {
|
|
79
|
-
singleCount++;
|
|
80
|
-
if (currentQuote === '"') {
|
|
81
|
-
hasRedundantEscape = true;
|
|
82
|
-
}
|
|
83
|
-
pos += 2;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (next === DOUBLE_QUOTE) {
|
|
87
|
-
doubleCount++;
|
|
88
|
-
if (currentQuote === "'") {
|
|
89
|
-
hasRedundantEscape = true;
|
|
90
|
-
}
|
|
91
|
-
pos += 2;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
pos += 2;
|
|
95
|
-
continue;
|
|
133
|
+
if (code !== BACKSLASH) {
|
|
134
|
+
if (code === SINGLE_QUOTE) {
|
|
135
|
+
singleCount++;
|
|
136
|
+
} else if (code === DOUBLE_QUOTE) {
|
|
137
|
+
doubleCount++;
|
|
96
138
|
}
|
|
97
139
|
pos++;
|
|
98
140
|
continue;
|
|
99
141
|
}
|
|
100
|
-
|
|
142
|
+
const newlineLen = getEscapedNewlineLength(inner, pos, len);
|
|
143
|
+
if (newlineLen > 0) {
|
|
144
|
+
hasEscapedNewline = true;
|
|
145
|
+
pos += newlineLen;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const next = inner.charCodeAt(pos + 1);
|
|
149
|
+
if (next === SINGLE_QUOTE) {
|
|
101
150
|
singleCount++;
|
|
102
|
-
|
|
151
|
+
hasRedundantEscape ||= currentQuote === '"';
|
|
152
|
+
} else if (next === DOUBLE_QUOTE) {
|
|
103
153
|
doubleCount++;
|
|
154
|
+
hasRedundantEscape ||= currentQuote === "'";
|
|
104
155
|
}
|
|
105
|
-
pos
|
|
156
|
+
pos += 2;
|
|
106
157
|
}
|
|
107
158
|
|
|
108
159
|
return { singleCount, doubleCount, hasEscapedNewline, hasRedundantEscape };
|
|
@@ -151,35 +202,55 @@ function reconstructString(inner, targetQuote) {
|
|
|
151
202
|
const chunks = [targetQuote];
|
|
152
203
|
let cursor = 0;
|
|
153
204
|
let pos = 0;
|
|
205
|
+
// A hex escape without a whitespace terminator would absorb a following hex
|
|
206
|
+
// digit or whitespace once the line continuations after it are removed.
|
|
207
|
+
let openHexEnd = -1;
|
|
208
|
+
let openHexIsShort = false;
|
|
154
209
|
|
|
155
210
|
while (pos < len) {
|
|
156
211
|
const code = inner.charCodeAt(pos);
|
|
157
212
|
if (code === BACKSLASH) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
chunks.push(inner.slice(cursor, pos));
|
|
163
|
-
}
|
|
164
|
-
pos += newlineLen;
|
|
165
|
-
cursor = pos;
|
|
166
|
-
continue;
|
|
213
|
+
const newlineLen = getEscapedNewlineLength(inner, pos, len);
|
|
214
|
+
if (newlineLen > 0) {
|
|
215
|
+
if (pos > cursor) {
|
|
216
|
+
chunks.push(inner.slice(cursor, pos));
|
|
167
217
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
pos
|
|
174
|
-
|
|
175
|
-
|
|
218
|
+
const followsOpenHex = pos === openHexEnd;
|
|
219
|
+
pos += newlineLen;
|
|
220
|
+
cursor = pos;
|
|
221
|
+
if (followsOpenHex) {
|
|
222
|
+
openHexEnd = pos;
|
|
223
|
+
chunks.push(getHexEscapeDelimiter(inner, pos, openHexIsShort));
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const next = inner.charCodeAt(pos + 1);
|
|
229
|
+
if (next === otherCode) {
|
|
230
|
+
if (pos > cursor) {
|
|
231
|
+
chunks.push(inner.slice(cursor, pos));
|
|
176
232
|
}
|
|
233
|
+
chunks.push(otherQuote);
|
|
177
234
|
pos += 2;
|
|
235
|
+
cursor = pos;
|
|
178
236
|
continue;
|
|
179
237
|
}
|
|
180
|
-
|
|
238
|
+
|
|
239
|
+
if (isHexDigit(next)) {
|
|
240
|
+
const digitsEnd = getHexDigitsEnd(inner, pos);
|
|
241
|
+
const terminatorLen = getWhitespaceLength(inner, digitsEnd);
|
|
242
|
+
if (terminatorLen === 0) {
|
|
243
|
+
openHexEnd = digitsEnd;
|
|
244
|
+
openHexIsShort = digitsEnd - pos < 7;
|
|
245
|
+
}
|
|
246
|
+
pos = digitsEnd + terminatorLen;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pos += 2;
|
|
181
251
|
continue;
|
|
182
252
|
}
|
|
253
|
+
|
|
183
254
|
if (code === targetCode) {
|
|
184
255
|
if (pos > cursor) {
|
|
185
256
|
chunks.push(inner.slice(cursor, pos));
|
|
@@ -189,6 +260,7 @@ function reconstructString(inner, targetQuote) {
|
|
|
189
260
|
cursor = pos;
|
|
190
261
|
continue;
|
|
191
262
|
}
|
|
263
|
+
|
|
192
264
|
pos++;
|
|
193
265
|
}
|
|
194
266
|
|
|
@@ -208,7 +280,13 @@ function reconstructString(inner, targetQuote) {
|
|
|
208
280
|
*/
|
|
209
281
|
function normalizeString(raw, preferredQuote) {
|
|
210
282
|
const currentQuote = raw[0];
|
|
283
|
+
const targetDelimiter = preferredQuote === 'single' ? "'" : '"';
|
|
211
284
|
const inner = raw.slice(1, -1);
|
|
285
|
+
|
|
286
|
+
if (currentQuote === targetDelimiter && !inner.includes('\\')) {
|
|
287
|
+
return raw;
|
|
288
|
+
}
|
|
289
|
+
|
|
212
290
|
const { singleCount, doubleCount, hasEscapedNewline, hasRedundantEscape } =
|
|
213
291
|
scanQuotes(inner, currentQuote);
|
|
214
292
|
|
|
@@ -236,25 +314,16 @@ function normalizeString(raw, preferredQuote) {
|
|
|
236
314
|
* @return {string}
|
|
237
315
|
*/
|
|
238
316
|
function normalize(value, preferredQuote) {
|
|
239
|
-
if (!value || (!value.includes("'") && !value.includes('"'))) {
|
|
240
|
-
return value;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
317
|
const chunks = [];
|
|
244
318
|
let cursor = 0;
|
|
245
|
-
let changed = false;
|
|
246
319
|
for (const [type, raw, start, end] of tokenize({ css: value })) {
|
|
247
|
-
if (type !== TokenType.String) continue;
|
|
248
|
-
if (!isClosedString(raw)) continue;
|
|
320
|
+
if (type !== TokenType.String || !isClosedString(raw)) continue;
|
|
249
321
|
const normalized = normalizeString(raw, preferredQuote);
|
|
250
|
-
if (normalized
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
chunks.push(value.slice(cursor, start));
|
|
254
|
-
chunks.push(normalized);
|
|
322
|
+
if (normalized === raw) continue;
|
|
323
|
+
chunks.push(value.slice(cursor, start), normalized);
|
|
255
324
|
cursor = end + 1;
|
|
256
325
|
}
|
|
257
|
-
if (
|
|
326
|
+
if (cursor === 0) return value;
|
|
258
327
|
chunks.push(value.slice(cursor));
|
|
259
328
|
return chunks.join('');
|
|
260
329
|
}
|
|
@@ -266,12 +335,14 @@ function normalize(value, preferredQuote) {
|
|
|
266
335
|
* @return {string}
|
|
267
336
|
*/
|
|
268
337
|
function minify(original, cache, preferredQuote) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
338
|
+
if (!original || (!original.includes("'") && !original.includes('"'))) {
|
|
339
|
+
return original;
|
|
340
|
+
}
|
|
341
|
+
if (cache.has(original)) {
|
|
342
|
+
return /** @type {string} */ (cache.get(original));
|
|
272
343
|
}
|
|
273
344
|
const newValue = normalize(original, preferredQuote);
|
|
274
|
-
cache.set(
|
|
345
|
+
cache.set(original, newValue);
|
|
275
346
|
return newValue;
|
|
276
347
|
}
|
|
277
348
|
|
|
@@ -287,13 +358,7 @@ function assignValue(decl, value) {
|
|
|
287
358
|
* @return {import('postcss').Plugin}
|
|
288
359
|
*/
|
|
289
360
|
function pluginCreator(opts) {
|
|
290
|
-
const { preferredQuote } =
|
|
291
|
-
{},
|
|
292
|
-
{
|
|
293
|
-
preferredQuote: 'double',
|
|
294
|
-
},
|
|
295
|
-
opts
|
|
296
|
-
);
|
|
361
|
+
const { preferredQuote = 'double' } = opts ?? {};
|
|
297
362
|
|
|
298
363
|
return {
|
|
299
364
|
postcssPlugin: 'postcss-normalize-string',
|
|
@@ -309,17 +374,19 @@ function pluginCreator(opts) {
|
|
|
309
374
|
case 'rule':
|
|
310
375
|
node.selector = minify(node.selector, cache, preferredQuote);
|
|
311
376
|
break;
|
|
312
|
-
case 'decl':
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
377
|
+
case 'decl': {
|
|
378
|
+
const rawValue = node.raws.value;
|
|
379
|
+
const value =
|
|
380
|
+
rawValue?.value === node.value
|
|
381
|
+
? (rawValue.raw ?? node.value)
|
|
382
|
+
: node.value;
|
|
383
|
+
assignValue(node, minify(value, cache, preferredQuote));
|
|
320
384
|
break;
|
|
385
|
+
}
|
|
321
386
|
case 'atrule':
|
|
322
|
-
|
|
387
|
+
if (node.name.toLowerCase() !== 'charset') {
|
|
388
|
+
node.params = minify(node.params, cache, preferredQuote);
|
|
389
|
+
}
|
|
323
390
|
break;
|
|
324
391
|
}
|
|
325
392
|
});
|
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAkWI,YAAkD,OAAO,GAA/C;IAAC,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAAC,CAAS;AAA7D,gEAAgE;AAChE;;;GAGG;AACH,iBAAS,aAAa,CAAC,IAAI,EAHhB,OAGgB,GAFf,OAAO,SAAS,EAAE,MAAM,CAqCnC;kBAnCQ,aAAa;iBAoCX,IAAI;;AAEf,QAAA,MAAM,aAAa,sBAAgB,CAAC;AAEpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
|