@thejaredwilcurt/csslop 0.0.49 → 0.0.51
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 +0 -1
- package/package.json +2 -2
- package/src/declarations/border.js +1 -1
- package/src/declarations/process.js +155 -1
- package/src/index.js +7 -7
- package/src/rules/optimize/at-rules.js +113 -0
- package/src/rules/optimize/declaration-writes.js +70 -0
- package/src/rules/optimize/empty-rules.js +51 -0
- package/src/rules/optimize/expand-nesting.js +236 -0
- package/src/rules/optimize/factor-parents.js +343 -0
- package/src/rules/optimize/index.js +71 -0
- package/src/rules/optimize/merge-declarations.js +199 -0
- package/src/rules/optimize/merge-nested-rules.js +117 -0
- package/src/rules/optimize/merge-selectors.js +252 -0
- package/src/rules/optimize/nest-rules.js +202 -0
- package/src/rules/optimize/override-index.js +264 -0
- package/src/rules/optimize/prune-overridden.js +267 -0
- package/src/rules/optimize/selector-normalization.js +60 -0
- package/src/rules/optimize/specificity.js +280 -0
- package/src/{position-try.js → rules/position-try.js} +1 -1
- package/src/rules/stringify.js +13 -0
- package/src/utilities.js +14 -0
- package/src/value/border.js +85 -0
- package/src/value/{colors.js → color/conversion.js} +3 -2
- package/src/value/color/functions.js +463 -0
- package/src/value/{light-dark.js → color/light-dark.js} +1 -1
- package/src/value/{color-mix.js → color/mix.js} +3 -3
- package/src/value/{relative-colors.js → color/relative.js} +3 -3
- package/src/value/color/shorten.js +93 -0
- package/src/value/gradients.js +1 -1
- package/src/value/hyphenate-limit-chars.js +72 -0
- package/src/value/initial-keywords.js +128 -0
- package/src/value/lengths.js +55 -0
- package/src/value/math/evaluate.js +36 -0
- package/src/value/math/fold.js +315 -0
- package/src/value/math/format.js +230 -0
- package/src/value/math/functions.js +302 -0
- package/src/value/math/index.js +219 -0
- package/src/value/math/parser.js +301 -0
- package/src/value/math/property-ranges.js +174 -0
- package/src/value/math/rewrite.js +222 -0
- package/src/value/math/tokenizer.js +160 -0
- package/src/value/math/unit-totals.js +194 -0
- package/src/value/minify.js +127 -1669
- package/src/value/numbers.js +28 -0
- package/src/value/positions.js +152 -0
- package/src/value/property-optimizations.js +485 -0
- package/src/value/segments.js +74 -0
- package/src/value/{svg-data-uri.js → svg/data-uri.js} +1 -1
- package/src/value/{svg-markup.js → svg/markup.js} +1 -1
- package/src/value/time.js +75 -0
- package/src/value/urls.js +242 -0
- package/src/value/whitespace.js +88 -0
- package/src/rules/optimize.js +0 -2314
- package/src/value/math-expression.js +0 -1156
- package/src/value/math.js +0 -886
- /package/src/value/{named-colors.js → color/named.js} +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Nests flat rules under a preceding rule whose selector their own selector starts with, so CSS nesting writes the shared part once.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Matches the part of a child selector that follows its parent when that part
|
|
7
|
+
* starts with an explicit combinator. Optional whitespace, then the combinator
|
|
8
|
+
* (`>`, `+`, or `~`) is captured, then optional whitespace, then the rest of
|
|
9
|
+
* the selector is captured.
|
|
10
|
+
*
|
|
11
|
+
* @type {RegExp}
|
|
12
|
+
*/
|
|
13
|
+
const LEADING_COMBINATOR_PATTERN = /^\s*([>+~])\s*(.+)$/;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Expresses the part of a child selector that follows its parent as the
|
|
17
|
+
* selector it would be once nested inside that parent.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} remainder The child selector with the parent prefix removed.
|
|
20
|
+
* @return {string|null} The nested selector, or null when the remainder cannot be nested.
|
|
21
|
+
*/
|
|
22
|
+
function nestSelectorRemainder (remainder) {
|
|
23
|
+
if (!remainder) {
|
|
24
|
+
return '&';
|
|
25
|
+
}
|
|
26
|
+
if (remainder.startsWith(':')) {
|
|
27
|
+
return '&' + remainder;
|
|
28
|
+
}
|
|
29
|
+
if (remainder.startsWith(' ')) {
|
|
30
|
+
return remainder.slice(1);
|
|
31
|
+
}
|
|
32
|
+
const combinatorMatch = remainder.match(LEADING_COMBINATOR_PATTERN);
|
|
33
|
+
if (combinatorMatch) {
|
|
34
|
+
return combinatorMatch[1] + combinatorMatch[2];
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds a reusable matcher that expresses child selectors as nested selectors
|
|
41
|
+
* relative to one parent. Most children tested against a parent do not start
|
|
42
|
+
* with it at all, so that cheap prefix check runs before anything else.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} parentSelector The parent selector string.
|
|
45
|
+
* @return {function(string): (string|null)} A matcher returning the nested selector, or null when the child cannot be nested.
|
|
46
|
+
*/
|
|
47
|
+
function createNestedSelectorMatcher (parentSelector) {
|
|
48
|
+
const parent = parentSelector.trim();
|
|
49
|
+
|
|
50
|
+
return (childSelector) => {
|
|
51
|
+
const child = childSelector.trim();
|
|
52
|
+
if (!child.startsWith(parent)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return nestSelectorRemainder(child.slice(parent.length));
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Attempts to express a child selector as a nested selector relative to a parent, returning the nested form or null if nesting is not possible.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} parentSelector The parent selector string.
|
|
63
|
+
* @param {string} childSelector The child selector string to try nesting.
|
|
64
|
+
* @return {string|null} The nested selector using & syntax, or null if the child cannot be nested under the parent.
|
|
65
|
+
*/
|
|
66
|
+
function tryNestSelector (parentSelector, childSelector) {
|
|
67
|
+
return createNestedSelectorMatcher(parentSelector)(childSelector);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Determines whether a character is CSS whitespace.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} character A single character from a selector.
|
|
74
|
+
* @return {boolean} Whether the character is whitespace.
|
|
75
|
+
*/
|
|
76
|
+
function isSelectorWhitespace (character) {
|
|
77
|
+
// Match a single whitespace character
|
|
78
|
+
return (/\s/).test(character);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Collects every prefix of a child selector that could act as its nesting
|
|
83
|
+
* parent. A parent always ends immediately before a pseudo-class colon, a
|
|
84
|
+
* descendant whitespace run, or the whitespace run leading into a combinator,
|
|
85
|
+
* so only those few positions can start a nestable remainder. Enumerating them
|
|
86
|
+
* turns "which earlier rule can host this one" into a set of key lookups
|
|
87
|
+
* instead of a comparison against every preceding rule.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} childSelector The trimmed child selector.
|
|
90
|
+
* @return {Set} The candidate parent selector strings.
|
|
91
|
+
*/
|
|
92
|
+
function collectCandidateParentSelectors (childSelector) {
|
|
93
|
+
const candidates = new Set();
|
|
94
|
+
for (let index = 1; index < childSelector.length; index++) {
|
|
95
|
+
const character = childSelector[index];
|
|
96
|
+
if (character === ':' || isSelectorWhitespace(character)) {
|
|
97
|
+
// The second colon of a pseudo-element is part of its `::` introducer,
|
|
98
|
+
// not a boundary between a potential parent and nested child.
|
|
99
|
+
if (character === ':' && childSelector[index - 1] === ':') {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
candidates.add(childSelector.slice(0, index));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (character !== '>' && character !== '+' && character !== '~') {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// A combinator may be separated from its parent by whitespace, which
|
|
109
|
+
// belongs to neither side, so the parent ends where that run begins.
|
|
110
|
+
let boundary = index;
|
|
111
|
+
while (boundary > 0 && isSelectorWhitespace(childSelector[boundary - 1])) {
|
|
112
|
+
boundary--;
|
|
113
|
+
}
|
|
114
|
+
if (boundary > 0) {
|
|
115
|
+
candidates.add(childSelector.slice(0, boundary));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return candidates;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Finds the most recently emitted rule that can host a child selector as a
|
|
123
|
+
* nested rule, matching the behaviour of scanning backwards through the emitted
|
|
124
|
+
* rules but only visiting the handful of rules whose selector is a viable
|
|
125
|
+
* parent prefix.
|
|
126
|
+
*
|
|
127
|
+
* @param {Array} emittedRules The rules emitted so far.
|
|
128
|
+
* @param {Map} indexBySelector Map of each emitted single-selector rule's selector to its index.
|
|
129
|
+
* @param {string} childSelector The trimmed child selector to nest.
|
|
130
|
+
* @return {object|null} The `parentIndex` and `nestedSelector`, or null when nothing can host the child.
|
|
131
|
+
*/
|
|
132
|
+
function findNestingParent (emittedRules, indexBySelector, childSelector) {
|
|
133
|
+
const parentIndexes = [];
|
|
134
|
+
for (const candidateParent of collectCandidateParentSelectors(childSelector)) {
|
|
135
|
+
const parentIndex = indexBySelector.get(candidateParent);
|
|
136
|
+
if (parentIndex !== undefined) {
|
|
137
|
+
parentIndexes.push(parentIndex);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// The nearest preceding parent wins, exactly as a backwards scan would.
|
|
141
|
+
parentIndexes.sort((first, second) => {
|
|
142
|
+
return second - first;
|
|
143
|
+
});
|
|
144
|
+
for (const parentIndex of parentIndexes) {
|
|
145
|
+
const nestedSelector = tryNestSelector(emittedRules[parentIndex].selectors[0], childSelector);
|
|
146
|
+
if (nestedSelector !== null) {
|
|
147
|
+
return {
|
|
148
|
+
nestedSelector,
|
|
149
|
+
parentIndex
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Groups flat CSS rules into nested structures where a child selector can be expressed relative to a preceding parent, reducing output size through CSS nesting.
|
|
158
|
+
*
|
|
159
|
+
* @param {Array} rules The flat AST rule nodes to nest.
|
|
160
|
+
* @return {Array} A new array of rules with applicable children nested under their parents.
|
|
161
|
+
*/
|
|
162
|
+
function nestFlatRules (rules) {
|
|
163
|
+
const result = [];
|
|
164
|
+
const indexBySelector = new Map();
|
|
165
|
+
for (const rule of rules) {
|
|
166
|
+
if (rule.type !== 'rule' || rule.selectors?.length !== 1) {
|
|
167
|
+
result.push(rule);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const childSelector = rule.selectors[0].trim();
|
|
171
|
+
const nesting = findNestingParent(result, indexBySelector, childSelector);
|
|
172
|
+
if (nesting) {
|
|
173
|
+
const parentRule = result[nesting.parentIndex];
|
|
174
|
+
parentRule.declarations = parentRule.declarations || [];
|
|
175
|
+
parentRule.declarations.push({ ...rule, selectors: [nesting.nestedSelector] });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
indexBySelector.set(childSelector, result.length);
|
|
179
|
+
result.push(rule);
|
|
180
|
+
}
|
|
181
|
+
for (const rule of result) {
|
|
182
|
+
if (rule.type === 'rule' && rule.declarations) {
|
|
183
|
+
const innerRules = rule.declarations.filter((declaration) => {
|
|
184
|
+
return declaration.type === 'rule';
|
|
185
|
+
});
|
|
186
|
+
if (innerRules.length > 0) {
|
|
187
|
+
const nonRuleDeclarations = rule.declarations.filter((declaration) => {
|
|
188
|
+
return declaration.type !== 'rule';
|
|
189
|
+
});
|
|
190
|
+
rule.declarations = [...nonRuleDeclarations, ...nestFlatRules(innerRules)];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export {
|
|
198
|
+
collectCandidateParentSelectors,
|
|
199
|
+
createNestedSelectorMatcher,
|
|
200
|
+
isSelectorWhitespace,
|
|
201
|
+
nestFlatRules
|
|
202
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Indexes which properties each rule writes to and how strongly, so a merge can ask whether moving declarations past later rules would change the cascade.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isCustomProperty } from '../../utilities.js';
|
|
6
|
+
|
|
7
|
+
import { expandToOverridableProperties } from './declaration-writes.js';
|
|
8
|
+
import {
|
|
9
|
+
addSpecificity,
|
|
10
|
+
compareSpecificity,
|
|
11
|
+
maximumRuleSpecificity,
|
|
12
|
+
minimumRuleSpecificity
|
|
13
|
+
} from './specificity.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The property that rewrites every other standard property, and so conflicts
|
|
17
|
+
* with any declaration it is ordered against.
|
|
18
|
+
*
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
const RESET_ALL_PROPERTY = 'all';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Index key standing for "declares anything at all", used to order a rule that
|
|
25
|
+
* sets `all` against every other declaration.
|
|
26
|
+
*
|
|
27
|
+
* @type {symbol}
|
|
28
|
+
*/
|
|
29
|
+
const ANY_PROPERTY = Symbol('any property');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} RuleWriteProfile
|
|
33
|
+
* @property {Map} specificityByProperty The strongest specificity each leaf property is written with.
|
|
34
|
+
* @property {Array} weakestSpecificity The weakest specificity the rule's declarations are applied with.
|
|
35
|
+
* @property {Array} strongestSpecificity The strongest specificity anything in the rule is written with.
|
|
36
|
+
* @property {boolean} writesStandardProperty Whether the rule writes any property other than a custom property.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Notes in a profile that a leaf property is written with a specificity,
|
|
41
|
+
* keeping only the strongest specificity seen for it.
|
|
42
|
+
*
|
|
43
|
+
* @param {RuleWriteProfile} profile The profile to extend.
|
|
44
|
+
* @param {string} property The leaf property name.
|
|
45
|
+
* @param {Array} specificity The specificity the property is written with.
|
|
46
|
+
*/
|
|
47
|
+
function addWrittenProperty (profile, property, specificity) {
|
|
48
|
+
const strongestSoFar = profile.specificityByProperty.get(property);
|
|
49
|
+
if (!strongestSoFar || compareSpecificity(specificity, strongestSoFar) > 0) {
|
|
50
|
+
profile.specificityByProperty.set(property, specificity);
|
|
51
|
+
}
|
|
52
|
+
if (compareSpecificity(specificity, profile.strongestSpecificity) > 0) {
|
|
53
|
+
profile.strongestSpecificity = specificity;
|
|
54
|
+
}
|
|
55
|
+
profile.writesStandardProperty = profile.writesStandardProperty || !isCustomProperty(property);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adds every leaf property a rule's declarations write to into a profile,
|
|
60
|
+
* descending into nested rules, whose declarations only ever match through the
|
|
61
|
+
* parent and so carry the specificity of both selectors.
|
|
62
|
+
*
|
|
63
|
+
* @param {RuleWriteProfile} profile The profile to extend.
|
|
64
|
+
* @param {object} rule The AST rule node to read declarations from.
|
|
65
|
+
* @param {Array} specificity The specificity the rule's own declarations apply with.
|
|
66
|
+
*/
|
|
67
|
+
function addWrittenProperties (profile, rule, specificity) {
|
|
68
|
+
for (const declaration of rule.declarations || []) {
|
|
69
|
+
if (declaration.type === 'rule') {
|
|
70
|
+
const nestedSpecificity = addSpecificity(specificity, maximumRuleSpecificity(declaration.selectors));
|
|
71
|
+
addWrittenProperties(profile, declaration, nestedSpecificity);
|
|
72
|
+
} else if (declaration.type === 'declaration' && declaration.property) {
|
|
73
|
+
for (const leafProperty of expandToOverridableProperties(declaration.property)) {
|
|
74
|
+
addWrittenProperty(profile, leafProperty, specificity);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Summarizes which properties a rule writes to and how strongly, which is
|
|
82
|
+
* everything the cascade needs to know about a rule to decide whether it may be
|
|
83
|
+
* reordered against another one.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} rule The AST rule node.
|
|
86
|
+
* @return {RuleWriteProfile} The rule's write profile.
|
|
87
|
+
*/
|
|
88
|
+
function buildRuleWriteProfile (rule) {
|
|
89
|
+
const profile = {
|
|
90
|
+
specificityByProperty: new Map(),
|
|
91
|
+
weakestSpecificity: minimumRuleSpecificity(rule.selectors),
|
|
92
|
+
strongestSpecificity: [0, 0, 0],
|
|
93
|
+
writesStandardProperty: false
|
|
94
|
+
};
|
|
95
|
+
addWrittenProperties(profile, rule, maximumRuleSpecificity(rule.selectors));
|
|
96
|
+
return profile;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Folds one rule's writes into another's profile, which is how a profile stays
|
|
101
|
+
* current after a merge appends declarations to a rule. Both rules share the
|
|
102
|
+
* same selectors, so the absorbed writes keep their recorded specificity.
|
|
103
|
+
*
|
|
104
|
+
* @param {RuleWriteProfile} profile The profile of the rule that grew.
|
|
105
|
+
* @param {RuleWriteProfile} absorbedWrites The profile of the rule whose declarations were appended.
|
|
106
|
+
*/
|
|
107
|
+
function absorbWriteProfile (profile, absorbedWrites) {
|
|
108
|
+
for (const [property, specificity] of absorbedWrites.specificityByProperty) {
|
|
109
|
+
addWrittenProperty(profile, property, specificity);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Tracks which properties each rule writes to and where it sits, so that a
|
|
115
|
+
* merge can ask whether anything after a position could override the
|
|
116
|
+
* declarations it wants to move, instead of rescanning the stylesheet once per
|
|
117
|
+
* merge.
|
|
118
|
+
*
|
|
119
|
+
* Entries for a property are appended in increasing position order, and each
|
|
120
|
+
* new entry discards the earlier ones it dominates, since a declaration that is
|
|
121
|
+
* both later and at least as specific blocks everything a weaker, earlier one
|
|
122
|
+
* would. What remains is a list whose specificity strictly decreases as
|
|
123
|
+
* position grows, so the first entry past a queried position is also the
|
|
124
|
+
* strongest one past it.
|
|
125
|
+
*
|
|
126
|
+
* @param {Map} profileByRule The write profiles already built for rules, shared by every pass of one merge.
|
|
127
|
+
* @return {object} An index exposing `recordRule` and `blocksRelocation`.
|
|
128
|
+
*/
|
|
129
|
+
function createOverrideIndex (profileByRule) {
|
|
130
|
+
const entriesByProperty = new Map();
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Records that a property is written at a position with a given specificity.
|
|
134
|
+
*
|
|
135
|
+
* @param {string|symbol} property The leaf property name, or an index key.
|
|
136
|
+
* @param {number} position The slot the writing rule occupies.
|
|
137
|
+
* @param {Array} specificity The specificity the declaration applies with.
|
|
138
|
+
*/
|
|
139
|
+
function addEntry (property, position, specificity) {
|
|
140
|
+
let entries = entriesByProperty.get(property);
|
|
141
|
+
if (!entries) {
|
|
142
|
+
entries = [];
|
|
143
|
+
entriesByProperty.set(property, entries);
|
|
144
|
+
}
|
|
145
|
+
while (entries.length && compareSpecificity(entries[entries.length - 1].specificity, specificity) <= 0) {
|
|
146
|
+
entries.pop();
|
|
147
|
+
}
|
|
148
|
+
entries.push({ position, specificity });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Whether some rule after a position writes to a property with at least a
|
|
153
|
+
* given specificity, and could therefore win the cascade against it.
|
|
154
|
+
*
|
|
155
|
+
* @param {string|symbol} property The leaf property name, or an index key.
|
|
156
|
+
* @param {number} position The position to search after.
|
|
157
|
+
* @param {Array} specificity The specificity to compare against.
|
|
158
|
+
* @return {boolean} True when a later rule could override the property.
|
|
159
|
+
*/
|
|
160
|
+
function hasStrongerEntryAfter (property, position, specificity) {
|
|
161
|
+
const entries = entriesByProperty.get(property);
|
|
162
|
+
if (!entries) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
let low = 0;
|
|
166
|
+
let high = entries.length;
|
|
167
|
+
while (low < high) {
|
|
168
|
+
const middle = Math.floor((low + high) / 2);
|
|
169
|
+
if (entries[middle].position > position) {
|
|
170
|
+
high = middle;
|
|
171
|
+
} else {
|
|
172
|
+
low = middle + 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (low === entries.length) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return compareSpecificity(entries[low].specificity, specificity) >= 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Returns a rule's write profile, building it the first time it is needed.
|
|
183
|
+
* Rules only ever grow here, by absorbing another rule's declarations, and
|
|
184
|
+
* `absorbRule` folds those into the profile, so a profile stays valid for
|
|
185
|
+
* every pass of the merge.
|
|
186
|
+
*
|
|
187
|
+
* @param {object} rule The AST rule node.
|
|
188
|
+
* @return {RuleWriteProfile} The rule's write profile.
|
|
189
|
+
*/
|
|
190
|
+
function getWriteProfile (rule) {
|
|
191
|
+
let profile = profileByRule.get(rule);
|
|
192
|
+
if (!profile) {
|
|
193
|
+
profile = buildRuleWriteProfile(rule);
|
|
194
|
+
profileByRule.set(rule, profile);
|
|
195
|
+
}
|
|
196
|
+
return profile;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Records every property a rule writes to at the position it occupies.
|
|
201
|
+
*
|
|
202
|
+
* @param {object} rule The AST rule node.
|
|
203
|
+
* @param {number} position The slot the rule occupies.
|
|
204
|
+
*/
|
|
205
|
+
function recordRule (rule, position) {
|
|
206
|
+
const profile = getWriteProfile(rule);
|
|
207
|
+
if (!profile.specificityByProperty.size) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const [property, specificity] of profile.specificityByProperty) {
|
|
211
|
+
addEntry(property, position, specificity);
|
|
212
|
+
}
|
|
213
|
+
addEntry(ANY_PROPERTY, position, profile.strongestSpecificity);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Folds a rule's writes into the rule that just absorbed its declarations, so
|
|
218
|
+
* the grown rule is treated as writing to both sets of properties.
|
|
219
|
+
*
|
|
220
|
+
* @param {object} rule The rule that grew.
|
|
221
|
+
* @param {object} absorbedRule The rule whose declarations were appended to it.
|
|
222
|
+
*/
|
|
223
|
+
function absorbRule (rule, absorbedRule) {
|
|
224
|
+
absorbWriteProfile(getWriteProfile(rule), getWriteProfile(absorbedRule));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether moving a rule's declarations past everything recorded after a
|
|
229
|
+
* position would change which declaration wins the cascade. Only a rule that
|
|
230
|
+
* is strictly less specific is guaranteed to lose either way, so anything of
|
|
231
|
+
* equal or greater specificity that writes to the same properties blocks the
|
|
232
|
+
* move.
|
|
233
|
+
*
|
|
234
|
+
* @param {object} movingRule The rule whose declarations would relocate.
|
|
235
|
+
* @param {number} afterPosition The position the declarations would move across.
|
|
236
|
+
* @return {boolean} True when the relocation is unsafe.
|
|
237
|
+
*/
|
|
238
|
+
function blocksRelocation (movingRule, afterPosition) {
|
|
239
|
+
const profile = getWriteProfile(movingRule);
|
|
240
|
+
const specificity = profile.weakestSpecificity;
|
|
241
|
+
for (const property of profile.specificityByProperty.keys()) {
|
|
242
|
+
if (hasStrongerEntryAfter(property, afterPosition, specificity)) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (profile.writesStandardProperty && hasStrongerEntryAfter(RESET_ALL_PROPERTY, afterPosition, specificity)) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
return (
|
|
250
|
+
profile.specificityByProperty.has(RESET_ALL_PROPERTY) &&
|
|
251
|
+
hasStrongerEntryAfter(ANY_PROPERTY, afterPosition, specificity)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
absorbRule,
|
|
257
|
+
blocksRelocation,
|
|
258
|
+
recordRule
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export {
|
|
263
|
+
createOverrideIndex
|
|
264
|
+
};
|