postcss-merge-idents 9.0.5 → 9.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/src/index.js +236 -171
- package/src/lib/grammar.js +33 -56
- package/src/lib/protectedNames.js +177 -0
- package/src/lib/trailingEscape.js +75 -0
- package/src/lib/valueRewriter.js +143 -162
- package/src/lib/valueWalk.js +63 -0
- package/types/index.d.ts +6 -8
- package/types/index.d.ts.map +1 -1
- package/types/lib/grammar.d.ts +12 -11
- package/types/lib/grammar.d.ts.map +1 -1
- package/types/lib/protectedNames.d.ts +41 -0
- package/types/lib/protectedNames.d.ts.map +1 -0
- package/types/lib/trailingEscape.d.ts +13 -0
- package/types/lib/trailingEscape.d.ts.map +1 -0
- package/types/lib/valueRewriter.d.ts +42 -11
- package/types/lib/valueRewriter.d.ts.map +1 -1
- package/types/lib/valueWalk.d.ts +26 -0
- package/types/lib/valueWalk.d.ts.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-merge-idents",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.7",
|
|
4
4
|
"description": "Merge keyframe and counter style identifiers.",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@csstools/css-tokenizer": "^4.0.2",
|
|
38
|
-
"cssnano-utils": "^8.
|
|
38
|
+
"cssnano-utils": "^8.2.1"
|
|
39
39
|
},
|
|
40
40
|
"bugs": {
|
|
41
41
|
"url": "https://github.com/cssnano/cssnano/issues"
|
|
@@ -44,15 +44,16 @@
|
|
|
44
44
|
"node": "^22.22.3 || ^24.15.0 || >=26.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@webref/css": "8.7.
|
|
48
|
-
"postcss": "^8.5.
|
|
47
|
+
"@webref/css": "8.7.6",
|
|
48
|
+
"postcss": "^8.5.29"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"postcss": "^8.5.
|
|
51
|
+
"postcss": "^8.5.29"
|
|
52
52
|
},
|
|
53
53
|
"type": "module",
|
|
54
54
|
"scripts": {
|
|
55
55
|
"acquire": "node ./script/acquire.js",
|
|
56
|
+
"fuzz": "node ./script/fuzz.js",
|
|
56
57
|
"test": "node --test"
|
|
57
58
|
}
|
|
58
59
|
}
|
package/src/index.js
CHANGED
|
@@ -1,178 +1,192 @@
|
|
|
1
1
|
import cssnanoUtils from 'cssnano-utils';
|
|
2
2
|
import { LayerRegistry, compareAtRulePriority } from './lib/layerRegistry.js';
|
|
3
3
|
import {
|
|
4
|
-
classifyDeclaration,
|
|
5
4
|
getBody,
|
|
6
|
-
|
|
5
|
+
getConditionContainer,
|
|
7
6
|
parseAtRuleName,
|
|
7
|
+
VENDOR_PREFIX,
|
|
8
8
|
} from './lib/grammar.js';
|
|
9
|
-
import {
|
|
9
|
+
import { scanDeclarations } from './lib/protectedNames.js';
|
|
10
|
+
import { createReplacement, rewriteDeclaration } from './lib/valueRewriter.js';
|
|
10
11
|
|
|
11
12
|
const { asciiLowerCase } = cssnanoUtils;
|
|
12
13
|
|
|
13
14
|
/**
|
|
15
|
+
* @typedef {Array<{ name: string, copies: AtRuleEntry[] }>} Group names that
|
|
16
|
+
* are defined in the same at-rules and containers with the same body
|
|
17
|
+
*
|
|
14
18
|
* @typedef {{
|
|
15
19
|
* rule: import('postcss').AtRule,
|
|
16
20
|
* body?: string,
|
|
17
21
|
* layerPriority: number[],
|
|
18
22
|
* documentIndex: number,
|
|
23
|
+
* family: string,
|
|
24
|
+
* containerId: number,
|
|
19
25
|
* parsed: { isString: boolean, isReservedName: boolean, key: string, tokenText: string }
|
|
20
26
|
* }} AtRuleEntry
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {AtRuleEntry[]} entries
|
|
31
|
+
* @return {AtRuleEntry}
|
|
32
|
+
*/
|
|
33
|
+
function highestPriority(entries) {
|
|
34
|
+
let best = entries[0];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (compareAtRulePriority(best, entry) < 0) {
|
|
37
|
+
best = entry;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return best;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every name of a group behaves alike, so the cascade priority does not
|
|
45
|
+
* matter for correctness; the shortest spelling minimizes output size and
|
|
46
|
+
* priority breaks ties.
|
|
21
47
|
*
|
|
22
|
-
* @
|
|
23
|
-
*
|
|
24
|
-
* resolver?: (key: string) => { text: string, key: string } | undefined,
|
|
25
|
-
* definedNames?: Set<string>
|
|
26
|
-
* }} FamilyRecord
|
|
48
|
+
* @param {AtRuleEntry[]} entries
|
|
49
|
+
* @return {AtRuleEntry}
|
|
27
50
|
*/
|
|
51
|
+
function shortestSpelling(entries) {
|
|
52
|
+
const shortest = Math.min(
|
|
53
|
+
...entries.map(({ parsed }) => parsed.tokenText.length)
|
|
54
|
+
);
|
|
55
|
+
return highestPriority(
|
|
56
|
+
entries.filter(({ parsed }) => parsed.tokenText.length === shortest)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
28
59
|
|
|
29
60
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* stylesheet that spells one is not free to take another spelling.
|
|
61
|
+
* Removes repeat definitions of one name that share a body and a condition
|
|
62
|
+
* container, keeping the one that wins the cascade: the kept definition is
|
|
63
|
+
* present whenever a removed one is.
|
|
34
64
|
*
|
|
35
|
-
* @param {
|
|
65
|
+
* @param {AtRuleEntry[]} copies definitions of one name
|
|
36
66
|
* @param {Set<import('postcss').AtRule>} removals
|
|
37
|
-
* @return {AtRuleEntry[]}
|
|
67
|
+
* @return {AtRuleEntry[]} the definitions that remain
|
|
38
68
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for (const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
eligible.push(list[0]);
|
|
47
|
-
}
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const firstBody = getBody(list[0]);
|
|
51
|
-
if (!list.every((item) => getBody(item) === firstBody)) {
|
|
69
|
+
function collapseSameNameCopies(copies, removals) {
|
|
70
|
+
if (copies.length < 2) {
|
|
71
|
+
return copies;
|
|
72
|
+
}
|
|
73
|
+
for (const group of Map.groupBy(copies, placementOf).values()) {
|
|
74
|
+
const body = getBody(group[0]);
|
|
75
|
+
if (group.length < 2 || !group.every((copy) => getBody(copy) === body)) {
|
|
52
76
|
continue;
|
|
53
77
|
}
|
|
54
|
-
|
|
55
|
-
for (
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
for (const item of list) {
|
|
61
|
-
if (item !== survivor) {
|
|
62
|
-
removals.add(item.rule);
|
|
78
|
+
const survivor = highestPriority(group);
|
|
79
|
+
for (const copy of group) {
|
|
80
|
+
if (copy !== survivor) {
|
|
81
|
+
removals.add(copy.rule);
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
|
-
if (!survivor.parsed.isReservedName) {
|
|
66
|
-
eligible.push(survivor);
|
|
67
|
-
}
|
|
68
84
|
}
|
|
85
|
+
return copies.filter((copy) => !removals.has(copy.rule));
|
|
86
|
+
}
|
|
69
87
|
|
|
70
|
-
|
|
88
|
+
/**
|
|
89
|
+
* @param {AtRuleEntry} entry
|
|
90
|
+
* @return {string} the at-rule family and condition container defining it
|
|
91
|
+
*/
|
|
92
|
+
function placementOf(entry) {
|
|
93
|
+
return `${entry.family}@${entry.containerId}`;
|
|
71
94
|
}
|
|
72
95
|
|
|
73
96
|
/**
|
|
74
|
-
*
|
|
97
|
+
* Makes each name of a group of interchangeable names, except the one that
|
|
98
|
+
* wins the cascade, refer to the winner. A name that substituted text may
|
|
99
|
+
* spell keeps its definition and is preferred as the merge target.
|
|
100
|
+
*
|
|
101
|
+
* @param {Group} group
|
|
102
|
+
* @param {Set<string>} protectedNames
|
|
103
|
+
* @param {Map<string, import('./lib/valueRewriter.js').Replacement>} renames
|
|
75
104
|
* @param {Set<import('postcss').AtRule>} removals
|
|
76
|
-
* @return {
|
|
105
|
+
* @return {void}
|
|
77
106
|
*/
|
|
78
|
-
function
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
function mergeInterchangeable(group, protectedNames, renames, removals) {
|
|
108
|
+
const protectedMembers = group.filter(({ name }) => protectedNames.has(name));
|
|
109
|
+
const targetCandidates = (
|
|
110
|
+
protectedMembers.length > 0 ? protectedMembers : group
|
|
111
|
+
).flatMap(({ copies }) => copies);
|
|
112
|
+
// An identifier reference is shorter than a string and more widely
|
|
113
|
+
// supported as a keyframes name.
|
|
114
|
+
const identifiers = targetCandidates.filter(({ parsed }) => !parsed.isString);
|
|
115
|
+
const target = shortestSpelling(
|
|
116
|
+
identifiers.length > 0 ? identifiers : targetCandidates
|
|
117
|
+
);
|
|
118
|
+
const replacement = createReplacement(target.parsed);
|
|
119
|
+
for (const { name, copies } of group) {
|
|
120
|
+
if (name === target.parsed.key || protectedNames.has(name)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
renames.set(name, replacement);
|
|
124
|
+
for (const copy of copies) {
|
|
125
|
+
removals.add(copy.rule);
|
|
126
|
+
}
|
|
90
127
|
}
|
|
128
|
+
}
|
|
91
129
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Finds the groups of interchangeable names of one namespace and removes
|
|
132
|
+
* repeat definitions of a single name. Two names are interchangeable when
|
|
133
|
+
* their definitions, in cascade order, sit in the same at-rule families and
|
|
134
|
+
* condition containers with the same bodies: whichever definition a browser
|
|
135
|
+
* picks for one name, it picks the same body for the other. This lets
|
|
136
|
+
* `@-webkit-keyframes` and `@keyframes` pairs merge although their bodies
|
|
137
|
+
* differ. A string and an ident of one value are one name. Bodies are
|
|
138
|
+
* serialized only for names that share where they are defined.
|
|
139
|
+
*
|
|
140
|
+
* @param {AtRuleEntry[]} entries
|
|
141
|
+
* @param {Set<import('postcss').AtRule>} removals
|
|
142
|
+
* @return {Group[]} groups of two or more names
|
|
143
|
+
*/
|
|
144
|
+
function findInterchangeableGroups(entries, removals) {
|
|
145
|
+
if (entries.length < 2) {
|
|
146
|
+
return [];
|
|
98
147
|
}
|
|
99
148
|
|
|
100
|
-
/** @type {
|
|
101
|
-
const
|
|
102
|
-
for (const
|
|
103
|
-
|
|
104
|
-
if (!
|
|
105
|
-
|
|
106
|
-
byName.set(entry.parsed.key, nameList);
|
|
149
|
+
/** @type {Group} */
|
|
150
|
+
const candidates = [];
|
|
151
|
+
for (const [name, all] of Map.groupBy(entries, ({ parsed }) => parsed.key)) {
|
|
152
|
+
const copies = collapseSameNameCopies(all, removals);
|
|
153
|
+
if (!copies.some(({ parsed }) => parsed.isReservedName)) {
|
|
154
|
+
candidates.push({ name, copies: copies.toSorted(compareAtRulePriority) });
|
|
107
155
|
}
|
|
108
|
-
nameList.push(entry);
|
|
109
156
|
}
|
|
110
157
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for (const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!group) {
|
|
120
|
-
group = [];
|
|
121
|
-
groupsByBody.set(groupKey, group);
|
|
158
|
+
/** @type {Group[]} */
|
|
159
|
+
const groups = [];
|
|
160
|
+
const bySites = Map.groupBy(candidates, ({ copies }) =>
|
|
161
|
+
copies.map(placementOf).join(' ')
|
|
162
|
+
);
|
|
163
|
+
for (const sameSites of bySites.values()) {
|
|
164
|
+
if (sameSites.length < 2) {
|
|
165
|
+
continue;
|
|
122
166
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
for (const group of groupsByBody.values()) {
|
|
130
|
-
if (group.length > 1) {
|
|
131
|
-
let survivor = group[0];
|
|
132
|
-
for (let i = 1; i < group.length; i++) {
|
|
133
|
-
if (compareAtRulePriority(survivor, group[i]) < 0) {
|
|
134
|
-
survivor = group[i];
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
for (const item of group) {
|
|
138
|
-
if (item !== survivor) {
|
|
139
|
-
removals.add(item.rule);
|
|
140
|
-
resolved.set(item.parsed.key, {
|
|
141
|
-
text: survivor.parsed.tokenText,
|
|
142
|
-
key: survivor.parsed.key,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
167
|
+
const byBodies = Map.groupBy(sameSites, ({ copies }) =>
|
|
168
|
+
JSON.stringify(copies.map(getBody))
|
|
169
|
+
);
|
|
170
|
+
for (const sameBodies of byBodies.values()) {
|
|
171
|
+
if (sameBodies.length > 1) {
|
|
172
|
+
groups.push(sameBodies);
|
|
145
173
|
}
|
|
146
174
|
}
|
|
147
175
|
}
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
resolver: (key) => resolved.get(key),
|
|
151
|
-
definedNames,
|
|
152
|
-
hasReplacements: resolved.size > 0,
|
|
153
|
-
};
|
|
176
|
+
return groups;
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* @param {Map<import('postcss').Container, Map<string, FamilyRecord>>} scopes
|
|
160
|
-
* @param {import('postcss').Container} container
|
|
161
|
-
* @param {string} name
|
|
162
|
-
* @return {FamilyRecord}
|
|
180
|
+
* @param {string} family lowercase at-rule name
|
|
181
|
+
* @return {'keyframes' | 'counter-style' | undefined}
|
|
163
182
|
*/
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!familyData) {
|
|
172
|
-
familyData = { entries: [] };
|
|
173
|
-
containerScope.set(name, familyData);
|
|
174
|
-
}
|
|
175
|
-
return familyData;
|
|
183
|
+
function namespaceOf(family) {
|
|
184
|
+
// Only the prefixes whose animation properties the rewriter recognizes
|
|
185
|
+
// qualify; a reference to any other family would be left dangling.
|
|
186
|
+
const unprefixed = family.replace(VENDOR_PREFIX, '');
|
|
187
|
+
return unprefixed === 'keyframes' || unprefixed === 'counter-style'
|
|
188
|
+
? unprefixed
|
|
189
|
+
: undefined;
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
/**
|
|
@@ -181,79 +195,116 @@ function familyRecordFor(scopes, container, name) {
|
|
|
181
195
|
*/
|
|
182
196
|
function mergeAtRules(css) {
|
|
183
197
|
const layerRegistry = new LayerRegistry();
|
|
184
|
-
|
|
185
|
-
|
|
198
|
+
/** @type {Map<'keyframes' | 'counter-style', AtRuleEntry[]>} */
|
|
199
|
+
const namespaces = new Map([
|
|
200
|
+
['keyframes', []],
|
|
201
|
+
['counter-style', []],
|
|
202
|
+
]);
|
|
203
|
+
/** @type {Map<import('postcss').Container, number>} */
|
|
204
|
+
const containerIds = new Map();
|
|
205
|
+
/** @type {import('postcss').AtRule[]} */
|
|
206
|
+
const functionRules = [];
|
|
207
|
+
/** @type {import('postcss').AtRule[]} */
|
|
208
|
+
const parameterRules = [];
|
|
209
|
+
/** @type {import('postcss').AtRule[]} */
|
|
210
|
+
const conditionRules = [];
|
|
186
211
|
let documentIndex = 0;
|
|
187
212
|
|
|
188
|
-
/** @type {Map<import('postcss').Container, Map<string, FamilyRecord>>} */
|
|
189
|
-
const scopes = new Map();
|
|
190
|
-
|
|
191
213
|
css.walkAtRules((atRule) => {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
214
|
+
const family = asciiLowerCase(atRule.name);
|
|
215
|
+
if (family === 'layer') {
|
|
194
216
|
layerRegistry.declareLayerAtRule(atRule);
|
|
195
217
|
return;
|
|
196
218
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
if (family === 'function') {
|
|
220
|
+
functionRules.push(atRule);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// Mixin parameter defaults and @apply arguments are substituted text.
|
|
224
|
+
if (family === 'mixin' || family === 'apply') {
|
|
225
|
+
parameterRules.push(atRule);
|
|
201
226
|
return;
|
|
202
227
|
}
|
|
203
228
|
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
counterStyleCount++;
|
|
229
|
+
if (family === 'container' || family === 'when' || family === 'else') {
|
|
230
|
+
conditionRules.push(atRule);
|
|
231
|
+
return;
|
|
208
232
|
}
|
|
209
233
|
|
|
210
|
-
const
|
|
211
|
-
const
|
|
234
|
+
const namespace = namespaceOf(family);
|
|
235
|
+
const params = atRule.raws?.between
|
|
236
|
+
? atRule.params + atRule.raws.between
|
|
237
|
+
: atRule.params;
|
|
238
|
+
const parsed = namespace && parseAtRuleName(params, family);
|
|
239
|
+
// A statement without a block defines nothing.
|
|
240
|
+
if (!namespace || !atRule.nodes || !parsed) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const entries = /** @type {AtRuleEntry[]} */ (namespaces.get(namespace));
|
|
212
244
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
layerPriority: layerRegistry.getPriority(atRule),
|
|
219
|
-
documentIndex: documentIndex++,
|
|
220
|
-
});
|
|
245
|
+
const container = getConditionContainer(atRule);
|
|
246
|
+
let containerId = containerIds.get(container);
|
|
247
|
+
if (containerId === undefined) {
|
|
248
|
+
containerId = containerIds.size;
|
|
249
|
+
containerIds.set(container, containerId);
|
|
221
250
|
}
|
|
251
|
+
entries.push({
|
|
252
|
+
rule: atRule,
|
|
253
|
+
parsed,
|
|
254
|
+
family,
|
|
255
|
+
containerId,
|
|
256
|
+
layerPriority: layerRegistry.getPriority(atRule),
|
|
257
|
+
documentIndex: documentIndex++,
|
|
258
|
+
});
|
|
222
259
|
});
|
|
223
260
|
|
|
224
|
-
if (
|
|
261
|
+
if ([...namespaces.values()].every((entries) => entries.length < 2)) {
|
|
225
262
|
return;
|
|
226
263
|
}
|
|
227
264
|
|
|
228
265
|
/** @type {Set<import('postcss').AtRule>} */
|
|
229
266
|
const removals = new Set();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
267
|
+
const groupsByNamespace = new Map(
|
|
268
|
+
[...namespaces].map(([namespace, entries]) => [
|
|
269
|
+
namespace,
|
|
270
|
+
findInterchangeableGroups(entries, removals),
|
|
271
|
+
])
|
|
272
|
+
);
|
|
273
|
+
const groupedNames = new Set(
|
|
274
|
+
[...groupsByNamespace.values()].flatMap((groups) =>
|
|
275
|
+
groups.flatMap((group) => group.map(({ name }) => name))
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
// The protection scan is needed only to merge names, never to remove
|
|
279
|
+
// repeat definitions of one name.
|
|
280
|
+
const { protectedNames, references } =
|
|
281
|
+
groupedNames.size > 0
|
|
282
|
+
? scanDeclarations(
|
|
283
|
+
css,
|
|
284
|
+
groupedNames,
|
|
285
|
+
functionRules,
|
|
286
|
+
parameterRules,
|
|
287
|
+
conditionRules
|
|
288
|
+
)
|
|
289
|
+
: { protectedNames: new Set(), references: [] };
|
|
290
|
+
/** @type {Map<string, Map<string, import('./lib/valueRewriter.js').Replacement>>} */
|
|
291
|
+
const renames = new Map();
|
|
292
|
+
for (const [namespace, groups] of groupsByNamespace) {
|
|
293
|
+
/** @type {Map<string, import('./lib/valueRewriter.js').Replacement>} */
|
|
294
|
+
const namespaceRenames = new Map();
|
|
295
|
+
for (const group of groups) {
|
|
296
|
+
mergeInterchangeable(group, protectedNames, namespaceRenames, removals);
|
|
240
297
|
}
|
|
298
|
+
renames.set(namespace, namespaceRenames);
|
|
241
299
|
}
|
|
242
300
|
|
|
243
301
|
if (removals.size === 0) {
|
|
244
302
|
return;
|
|
245
303
|
}
|
|
246
304
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
css.walkDecls((decl) => {
|
|
252
|
-
const classification = classifyDeclaration(decl);
|
|
253
|
-
if (classification) {
|
|
254
|
-
rewriteDeclaration(decl, classification, scopes, singleScope);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
305
|
+
// Removing a repeat definition of one name leaves no reference to respell.
|
|
306
|
+
if ([...renames.values()].some((namespaceRenames) => namespaceRenames.size)) {
|
|
307
|
+
rewriteReferences(references, renames);
|
|
257
308
|
}
|
|
258
309
|
|
|
259
310
|
for (const node of removals) {
|
|
@@ -261,6 +312,20 @@ function mergeAtRules(css) {
|
|
|
261
312
|
}
|
|
262
313
|
}
|
|
263
314
|
|
|
315
|
+
/**
|
|
316
|
+
* @param {import('./lib/protectedNames.js').Reference[]} references
|
|
317
|
+
* @param {Map<string, Map<string, import('./lib/valueRewriter.js').Replacement>>} renames
|
|
318
|
+
* @return {void}
|
|
319
|
+
*/
|
|
320
|
+
function rewriteReferences(references, renames) {
|
|
321
|
+
for (const { decl, classification, value } of references) {
|
|
322
|
+
const namespaceRenames = renames.get(classification.namespace);
|
|
323
|
+
if (namespaceRenames?.size) {
|
|
324
|
+
rewriteDeclaration(decl, classification.kind, namespaceRenames, value);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
264
329
|
/**
|
|
265
330
|
* @return {import('postcss').Plugin}
|
|
266
331
|
*/
|