@projectwallace/css-analyzer 9.2.1 → 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.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 };