postcss-normalize-string 5.0.2 → 5.1.0
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 +6 -8
- package/src/index.js +319 -0
- package/types/index.d.ts +38 -0
- package/dist/index.js +0 -277
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-normalize-string",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Normalize wrapping quotes for CSS string literals.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
6
7
|
"files": [
|
|
7
|
-
"
|
|
8
|
-
"LICENSE-MIT"
|
|
8
|
+
"src",
|
|
9
|
+
"LICENSE-MIT",
|
|
10
|
+
"types"
|
|
9
11
|
],
|
|
10
12
|
"keywords": [
|
|
11
13
|
"css",
|
|
@@ -35,9 +37,5 @@
|
|
|
35
37
|
"peerDependencies": {
|
|
36
38
|
"postcss": "^8.2.15"
|
|
37
39
|
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"prebuild": "rimraf dist",
|
|
40
|
-
"build": "babel src --config-file ../../babel.config.json --out-dir dist --ignore \"**/__tests__/\""
|
|
41
|
-
},
|
|
42
40
|
"readme": "# [postcss][postcss]-normalize-string\n\n> Normalize strings with PostCSS.\n\n## Install\n\nWith [npm](https://npmjs.org/package/postcss-normalize-string) do:\n\n```\nnpm install postcss-normalize-string --save\n```\n\n## Example\n\n### Input\n\n```css\np:after{ content: '\\\\'string\\\\' is intact' }\n```\n\n### Output\n\n```css\np:after{ content:\"'string' is intact\" }\n```\n\n## Usage\n\nSee the [PostCSS documentation](https://github.com/postcss/postcss#usage) for\nexamples for your environment.\n\n## API\n\n### normalize([options])\n\n#### options\n\n##### preferredQuote\n\nType: `string`\nDefault: `double`\n\nSets what type of quote to prefer. Possible values are `single` and `double`.\n\n```js\nvar css = 'p:after{content:\"\"}';\nconsole.log(postcss(normalize({preferredQuote: 'single'})).process(css).css);\n//=> p:after{content:''}\n```\n\n## Contributors\n\nSee [CONTRIBUTORS.md](https://github.com/cssnano/cssnano/blob/master/CONTRIBUTORS.md).\n\n## License\n\nMIT © [Ben Briggs](http://beneb.info)\n\n[postcss]: https://github.com/postcss/postcss\n"
|
|
43
41
|
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const valueParser = require('postcss-value-parser');
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Constants (parser usage)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const SINGLE_QUOTE = "'".charCodeAt(0);
|
|
9
|
+
const DOUBLE_QUOTE = '"'.charCodeAt(0);
|
|
10
|
+
const BACKSLASH = '\\'.charCodeAt(0);
|
|
11
|
+
const NEWLINE = '\n'.charCodeAt(0);
|
|
12
|
+
const SPACE = ' '.charCodeAt(0);
|
|
13
|
+
const FEED = '\f'.charCodeAt(0);
|
|
14
|
+
const TAB = '\t'.charCodeAt(0);
|
|
15
|
+
const CR = '\r'.charCodeAt(0);
|
|
16
|
+
|
|
17
|
+
const WORD_END = /[ \n\t\r\f'"\\]/g;
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* Constants (node type strings)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const C_STRING = 'string';
|
|
24
|
+
const C_ESCAPED_SINGLE_QUOTE = 'escapedSingleQuote';
|
|
25
|
+
const C_ESCAPED_DOUBLE_QUOTE = 'escapedDoubleQuote';
|
|
26
|
+
const C_SINGLE_QUOTE = 'singleQuote';
|
|
27
|
+
const C_DOUBLE_QUOTE = 'doubleQuote';
|
|
28
|
+
const C_NEWLINE = 'newline';
|
|
29
|
+
const C_SINGLE = 'single';
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Literals
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const L_SINGLE_QUOTE = `'`;
|
|
36
|
+
const L_DOUBLE_QUOTE = `"`;
|
|
37
|
+
const L_NEWLINE = `\\\n`;
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Parser nodes
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const T_ESCAPED_SINGLE_QUOTE = { type: C_ESCAPED_SINGLE_QUOTE, value: `\\'` };
|
|
44
|
+
const T_ESCAPED_DOUBLE_QUOTE = { type: C_ESCAPED_DOUBLE_QUOTE, value: `\\"` };
|
|
45
|
+
const T_SINGLE_QUOTE = { type: C_SINGLE_QUOTE, value: L_SINGLE_QUOTE };
|
|
46
|
+
const T_DOUBLE_QUOTE = { type: C_DOUBLE_QUOTE, value: L_DOUBLE_QUOTE };
|
|
47
|
+
const 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
|
+
*/
|
|
60
|
+
function 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
|
+
*/
|
|
75
|
+
function 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
|
+
*/
|
|
183
|
+
function 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
|
+
*/
|
|
213
|
+
function 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
|
+
*/
|
|
238
|
+
function 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
|
+
*/
|
|
270
|
+
function 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
|
+
*/
|
|
286
|
+
function 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
|
+
|
|
318
|
+
pluginCreator.postcss = true;
|
|
319
|
+
module.exports = pluginCreator;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export = pluginCreator;
|
|
2
|
+
/** @typedef {{preferredQuote?: 'double' | 'single'}} Options */
|
|
3
|
+
/**
|
|
4
|
+
* @type {import('postcss').PluginCreator<Options>}
|
|
5
|
+
* @param {Options} opts
|
|
6
|
+
* @return {import('postcss').Plugin}
|
|
7
|
+
*/
|
|
8
|
+
declare function pluginCreator(opts: Options): import('postcss').Plugin;
|
|
9
|
+
declare namespace pluginCreator {
|
|
10
|
+
export { postcss, StringAstNode, StringAst, Options };
|
|
11
|
+
}
|
|
12
|
+
type Options = {
|
|
13
|
+
preferredQuote?: 'double' | 'single';
|
|
14
|
+
};
|
|
15
|
+
declare var postcss: true;
|
|
16
|
+
type StringAstNode = {
|
|
17
|
+
type: string;
|
|
18
|
+
value: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: string;
|
|
21
|
+
value: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: string;
|
|
24
|
+
value: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: string;
|
|
27
|
+
value: string;
|
|
28
|
+
};
|
|
29
|
+
type StringAst = {
|
|
30
|
+
nodes: StringAstNode[];
|
|
31
|
+
types: {
|
|
32
|
+
escapedSingleQuote: number;
|
|
33
|
+
escapedDoubleQuote: number;
|
|
34
|
+
singleQuote: number;
|
|
35
|
+
doubleQuote: number;
|
|
36
|
+
};
|
|
37
|
+
quotes: boolean;
|
|
38
|
+
};
|
package/dist/index.js
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
|
|
8
|
-
var _postcssValueParser = _interopRequireDefault(require("postcss-value-parser"));
|
|
9
|
-
|
|
10
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
* Constants (parser usage)
|
|
14
|
-
*/
|
|
15
|
-
const SINGLE_QUOTE = "'".charCodeAt(0);
|
|
16
|
-
const DOUBLE_QUOTE = '"'.charCodeAt(0);
|
|
17
|
-
const BACKSLASH = '\\'.charCodeAt(0);
|
|
18
|
-
const NEWLINE = '\n'.charCodeAt(0);
|
|
19
|
-
const SPACE = ' '.charCodeAt(0);
|
|
20
|
-
const FEED = '\f'.charCodeAt(0);
|
|
21
|
-
const TAB = '\t'.charCodeAt(0);
|
|
22
|
-
const CR = '\r'.charCodeAt(0);
|
|
23
|
-
const WORD_END = /[ \n\t\r\f'"\\]/g;
|
|
24
|
-
/*
|
|
25
|
-
* Constants (node type strings)
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const C_STRING = 'string';
|
|
29
|
-
const C_ESCAPED_SINGLE_QUOTE = 'escapedSingleQuote';
|
|
30
|
-
const C_ESCAPED_DOUBLE_QUOTE = 'escapedDoubleQuote';
|
|
31
|
-
const C_SINGLE_QUOTE = 'singleQuote';
|
|
32
|
-
const C_DOUBLE_QUOTE = 'doubleQuote';
|
|
33
|
-
const C_NEWLINE = 'newline';
|
|
34
|
-
const C_SINGLE = 'single';
|
|
35
|
-
/*
|
|
36
|
-
* Literals
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
const L_SINGLE_QUOTE = `'`;
|
|
40
|
-
const L_DOUBLE_QUOTE = `"`;
|
|
41
|
-
const L_NEWLINE = `\\\n`;
|
|
42
|
-
/*
|
|
43
|
-
* Parser nodes
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
const T_ESCAPED_SINGLE_QUOTE = {
|
|
47
|
-
type: C_ESCAPED_SINGLE_QUOTE,
|
|
48
|
-
value: `\\'`
|
|
49
|
-
};
|
|
50
|
-
const T_ESCAPED_DOUBLE_QUOTE = {
|
|
51
|
-
type: C_ESCAPED_DOUBLE_QUOTE,
|
|
52
|
-
value: `\\"`
|
|
53
|
-
};
|
|
54
|
-
const T_SINGLE_QUOTE = {
|
|
55
|
-
type: C_SINGLE_QUOTE,
|
|
56
|
-
value: L_SINGLE_QUOTE
|
|
57
|
-
};
|
|
58
|
-
const T_DOUBLE_QUOTE = {
|
|
59
|
-
type: C_DOUBLE_QUOTE,
|
|
60
|
-
value: L_DOUBLE_QUOTE
|
|
61
|
-
};
|
|
62
|
-
const T_NEWLINE = {
|
|
63
|
-
type: C_NEWLINE,
|
|
64
|
-
value: L_NEWLINE
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
function stringify(ast) {
|
|
68
|
-
return ast.nodes.reduce((str, {
|
|
69
|
-
value
|
|
70
|
-
}) => {
|
|
71
|
-
// Collapse multiple line strings automatically
|
|
72
|
-
if (value === L_NEWLINE) {
|
|
73
|
-
return str;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return str + value;
|
|
77
|
-
}, '');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function parse(str) {
|
|
81
|
-
let code, next, value;
|
|
82
|
-
let pos = 0;
|
|
83
|
-
let len = str.length;
|
|
84
|
-
const ast = {
|
|
85
|
-
nodes: [],
|
|
86
|
-
types: {
|
|
87
|
-
escapedSingleQuote: 0,
|
|
88
|
-
escapedDoubleQuote: 0,
|
|
89
|
-
singleQuote: 0,
|
|
90
|
-
doubleQuote: 0
|
|
91
|
-
},
|
|
92
|
-
quotes: false
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
while (pos < len) {
|
|
96
|
-
code = str.charCodeAt(pos);
|
|
97
|
-
|
|
98
|
-
switch (code) {
|
|
99
|
-
case SPACE:
|
|
100
|
-
case TAB:
|
|
101
|
-
case CR:
|
|
102
|
-
case FEED:
|
|
103
|
-
next = pos;
|
|
104
|
-
|
|
105
|
-
do {
|
|
106
|
-
next += 1;
|
|
107
|
-
code = str.charCodeAt(next);
|
|
108
|
-
} while (code === SPACE || code === NEWLINE || code === TAB || code === CR || code === FEED);
|
|
109
|
-
|
|
110
|
-
ast.nodes.push({
|
|
111
|
-
type: 'space',
|
|
112
|
-
value: str.slice(pos, next)
|
|
113
|
-
});
|
|
114
|
-
pos = next - 1;
|
|
115
|
-
break;
|
|
116
|
-
|
|
117
|
-
case SINGLE_QUOTE:
|
|
118
|
-
ast.nodes.push(T_SINGLE_QUOTE);
|
|
119
|
-
ast.types[C_SINGLE_QUOTE]++;
|
|
120
|
-
ast.quotes = true;
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case DOUBLE_QUOTE:
|
|
124
|
-
ast.nodes.push(T_DOUBLE_QUOTE);
|
|
125
|
-
ast.types[C_DOUBLE_QUOTE]++;
|
|
126
|
-
ast.quotes = true;
|
|
127
|
-
break;
|
|
128
|
-
|
|
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
|
-
/*
|
|
151
|
-
* We need to fall through here to handle the token as
|
|
152
|
-
* a whole word. The missing 'break' is intentional.
|
|
153
|
-
*/
|
|
154
|
-
|
|
155
|
-
default:
|
|
156
|
-
WORD_END.lastIndex = pos + 1;
|
|
157
|
-
WORD_END.test(str);
|
|
158
|
-
|
|
159
|
-
if (WORD_END.lastIndex === 0) {
|
|
160
|
-
next = len - 1;
|
|
161
|
-
} else {
|
|
162
|
-
next = WORD_END.lastIndex - 2;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
value = str.slice(pos, next + 1);
|
|
166
|
-
ast.nodes.push({
|
|
167
|
-
type: C_STRING,
|
|
168
|
-
value
|
|
169
|
-
});
|
|
170
|
-
pos = next;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
pos++;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return ast;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function changeWrappingQuotes(node, ast) {
|
|
180
|
-
const {
|
|
181
|
-
types
|
|
182
|
-
} = ast;
|
|
183
|
-
|
|
184
|
-
if (types[C_SINGLE_QUOTE] || types[C_DOUBLE_QUOTE]) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (node.quote === L_SINGLE_QUOTE && types[C_ESCAPED_SINGLE_QUOTE] > 0 && !types[C_ESCAPED_DOUBLE_QUOTE]) {
|
|
189
|
-
node.quote = L_DOUBLE_QUOTE;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (node.quote === L_DOUBLE_QUOTE && types[C_ESCAPED_DOUBLE_QUOTE] > 0 && !types[C_ESCAPED_SINGLE_QUOTE]) {
|
|
193
|
-
node.quote = L_SINGLE_QUOTE;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
ast.nodes = ast.nodes.reduce((newAst, child) => {
|
|
197
|
-
if (child.type === C_ESCAPED_DOUBLE_QUOTE && node.quote === L_SINGLE_QUOTE) {
|
|
198
|
-
return [...newAst, T_DOUBLE_QUOTE];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (child.type === C_ESCAPED_SINGLE_QUOTE && node.quote === L_DOUBLE_QUOTE) {
|
|
202
|
-
return [...newAst, T_SINGLE_QUOTE];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return [...newAst, child];
|
|
206
|
-
}, []);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function normalize(value, preferredQuote) {
|
|
210
|
-
if (!value || !value.length) {
|
|
211
|
-
return value;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return (0, _postcssValueParser.default)(value).walk(child => {
|
|
215
|
-
if (child.type !== C_STRING) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const ast = parse(child.value);
|
|
220
|
-
|
|
221
|
-
if (ast.quotes) {
|
|
222
|
-
changeWrappingQuotes(child, ast);
|
|
223
|
-
} else if (preferredQuote === C_SINGLE) {
|
|
224
|
-
child.quote = L_SINGLE_QUOTE;
|
|
225
|
-
} else {
|
|
226
|
-
child.quote = L_DOUBLE_QUOTE;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
child.value = stringify(ast);
|
|
230
|
-
}).toString();
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const params = {
|
|
234
|
-
rule: 'selector',
|
|
235
|
-
decl: 'value',
|
|
236
|
-
atrule: 'params'
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
function pluginCreator(opts) {
|
|
240
|
-
const {
|
|
241
|
-
preferredQuote
|
|
242
|
-
} = Object.assign({}, {
|
|
243
|
-
preferredQuote: 'double'
|
|
244
|
-
}, opts);
|
|
245
|
-
return {
|
|
246
|
-
postcssPlugin: 'postcss-normalize-string',
|
|
247
|
-
|
|
248
|
-
OnceExit(css) {
|
|
249
|
-
const cache = new Map();
|
|
250
|
-
css.walk(node => {
|
|
251
|
-
const {
|
|
252
|
-
type
|
|
253
|
-
} = node;
|
|
254
|
-
|
|
255
|
-
if (Object.prototype.hasOwnProperty.call(params, type)) {
|
|
256
|
-
const param = params[type];
|
|
257
|
-
const key = node[param] + '|' + preferredQuote;
|
|
258
|
-
|
|
259
|
-
if (cache.has(key)) {
|
|
260
|
-
node[param] = cache.get(key);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const newValue = normalize(node[param], preferredQuote);
|
|
265
|
-
node[param] = newValue;
|
|
266
|
-
cache.set(key, newValue);
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
pluginCreator.postcss = true;
|
|
275
|
-
var _default = pluginCreator;
|
|
276
|
-
exports.default = _default;
|
|
277
|
-
module.exports = exports.default;
|