@projectwallace/css-analyzer 9.3.0 → 9.4.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/dist/index.d.ts +612 -592
- package/dist/index.js +1606 -0
- package/package.json +9 -10
- package/dist/css-analyzer.js +0 -2900
package/dist/index.js
ADDED
|
@@ -0,0 +1,1606 @@
|
|
|
1
|
+
import { ATTRIBUTE_SELECTOR, AT_RULE, BLOCK, BREAK, CLASS_SELECTOR, COMBINATOR, CONTAINER_QUERY, DECLARATION, DIMENSION, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_TYPE, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, SKIP, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, URL, is_custom, is_vendor_prefixed, parse, str_equals, str_starts_with, walk } from "@projectwallace/css-parser";
|
|
2
|
+
import { parse_selector } from "@projectwallace/css-parser/parse-selector";
|
|
3
|
+
//#region src/atrules/atrules.ts
|
|
4
|
+
/**
|
|
5
|
+
* Check if an @supports atRule is a browserhack (Wallace parser version)
|
|
6
|
+
* @param node - The Atrule CSSNode from Wallace parser
|
|
7
|
+
*/
|
|
8
|
+
function isSupportsBrowserhack(node, on_hack) {
|
|
9
|
+
walk(node, function(n) {
|
|
10
|
+
if (n.type === SUPPORTS_QUERY) {
|
|
11
|
+
const normalizedPrelude = (n.prelude || n.value || "").toString().toLowerCase().replaceAll(/\s+/g, "");
|
|
12
|
+
if (normalizedPrelude.includes("-webkit-appearance:none")) {
|
|
13
|
+
on_hack("-webkit-appearance: none");
|
|
14
|
+
return BREAK;
|
|
15
|
+
}
|
|
16
|
+
if (normalizedPrelude.includes("-moz-appearance:meterbar")) {
|
|
17
|
+
on_hack("-moz-appearance: meterbar");
|
|
18
|
+
return BREAK;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a @media atRule is a browserhack (Wallace parser version)
|
|
25
|
+
* @param node - The Atrule CSSNode from Wallace parser
|
|
26
|
+
* @returns true if the atrule is a browserhack
|
|
27
|
+
*/
|
|
28
|
+
function isMediaBrowserhack(node, on_hack) {
|
|
29
|
+
walk(node, function(n) {
|
|
30
|
+
if (n.type === MEDIA_TYPE) {
|
|
31
|
+
const text = n.text || "";
|
|
32
|
+
if (text.startsWith("\\0")) {
|
|
33
|
+
on_hack("\\0");
|
|
34
|
+
return BREAK;
|
|
35
|
+
}
|
|
36
|
+
if (text.includes("\\9")) {
|
|
37
|
+
on_hack("\\9");
|
|
38
|
+
return BREAK;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (n.type === MEDIA_FEATURE) {
|
|
42
|
+
const name = n.property || "";
|
|
43
|
+
if (str_equals("-moz-images-in-menus", name)) {
|
|
44
|
+
on_hack("-moz-images-in-menus");
|
|
45
|
+
return BREAK;
|
|
46
|
+
}
|
|
47
|
+
if (str_equals("min--moz-device-pixel-ratio", name)) {
|
|
48
|
+
on_hack("min--moz-device-pixel-ratio");
|
|
49
|
+
return BREAK;
|
|
50
|
+
}
|
|
51
|
+
if (str_equals("-ms-high-contrast", name)) {
|
|
52
|
+
on_hack("-ms-high-contrast");
|
|
53
|
+
return BREAK;
|
|
54
|
+
}
|
|
55
|
+
if (str_equals("min-resolution", name) && n.has_children) {
|
|
56
|
+
for (const child of n) if (child.type === DIMENSION && child.value === .001 && str_equals("dpcm", child.unit || "")) {
|
|
57
|
+
on_hack("min-resolution: .001dpcm");
|
|
58
|
+
return BREAK;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (str_equals("-webkit-min-device-pixel-ratio", name) && n.has_children) {
|
|
62
|
+
for (const child of n) if (child.type === NUMBER && (child.value === 0 || child.value === 1e4)) {
|
|
63
|
+
on_hack("-webkit-min-device-pixel-ratio");
|
|
64
|
+
return BREAK;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (n.has_children) {
|
|
68
|
+
for (const child of n) if (child.type === IDENTIFIER && child.text === "\\0") {
|
|
69
|
+
on_hack("\\0");
|
|
70
|
+
return BREAK;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/keyword-set.ts
|
|
78
|
+
/**
|
|
79
|
+
* @description A Set-like construct to search CSS keywords in a case-insensitive way
|
|
80
|
+
*/
|
|
81
|
+
var KeywordSet = class {
|
|
82
|
+
set;
|
|
83
|
+
constructor(items) {
|
|
84
|
+
/** @type {Set<string>} */
|
|
85
|
+
this.set = new Set(items);
|
|
86
|
+
}
|
|
87
|
+
has(item) {
|
|
88
|
+
return this.set.has(item.toLowerCase());
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/string-utils.ts
|
|
93
|
+
function unquote(str) {
|
|
94
|
+
return str.replaceAll(/(?:^['"])|(?:['"]$)/g, "");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Case-insensitive compare two character codes
|
|
98
|
+
* @see https://github.com/csstree/csstree/blob/41f276e8862d8223eeaa01a3d113ab70bb13d2d9/lib/tokenizer/utils.js#L22
|
|
99
|
+
*/
|
|
100
|
+
function compareChar(referenceCode, testCode) {
|
|
101
|
+
if (testCode >= 65 && testCode <= 90) testCode = testCode | 32;
|
|
102
|
+
return referenceCode === testCode;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Case-insensitive testing whether a string ends with a given substring
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* endsWith('test', 'my-test') // true
|
|
109
|
+
* endsWith('test', 'est') // false
|
|
110
|
+
*
|
|
111
|
+
* @param base e.g. '-webkit-transform'
|
|
112
|
+
* @param maybe e.g. 'transform'
|
|
113
|
+
* @returns true if `test` ends with `base`, false otherwise
|
|
114
|
+
*/
|
|
115
|
+
function endsWith(base, maybe) {
|
|
116
|
+
if (base === maybe) return true;
|
|
117
|
+
let len = maybe.length;
|
|
118
|
+
let offset = len - base.length;
|
|
119
|
+
if (offset < 0) return false;
|
|
120
|
+
for (let i = len - 1; i >= offset; i--) if (compareChar(base.charCodeAt(i - offset), maybe.charCodeAt(i)) === false) return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/selectors/utils.ts
|
|
125
|
+
const PSEUDO_FUNCTIONS = new KeywordSet([
|
|
126
|
+
"nth-child",
|
|
127
|
+
"where",
|
|
128
|
+
"not",
|
|
129
|
+
"is",
|
|
130
|
+
"has",
|
|
131
|
+
"nth-last-child",
|
|
132
|
+
"matches",
|
|
133
|
+
"-webkit-any",
|
|
134
|
+
"-moz-any"
|
|
135
|
+
]);
|
|
136
|
+
function isPrefixed(selector, on_selector) {
|
|
137
|
+
walk(selector, function(node) {
|
|
138
|
+
if (node.type === PSEUDO_ELEMENT_SELECTOR || node.type === PSEUDO_CLASS_SELECTOR || node.type === TYPE_SELECTOR) {
|
|
139
|
+
if (node.is_vendor_prefixed) {
|
|
140
|
+
let prefix = "";
|
|
141
|
+
if (node.type === PSEUDO_CLASS_SELECTOR) prefix = ":";
|
|
142
|
+
else if (node.type === PSEUDO_ELEMENT_SELECTOR) prefix = "::";
|
|
143
|
+
on_selector(prefix + (node.name || node.text));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if a Wallace selector is an accessibility selector (has aria-* or role attribute)
|
|
150
|
+
*/
|
|
151
|
+
function isAccessibility(selector, on_selector) {
|
|
152
|
+
function normalize(node) {
|
|
153
|
+
let clone = node.clone();
|
|
154
|
+
if (clone.value) return "[" + clone.name?.toLowerCase() + clone.attr_operator + "\"" + unquote(clone.value.toString()) + "\"]";
|
|
155
|
+
return "[" + clone.name?.toLowerCase() + "]";
|
|
156
|
+
}
|
|
157
|
+
walk(selector, function(node) {
|
|
158
|
+
if (node.type === ATTRIBUTE_SELECTOR) {
|
|
159
|
+
const name = node.name || "";
|
|
160
|
+
if (str_equals("role", name) || str_starts_with(name, "aria-")) on_selector(normalize(node));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get the Complexity for a Wallace Selector Node
|
|
166
|
+
* @param selector - Wallace CSSNode for a Selector
|
|
167
|
+
* @return The numeric complexity of the Selector
|
|
168
|
+
*/
|
|
169
|
+
function getComplexity(selector) {
|
|
170
|
+
let complexity = 0;
|
|
171
|
+
function findSelectors(node, complexities) {
|
|
172
|
+
walk(node, function(n) {
|
|
173
|
+
if (n.type === SELECTOR) complexities.push(getComplexity(n));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
walk(selector, function(node) {
|
|
177
|
+
const type = node.type;
|
|
178
|
+
if (type === SELECTOR) return;
|
|
179
|
+
if (type === NTH_SELECTOR) {
|
|
180
|
+
if (node.text && node.text.trim()) complexity++;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
complexity++;
|
|
184
|
+
if (type === PSEUDO_ELEMENT_SELECTOR || type === TYPE_SELECTOR || type === PSEUDO_CLASS_SELECTOR) {
|
|
185
|
+
if (node.is_vendor_prefixed) complexity++;
|
|
186
|
+
}
|
|
187
|
+
if (type === ATTRIBUTE_SELECTOR) {
|
|
188
|
+
if (node.value) complexity++;
|
|
189
|
+
return SKIP;
|
|
190
|
+
}
|
|
191
|
+
if (type === PSEUDO_CLASS_SELECTOR) {
|
|
192
|
+
const name = node.name || "";
|
|
193
|
+
if (PSEUDO_FUNCTIONS.has(name.toLowerCase())) {
|
|
194
|
+
const childComplexities = [];
|
|
195
|
+
if (node.has_children) for (const child of node) if (child.type === SELECTOR) childComplexities.push(getComplexity(child));
|
|
196
|
+
else findSelectors(child, childComplexities);
|
|
197
|
+
if (childComplexities.length > 0) {
|
|
198
|
+
for (const c of childComplexities) complexity += c;
|
|
199
|
+
return SKIP;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return complexity;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Walk a selector node and trigger a callback every time a Combinator was found
|
|
208
|
+
*/
|
|
209
|
+
function getCombinators(selector, onMatch) {
|
|
210
|
+
walk(selector, function(node) {
|
|
211
|
+
if (node.type === COMBINATOR) onMatch({
|
|
212
|
+
name: node.name?.trim() === "" ? " " : node.name,
|
|
213
|
+
loc: {
|
|
214
|
+
offset: node.start,
|
|
215
|
+
line: node.line,
|
|
216
|
+
column: node.column,
|
|
217
|
+
length: 1
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/selectors/specificity.ts
|
|
224
|
+
function compare(s1, s2) {
|
|
225
|
+
if (s1[0] === s2[0]) {
|
|
226
|
+
if (s1[1] === s2[1]) return s1[2] - s2[2];
|
|
227
|
+
return s1[1] - s2[1];
|
|
228
|
+
}
|
|
229
|
+
return s1[0] - s2[0];
|
|
230
|
+
}
|
|
231
|
+
function max(list) {
|
|
232
|
+
return list.sort(compare).at(-1);
|
|
233
|
+
}
|
|
234
|
+
const calculateForAST = (selectorAST) => {
|
|
235
|
+
let a = 0;
|
|
236
|
+
let b = 0;
|
|
237
|
+
let c = 0;
|
|
238
|
+
let current = selectorAST.first_child;
|
|
239
|
+
while (current) {
|
|
240
|
+
switch (current.type) {
|
|
241
|
+
case ID_SELECTOR:
|
|
242
|
+
a += 1;
|
|
243
|
+
break;
|
|
244
|
+
case ATTRIBUTE_SELECTOR:
|
|
245
|
+
case CLASS_SELECTOR:
|
|
246
|
+
b += 1;
|
|
247
|
+
break;
|
|
248
|
+
case PSEUDO_CLASS_SELECTOR:
|
|
249
|
+
switch (current.name?.toLowerCase()) {
|
|
250
|
+
case "where": break;
|
|
251
|
+
case "-webkit-any":
|
|
252
|
+
case "any":
|
|
253
|
+
if (current.first_child) b += 1;
|
|
254
|
+
break;
|
|
255
|
+
case "-moz-any":
|
|
256
|
+
case "is":
|
|
257
|
+
case "matches":
|
|
258
|
+
case "not":
|
|
259
|
+
case "has":
|
|
260
|
+
if (current.has_children) {
|
|
261
|
+
const childSelectorList = current.first_child;
|
|
262
|
+
if (childSelectorList?.type === SELECTOR_LIST) {
|
|
263
|
+
const max1 = max(calculate(childSelectorList));
|
|
264
|
+
a += max1[0];
|
|
265
|
+
b += max1[1];
|
|
266
|
+
c += max1[2];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
case "nth-child":
|
|
271
|
+
case "nth-last-child":
|
|
272
|
+
b += 1;
|
|
273
|
+
const nthOf = current.first_child;
|
|
274
|
+
if (nthOf?.type === NTH_OF_SELECTOR && nthOf.selector) {
|
|
275
|
+
const max2 = max(calculate(nthOf.selector));
|
|
276
|
+
a += max2[0];
|
|
277
|
+
b += max2[1];
|
|
278
|
+
c += max2[2];
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
case "host-context":
|
|
282
|
+
case "host":
|
|
283
|
+
b += 1;
|
|
284
|
+
const childSelector = current.first_child?.first_child;
|
|
285
|
+
if (childSelector?.type === SELECTOR) {
|
|
286
|
+
let childPart = childSelector.first_child;
|
|
287
|
+
while (childPart) {
|
|
288
|
+
if (childPart.type === COMBINATOR) break;
|
|
289
|
+
const partSpecificity = calculateForAST({
|
|
290
|
+
type_name: "Selector",
|
|
291
|
+
first_child: childPart,
|
|
292
|
+
has_children: true
|
|
293
|
+
});
|
|
294
|
+
a += partSpecificity[0] ?? 0;
|
|
295
|
+
b += partSpecificity[1] ?? 0;
|
|
296
|
+
c += partSpecificity[2] ?? 0;
|
|
297
|
+
childPart = childPart.next_sibling;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
case "after":
|
|
302
|
+
case "before":
|
|
303
|
+
case "first-letter":
|
|
304
|
+
case "first-line":
|
|
305
|
+
c += 1;
|
|
306
|
+
break;
|
|
307
|
+
default:
|
|
308
|
+
b += 1;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
case PSEUDO_ELEMENT_SELECTOR:
|
|
313
|
+
switch (current.name?.toLowerCase()) {
|
|
314
|
+
case "slotted":
|
|
315
|
+
c += 1;
|
|
316
|
+
const childSelector = current.first_child?.first_child;
|
|
317
|
+
if (childSelector?.type === SELECTOR) {
|
|
318
|
+
let childPart = childSelector.first_child;
|
|
319
|
+
while (childPart) {
|
|
320
|
+
if (childPart.type === COMBINATOR) break;
|
|
321
|
+
const partSpecificity = calculateForAST({
|
|
322
|
+
type_name: "Selector",
|
|
323
|
+
first_child: childPart,
|
|
324
|
+
has_children: true
|
|
325
|
+
});
|
|
326
|
+
a += partSpecificity[0] ?? 0;
|
|
327
|
+
b += partSpecificity[1] ?? 0;
|
|
328
|
+
c += partSpecificity[2] ?? 0;
|
|
329
|
+
childPart = childPart.next_sibling;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
case "view-transition-group":
|
|
334
|
+
case "view-transition-image-pair":
|
|
335
|
+
case "view-transition-old":
|
|
336
|
+
case "view-transition-new":
|
|
337
|
+
if (current.first_child?.text === "*") break;
|
|
338
|
+
c += 1;
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
c += 1;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
case TYPE_SELECTOR:
|
|
346
|
+
let typeSelector = current.name ?? "";
|
|
347
|
+
if (typeSelector.includes("|")) typeSelector = typeSelector.split("|")[1] ?? "";
|
|
348
|
+
if (typeSelector !== "*") c += 1;
|
|
349
|
+
break;
|
|
350
|
+
default: break;
|
|
351
|
+
}
|
|
352
|
+
current = current.next_sibling;
|
|
353
|
+
}
|
|
354
|
+
return [
|
|
355
|
+
a,
|
|
356
|
+
b,
|
|
357
|
+
c
|
|
358
|
+
];
|
|
359
|
+
};
|
|
360
|
+
const convertToAST = (source) => {
|
|
361
|
+
if (typeof source === "string") try {
|
|
362
|
+
return parse_selector(source);
|
|
363
|
+
} catch (e) {
|
|
364
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
365
|
+
throw new TypeError(`Could not convert passed in source '${source}' to SelectorList: ${message}`);
|
|
366
|
+
}
|
|
367
|
+
if (source instanceof Object) {
|
|
368
|
+
if (source.type === SELECTOR_LIST) return source;
|
|
369
|
+
throw new TypeError(`Passed in source is an Object but no AST / AST of the type SelectorList`);
|
|
370
|
+
}
|
|
371
|
+
throw new TypeError(`Passed in source is not a String nor an Object. I don't know what to do with it.`);
|
|
372
|
+
};
|
|
373
|
+
const calculate = (selector) => {
|
|
374
|
+
if (!selector) return [];
|
|
375
|
+
const ast = convertToAST(selector);
|
|
376
|
+
const specificities = [];
|
|
377
|
+
let selectorNode = ast.first_child;
|
|
378
|
+
while (selectorNode) {
|
|
379
|
+
specificities.push(calculateForAST(selectorNode));
|
|
380
|
+
selectorNode = selectorNode.next_sibling;
|
|
381
|
+
}
|
|
382
|
+
return specificities;
|
|
383
|
+
};
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/values/colors.ts
|
|
386
|
+
const namedColors = new KeywordSet([
|
|
387
|
+
"white",
|
|
388
|
+
"black",
|
|
389
|
+
"red",
|
|
390
|
+
"gray",
|
|
391
|
+
"silver",
|
|
392
|
+
"grey",
|
|
393
|
+
"green",
|
|
394
|
+
"orange",
|
|
395
|
+
"blue",
|
|
396
|
+
"dimgray",
|
|
397
|
+
"whitesmoke",
|
|
398
|
+
"lightgray",
|
|
399
|
+
"lightgrey",
|
|
400
|
+
"yellow",
|
|
401
|
+
"gold",
|
|
402
|
+
"pink",
|
|
403
|
+
"gainsboro",
|
|
404
|
+
"magenta",
|
|
405
|
+
"purple",
|
|
406
|
+
"darkgray",
|
|
407
|
+
"navy",
|
|
408
|
+
"darkred",
|
|
409
|
+
"teal",
|
|
410
|
+
"maroon",
|
|
411
|
+
"darkgrey",
|
|
412
|
+
"tomato",
|
|
413
|
+
"darkorange",
|
|
414
|
+
"brown",
|
|
415
|
+
"crimson",
|
|
416
|
+
"lightyellow",
|
|
417
|
+
"slategray",
|
|
418
|
+
"salmon",
|
|
419
|
+
"lightgreen",
|
|
420
|
+
"lightblue",
|
|
421
|
+
"orangered",
|
|
422
|
+
"aliceblue",
|
|
423
|
+
"dodgerblue",
|
|
424
|
+
"lime",
|
|
425
|
+
"darkblue",
|
|
426
|
+
"darkgoldenrod",
|
|
427
|
+
"skyblue",
|
|
428
|
+
"royalblue",
|
|
429
|
+
"darkgreen",
|
|
430
|
+
"ivory",
|
|
431
|
+
"olive",
|
|
432
|
+
"aqua",
|
|
433
|
+
"turquoise",
|
|
434
|
+
"cyan",
|
|
435
|
+
"khaki",
|
|
436
|
+
"beige",
|
|
437
|
+
"snow",
|
|
438
|
+
"ghostwhite",
|
|
439
|
+
"limegreen",
|
|
440
|
+
"coral",
|
|
441
|
+
"dimgrey",
|
|
442
|
+
"hotpink",
|
|
443
|
+
"midnightblue",
|
|
444
|
+
"firebrick",
|
|
445
|
+
"indigo",
|
|
446
|
+
"wheat",
|
|
447
|
+
"mediumblue",
|
|
448
|
+
"lightpink",
|
|
449
|
+
"plum",
|
|
450
|
+
"azure",
|
|
451
|
+
"violet",
|
|
452
|
+
"lavender",
|
|
453
|
+
"deepskyblue",
|
|
454
|
+
"darkslategrey",
|
|
455
|
+
"goldenrod",
|
|
456
|
+
"cornflowerblue",
|
|
457
|
+
"lightskyblue",
|
|
458
|
+
"indianred",
|
|
459
|
+
"yellowgreen",
|
|
460
|
+
"saddlebrown",
|
|
461
|
+
"palegreen",
|
|
462
|
+
"bisque",
|
|
463
|
+
"tan",
|
|
464
|
+
"antiquewhite",
|
|
465
|
+
"steelblue",
|
|
466
|
+
"forestgreen",
|
|
467
|
+
"fuchsia",
|
|
468
|
+
"mediumaquamarine",
|
|
469
|
+
"seagreen",
|
|
470
|
+
"sienna",
|
|
471
|
+
"deeppink",
|
|
472
|
+
"mediumseagreen",
|
|
473
|
+
"peru",
|
|
474
|
+
"greenyellow",
|
|
475
|
+
"lightgoldenrodyellow",
|
|
476
|
+
"orchid",
|
|
477
|
+
"cadetblue",
|
|
478
|
+
"navajowhite",
|
|
479
|
+
"lightsteelblue",
|
|
480
|
+
"slategrey",
|
|
481
|
+
"linen",
|
|
482
|
+
"lightseagreen",
|
|
483
|
+
"darkcyan",
|
|
484
|
+
"lightcoral",
|
|
485
|
+
"aquamarine",
|
|
486
|
+
"blueviolet",
|
|
487
|
+
"cornsilk",
|
|
488
|
+
"lightsalmon",
|
|
489
|
+
"chocolate",
|
|
490
|
+
"lightslategray",
|
|
491
|
+
"floralwhite",
|
|
492
|
+
"darkturquoise",
|
|
493
|
+
"darkslategray",
|
|
494
|
+
"rebeccapurple",
|
|
495
|
+
"burlywood",
|
|
496
|
+
"chartreuse",
|
|
497
|
+
"lightcyan",
|
|
498
|
+
"lemonchiffon",
|
|
499
|
+
"palevioletred",
|
|
500
|
+
"darkslateblue",
|
|
501
|
+
"mediumpurple",
|
|
502
|
+
"lawngreen",
|
|
503
|
+
"slateblue",
|
|
504
|
+
"darkseagreen",
|
|
505
|
+
"blanchedalmond",
|
|
506
|
+
"mistyrose",
|
|
507
|
+
"darkolivegreen",
|
|
508
|
+
"seashell",
|
|
509
|
+
"olivedrab",
|
|
510
|
+
"peachpuff",
|
|
511
|
+
"darkviolet",
|
|
512
|
+
"powderblue",
|
|
513
|
+
"darkmagenta",
|
|
514
|
+
"lightslategrey",
|
|
515
|
+
"honeydew",
|
|
516
|
+
"palegoldenrod",
|
|
517
|
+
"darkkhaki",
|
|
518
|
+
"oldlace",
|
|
519
|
+
"mintcream",
|
|
520
|
+
"sandybrown",
|
|
521
|
+
"mediumturquoise",
|
|
522
|
+
"papayawhip",
|
|
523
|
+
"paleturquoise",
|
|
524
|
+
"mediumvioletred",
|
|
525
|
+
"thistle",
|
|
526
|
+
"springgreen",
|
|
527
|
+
"moccasin",
|
|
528
|
+
"rosybrown",
|
|
529
|
+
"lavenderblush",
|
|
530
|
+
"mediumslateblue",
|
|
531
|
+
"darkorchid",
|
|
532
|
+
"mediumorchid",
|
|
533
|
+
"darksalmon",
|
|
534
|
+
"mediumspringgreen"
|
|
535
|
+
]);
|
|
536
|
+
const systemColors = new KeywordSet([
|
|
537
|
+
"accentcolor",
|
|
538
|
+
"accentcolortext",
|
|
539
|
+
"activetext",
|
|
540
|
+
"buttonborder",
|
|
541
|
+
"buttonface",
|
|
542
|
+
"buttontext",
|
|
543
|
+
"canvas",
|
|
544
|
+
"canvastext",
|
|
545
|
+
"field",
|
|
546
|
+
"fieldtext",
|
|
547
|
+
"graytext",
|
|
548
|
+
"highlight",
|
|
549
|
+
"highlighttext",
|
|
550
|
+
"linktext",
|
|
551
|
+
"mark",
|
|
552
|
+
"marktext",
|
|
553
|
+
"selecteditem",
|
|
554
|
+
"selecteditemtext",
|
|
555
|
+
"visitedtext"
|
|
556
|
+
]);
|
|
557
|
+
const colorFunctions = new KeywordSet([
|
|
558
|
+
"rgba",
|
|
559
|
+
"rgb",
|
|
560
|
+
"hsla",
|
|
561
|
+
"hsl",
|
|
562
|
+
"oklch",
|
|
563
|
+
"color",
|
|
564
|
+
"hwb",
|
|
565
|
+
"lch",
|
|
566
|
+
"lab",
|
|
567
|
+
"oklab"
|
|
568
|
+
]);
|
|
569
|
+
const colorKeywords = new KeywordSet(["transparent", "currentcolor"]);
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/values/values.ts
|
|
572
|
+
const keywords = new KeywordSet([
|
|
573
|
+
"auto",
|
|
574
|
+
"none",
|
|
575
|
+
"inherit",
|
|
576
|
+
"initial",
|
|
577
|
+
"unset",
|
|
578
|
+
"revert",
|
|
579
|
+
"revert-layer"
|
|
580
|
+
]);
|
|
581
|
+
/**
|
|
582
|
+
* Test whether a value is a reset (0, 0px, -0.0e0 etc.)
|
|
583
|
+
*/
|
|
584
|
+
function isValueReset(node) {
|
|
585
|
+
for (let child of node.children) {
|
|
586
|
+
if (child.type === NUMBER && child.value === 0) continue;
|
|
587
|
+
if (child.type === DIMENSION && child.value === 0) continue;
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/values/destructure-font-shorthand.ts
|
|
594
|
+
const SYSTEM_FONTS = new KeywordSet([
|
|
595
|
+
"caption",
|
|
596
|
+
"icon",
|
|
597
|
+
"menu",
|
|
598
|
+
"message-box",
|
|
599
|
+
"small-caption",
|
|
600
|
+
"status-bar"
|
|
601
|
+
]);
|
|
602
|
+
const SIZE_KEYWORDS = new KeywordSet([
|
|
603
|
+
"xx-small",
|
|
604
|
+
"x-small",
|
|
605
|
+
"small",
|
|
606
|
+
"medium",
|
|
607
|
+
"large",
|
|
608
|
+
"x-large",
|
|
609
|
+
"xx-large",
|
|
610
|
+
"xxx-large",
|
|
611
|
+
"smaller",
|
|
612
|
+
"larger"
|
|
613
|
+
]);
|
|
614
|
+
const COMMA = 44;
|
|
615
|
+
const SLASH = 47;
|
|
616
|
+
function destructure(value, cb) {
|
|
617
|
+
let font_family = [void 0, void 0];
|
|
618
|
+
let font_size;
|
|
619
|
+
let line_height;
|
|
620
|
+
if (value.first_child.type === FUNCTION && value.first_child.name?.toLowerCase() === "var") return null;
|
|
621
|
+
let prev;
|
|
622
|
+
for (let node of value.children) {
|
|
623
|
+
let next = node.next_sibling;
|
|
624
|
+
if (node.type === IDENTIFIER && keywords.has(node.name)) cb({
|
|
625
|
+
type: "keyword",
|
|
626
|
+
value: node.name
|
|
627
|
+
});
|
|
628
|
+
if (next && next.type === OPERATOR && next.text.charCodeAt(0) === SLASH) {
|
|
629
|
+
font_size = node.text;
|
|
630
|
+
prev = node;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (prev?.type === OPERATOR && prev.text.charCodeAt(0) === SLASH) {
|
|
634
|
+
line_height = node.text;
|
|
635
|
+
prev = node;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (next?.type === OPERATOR && next.text.charCodeAt(0) === COMMA && !font_family[0]) {
|
|
639
|
+
font_family[0] = node;
|
|
640
|
+
if (!font_size && prev) font_size = prev.text;
|
|
641
|
+
prev = node;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (node.next_sibling === null) {
|
|
645
|
+
font_family[1] = node;
|
|
646
|
+
if (!font_size && !font_family[0] && prev) font_size = prev.text;
|
|
647
|
+
prev = node;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (node.type === NUMBER) {
|
|
651
|
+
prev = node;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (node.type === IDENTIFIER) {
|
|
655
|
+
let name = node.name;
|
|
656
|
+
if (name && SIZE_KEYWORDS.has(name)) {
|
|
657
|
+
font_size = name;
|
|
658
|
+
prev = node;
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
prev = node;
|
|
663
|
+
}
|
|
664
|
+
let family = font_family[0] || font_family[1] ? value.text.substring((font_family?.[0] || font_family?.[1] || { start: value.start }).start - value.start, font_family[1] ? font_family[1].end - value.start : value.text.length) : null;
|
|
665
|
+
return {
|
|
666
|
+
font_size,
|
|
667
|
+
line_height,
|
|
668
|
+
font_family: family
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/values/animations.ts
|
|
673
|
+
const TIMING_KEYWORDS = new KeywordSet([
|
|
674
|
+
"linear",
|
|
675
|
+
"ease",
|
|
676
|
+
"ease-in",
|
|
677
|
+
"ease-out",
|
|
678
|
+
"ease-in-out",
|
|
679
|
+
"step-start",
|
|
680
|
+
"step-end"
|
|
681
|
+
]);
|
|
682
|
+
const TIMING_FUNCTION_VALUES = new KeywordSet(["cubic-bezier", "steps"]);
|
|
683
|
+
function analyzeAnimation(children, cb) {
|
|
684
|
+
let durationFound = false;
|
|
685
|
+
for (let child of children) {
|
|
686
|
+
let type = child.type;
|
|
687
|
+
let name = child.name;
|
|
688
|
+
if (type === OPERATOR) durationFound = false;
|
|
689
|
+
else if (type === DIMENSION && durationFound === false) {
|
|
690
|
+
durationFound = true;
|
|
691
|
+
cb({
|
|
692
|
+
type: "duration",
|
|
693
|
+
value: child
|
|
694
|
+
});
|
|
695
|
+
} else if (type === IDENTIFIER && name) {
|
|
696
|
+
if (TIMING_KEYWORDS.has(name)) cb({
|
|
697
|
+
type: "fn",
|
|
698
|
+
value: child
|
|
699
|
+
});
|
|
700
|
+
else if (keywords.has(name)) cb({
|
|
701
|
+
type: "keyword",
|
|
702
|
+
value: child
|
|
703
|
+
});
|
|
704
|
+
} else if (type === FUNCTION && name && TIMING_FUNCTION_VALUES.has(name)) cb({
|
|
705
|
+
type: "fn",
|
|
706
|
+
value: child
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region src/values/vendor-prefix.ts
|
|
712
|
+
function isValuePrefixed(node, on_value) {
|
|
713
|
+
walk(node, function(child) {
|
|
714
|
+
if (child.is_vendor_prefixed) on_value(child.name || child.text);
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/collection.ts
|
|
719
|
+
var Collection = class {
|
|
720
|
+
#items;
|
|
721
|
+
#total;
|
|
722
|
+
#nodes = [];
|
|
723
|
+
#useLocations;
|
|
724
|
+
constructor(useLocations = false) {
|
|
725
|
+
this.#items = /* @__PURE__ */ new Map();
|
|
726
|
+
this.#total = 0;
|
|
727
|
+
if (useLocations) this.#nodes = [];
|
|
728
|
+
this.#useLocations = useLocations;
|
|
729
|
+
}
|
|
730
|
+
p(item, node_location) {
|
|
731
|
+
let index = this.#total;
|
|
732
|
+
if (this.#useLocations) {
|
|
733
|
+
let position = index * 4;
|
|
734
|
+
this.#nodes[position] = node_location.line;
|
|
735
|
+
this.#nodes[position + 1] = node_location.column;
|
|
736
|
+
this.#nodes[position + 2] = node_location.offset;
|
|
737
|
+
this.#nodes[position + 3] = node_location.length;
|
|
738
|
+
}
|
|
739
|
+
if (this.#items.has(item)) {
|
|
740
|
+
this.#items.get(item).push(index);
|
|
741
|
+
this.#total++;
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
this.#items.set(item, [index]);
|
|
745
|
+
this.#total++;
|
|
746
|
+
}
|
|
747
|
+
size() {
|
|
748
|
+
return this.#total;
|
|
749
|
+
}
|
|
750
|
+
c() {
|
|
751
|
+
let uniqueWithLocations = /* @__PURE__ */ new Map();
|
|
752
|
+
let unique = {};
|
|
753
|
+
let useLocations = this.#useLocations;
|
|
754
|
+
let items = this.#items;
|
|
755
|
+
let _nodes = this.#nodes;
|
|
756
|
+
let size = items.size;
|
|
757
|
+
items.forEach((list, key) => {
|
|
758
|
+
if (useLocations) {
|
|
759
|
+
let nodes = list.map(function(index) {
|
|
760
|
+
let position = index * 4;
|
|
761
|
+
return {
|
|
762
|
+
line: _nodes[position],
|
|
763
|
+
column: _nodes[position + 1],
|
|
764
|
+
offset: _nodes[position + 2],
|
|
765
|
+
length: _nodes[position + 3]
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
uniqueWithLocations.set(key, nodes);
|
|
769
|
+
} else unique[key] = list.length;
|
|
770
|
+
});
|
|
771
|
+
let total = this.#total;
|
|
772
|
+
if (useLocations) return {
|
|
773
|
+
total,
|
|
774
|
+
totalUnique: size,
|
|
775
|
+
unique,
|
|
776
|
+
uniquenessRatio: total === 0 ? 0 : size / total,
|
|
777
|
+
uniqueWithLocations: Object.fromEntries(uniqueWithLocations)
|
|
778
|
+
};
|
|
779
|
+
return {
|
|
780
|
+
total,
|
|
781
|
+
totalUnique: size,
|
|
782
|
+
unique,
|
|
783
|
+
uniquenessRatio: total === 0 ? 0 : size / total,
|
|
784
|
+
uniqueWithLocations: void 0
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/context-collection.ts
|
|
790
|
+
var ContextCollection = class {
|
|
791
|
+
#list;
|
|
792
|
+
#contexts;
|
|
793
|
+
#useLocations;
|
|
794
|
+
constructor(useLocations) {
|
|
795
|
+
this.#list = new Collection(useLocations);
|
|
796
|
+
this.#contexts = /* @__PURE__ */ new Map();
|
|
797
|
+
this.#useLocations = useLocations;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Add an item to this #list's context
|
|
801
|
+
* @param item Item to push
|
|
802
|
+
* @param context Context to push Item to
|
|
803
|
+
* @param node_location
|
|
804
|
+
*/
|
|
805
|
+
push(item, context, node_location) {
|
|
806
|
+
this.#list.p(item, node_location);
|
|
807
|
+
if (!this.#contexts.has(context)) this.#contexts.set(context, new Collection(this.#useLocations));
|
|
808
|
+
this.#contexts.get(context).p(item, node_location);
|
|
809
|
+
}
|
|
810
|
+
count() {
|
|
811
|
+
let itemsPerContext = /* @__PURE__ */ new Map();
|
|
812
|
+
for (let [context, value] of this.#contexts.entries()) itemsPerContext.set(context, value.c());
|
|
813
|
+
return Object.assign(this.#list.c(), { itemsPerContext: Object.fromEntries(itemsPerContext) });
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region src/aggregate-collection.ts
|
|
818
|
+
/**
|
|
819
|
+
* Find the mode (most occurring value) in an array of Numbers
|
|
820
|
+
* Takes the mean/average of multiple values if multiple values occur the same amount of times.
|
|
821
|
+
*
|
|
822
|
+
* @see https://github.com/angus-c/just/blob/684af9ca0c7808bc78543ec89379b1fdfce502b1/packages/array-mode/index.js
|
|
823
|
+
* @param arr - Array to find the mode value for
|
|
824
|
+
* @returns mode - The `mode` value of `arr`
|
|
825
|
+
*/
|
|
826
|
+
function Mode(arr) {
|
|
827
|
+
let frequencies = /* @__PURE__ */ new Map();
|
|
828
|
+
let maxOccurrences = -1;
|
|
829
|
+
let maxOccurenceCount = 0;
|
|
830
|
+
let sum = 0;
|
|
831
|
+
let len = arr.length;
|
|
832
|
+
for (let i = 0; i < len; i++) {
|
|
833
|
+
let element = arr[i];
|
|
834
|
+
let updatedCount = (frequencies.get(element) || 0) + 1;
|
|
835
|
+
frequencies.set(element, updatedCount);
|
|
836
|
+
if (updatedCount > maxOccurrences) {
|
|
837
|
+
maxOccurrences = updatedCount;
|
|
838
|
+
maxOccurenceCount = 0;
|
|
839
|
+
sum = 0;
|
|
840
|
+
}
|
|
841
|
+
if (updatedCount >= maxOccurrences) {
|
|
842
|
+
maxOccurenceCount++;
|
|
843
|
+
sum += element;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return sum / maxOccurenceCount;
|
|
847
|
+
}
|
|
848
|
+
var AggregateCollection = class {
|
|
849
|
+
#items;
|
|
850
|
+
#sum;
|
|
851
|
+
constructor() {
|
|
852
|
+
this.#items = [];
|
|
853
|
+
this.#sum = 0;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Add a new Integer at the end of this AggregateCollection
|
|
857
|
+
* @param item - The item to add
|
|
858
|
+
*/
|
|
859
|
+
push(item) {
|
|
860
|
+
this.#items.push(item);
|
|
861
|
+
this.#sum += item;
|
|
862
|
+
}
|
|
863
|
+
size() {
|
|
864
|
+
return this.#items.length;
|
|
865
|
+
}
|
|
866
|
+
aggregate() {
|
|
867
|
+
let len = this.#items.length;
|
|
868
|
+
if (len === 0) return {
|
|
869
|
+
min: 0,
|
|
870
|
+
max: 0,
|
|
871
|
+
mean: 0,
|
|
872
|
+
mode: 0,
|
|
873
|
+
range: 0,
|
|
874
|
+
sum: 0
|
|
875
|
+
};
|
|
876
|
+
let sorted = this.#items.slice().sort((a, b) => a - b);
|
|
877
|
+
let min = sorted[0];
|
|
878
|
+
let max = sorted[len - 1];
|
|
879
|
+
let mode = Mode(sorted);
|
|
880
|
+
let sum = this.#sum;
|
|
881
|
+
return {
|
|
882
|
+
min,
|
|
883
|
+
max,
|
|
884
|
+
mean: sum / len,
|
|
885
|
+
mode,
|
|
886
|
+
range: max - min,
|
|
887
|
+
sum
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
toArray() {
|
|
891
|
+
return this.#items;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region src/stylesheet/stylesheet.ts
|
|
896
|
+
function getEmbedType(embed) {
|
|
897
|
+
let start = 5;
|
|
898
|
+
let semicolon = embed.indexOf(";");
|
|
899
|
+
let comma = embed.indexOf(",");
|
|
900
|
+
if (semicolon === -1) return embed.substring(start, comma);
|
|
901
|
+
if (comma !== -1 && comma < semicolon) return embed.substring(start, comma);
|
|
902
|
+
return embed.substring(start, semicolon);
|
|
903
|
+
}
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/values/browserhacks.ts
|
|
906
|
+
function isIe9Hack(node) {
|
|
907
|
+
let children = node.children;
|
|
908
|
+
if (children) {
|
|
909
|
+
let last = children.at(-1);
|
|
910
|
+
return last && last.type === IDENTIFIER && endsWith("\\9", last.text) ? true : false;
|
|
911
|
+
}
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/properties/property-utils.ts
|
|
916
|
+
const SPACING_RESET_PROPERTIES = new Set([
|
|
917
|
+
"margin",
|
|
918
|
+
"margin-block",
|
|
919
|
+
"margin-inline",
|
|
920
|
+
"margin-top",
|
|
921
|
+
"margin-block-start",
|
|
922
|
+
"margin-block-end",
|
|
923
|
+
"margin-inline-end",
|
|
924
|
+
"margin-inline-end",
|
|
925
|
+
"margin-right",
|
|
926
|
+
"margin-bottom",
|
|
927
|
+
"margin-left",
|
|
928
|
+
"padding",
|
|
929
|
+
"padding-block",
|
|
930
|
+
"padding-inline",
|
|
931
|
+
"padding-top",
|
|
932
|
+
"padding-right",
|
|
933
|
+
"padding-bottom",
|
|
934
|
+
"padding-left",
|
|
935
|
+
"padding-block-start",
|
|
936
|
+
"padding-block-end",
|
|
937
|
+
"padding-inline-start",
|
|
938
|
+
"padding-inline-end"
|
|
939
|
+
]);
|
|
940
|
+
const border_radius_properties = new KeywordSet([
|
|
941
|
+
"border-radius",
|
|
942
|
+
"border-top-left-radius",
|
|
943
|
+
"border-top-right-radius",
|
|
944
|
+
"border-bottom-right-radius",
|
|
945
|
+
"border-bottom-left-radius",
|
|
946
|
+
"border-start-start-radius",
|
|
947
|
+
"border-start-end-radius",
|
|
948
|
+
"border-end-end-radius",
|
|
949
|
+
"border-end-start-radius"
|
|
950
|
+
]);
|
|
951
|
+
/**
|
|
952
|
+
* @see https://github.com/csstree/csstree/blob/master/lib/utils/names.js#L69
|
|
953
|
+
*/
|
|
954
|
+
function isHack(property) {
|
|
955
|
+
if (is_custom(property) || is_vendor_prefixed(property)) return false;
|
|
956
|
+
let code = property.charCodeAt(0);
|
|
957
|
+
return code === 47 || code === 42 || code === 95 || code === 43 || code === 38 || code === 36 || code === 35;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Get the normalized basename for a property with a vendor prefix
|
|
961
|
+
* @returns The property name without vendor prefix
|
|
962
|
+
*/
|
|
963
|
+
function basename(property) {
|
|
964
|
+
if (is_custom(property)) return property;
|
|
965
|
+
if (is_vendor_prefixed(property)) return property.slice(property.indexOf("-", 2) + 1).toLowerCase();
|
|
966
|
+
if (isHack(property)) return property.slice(1).toLowerCase();
|
|
967
|
+
return property.toLowerCase();
|
|
968
|
+
}
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region src/vendor-prefix.ts
|
|
971
|
+
/** Kept for backwards compatibility */
|
|
972
|
+
function hasVendorPrefix(keyword) {
|
|
973
|
+
return is_vendor_prefixed(keyword);
|
|
974
|
+
}
|
|
975
|
+
//#endregion
|
|
976
|
+
//#region src/index.ts
|
|
977
|
+
function ratio(part, total) {
|
|
978
|
+
if (total === 0) return 0;
|
|
979
|
+
return part / total;
|
|
980
|
+
}
|
|
981
|
+
function analyze(css, options = {}) {
|
|
982
|
+
if (options.useLocations === true) return analyzeInternal(css, options, true);
|
|
983
|
+
return analyzeInternal(css, options, false);
|
|
984
|
+
}
|
|
985
|
+
function analyzeInternal(css, options, useLocations) {
|
|
986
|
+
let start = Date.now();
|
|
987
|
+
let linesOfCode = (css.match(/\n/g) || []).length + 1;
|
|
988
|
+
let totalComments = 0;
|
|
989
|
+
let commentsSize = 0;
|
|
990
|
+
let embedSize = 0;
|
|
991
|
+
let embedTypes = {
|
|
992
|
+
total: 0,
|
|
993
|
+
unique: /* @__PURE__ */ new Map()
|
|
994
|
+
};
|
|
995
|
+
let startParse = Date.now();
|
|
996
|
+
let ast = parse(css, { on_comment({ length }) {
|
|
997
|
+
totalComments++;
|
|
998
|
+
commentsSize += length;
|
|
999
|
+
} });
|
|
1000
|
+
let startAnalysis = Date.now();
|
|
1001
|
+
let atrules = new Collection(useLocations);
|
|
1002
|
+
let atRuleComplexities = new AggregateCollection();
|
|
1003
|
+
/** @type {Record<string, string>[]} */
|
|
1004
|
+
let fontfaces = [];
|
|
1005
|
+
let fontfaces_with_loc = new Collection(useLocations);
|
|
1006
|
+
let layers = new Collection(useLocations);
|
|
1007
|
+
let imports = new Collection(useLocations);
|
|
1008
|
+
let medias = new Collection(useLocations);
|
|
1009
|
+
let mediaBrowserhacks = new Collection(useLocations);
|
|
1010
|
+
let mediaFeatures = new Collection(useLocations);
|
|
1011
|
+
let charsets = new Collection(useLocations);
|
|
1012
|
+
let supports = new Collection(useLocations);
|
|
1013
|
+
let supportsBrowserhacks = new Collection(useLocations);
|
|
1014
|
+
let keyframes = new Collection(useLocations);
|
|
1015
|
+
let prefixedKeyframes = new Collection(useLocations);
|
|
1016
|
+
let containers = new Collection(useLocations);
|
|
1017
|
+
let containerNames = new Collection(useLocations);
|
|
1018
|
+
let registeredProperties = new Collection(useLocations);
|
|
1019
|
+
let functions = new Collection(useLocations);
|
|
1020
|
+
let scopes = new Collection(useLocations);
|
|
1021
|
+
let atruleNesting = new AggregateCollection();
|
|
1022
|
+
let uniqueAtruleNesting = new Collection(useLocations);
|
|
1023
|
+
let totalRules = 0;
|
|
1024
|
+
let emptyRules = 0;
|
|
1025
|
+
let ruleSizes = new AggregateCollection();
|
|
1026
|
+
let selectorsPerRule = new AggregateCollection();
|
|
1027
|
+
let declarationsPerRule = new AggregateCollection();
|
|
1028
|
+
let uniqueRuleSize = new Collection(useLocations);
|
|
1029
|
+
let uniqueSelectorsPerRule = new Collection(useLocations);
|
|
1030
|
+
let uniqueDeclarationsPerRule = new Collection(useLocations);
|
|
1031
|
+
let ruleNesting = new AggregateCollection();
|
|
1032
|
+
let uniqueRuleNesting = new Collection(useLocations);
|
|
1033
|
+
let keyframeSelectors = new Collection(useLocations);
|
|
1034
|
+
let uniqueSelectors = /* @__PURE__ */ new Set();
|
|
1035
|
+
let prefixedSelectors = new Collection(useLocations);
|
|
1036
|
+
let maxSpecificity;
|
|
1037
|
+
let minSpecificity;
|
|
1038
|
+
let specificityA = new AggregateCollection();
|
|
1039
|
+
let specificityB = new AggregateCollection();
|
|
1040
|
+
let specificityC = new AggregateCollection();
|
|
1041
|
+
let uniqueSpecificities = new Collection(useLocations);
|
|
1042
|
+
let selectorComplexities = new AggregateCollection();
|
|
1043
|
+
let uniqueSelectorComplexities = new Collection(useLocations);
|
|
1044
|
+
let specificities = [];
|
|
1045
|
+
let ids = new Collection(useLocations);
|
|
1046
|
+
let a11y = new Collection(useLocations);
|
|
1047
|
+
let pseudoClasses = new Collection(useLocations);
|
|
1048
|
+
let pseudoElements = new Collection(useLocations);
|
|
1049
|
+
let attributeSelectors = new Collection(useLocations);
|
|
1050
|
+
let customElementSelectors = new Collection(useLocations);
|
|
1051
|
+
let combinators = new Collection(useLocations);
|
|
1052
|
+
let selectorNesting = new AggregateCollection();
|
|
1053
|
+
let uniqueSelectorNesting = new Collection(useLocations);
|
|
1054
|
+
let uniqueDeclarations = /* @__PURE__ */ new Set();
|
|
1055
|
+
let totalDeclarations = 0;
|
|
1056
|
+
let declarationComplexities = new AggregateCollection();
|
|
1057
|
+
let importantDeclarations = 0;
|
|
1058
|
+
let importantsInKeyframes = 0;
|
|
1059
|
+
let importantCustomProperties = new Collection(useLocations);
|
|
1060
|
+
let declarationNesting = new AggregateCollection();
|
|
1061
|
+
let uniqueDeclarationNesting = new Collection(useLocations);
|
|
1062
|
+
let properties = new Collection(useLocations);
|
|
1063
|
+
let propertyHacks = new Collection(useLocations);
|
|
1064
|
+
let propertyVendorPrefixes = new Collection(useLocations);
|
|
1065
|
+
let customProperties = new Collection(useLocations);
|
|
1066
|
+
let propertyComplexities = new AggregateCollection();
|
|
1067
|
+
let valueComplexities = new AggregateCollection();
|
|
1068
|
+
let vendorPrefixedValues = new Collection(useLocations);
|
|
1069
|
+
let valueBrowserhacks = new Collection(useLocations);
|
|
1070
|
+
let displays = new Collection(useLocations);
|
|
1071
|
+
let zindex = new Collection(useLocations);
|
|
1072
|
+
let textShadows = new Collection(useLocations);
|
|
1073
|
+
let boxShadows = new Collection(useLocations);
|
|
1074
|
+
let fontFamilies = new Collection(useLocations);
|
|
1075
|
+
let fontSizes = new Collection(useLocations);
|
|
1076
|
+
let lineHeights = new Collection(useLocations);
|
|
1077
|
+
let timingFunctions = new Collection(useLocations);
|
|
1078
|
+
let durations = new Collection(useLocations);
|
|
1079
|
+
let colors = new ContextCollection(useLocations);
|
|
1080
|
+
let colorFormats = new Collection(useLocations);
|
|
1081
|
+
let units = new ContextCollection(useLocations);
|
|
1082
|
+
let gradients = new Collection(useLocations);
|
|
1083
|
+
let valueKeywords = new Collection(useLocations);
|
|
1084
|
+
let borderRadiuses = new ContextCollection(useLocations);
|
|
1085
|
+
let resets = new Collection(useLocations);
|
|
1086
|
+
function toLoc(node) {
|
|
1087
|
+
return {
|
|
1088
|
+
line: node.line,
|
|
1089
|
+
column: node.column,
|
|
1090
|
+
offset: node.start,
|
|
1091
|
+
length: node.length
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
let keyframesDepth = -1;
|
|
1095
|
+
walk(ast, (node, depth) => {
|
|
1096
|
+
if (keyframesDepth >= 0 && depth <= keyframesDepth) keyframesDepth = -1;
|
|
1097
|
+
let inKeyframes = keyframesDepth >= 0 && depth > keyframesDepth;
|
|
1098
|
+
if (node.type === AT_RULE) {
|
|
1099
|
+
let atruleLoc = toLoc(node);
|
|
1100
|
+
atruleNesting.push(depth);
|
|
1101
|
+
uniqueAtruleNesting.p(depth, atruleLoc);
|
|
1102
|
+
let normalized_name = basename(node.name ?? "");
|
|
1103
|
+
atrules.p(normalized_name, atruleLoc);
|
|
1104
|
+
if (normalized_name === "font-face") {
|
|
1105
|
+
let descriptors = Object.create(null);
|
|
1106
|
+
if (useLocations) fontfaces_with_loc.p(node.start, toLoc(node));
|
|
1107
|
+
let block = node.children.find((child) => child.type === BLOCK);
|
|
1108
|
+
for (let descriptor of block?.children || []) if (descriptor.type === DECLARATION && descriptor.value) descriptors[descriptor.property] = descriptor.value.text;
|
|
1109
|
+
atRuleComplexities.push(1);
|
|
1110
|
+
fontfaces.push(descriptors);
|
|
1111
|
+
}
|
|
1112
|
+
if (node.prelude === null || node.prelude === void 0) {
|
|
1113
|
+
if (normalized_name === "layer") {
|
|
1114
|
+
layers.p("<anonymous>", toLoc(node));
|
|
1115
|
+
atRuleComplexities.push(2);
|
|
1116
|
+
}
|
|
1117
|
+
} else {
|
|
1118
|
+
let complexity = 1;
|
|
1119
|
+
if (normalized_name === "media") {
|
|
1120
|
+
medias.p(node.prelude.text, toLoc(node));
|
|
1121
|
+
isMediaBrowserhack(node.prelude, (hack) => {
|
|
1122
|
+
mediaBrowserhacks.p(hack, toLoc(node));
|
|
1123
|
+
complexity++;
|
|
1124
|
+
});
|
|
1125
|
+
} else if (normalized_name === "supports") {
|
|
1126
|
+
supports.p(node.prelude.text, toLoc(node));
|
|
1127
|
+
isSupportsBrowserhack(node.prelude, (hack) => {
|
|
1128
|
+
supportsBrowserhacks.p(hack, toLoc(node));
|
|
1129
|
+
complexity++;
|
|
1130
|
+
});
|
|
1131
|
+
} else if (normalized_name.endsWith("keyframes")) {
|
|
1132
|
+
let prelude = node.prelude.text;
|
|
1133
|
+
keyframes.p(prelude, toLoc(node));
|
|
1134
|
+
if (node.is_vendor_prefixed) {
|
|
1135
|
+
prefixedKeyframes.p(`@${node.name?.toLowerCase()} ${node.prelude.text}`, toLoc(node));
|
|
1136
|
+
complexity++;
|
|
1137
|
+
}
|
|
1138
|
+
keyframesDepth = depth;
|
|
1139
|
+
} else if (normalized_name === "layer") for (let layer of node.prelude.text.split(",").map((s) => s.trim())) layers.p(layer, toLoc(node));
|
|
1140
|
+
else if (normalized_name === "import") {
|
|
1141
|
+
imports.p(node.prelude.text, toLoc(node));
|
|
1142
|
+
if (node.prelude.has_children) {
|
|
1143
|
+
for (let child of node.prelude) if (child.type === SUPPORTS_QUERY && typeof child.value === "string") supports.p(child.value, toLoc(child));
|
|
1144
|
+
else if (child.type === LAYER_NAME && typeof child.value === "string") layers.p(child.value, toLoc(child));
|
|
1145
|
+
}
|
|
1146
|
+
} else if (normalized_name === "container") {
|
|
1147
|
+
containers.p(node.prelude.text, toLoc(node));
|
|
1148
|
+
if (node.prelude.first_child?.type === CONTAINER_QUERY) {
|
|
1149
|
+
if (node.prelude.first_child.first_child?.type === IDENTIFIER) containerNames.p(node.prelude.first_child.first_child.text, toLoc(node));
|
|
1150
|
+
}
|
|
1151
|
+
} else if (normalized_name === "property") registeredProperties.p(node.prelude.text, toLoc(node));
|
|
1152
|
+
else if (normalized_name === "function") {
|
|
1153
|
+
let prelude = node.prelude.text;
|
|
1154
|
+
let name = prelude.includes("(") ? prelude.slice(0, prelude.indexOf("(")).trim() : prelude.trim();
|
|
1155
|
+
functions.p(name, toLoc(node));
|
|
1156
|
+
} else if (normalized_name === "charset") charsets.p(node.prelude.text.toLowerCase(), toLoc(node));
|
|
1157
|
+
else if (normalized_name === "scope") scopes.p(node.prelude.text, toLoc(node));
|
|
1158
|
+
atRuleComplexities.push(complexity);
|
|
1159
|
+
}
|
|
1160
|
+
} else if (node.type === STYLE_RULE) if (inKeyframes && node.prelude) {
|
|
1161
|
+
if (node.prelude.type === SELECTOR_LIST && node.prelude.children.length > 0) for (let keyframe_selector of node.prelude.children) keyframeSelectors.p(keyframe_selector.text, toLoc(keyframe_selector));
|
|
1162
|
+
} else {
|
|
1163
|
+
totalRules++;
|
|
1164
|
+
if (node.block?.is_empty) emptyRules++;
|
|
1165
|
+
let numSelectors = 0;
|
|
1166
|
+
let numDeclarations = 0;
|
|
1167
|
+
let loc = toLoc(node);
|
|
1168
|
+
if (node.prelude) {
|
|
1169
|
+
for (const selector of node.prelude.children) if (selector.type === SELECTOR) numSelectors++;
|
|
1170
|
+
}
|
|
1171
|
+
if (node.block) {
|
|
1172
|
+
for (const declaration of node.block.children) if (declaration.type === DECLARATION) numDeclarations++;
|
|
1173
|
+
}
|
|
1174
|
+
ruleSizes.push(numSelectors + numDeclarations);
|
|
1175
|
+
uniqueRuleSize.p(numSelectors + numDeclarations, loc);
|
|
1176
|
+
selectorsPerRule.push(numSelectors);
|
|
1177
|
+
uniqueSelectorsPerRule.p(numSelectors, loc);
|
|
1178
|
+
declarationsPerRule.push(numDeclarations);
|
|
1179
|
+
uniqueDeclarationsPerRule.p(numDeclarations, loc);
|
|
1180
|
+
ruleNesting.push(depth);
|
|
1181
|
+
uniqueRuleNesting.p(depth, loc);
|
|
1182
|
+
}
|
|
1183
|
+
else if (node.type === SELECTOR) {
|
|
1184
|
+
if (inKeyframes) return SKIP;
|
|
1185
|
+
let loc = toLoc(node);
|
|
1186
|
+
selectorNesting.push(depth > 0 ? depth - 1 : 0);
|
|
1187
|
+
uniqueSelectorNesting.p(depth > 0 ? depth - 1 : 0, loc);
|
|
1188
|
+
uniqueSelectors.add(node.text);
|
|
1189
|
+
let complexity = getComplexity(node);
|
|
1190
|
+
selectorComplexities.push(complexity);
|
|
1191
|
+
uniqueSelectorComplexities.p(complexity, loc);
|
|
1192
|
+
isPrefixed(node, (prefix) => {
|
|
1193
|
+
prefixedSelectors.p(prefix.toLowerCase(), loc);
|
|
1194
|
+
});
|
|
1195
|
+
isAccessibility(node, (a11y_selector) => {
|
|
1196
|
+
a11y.p(a11y_selector, loc);
|
|
1197
|
+
});
|
|
1198
|
+
walk(node, (child) => {
|
|
1199
|
+
if (child.type === ATTRIBUTE_SELECTOR) attributeSelectors.p(child.name?.toLowerCase() ?? "", loc);
|
|
1200
|
+
else if (child.type === TYPE_SELECTOR && child.name?.includes("-")) customElementSelectors.p(child.name.toLowerCase(), loc);
|
|
1201
|
+
else if (child.type === PSEUDO_CLASS_SELECTOR) pseudoClasses.p(child.name?.toLowerCase() ?? "", loc);
|
|
1202
|
+
else if (child.type === PSEUDO_ELEMENT_SELECTOR) pseudoElements.p(child.name?.toLowerCase() ?? "", loc);
|
|
1203
|
+
});
|
|
1204
|
+
getCombinators(node, (combinator) => {
|
|
1205
|
+
let name = combinator.name.trim() === "" ? " " : combinator.name;
|
|
1206
|
+
combinators.p(name, combinator.loc);
|
|
1207
|
+
});
|
|
1208
|
+
let specificity = calculateForAST(node);
|
|
1209
|
+
let [sa, sb, sc] = specificity;
|
|
1210
|
+
uniqueSpecificities.p(specificity.toString(), loc);
|
|
1211
|
+
specificityA.push(sa);
|
|
1212
|
+
specificityB.push(sb);
|
|
1213
|
+
specificityC.push(sc);
|
|
1214
|
+
if (maxSpecificity === void 0) maxSpecificity = specificity;
|
|
1215
|
+
if (minSpecificity === void 0) minSpecificity = specificity;
|
|
1216
|
+
if (minSpecificity !== void 0 && compareSpecificity(minSpecificity, specificity) < 0) minSpecificity = specificity;
|
|
1217
|
+
if (maxSpecificity !== void 0 && compareSpecificity(maxSpecificity, specificity) > 0) maxSpecificity = specificity;
|
|
1218
|
+
specificities.push(specificity);
|
|
1219
|
+
if (sa > 0) ids.p(node.text, loc);
|
|
1220
|
+
return SKIP;
|
|
1221
|
+
} else if (node.type === DECLARATION) {
|
|
1222
|
+
totalDeclarations++;
|
|
1223
|
+
uniqueDeclarations.add(node.text);
|
|
1224
|
+
let loc = toLoc(node);
|
|
1225
|
+
let declarationDepth = depth > 0 ? depth - 1 : 0;
|
|
1226
|
+
declarationNesting.push(declarationDepth);
|
|
1227
|
+
uniqueDeclarationNesting.p(declarationDepth, loc);
|
|
1228
|
+
let complexity = 1;
|
|
1229
|
+
if (node.is_important) {
|
|
1230
|
+
complexity++;
|
|
1231
|
+
if (!node.text.toLowerCase().includes("!important")) valueBrowserhacks.p("!ie", toLoc(node.value));
|
|
1232
|
+
if (inKeyframes) {
|
|
1233
|
+
importantsInKeyframes++;
|
|
1234
|
+
complexity++;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
declarationComplexities.push(complexity);
|
|
1238
|
+
let { is_important, property, is_browserhack, is_vendor_prefixed } = node;
|
|
1239
|
+
if (!property) return;
|
|
1240
|
+
let propertyLoc = toLoc(node);
|
|
1241
|
+
propertyLoc.length = property.length;
|
|
1242
|
+
let normalizedProperty = basename(property);
|
|
1243
|
+
properties.p(normalizedProperty, propertyLoc);
|
|
1244
|
+
if (is_important) importantDeclarations++;
|
|
1245
|
+
if (is_vendor_prefixed) {
|
|
1246
|
+
propertyComplexities.push(2);
|
|
1247
|
+
propertyVendorPrefixes.p(property, propertyLoc);
|
|
1248
|
+
} else if (is_custom(property)) {
|
|
1249
|
+
customProperties.p(property, propertyLoc);
|
|
1250
|
+
propertyComplexities.push(is_important ? 3 : 2);
|
|
1251
|
+
if (is_important) importantCustomProperties.p(property, propertyLoc);
|
|
1252
|
+
} else if (is_browserhack) {
|
|
1253
|
+
propertyHacks.p(property.charAt(0), propertyLoc);
|
|
1254
|
+
propertyComplexities.push(2);
|
|
1255
|
+
} else propertyComplexities.push(1);
|
|
1256
|
+
{
|
|
1257
|
+
let value = node.value;
|
|
1258
|
+
let { text } = value;
|
|
1259
|
+
let valueLoc = toLoc(value);
|
|
1260
|
+
let complexity = 1;
|
|
1261
|
+
if (keywords.has(text)) {
|
|
1262
|
+
valueKeywords.p(text.toLowerCase(), valueLoc);
|
|
1263
|
+
valueComplexities.push(complexity);
|
|
1264
|
+
if (normalizedProperty === "display") displays.p(text.toLowerCase(), valueLoc);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
isValuePrefixed(value, (prefixed) => {
|
|
1268
|
+
vendorPrefixedValues.p(prefixed.toLowerCase(), valueLoc);
|
|
1269
|
+
complexity++;
|
|
1270
|
+
});
|
|
1271
|
+
if (isIe9Hack(value)) {
|
|
1272
|
+
valueBrowserhacks.p("\\9", valueLoc);
|
|
1273
|
+
text = text.slice(0, -2);
|
|
1274
|
+
complexity++;
|
|
1275
|
+
}
|
|
1276
|
+
valueComplexities.push(complexity);
|
|
1277
|
+
if (SPACING_RESET_PROPERTIES.has(normalizedProperty)) {
|
|
1278
|
+
if (isValueReset(value)) resets.p(normalizedProperty, valueLoc);
|
|
1279
|
+
} else if (normalizedProperty === "display") if (/var\(/i.test(text)) displays.p(text, valueLoc);
|
|
1280
|
+
else displays.p(text.toLowerCase(), valueLoc);
|
|
1281
|
+
else if (normalizedProperty === "z-index") {
|
|
1282
|
+
zindex.p(text, valueLoc);
|
|
1283
|
+
return SKIP;
|
|
1284
|
+
} else if (normalizedProperty === "font") {
|
|
1285
|
+
if (!SYSTEM_FONTS.has(text)) {
|
|
1286
|
+
let result = destructure(value, function(item) {
|
|
1287
|
+
if (item.type === "keyword") valueKeywords.p(item.value.toLowerCase(), valueLoc);
|
|
1288
|
+
});
|
|
1289
|
+
if (!result) return SKIP;
|
|
1290
|
+
let { font_size, line_height, font_family } = result;
|
|
1291
|
+
if (font_family) fontFamilies.p(font_family, valueLoc);
|
|
1292
|
+
if (font_size) fontSizes.p(font_size.toLowerCase(), valueLoc);
|
|
1293
|
+
if (line_height) lineHeights.p(line_height.toLowerCase(), valueLoc);
|
|
1294
|
+
}
|
|
1295
|
+
} else if (normalizedProperty === "font-size") {
|
|
1296
|
+
if (!SYSTEM_FONTS.has(text)) {
|
|
1297
|
+
let normalized = text.toLowerCase();
|
|
1298
|
+
if (normalized.includes("var(")) fontSizes.p(text, valueLoc);
|
|
1299
|
+
else fontSizes.p(normalized, valueLoc);
|
|
1300
|
+
}
|
|
1301
|
+
} else if (normalizedProperty === "font-family") {
|
|
1302
|
+
if (!SYSTEM_FONTS.has(text)) fontFamilies.p(text, valueLoc);
|
|
1303
|
+
return SKIP;
|
|
1304
|
+
} else if (normalizedProperty === "line-height") {
|
|
1305
|
+
let normalized = text.toLowerCase();
|
|
1306
|
+
if (normalized.includes("var(")) lineHeights.p(text, valueLoc);
|
|
1307
|
+
else lineHeights.p(normalized, valueLoc);
|
|
1308
|
+
} else if (normalizedProperty === "transition" || normalizedProperty === "animation") {
|
|
1309
|
+
analyzeAnimation(value.children, function(item) {
|
|
1310
|
+
if (item.type === "fn") timingFunctions.p(item.value.text.toLowerCase(), valueLoc);
|
|
1311
|
+
else if (item.type === "duration") durations.p(item.value.text.toLowerCase(), valueLoc);
|
|
1312
|
+
else if (item.type === "keyword") valueKeywords.p(item.value.text.toLowerCase(), valueLoc);
|
|
1313
|
+
});
|
|
1314
|
+
return SKIP;
|
|
1315
|
+
} else if (normalizedProperty === "animation-duration" || normalizedProperty === "transition-duration") {
|
|
1316
|
+
for (let child of value.children) if (child.type !== OPERATOR) {
|
|
1317
|
+
let text = child.text;
|
|
1318
|
+
if (/var\(/i.test(text)) durations.p(text, valueLoc);
|
|
1319
|
+
else durations.p(text.toLowerCase(), valueLoc);
|
|
1320
|
+
}
|
|
1321
|
+
} else if (normalizedProperty === "transition-timing-function" || normalizedProperty === "animation-timing-function") {
|
|
1322
|
+
for (let child of value.children) if (child.type !== OPERATOR) timingFunctions.p(child.text, valueLoc);
|
|
1323
|
+
} else if (normalizedProperty === "container-name") containerNames.p(text, valueLoc);
|
|
1324
|
+
else if (normalizedProperty === "container") {
|
|
1325
|
+
if (value.first_child?.type === IDENTIFIER) containerNames.p(value.first_child.text, valueLoc);
|
|
1326
|
+
} else if (border_radius_properties.has(normalizedProperty)) borderRadiuses.push(text, property, valueLoc);
|
|
1327
|
+
else if (normalizedProperty === "text-shadow") textShadows.p(text, valueLoc);
|
|
1328
|
+
else if (normalizedProperty === "box-shadow") boxShadows.p(text, valueLoc);
|
|
1329
|
+
let valueHasIe9Hack = isIe9Hack(value);
|
|
1330
|
+
walk(value, (valueNode) => {
|
|
1331
|
+
switch (valueNode.type) {
|
|
1332
|
+
case DIMENSION: {
|
|
1333
|
+
let unit = valueNode.unit?.toLowerCase() ?? "";
|
|
1334
|
+
let loc = toLoc(valueNode);
|
|
1335
|
+
units.push(unit, property, loc);
|
|
1336
|
+
return SKIP;
|
|
1337
|
+
}
|
|
1338
|
+
case HASH: {
|
|
1339
|
+
let hashText = valueNode.text;
|
|
1340
|
+
if (!hashText || !hashText.startsWith("#")) return SKIP;
|
|
1341
|
+
let hashValue = hashText.toLowerCase();
|
|
1342
|
+
if (valueHasIe9Hack && !hashValue.endsWith("\\9")) hashValue = hashValue + "\\9";
|
|
1343
|
+
let hexLength = hashValue.length - 1;
|
|
1344
|
+
if (endsWith("\\9", hashValue)) hexLength = hexLength - 2;
|
|
1345
|
+
let hashLoc = toLoc(valueNode);
|
|
1346
|
+
colors.push(hashValue, property, hashLoc);
|
|
1347
|
+
colorFormats.p(`hex` + hexLength, hashLoc);
|
|
1348
|
+
return SKIP;
|
|
1349
|
+
}
|
|
1350
|
+
case IDENTIFIER: {
|
|
1351
|
+
let identifierText = valueNode.text;
|
|
1352
|
+
let identifierLoc = toLoc(valueNode);
|
|
1353
|
+
if (normalizedProperty === "font" || normalizedProperty === "font-family") return SKIP;
|
|
1354
|
+
if (keywords.has(identifierText)) valueKeywords.p(identifierText.toLowerCase(), identifierLoc);
|
|
1355
|
+
let nodeLen = identifierText.length;
|
|
1356
|
+
if (nodeLen > 20 || nodeLen < 3) return SKIP;
|
|
1357
|
+
if (colorKeywords.has(identifierText)) {
|
|
1358
|
+
let colorKeyword = identifierText.toLowerCase();
|
|
1359
|
+
colors.push(colorKeyword, property, identifierLoc);
|
|
1360
|
+
colorFormats.p(colorKeyword, identifierLoc);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (namedColors.has(identifierText)) {
|
|
1364
|
+
colors.push(identifierText.toLowerCase(), property, identifierLoc);
|
|
1365
|
+
colorFormats.p("named", identifierLoc);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (systemColors.has(identifierText)) {
|
|
1369
|
+
colors.push(identifierText.toLowerCase(), property, identifierLoc);
|
|
1370
|
+
colorFormats.p("system", identifierLoc);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
return SKIP;
|
|
1374
|
+
}
|
|
1375
|
+
case FUNCTION: {
|
|
1376
|
+
let funcName = valueNode.name;
|
|
1377
|
+
let funcLoc = toLoc(valueNode);
|
|
1378
|
+
if (colorFunctions.has(funcName)) {
|
|
1379
|
+
colors.push(valueNode.text, property, funcLoc);
|
|
1380
|
+
colorFormats.p(funcName.toLowerCase(), funcLoc);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (endsWith("gradient", funcName)) {
|
|
1384
|
+
gradients.p(valueNode.text, funcLoc);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
} else if (node.type === URL) {
|
|
1392
|
+
let { value } = node;
|
|
1393
|
+
let embed = unquote(value || "");
|
|
1394
|
+
if (str_starts_with(embed, "data:")) {
|
|
1395
|
+
let size = embed.length;
|
|
1396
|
+
let type = getEmbedType(embed);
|
|
1397
|
+
embedTypes.total++;
|
|
1398
|
+
embedSize += size;
|
|
1399
|
+
let loc = {
|
|
1400
|
+
line: node.line,
|
|
1401
|
+
column: node.column,
|
|
1402
|
+
offset: node.start,
|
|
1403
|
+
length: node.length
|
|
1404
|
+
};
|
|
1405
|
+
if (embedTypes.unique.has(type)) {
|
|
1406
|
+
let item = embedTypes.unique.get(type);
|
|
1407
|
+
item.count++;
|
|
1408
|
+
item.size += size;
|
|
1409
|
+
embedTypes.unique.set(type, item);
|
|
1410
|
+
if (useLocations && item.uniqueWithLocations) item.uniqueWithLocations.push(loc);
|
|
1411
|
+
} else {
|
|
1412
|
+
let item = {
|
|
1413
|
+
count: 1,
|
|
1414
|
+
size,
|
|
1415
|
+
uniqueWithLocations: useLocations ? [loc] : void 0
|
|
1416
|
+
};
|
|
1417
|
+
embedTypes.unique.set(type, item);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
} else if (node.type === MEDIA_FEATURE) {
|
|
1421
|
+
if (node.property) mediaFeatures.p(node.property.toLowerCase(), toLoc(node));
|
|
1422
|
+
return SKIP;
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
let totalUniqueDeclarations = uniqueDeclarations.size;
|
|
1426
|
+
let totalSelectors = selectorComplexities.size();
|
|
1427
|
+
let specificitiesA = specificityA.aggregate();
|
|
1428
|
+
let specificitiesB = specificityB.aggregate();
|
|
1429
|
+
let specificitiesC = specificityC.aggregate();
|
|
1430
|
+
let totalUniqueSelectors = uniqueSelectors.size;
|
|
1431
|
+
let assign = Object.assign;
|
|
1432
|
+
let cssLen = css.length;
|
|
1433
|
+
let fontFacesCount = fontfaces.length;
|
|
1434
|
+
let atRuleComplexity = atRuleComplexities.aggregate();
|
|
1435
|
+
let selectorComplexity = selectorComplexities.aggregate();
|
|
1436
|
+
let declarationComplexity = declarationComplexities.aggregate();
|
|
1437
|
+
let propertyComplexity = propertyComplexities.aggregate();
|
|
1438
|
+
let valueComplexity = valueComplexities.aggregate();
|
|
1439
|
+
let atruleCount = atrules.c();
|
|
1440
|
+
return {
|
|
1441
|
+
stylesheet: {
|
|
1442
|
+
sourceLinesOfCode: atruleCount.total + totalSelectors + totalDeclarations + keyframeSelectors.size(),
|
|
1443
|
+
linesOfCode,
|
|
1444
|
+
size: cssLen,
|
|
1445
|
+
complexity: atRuleComplexity.sum + selectorComplexity.sum + declarationComplexity.sum + propertyComplexity.sum + valueComplexity.sum,
|
|
1446
|
+
comments: {
|
|
1447
|
+
total: totalComments,
|
|
1448
|
+
size: commentsSize
|
|
1449
|
+
},
|
|
1450
|
+
embeddedContent: {
|
|
1451
|
+
size: {
|
|
1452
|
+
total: embedSize,
|
|
1453
|
+
ratio: ratio(embedSize, cssLen)
|
|
1454
|
+
},
|
|
1455
|
+
types: {
|
|
1456
|
+
total: embedTypes.total,
|
|
1457
|
+
totalUnique: embedTypes.unique.size,
|
|
1458
|
+
uniquenessRatio: ratio(embedTypes.unique.size, embedTypes.total),
|
|
1459
|
+
unique: Object.fromEntries(embedTypes.unique)
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
atrules: assign(atruleCount, {
|
|
1464
|
+
fontface: assign({
|
|
1465
|
+
total: fontFacesCount,
|
|
1466
|
+
totalUnique: fontFacesCount,
|
|
1467
|
+
unique: fontfaces,
|
|
1468
|
+
uniquenessRatio: fontFacesCount === 0 ? 0 : 1
|
|
1469
|
+
}, useLocations ? { uniqueWithLocations: fontfaces_with_loc.c().uniqueWithLocations } : {}),
|
|
1470
|
+
import: imports.c(),
|
|
1471
|
+
media: assign(medias.c(), {
|
|
1472
|
+
browserhacks: mediaBrowserhacks.c(),
|
|
1473
|
+
features: mediaFeatures.c()
|
|
1474
|
+
}),
|
|
1475
|
+
charset: charsets.c(),
|
|
1476
|
+
supports: assign(supports.c(), { browserhacks: supportsBrowserhacks.c() }),
|
|
1477
|
+
keyframes: assign(keyframes.c(), { prefixed: assign(prefixedKeyframes.c(), { ratio: ratio(prefixedKeyframes.size(), keyframes.size()) }) }),
|
|
1478
|
+
container: assign(containers.c(), { names: containerNames.c() }),
|
|
1479
|
+
layer: layers.c(),
|
|
1480
|
+
property: registeredProperties.c(),
|
|
1481
|
+
function: functions.c(),
|
|
1482
|
+
scope: scopes.c(),
|
|
1483
|
+
complexity: atRuleComplexity,
|
|
1484
|
+
nesting: assign(atruleNesting.aggregate(), { items: atruleNesting.toArray() }, uniqueAtruleNesting.c())
|
|
1485
|
+
}),
|
|
1486
|
+
rules: {
|
|
1487
|
+
total: totalRules,
|
|
1488
|
+
empty: {
|
|
1489
|
+
total: emptyRules,
|
|
1490
|
+
ratio: ratio(emptyRules, totalRules)
|
|
1491
|
+
},
|
|
1492
|
+
sizes: assign(ruleSizes.aggregate(), { items: ruleSizes.toArray() }, uniqueRuleSize.c()),
|
|
1493
|
+
nesting: assign(ruleNesting.aggregate(), { items: ruleNesting.toArray() }, uniqueRuleNesting.c()),
|
|
1494
|
+
selectors: assign(selectorsPerRule.aggregate(), { items: selectorsPerRule.toArray() }, uniqueSelectorsPerRule.c()),
|
|
1495
|
+
declarations: assign(declarationsPerRule.aggregate(), { items: declarationsPerRule.toArray() }, uniqueDeclarationsPerRule.c())
|
|
1496
|
+
},
|
|
1497
|
+
selectors: {
|
|
1498
|
+
total: totalSelectors,
|
|
1499
|
+
totalUnique: totalUniqueSelectors,
|
|
1500
|
+
uniquenessRatio: ratio(totalUniqueSelectors, totalSelectors),
|
|
1501
|
+
specificity: assign({
|
|
1502
|
+
min: minSpecificity === void 0 ? [
|
|
1503
|
+
0,
|
|
1504
|
+
0,
|
|
1505
|
+
0
|
|
1506
|
+
] : minSpecificity,
|
|
1507
|
+
max: maxSpecificity === void 0 ? [
|
|
1508
|
+
0,
|
|
1509
|
+
0,
|
|
1510
|
+
0
|
|
1511
|
+
] : maxSpecificity,
|
|
1512
|
+
sum: [
|
|
1513
|
+
specificitiesA.sum,
|
|
1514
|
+
specificitiesB.sum,
|
|
1515
|
+
specificitiesC.sum
|
|
1516
|
+
],
|
|
1517
|
+
mean: [
|
|
1518
|
+
specificitiesA.mean,
|
|
1519
|
+
specificitiesB.mean,
|
|
1520
|
+
specificitiesC.mean
|
|
1521
|
+
],
|
|
1522
|
+
mode: [
|
|
1523
|
+
specificitiesA.mode,
|
|
1524
|
+
specificitiesB.mode,
|
|
1525
|
+
specificitiesC.mode
|
|
1526
|
+
],
|
|
1527
|
+
items: specificities
|
|
1528
|
+
}, uniqueSpecificities.c()),
|
|
1529
|
+
complexity: assign(selectorComplexity, uniqueSelectorComplexities.c(), { items: selectorComplexities.toArray() }),
|
|
1530
|
+
nesting: assign(selectorNesting.aggregate(), { items: selectorNesting.toArray() }, uniqueSelectorNesting.c()),
|
|
1531
|
+
id: assign(ids.c(), { ratio: ratio(ids.size(), totalSelectors) }),
|
|
1532
|
+
pseudoClasses: pseudoClasses.c(),
|
|
1533
|
+
pseudoElements: pseudoElements.c(),
|
|
1534
|
+
accessibility: assign(a11y.c(), { ratio: ratio(a11y.size(), totalSelectors) }),
|
|
1535
|
+
attributes: attributeSelectors.c(),
|
|
1536
|
+
customElements: customElementSelectors.c(),
|
|
1537
|
+
keyframes: keyframeSelectors.c(),
|
|
1538
|
+
prefixed: assign(prefixedSelectors.c(), { ratio: ratio(prefixedSelectors.size(), totalSelectors) }),
|
|
1539
|
+
combinators: combinators.c()
|
|
1540
|
+
},
|
|
1541
|
+
declarations: {
|
|
1542
|
+
total: totalDeclarations,
|
|
1543
|
+
totalUnique: totalUniqueDeclarations,
|
|
1544
|
+
uniquenessRatio: ratio(totalUniqueDeclarations, totalDeclarations),
|
|
1545
|
+
importants: {
|
|
1546
|
+
total: importantDeclarations,
|
|
1547
|
+
ratio: ratio(importantDeclarations, totalDeclarations),
|
|
1548
|
+
inKeyframes: {
|
|
1549
|
+
total: importantsInKeyframes,
|
|
1550
|
+
ratio: ratio(importantsInKeyframes, importantDeclarations)
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
complexity: declarationComplexity,
|
|
1554
|
+
nesting: assign(declarationNesting.aggregate(), { items: declarationNesting.toArray() }, uniqueDeclarationNesting.c())
|
|
1555
|
+
},
|
|
1556
|
+
properties: assign(properties.c(), {
|
|
1557
|
+
prefixed: assign(propertyVendorPrefixes.c(), { ratio: ratio(propertyVendorPrefixes.size(), properties.size()) }),
|
|
1558
|
+
custom: assign(customProperties.c(), {
|
|
1559
|
+
ratio: ratio(customProperties.size(), properties.size()),
|
|
1560
|
+
importants: assign(importantCustomProperties.c(), { ratio: ratio(importantCustomProperties.size(), customProperties.size()) })
|
|
1561
|
+
}),
|
|
1562
|
+
browserhacks: assign(propertyHacks.c(), { ratio: ratio(propertyHacks.size(), properties.size()) }),
|
|
1563
|
+
complexity: propertyComplexity
|
|
1564
|
+
}),
|
|
1565
|
+
values: {
|
|
1566
|
+
colors: assign(colors.count(), { formats: colorFormats.c() }),
|
|
1567
|
+
gradients: gradients.c(),
|
|
1568
|
+
fontFamilies: fontFamilies.c(),
|
|
1569
|
+
fontSizes: fontSizes.c(),
|
|
1570
|
+
lineHeights: lineHeights.c(),
|
|
1571
|
+
zindexes: zindex.c(),
|
|
1572
|
+
textShadows: textShadows.c(),
|
|
1573
|
+
boxShadows: boxShadows.c(),
|
|
1574
|
+
borderRadiuses: borderRadiuses.count(),
|
|
1575
|
+
animations: {
|
|
1576
|
+
durations: durations.c(),
|
|
1577
|
+
timingFunctions: timingFunctions.c()
|
|
1578
|
+
},
|
|
1579
|
+
prefixes: vendorPrefixedValues.c(),
|
|
1580
|
+
browserhacks: valueBrowserhacks.c(),
|
|
1581
|
+
units: units.count(),
|
|
1582
|
+
complexity: valueComplexity,
|
|
1583
|
+
keywords: valueKeywords.c(),
|
|
1584
|
+
resets: resets.c(),
|
|
1585
|
+
displays: displays.c()
|
|
1586
|
+
},
|
|
1587
|
+
__meta__: {
|
|
1588
|
+
parseTime: startAnalysis - startParse,
|
|
1589
|
+
analyzeTime: Date.now() - startAnalysis,
|
|
1590
|
+
total: Date.now() - start
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Compare specificity A to Specificity B
|
|
1596
|
+
* @returns 0 when a==b, 1 when a<b, -1 when a>b
|
|
1597
|
+
*/
|
|
1598
|
+
function compareSpecificity(a, b) {
|
|
1599
|
+
if (a[0] === b[0]) {
|
|
1600
|
+
if (a[1] === b[1]) return b[2] - a[2];
|
|
1601
|
+
return b[1] - a[1];
|
|
1602
|
+
}
|
|
1603
|
+
return b[0] - a[0];
|
|
1604
|
+
}
|
|
1605
|
+
//#endregion
|
|
1606
|
+
export { KeywordSet, analyze, calculate as calculateSpecificity, colorFunctions, colorKeywords, compareSpecificity, keywords as cssKeywords, hasVendorPrefix, isAccessibility as isAccessibilitySelector, isMediaBrowserhack, isHack as isPropertyHack, isPrefixed as isSelectorPrefixed, isSupportsBrowserhack, isValuePrefixed, namedColors, getComplexity as selectorComplexity, systemColors };
|