@thejaredwilcurt/csslop 0.0.37 → 0.0.39
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/README.md +7 -2
- package/package.json +8 -6
- package/src/declarations/background.js +29 -37
- package/src/declarations/border.js +1 -1
- package/src/declarations/fold.js +1 -1
- package/src/declarations/process.js +17 -8
- package/src/declarations/shorthand-values.js +1 -1
- package/src/index.js +19 -46
- package/src/parser/at-rules.js +291 -0
- package/src/parser/index.js +826 -0
- package/src/parser/positions.js +71 -0
- package/src/parser/scanner.js +229 -0
- package/src/parser/source-search.js +287 -0
- package/src/parser/tokenizer.js +396 -0
- package/src/preprocess.js +20 -228
- package/src/rules/lang.js +330 -0
- package/src/rules/normalize.js +80 -0
- package/src/rules/optimize.js +260 -120
- package/src/rules/selectors.js +67 -223
- package/src/rules/stringify.js +18 -13
- package/src/value/color-mix.js +22 -47
- package/src/value/gradients.js +44 -71
- package/src/value/light-dark.js +9 -94
- package/src/value/math.js +163 -1
- package/src/value/minify.js +139 -52
- package/src/value/relative-colors.js +15 -78
- package/src/value/shared.js +1 -0
- package/src/value/tokens.js +4 -387
- package/src/value/transforms.js +20 -53
- package/src/value/syntax.js +0 -144
package/src/rules/optimize.js
CHANGED
|
@@ -6,6 +6,11 @@ import {
|
|
|
6
6
|
expandToLeafProperties,
|
|
7
7
|
getOverridesOf
|
|
8
8
|
} from '../declarations/config.js';
|
|
9
|
+
import {
|
|
10
|
+
findMatchingBracket,
|
|
11
|
+
findMatchingParenthesis,
|
|
12
|
+
splitTopLevelCommaList
|
|
13
|
+
} from '../parser/source-search.js';
|
|
9
14
|
import { escapeRegexString } from '../utilities.js';
|
|
10
15
|
|
|
11
16
|
import {
|
|
@@ -13,13 +18,82 @@ import {
|
|
|
13
18
|
normalizeMedia
|
|
14
19
|
} from './normalize.js';
|
|
15
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Reports whether an entry of a rule's body is itself a nested rule rather
|
|
23
|
+
* than a plain declaration.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} entry An entry of a rule's declaration list.
|
|
26
|
+
* @return {boolean} True when the entry is a nested rule.
|
|
27
|
+
*/
|
|
28
|
+
function isNestedRule (entry) {
|
|
29
|
+
return entry.type === 'rule';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Expands the rules nested inside each rule's body, so a chain of wrappers
|
|
34
|
+
* collapses from the inside out and the outermost wrapper is then judged
|
|
35
|
+
* against an already flattened child. A rule's body holds the same kinds of
|
|
36
|
+
* entries a stylesheet does, so the body is expanded by the same rules, and
|
|
37
|
+
* the plain declarations it also holds pass through untouched.
|
|
38
|
+
*
|
|
39
|
+
* @param {Array} rules The AST rule nodes whose bodies may hold nested rules.
|
|
40
|
+
* @return {Array} The rules, each with its body expanded.
|
|
41
|
+
*/
|
|
42
|
+
function expandNestedRuleBodies (rules) {
|
|
43
|
+
return rules.map((rule) => {
|
|
44
|
+
if (rule.type !== 'rule' || !(rule.declarations || []).some(isNestedRule)) {
|
|
45
|
+
return rule;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...rule,
|
|
49
|
+
declarations: expandPureNestedRules(rule.declarations)
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reports whether a nested child selector still means the same thing once it
|
|
56
|
+
* is written out in full under its parent.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} childSelector The selector of a rule nested inside another.
|
|
59
|
+
* @return {boolean} True when the child can be written as a flat selector.
|
|
60
|
+
*/
|
|
61
|
+
function canFlattenChildSelector (childSelector) {
|
|
62
|
+
const child = childSelector.trim();
|
|
63
|
+
if (!child) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// A `&` anywhere but the very start puts the parent where flattening cannot:
|
|
67
|
+
// `.b &` means the parent follows the child rather than leading it
|
|
68
|
+
return !child.slice(1).includes('&');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Writes a nested child selector out in full, as the selector it stands for
|
|
73
|
+
* when read through its parent.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} parentSelector The selector of the rule the child is nested in.
|
|
76
|
+
* @param {string} childSelector The selector of the nested child rule.
|
|
77
|
+
* @return {string} The flat selector the nested pair stands for.
|
|
78
|
+
*/
|
|
79
|
+
function combineNestedSelector (parentSelector, childSelector) {
|
|
80
|
+
const parent = parentSelector.trim();
|
|
81
|
+
const child = childSelector.trim();
|
|
82
|
+
// A leading `&` names the parent itself, so it is what the parent replaces
|
|
83
|
+
if (child.startsWith('&')) {
|
|
84
|
+
return parent + child.slice(1);
|
|
85
|
+
}
|
|
86
|
+
return parent + ' ' + child;
|
|
87
|
+
}
|
|
88
|
+
|
|
16
89
|
/**
|
|
17
90
|
* Expands rules that contain only nested sub-rules into flat rules with combined selectors, enabling further merging when the combined selectors already exist elsewhere.
|
|
18
91
|
*
|
|
19
|
-
* @param {Array}
|
|
20
|
-
* @return {Array}
|
|
92
|
+
* @param {Array} inputRules The AST rule nodes to process.
|
|
93
|
+
* @return {Array} A new array of rules with pure-nested rules expanded.
|
|
21
94
|
*/
|
|
22
|
-
function expandPureNestedRules (
|
|
95
|
+
function expandPureNestedRules (inputRules) {
|
|
96
|
+
const rules = expandNestedRuleBodies(inputRules);
|
|
23
97
|
const flatSelectors = new Set();
|
|
24
98
|
for (const rule of rules) {
|
|
25
99
|
if (rule.type !== 'rule' || !rule.selectors?.length) {
|
|
@@ -77,16 +151,14 @@ function expandPureNestedRules (rules) {
|
|
|
77
151
|
canExpand = false;
|
|
78
152
|
break;
|
|
79
153
|
}
|
|
154
|
+
if (!nestedRule.selectors.every(canFlattenChildSelector)) {
|
|
155
|
+
canExpand = false;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
80
158
|
const combinedSelectors = [];
|
|
81
159
|
for (const parentSelector of rule.selectors) {
|
|
82
160
|
for (const childSelector of nestedRule.selectors) {
|
|
83
|
-
const
|
|
84
|
-
let combinedSelector;
|
|
85
|
-
if (trimmedChild.startsWith('&')) {
|
|
86
|
-
combinedSelector = trimmedChild.replace(/^&/, parentSelector.trim());
|
|
87
|
-
} else {
|
|
88
|
-
combinedSelector = parentSelector.trim() + ' ' + trimmedChild;
|
|
89
|
-
}
|
|
161
|
+
const combinedSelector = combineNestedSelector(parentSelector, childSelector);
|
|
90
162
|
combinedSelectors.push(combinedSelector);
|
|
91
163
|
if (flatSelectors.has(combinedSelector)) {
|
|
92
164
|
anyMatch = true;
|
|
@@ -495,88 +567,187 @@ function deduplicateSelectors (selectors) {
|
|
|
495
567
|
}
|
|
496
568
|
|
|
497
569
|
/**
|
|
498
|
-
*
|
|
570
|
+
* The rule body entries that style nothing themselves, and so have no say in
|
|
571
|
+
* whether one rule's body is carried by another's.
|
|
499
572
|
*
|
|
500
|
-
* @
|
|
501
|
-
* @return {Array} A new array of rules with declaration-compatible consecutive rules combined.
|
|
573
|
+
* @type {Set<string>}
|
|
502
574
|
*/
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
575
|
+
const UNSTYLED_BODY_ENTRY_TYPES = new Set(['whitespace', 'comment']);
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Splits a rule body into the declarations it sets directly and the blocks
|
|
579
|
+
* nested inside it, leaving out the entries that style nothing.
|
|
580
|
+
*
|
|
581
|
+
* @param {Array} declarations The rule body entries.
|
|
582
|
+
* @return {object} The body's `plainDeclarations` and `nestedBlocks`.
|
|
583
|
+
*/
|
|
584
|
+
function partitionRuleBody (declarations) {
|
|
585
|
+
const plainDeclarations = [];
|
|
586
|
+
const nestedBlocks = [];
|
|
587
|
+
for (const entry of declarations || []) {
|
|
588
|
+
if (UNSTYLED_BODY_ENTRY_TYPES.has(entry.type)) {
|
|
508
589
|
continue;
|
|
509
590
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
});
|
|
515
|
-
const currentDeclarations = (rule.declarations || []).filter((declaration) => {
|
|
516
|
-
return declaration.type !== 'whitespace' && declaration.property;
|
|
517
|
-
});
|
|
518
|
-
if (previousDeclarations.length > 0 && currentDeclarations.length > 0) {
|
|
519
|
-
const currentDeclarationMap = new Map(
|
|
520
|
-
currentDeclarations.map((declaration) => {
|
|
521
|
-
return [declaration.property, (declaration.value || '').trim()];
|
|
522
|
-
})
|
|
523
|
-
);
|
|
524
|
-
const previousIsSubset = previousDeclarations.every((declaration) => {
|
|
525
|
-
return currentDeclarationMap.get(declaration.property) === (declaration.value || '').trim();
|
|
526
|
-
});
|
|
527
|
-
const currentHasAllProperty = currentDeclarations.some((declaration) => {
|
|
528
|
-
return declaration.property === 'all';
|
|
529
|
-
});
|
|
530
|
-
if (previousIsSubset && !currentHasAllProperty) {
|
|
531
|
-
const commonProperties = new Set(
|
|
532
|
-
previousDeclarations.map((declaration) => {
|
|
533
|
-
return declaration.property;
|
|
534
|
-
})
|
|
535
|
-
);
|
|
536
|
-
const currentOnlyDeclarations = currentDeclarations.filter((declaration) => {
|
|
537
|
-
return !commonProperties.has(declaration.property);
|
|
538
|
-
});
|
|
539
|
-
result.pop();
|
|
540
|
-
const combinedSelectors = deduplicateSelectors([...previousRule.selectors, ...rule.selectors]);
|
|
541
|
-
result.push({ ...previousRule, selectors: combinedSelectors });
|
|
542
|
-
if (currentOnlyDeclarations.length > 0) {
|
|
543
|
-
result.push({ ...rule, declarations: currentOnlyDeclarations });
|
|
544
|
-
}
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
591
|
+
if (entry.property) {
|
|
592
|
+
plainDeclarations.push(entry);
|
|
593
|
+
} else {
|
|
594
|
+
nestedBlocks.push(entry);
|
|
548
595
|
}
|
|
549
|
-
result.push(rule);
|
|
550
596
|
}
|
|
551
|
-
return
|
|
597
|
+
return {
|
|
598
|
+
nestedBlocks,
|
|
599
|
+
plainDeclarations
|
|
600
|
+
};
|
|
552
601
|
}
|
|
553
602
|
|
|
554
603
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
604
|
+
* Describes a block nested inside a rule precisely enough to tell whether two
|
|
605
|
+
* rules nest the very same thing. Only plain nested rules can be described: an
|
|
606
|
+
* at-rule nested in a rule carries a prelude that decides when its body applies,
|
|
607
|
+
* so it is reported as indescribable rather than guessed at.
|
|
557
608
|
*
|
|
558
|
-
* @param {
|
|
559
|
-
* @return {
|
|
609
|
+
* @param {object} block A block nested inside a rule body.
|
|
610
|
+
* @return {string|null} The signature, or null when the block cannot be described.
|
|
560
611
|
*/
|
|
561
|
-
function
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
612
|
+
function nestedBlockSignature (block) {
|
|
613
|
+
if (block.type !== 'rule' || !block.selectors?.length) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
const {
|
|
617
|
+
nestedBlocks,
|
|
618
|
+
plainDeclarations
|
|
619
|
+
} = partitionRuleBody(block.declarations);
|
|
620
|
+
const declarationParts = plainDeclarations.map((declaration) => {
|
|
621
|
+
return declaration.property + ':' + (declaration.value || '').trim();
|
|
622
|
+
});
|
|
623
|
+
const nestedParts = nestedBlocksSignature(nestedBlocks);
|
|
624
|
+
if (nestedParts === null) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const selectors = block.selectors.map(normalizeSelector).join(',');
|
|
628
|
+
return selectors + '{' + declarationParts.join(';') + nestedParts + '}';
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Describes every block nested in a rule body as one comparable string, in the
|
|
633
|
+
* order the blocks are written, since that order decides which of them wins a
|
|
634
|
+
* conflict.
|
|
635
|
+
*
|
|
636
|
+
* @param {Array} nestedBlocks The blocks nested in a rule body.
|
|
637
|
+
* @return {string|null} The combined signature, or null when any block cannot be described.
|
|
638
|
+
*/
|
|
639
|
+
function nestedBlocksSignature (nestedBlocks) {
|
|
640
|
+
let combined = '';
|
|
641
|
+
for (const block of nestedBlocks) {
|
|
642
|
+
const signature = nestedBlockSignature(block);
|
|
643
|
+
if (signature === null) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
combined += signature;
|
|
647
|
+
}
|
|
648
|
+
return combined;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Reports whether a rule that nests blocks may take on further selectors. A
|
|
653
|
+
* nested selector resolves against its parent's whole selector list as `:is()`,
|
|
654
|
+
* which matches with the specificity of the strongest selector in that list, so
|
|
655
|
+
* the list may only grow while every selector in it weighs the same.
|
|
656
|
+
*
|
|
657
|
+
* @param {Array} selectors The selector list the merged rule would carry.
|
|
658
|
+
* @return {boolean} Whether the nested blocks keep their current weight.
|
|
659
|
+
*/
|
|
660
|
+
function selectorsShareSpecificity (selectors) {
|
|
661
|
+
const strongest = maximumRuleSpecificity(selectors);
|
|
662
|
+
const weakest = minimumRuleSpecificity(selectors);
|
|
663
|
+
return compareSpecificity(strongest, weakest) === 0;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Combines an earlier rule with the rule that follows it when everything the
|
|
668
|
+
* earlier one styles is restated by the later one, so the earlier selector can
|
|
669
|
+
* simply join the later rule's shared part and the remainder stays behind in a
|
|
670
|
+
* rule of its own.
|
|
671
|
+
*
|
|
672
|
+
* Both rules have to nest the same blocks. A block nested in only one of them
|
|
673
|
+
* would either be lost or start matching the other rule's selector, neither of
|
|
674
|
+
* which the two rules said.
|
|
675
|
+
*
|
|
676
|
+
* @param {object} previousRule The rule immediately in front of `rule`.
|
|
677
|
+
* @param {object} rule The rule being folded into its predecessor.
|
|
678
|
+
* @return {Array|null} The rules that replace the pair, or null when they cannot be combined.
|
|
679
|
+
*/
|
|
680
|
+
function mergeRuleIntoPredecessor (previousRule, rule) {
|
|
681
|
+
const isMergeableRulePair = (
|
|
682
|
+
previousRule?.type === 'rule' &&
|
|
683
|
+
previousRule.selectors?.length &&
|
|
684
|
+
rule.type === 'rule' &&
|
|
685
|
+
rule.selectors?.length
|
|
686
|
+
);
|
|
687
|
+
if (!isMergeableRulePair) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const previousBody = partitionRuleBody(previousRule.declarations);
|
|
691
|
+
const currentBody = partitionRuleBody(rule.declarations);
|
|
692
|
+
if (!previousBody.plainDeclarations.length || !currentBody.plainDeclarations.length) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
const previousNesting = nestedBlocksSignature(previousBody.nestedBlocks);
|
|
696
|
+
const currentNesting = nestedBlocksSignature(currentBody.nestedBlocks);
|
|
697
|
+
if (previousNesting === null || previousNesting !== currentNesting) {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
const currentValueByProperty = new Map(
|
|
701
|
+
currentBody.plainDeclarations.map((declaration) => {
|
|
702
|
+
return [declaration.property, (declaration.value || '').trim()];
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
const previousIsRestated = previousBody.plainDeclarations.every((declaration) => {
|
|
706
|
+
return currentValueByProperty.get(declaration.property) === (declaration.value || '').trim();
|
|
707
|
+
});
|
|
708
|
+
// `all` rewrites every other property, so the later rule restating a value is
|
|
709
|
+
// no promise that it applies the same way once `all` has run.
|
|
710
|
+
const currentResetsEverything = currentValueByProperty.has('all');
|
|
711
|
+
if (!previousIsRestated || currentResetsEverything) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
const combinedSelectors = deduplicateSelectors([...previousRule.selectors, ...rule.selectors]);
|
|
715
|
+
if (previousBody.nestedBlocks.length && !selectorsShareSpecificity(combinedSelectors)) {
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
const restatedProperties = new Set(
|
|
719
|
+
previousBody.plainDeclarations.map((declaration) => {
|
|
720
|
+
return declaration.property;
|
|
721
|
+
})
|
|
722
|
+
);
|
|
723
|
+
const remainingDeclarations = currentBody.plainDeclarations.filter((declaration) => {
|
|
724
|
+
return !restatedProperties.has(declaration.property);
|
|
725
|
+
});
|
|
726
|
+
const merged = [{ ...previousRule, selectors: combinedSelectors }];
|
|
727
|
+
if (remainingDeclarations.length) {
|
|
728
|
+
merged.push({ ...rule, declarations: remainingDeclarations });
|
|
729
|
+
}
|
|
730
|
+
return merged;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Merges consecutive rules whose declarations are a subset of the following rule, combining their selectors and splitting out any extra declarations.
|
|
735
|
+
*
|
|
736
|
+
* @param {Array} rules The AST rule nodes to merge.
|
|
737
|
+
* @return {Array} A new array of rules with declaration-compatible consecutive rules combined.
|
|
738
|
+
*/
|
|
739
|
+
function mergeByDeclarations (rules) {
|
|
740
|
+
const result = [];
|
|
741
|
+
for (const rule of rules) {
|
|
742
|
+
const merged = mergeRuleIntoPredecessor(result[result.length - 1], rule);
|
|
743
|
+
if (merged) {
|
|
744
|
+
result.pop();
|
|
745
|
+
result.push(...merged);
|
|
746
|
+
continue;
|
|
576
747
|
}
|
|
748
|
+
result.push(rule);
|
|
577
749
|
}
|
|
578
|
-
|
|
579
|
-
return selectors;
|
|
750
|
+
return result;
|
|
580
751
|
}
|
|
581
752
|
|
|
582
753
|
/**
|
|
@@ -595,28 +766,6 @@ function skipSelectorName (text, start) {
|
|
|
595
766
|
return index;
|
|
596
767
|
}
|
|
597
768
|
|
|
598
|
-
/**
|
|
599
|
-
* Finds the index of the closing parenthesis matching the one at openIndex.
|
|
600
|
-
*
|
|
601
|
-
* @param {string} text The text to scan.
|
|
602
|
-
* @param {number} openIndex Index of the opening parenthesis.
|
|
603
|
-
* @return {number} Index of the matching close parenthesis, or text length.
|
|
604
|
-
*/
|
|
605
|
-
function findMatchingParenthesisIndex (text, openIndex) {
|
|
606
|
-
let depth = 0;
|
|
607
|
-
for (let index = openIndex; index < text.length; index++) {
|
|
608
|
-
if (text[index] === '(') {
|
|
609
|
-
depth++;
|
|
610
|
-
} else if (text[index] === ')') {
|
|
611
|
-
depth--;
|
|
612
|
-
if (depth === 0) {
|
|
613
|
-
return index;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return text.length;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
769
|
/**
|
|
621
770
|
* Compares two specificity tuples [ids, classes, types] lexicographically.
|
|
622
771
|
*
|
|
@@ -657,20 +806,9 @@ function computeSpecificity (selector) {
|
|
|
657
806
|
index = skipSelectorName(text, index + 1);
|
|
658
807
|
} else if (character === '[') {
|
|
659
808
|
specificity[1]++;
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (text[index] === '[') {
|
|
664
|
-
bracketDepth++;
|
|
665
|
-
} else if (text[index] === ']') {
|
|
666
|
-
bracketDepth--;
|
|
667
|
-
if (bracketDepth === 0) {
|
|
668
|
-
index++;
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
index++;
|
|
673
|
-
}
|
|
809
|
+
// An attribute selector that never closes runs to the end of the selector
|
|
810
|
+
const closeBracketIndex = findMatchingBracket(text, index);
|
|
811
|
+
index = closeBracketIndex === -1 ? text.length : closeBracketIndex + 1;
|
|
674
812
|
} else if (character === ':') {
|
|
675
813
|
if (text[index + 1] === ':') {
|
|
676
814
|
specificity[2]++;
|
|
@@ -680,8 +818,10 @@ function computeSpecificity (selector) {
|
|
|
680
818
|
const nameEnd = skipSelectorName(text, nameStart);
|
|
681
819
|
const name = text.slice(nameStart, nameEnd).toLowerCase();
|
|
682
820
|
if (text[nameEnd] === '(') {
|
|
683
|
-
|
|
684
|
-
const
|
|
821
|
+
// An argument list that never closes runs to the end of the selector
|
|
822
|
+
const closeIndex = findMatchingParenthesis(text, nameEnd);
|
|
823
|
+
const argumentsEnd = closeIndex === -1 ? text.length : closeIndex;
|
|
824
|
+
const inner = text.slice(nameEnd + 1, argumentsEnd);
|
|
685
825
|
if (name === 'where') {
|
|
686
826
|
// :where() contributes zero specificity
|
|
687
827
|
} else if (name === 'is' || name === 'not' || name === 'has' || name === 'matches') {
|
|
@@ -692,7 +832,7 @@ function computeSpecificity (selector) {
|
|
|
692
832
|
} else {
|
|
693
833
|
specificity[1]++;
|
|
694
834
|
}
|
|
695
|
-
index =
|
|
835
|
+
index = argumentsEnd + 1;
|
|
696
836
|
} else {
|
|
697
837
|
// Legacy single-colon pseudo-elements count as pseudo-elements
|
|
698
838
|
if (name === 'before' || name === 'after' || name === 'first-line' || name === 'first-letter') {
|
|
@@ -726,7 +866,7 @@ function computeSpecificity (selector) {
|
|
|
726
866
|
*/
|
|
727
867
|
function maxSelectorSpecificity (selectorList) {
|
|
728
868
|
let maximum = [0, 0, 0];
|
|
729
|
-
for (const selector of
|
|
869
|
+
for (const selector of splitTopLevelCommaList(selectorList)) {
|
|
730
870
|
const specificity = computeSpecificity(selector);
|
|
731
871
|
if (compareSpecificity(specificity, maximum) > 0) {
|
|
732
872
|
maximum = specificity;
|
|
@@ -757,7 +897,7 @@ function maximumRuleSpecificity (selectors) {
|
|
|
757
897
|
*/
|
|
758
898
|
function minimumRuleSpecificity (selectors) {
|
|
759
899
|
let minimum = null;
|
|
760
|
-
for (const selector of
|
|
900
|
+
for (const selector of splitTopLevelCommaList((selectors || []).join(','))) {
|
|
761
901
|
const specificity = computeSpecificity(selector);
|
|
762
902
|
if (!minimum || compareSpecificity(specificity, minimum) < 0) {
|
|
763
903
|
minimum = specificity;
|