postcss-merge-rules 5.0.4 → 5.1.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/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "postcss-merge-rules",
3
- "version": "5.0.4",
3
+ "version": "5.1.0",
4
4
  "description": "Merge CSS rules with PostCSS.",
5
- "main": "dist/index.js",
5
+ "types": "types/index.d.ts",
6
+ "main": "src/index.js",
6
7
  "files": [
7
8
  "LICENSE-MIT",
8
- "dist"
9
+ "src",
10
+ "types"
9
11
  ],
10
12
  "keywords": [
11
13
  "css",
@@ -24,7 +26,7 @@
24
26
  "dependencies": {
25
27
  "browserslist": "^4.16.6",
26
28
  "caniuse-api": "^3.0.0",
27
- "cssnano-utils": "^3.0.0",
29
+ "cssnano-utils": "^3.1.0",
28
30
  "postcss-selector-parser": "^6.0.5"
29
31
  },
30
32
  "bugs": {
@@ -34,15 +36,12 @@
34
36
  "node": "^10 || ^12 || >=14.0"
35
37
  },
36
38
  "devDependencies": {
39
+ "@types/caniuse-api": "^3.0.2",
37
40
  "postcss": "^8.2.15",
38
- "postcss-discard-comments": "^5.0.1"
41
+ "postcss-discard-comments": "^5.1.0"
39
42
  },
40
43
  "peerDependencies": {
41
44
  "postcss": "^8.2.15"
42
45
  },
43
- "scripts": {
44
- "prebuild": "rimraf dist",
45
- "build": "babel src --config-file ../../babel.config.json --out-dir dist --ignore \"**/__tests__/\""
46
- },
47
46
  "readme": "# [postcss][postcss]-merge-rules\n\n> Merge CSS rules with PostCSS.\n\n## Install\n\nWith [npm](https://npmjs.org/package/postcss-merge-rules) do:\n\n```\nnpm install postcss-merge-rules --save\n```\n\n## Examples\n\nThis module will attempt to merge *adjacent* CSS rules:\n\n### By declarations\n\n#### Input\n\n```css\na {\n color: blue;\n font-weight: bold\n}\n\np {\n color: blue;\n font-weight: bold\n}\n```\n\n#### Output\n\n```css\na,p {\n color: blue;\n font-weight: bold\n}\n```\n\n### By selectors\n\n#### Input\n\n```css\na {\n color: blue\n}\n\na {\n font-weight: bold\n}\n```\n\n#### Output\n\n```css\na {\n color: blue;\n font-weight: bold\n}\n```\n\n### By partial declarations\n\n#### Input\n\n```css\na {\n font-weight: bold\n}\n\np {\n color: blue;\n font-weight: bold\n}\n```\n\n#### Output\n\n```css\na,p {\n font-weight: bold\n}\n\np {\n color: blue\n}\n```\n\n## Usage\n\nSee the [PostCSS documentation](https://github.com/postcss/postcss#usage) for\nexamples for your environment.\n\n## Contributors\n\nSee [CONTRIBUTORS.md](https://github.com/cssnano/cssnano/blob/master/CONTRIBUTORS.md).\n\n## License\n\nMIT © [Ben Briggs](http://beneb.info)\n\n[postcss]: https://github.com/postcss/postcss\n"
48
47
  }
@@ -1,226 +1,214 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _browserslist = _interopRequireDefault(require("browserslist"));
9
-
10
- var _cssnanoUtils = require("cssnano-utils");
11
-
12
- var _ensureCompatibility = require("./lib/ensureCompatibility");
13
-
14
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1
+ 'use strict';
2
+ const browserslist = require('browserslist');
3
+ const { sameParent } = require('cssnano-utils');
4
+ const {
5
+ ensureCompatibility,
6
+ sameVendor,
7
+ noVendor,
8
+ } = require('./lib/ensureCompatibility');
15
9
 
16
10
  /**
17
- * @param {postcss.Declaration} a
18
- * @param {postcss.Declaration} b
11
+ * @param {import('postcss').Declaration} a
12
+ * @param {import('postcss').Declaration} b
19
13
  * @return {boolean}
20
14
  */
21
15
  function declarationIsEqual(a, b) {
22
- return a.important === b.important && a.prop === b.prop && a.value === b.value;
16
+ return (
17
+ a.important === b.important && a.prop === b.prop && a.value === b.value
18
+ );
23
19
  }
20
+
24
21
  /**
25
- * @param {postcss.Declaration[]} array
26
- * @param {postcss.Declaration} decl
22
+ * @param {import('postcss').Declaration[]} array
23
+ * @param {import('postcss').Declaration} decl
27
24
  * @return {number}
28
25
  */
29
-
30
-
31
26
  function indexOfDeclaration(array, decl) {
32
- return array.findIndex(d => declarationIsEqual(d, decl));
27
+ return array.findIndex((d) => declarationIsEqual(d, decl));
33
28
  }
29
+
34
30
  /**
35
31
  * Returns filtered array of matched or unmatched declarations
36
- * @param {postcss.Declaration[]} a
37
- * @param {postcss.Declaration[]} b
32
+ * @param {import('postcss').Declaration[]} a
33
+ * @param {import('postcss').Declaration[]} b
38
34
  * @param {boolean} [not=false]
39
- * @return {postcss.Declaration[]}
35
+ * @return {import('postcss').Declaration[]}
40
36
  */
41
-
42
-
43
37
  function intersect(a, b, not) {
44
- return a.filter(c => {
38
+ return a.filter((c) => {
45
39
  const index = ~indexOfDeclaration(b, c);
46
40
  return not ? !index : index;
47
41
  });
48
42
  }
43
+
49
44
  /**
50
- * @param {postcss.Declaration[]} a
51
- * @param {postcss.Declaration[]} b
45
+ * @param {import('postcss').Declaration[]} a
46
+ * @param {import('postcss').Declaration[]} b
52
47
  * @return {boolean}
53
48
  */
54
-
55
-
56
49
  function sameDeclarationsAndOrder(a, b) {
57
50
  if (a.length !== b.length) {
58
51
  return false;
59
52
  }
60
-
61
53
  return a.every((d, index) => declarationIsEqual(d, b[index]));
62
54
  }
55
+
63
56
  /**
64
- * @param {postcss.Rule} ruleA
65
- * @param {postcss.Rule} ruleB
57
+ * @param {import('postcss').Rule} ruleA
58
+ * @param {import('postcss').Rule} ruleB
66
59
  * @param {string[]=} browsers
67
60
  * @param {Map<string, boolean>=} compatibilityCache
68
61
  * @return {boolean}
69
62
  */
70
-
71
-
72
63
  function canMerge(ruleA, ruleB, browsers, compatibilityCache) {
73
64
  const a = ruleA.selectors;
74
65
  const b = ruleB.selectors;
66
+
75
67
  const selectors = a.concat(b);
76
68
 
77
- if (!(0, _ensureCompatibility.ensureCompatibility)(selectors, browsers, compatibilityCache)) {
69
+ if (!ensureCompatibility(selectors, browsers, compatibilityCache)) {
78
70
  return false;
79
71
  }
80
72
 
81
- const parent = (0, _cssnanoUtils.sameParent)(ruleA, ruleB);
82
- const {
83
- name
84
- } = ruleA.parent;
85
-
86
- if (parent && name && ~name.indexOf('keyframes')) {
73
+ const parent = sameParent(
74
+ /** @type {any} */ (ruleA),
75
+ /** @type {any} */ (ruleB)
76
+ );
77
+ const { name } = /** @type {any} */ (ruleA).parent;
78
+ if (parent && name && name.includes('keyframes')) {
87
79
  return false;
88
80
  }
89
-
90
- return parent && (selectors.every(_ensureCompatibility.noVendor) || (0, _ensureCompatibility.sameVendor)(a, b));
81
+ return parent && (selectors.every(noVendor) || sameVendor(a, b));
91
82
  }
83
+
92
84
  /**
93
- * @param {postcss.Rule} rule
94
- * @return {postcss.Declaration[]}
85
+ * @param {import('postcss').Rule} rule
86
+ * @return {import('postcss').Declaration[]}
95
87
  */
96
-
97
-
98
88
  function getDecls(rule) {
99
- return rule.nodes.filter(node => node.type === 'decl');
89
+ return /** @type {import('postcss').Declaration[]} */ (
90
+ rule.nodes.filter((node) => node.type === 'decl')
91
+ );
100
92
  }
101
93
 
102
- const joinSelectors = (...rules) => rules.map(s => s.selector).join();
94
+ /** @type {(...rules: import('postcss').Rule[]) => string} */
95
+ const joinSelectors = (...rules) => rules.map((s) => s.selector).join();
103
96
 
97
+ /**
98
+ * @param {...import('postcss').Rule} rules
99
+ * @return {number}
100
+ */
104
101
  function ruleLength(...rules) {
105
- return rules.map(r => r.nodes.length ? String(r) : '').join('').length;
102
+ return rules.map((r) => (r.nodes.length ? String(r) : '')).join('').length;
106
103
  }
104
+
107
105
  /**
108
106
  * @param {string} prop
109
- * @return {{prefix: string, base:string, rest:string[]}}
107
+ * @return {{prefix: string?, base:string?, rest:string[]}}
110
108
  */
111
-
112
-
113
109
  function splitProp(prop) {
114
110
  // Treat vendor prefixed properties as if they were unprefixed;
115
111
  // moving them when combined with non-prefixed properties can
116
112
  // cause issues. e.g. moving -webkit-background-clip when there
117
113
  // is a background shorthand definition.
118
- const parts = prop.split('-');
119
114
 
115
+ const parts = prop.split('-');
120
116
  if (prop[0] !== '-') {
121
117
  return {
122
118
  prefix: '',
123
119
  base: parts[0],
124
- rest: parts.slice(1)
120
+ rest: parts.slice(1),
125
121
  };
126
- } // Don't split css variables
127
-
128
-
122
+ }
123
+ // Don't split css variables
129
124
  if (prop[1] === '-') {
130
125
  return {
131
126
  prefix: null,
132
127
  base: null,
133
- rest: [prop]
128
+ rest: [prop],
134
129
  };
135
- } // Found prefix
136
-
137
-
130
+ }
131
+ // Found prefix
138
132
  return {
139
133
  prefix: parts[1],
140
134
  base: parts[2],
141
- rest: parts.slice(3)
135
+ rest: parts.slice(3),
142
136
  };
143
137
  }
138
+
144
139
  /**
145
140
  * @param {string} propA
146
141
  * @param {string} propB
147
142
  */
148
-
149
-
150
143
  function isConflictingProp(propA, propB) {
151
144
  if (propA === propB) {
152
145
  // Same specificity
153
146
  return true;
154
147
  }
155
-
156
148
  const a = splitProp(propA);
157
- const b = splitProp(propB); // Don't resort css variables
158
-
149
+ const b = splitProp(propB);
150
+ // Don't resort css variables
159
151
  if (!a.base && !b.base) {
160
152
  return true;
161
- } // Different base;
162
-
163
-
153
+ }
154
+ // Different base;
164
155
  if (a.base !== b.base) {
165
156
  return false;
166
- } // Conflict if rest-count mismatches
167
-
168
-
157
+ }
158
+ // Conflict if rest-count mismatches
169
159
  if (a.rest.length !== b.rest.length) {
170
160
  return true;
171
- } // Conflict if rest parameters are equal (same but unprefixed)
172
-
161
+ }
173
162
 
163
+ // Conflict if rest parameters are equal (same but unprefixed)
174
164
  return a.rest.every((s, index) => b.rest[index] === s);
175
165
  }
166
+
176
167
  /**
177
- * @param {postcss.Rule} first
178
- * @param {postcss.Rule} second
168
+ * @param {import('postcss').Rule} first
169
+ * @param {import('postcss').Rule} second
179
170
  * @return {boolean} merged
180
171
  */
181
-
182
-
183
172
  function mergeParents(first, second) {
184
173
  // Null check for detached rules
185
174
  if (!first.parent || !second.parent) {
186
175
  return false;
187
- } // Check if parents share node
188
-
176
+ }
189
177
 
178
+ // Check if parents share node
190
179
  if (first.parent === second.parent) {
191
180
  return false;
192
- } // sameParent() already called by canMerge()
181
+ }
193
182
 
183
+ // sameParent() already called by canMerge()
194
184
 
195
185
  second.remove();
196
186
  first.parent.append(second);
197
187
  return true;
198
188
  }
189
+
199
190
  /**
200
- * @param {postcss.Rule} first
201
- * @param {postcss.Rule} second
202
- * @return {postcss.Rule} mergedRule
191
+ * @param {import('postcss').Rule} first
192
+ * @param {import('postcss').Rule} second
193
+ * @return {import('postcss').Rule} mergedRule
203
194
  */
204
-
205
-
206
195
  function partialMerge(first, second) {
207
196
  let intersection = intersect(getDecls(first), getDecls(second));
208
-
209
197
  if (!intersection.length) {
210
198
  return second;
211
199
  }
212
-
213
200
  let nextRule = second.next();
214
-
215
201
  if (!nextRule) {
216
202
  // Grab next cousin
217
- const parentSibling = second.parent.next();
203
+ /** @type {any} */
204
+ const parentSibling =
205
+ /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
206
+ second.parent
207
+ ).next();
218
208
  nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
219
209
  }
220
-
221
210
  if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) {
222
211
  let nextIntersection = intersect(getDecls(second), getDecls(nextRule));
223
-
224
212
  if (nextIntersection.length > intersection.length) {
225
213
  mergeParents(second, nextRule);
226
214
  first = second;
@@ -229,45 +217,52 @@ function partialMerge(first, second) {
229
217
  }
230
218
  }
231
219
 
232
- const firstDecls = getDecls(first); // Filter out intersections with later conflicts in First
220
+ const firstDecls = getDecls(first);
233
221
 
222
+ // Filter out intersections with later conflicts in First
234
223
  intersection = intersection.filter((decl, intersectIndex) => {
235
224
  const indexOfDecl = indexOfDeclaration(firstDecls, decl);
236
- const nextConflictInFirst = firstDecls.slice(indexOfDecl + 1).filter(d => isConflictingProp(d.prop, decl.prop));
237
-
225
+ const nextConflictInFirst = firstDecls
226
+ .slice(indexOfDecl + 1)
227
+ .filter((d) => isConflictingProp(d.prop, decl.prop));
238
228
  if (!nextConflictInFirst.length) {
239
229
  return true;
240
230
  }
241
-
242
- const nextConflictInIntersection = intersection.slice(intersectIndex + 1).filter(d => isConflictingProp(d.prop, decl.prop));
243
-
231
+ const nextConflictInIntersection = intersection
232
+ .slice(intersectIndex + 1)
233
+ .filter((d) => isConflictingProp(d.prop, decl.prop));
244
234
  if (!nextConflictInIntersection.length) {
245
235
  return false;
246
236
  }
247
-
248
237
  if (nextConflictInFirst.length !== nextConflictInIntersection.length) {
249
238
  return false;
250
239
  }
240
+ return nextConflictInFirst.every((d, index) =>
241
+ declarationIsEqual(d, nextConflictInIntersection[index])
242
+ );
243
+ });
251
244
 
252
- return nextConflictInFirst.every((d, index) => declarationIsEqual(d, nextConflictInIntersection[index]));
253
- }); // Filter out intersections with previous conflicts in Second
254
-
245
+ // Filter out intersections with previous conflicts in Second
255
246
  const secondDecls = getDecls(second);
256
- intersection = intersection.filter(decl => {
257
- const nextConflictIndex = secondDecls.findIndex(d => isConflictingProp(d.prop, decl.prop));
258
-
247
+ intersection = intersection.filter((decl) => {
248
+ const nextConflictIndex = secondDecls.findIndex((d) =>
249
+ isConflictingProp(d.prop, decl.prop)
250
+ );
259
251
  if (nextConflictIndex === -1) {
260
252
  return false;
261
253
  }
262
-
263
254
  if (!declarationIsEqual(secondDecls[nextConflictIndex], decl)) {
264
255
  return false;
265
256
  }
266
-
267
- if (decl.prop.toLowerCase() !== 'direction' && decl.prop.toLowerCase() !== 'unicode-bidi' && secondDecls.some(declaration => declaration.prop.toLowerCase() === 'all')) {
257
+ if (
258
+ decl.prop.toLowerCase() !== 'direction' &&
259
+ decl.prop.toLowerCase() !== 'unicode-bidi' &&
260
+ secondDecls.some(
261
+ (declaration) => declaration.prop.toLowerCase() === 'all'
262
+ )
263
+ ) {
268
264
  return false;
269
265
  }
270
-
271
266
  secondDecls.splice(nextConflictIndex, 1);
272
267
  return true;
273
268
  });
@@ -280,58 +275,60 @@ function partialMerge(first, second) {
280
275
  const receivingBlock = second.clone();
281
276
  receivingBlock.selector = joinSelectors(first, second);
282
277
  receivingBlock.nodes = [];
283
- second.parent.insertBefore(second, receivingBlock);
278
+
279
+ /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
280
+ second.parent
281
+ ).insertBefore(second, receivingBlock);
282
+
284
283
  const firstClone = first.clone();
285
284
  const secondClone = second.clone();
285
+
286
286
  /**
287
- * @param {function(postcss.Declaration):void} callback
288
- * @return {function(postcss.Declaration)}
287
+ * @param {function(import('postcss').Declaration):void} callback
288
+ * @this {import('postcss').Rule}
289
+ * @return {function(import('postcss').Declaration)}
289
290
  */
290
-
291
291
  function moveDecl(callback) {
292
- return decl => {
292
+ return (decl) => {
293
293
  if (~indexOfDeclaration(intersection, decl)) {
294
294
  callback.call(this, decl);
295
295
  }
296
296
  };
297
297
  }
298
-
299
- firstClone.walkDecls(moveDecl(decl => {
300
- decl.remove();
301
- receivingBlock.append(decl);
302
- }));
303
- secondClone.walkDecls(moveDecl(decl => decl.remove()));
298
+ firstClone.walkDecls(
299
+ moveDecl((decl) => {
300
+ decl.remove();
301
+ receivingBlock.append(decl);
302
+ })
303
+ );
304
+ secondClone.walkDecls(moveDecl((decl) => decl.remove()));
304
305
  const merged = ruleLength(firstClone, receivingBlock, secondClone);
305
306
  const original = ruleLength(first, second);
306
-
307
307
  if (merged < original) {
308
308
  first.replaceWith(firstClone);
309
309
  second.replaceWith(secondClone);
310
- [firstClone, receivingBlock, secondClone].forEach(r => {
310
+ [firstClone, receivingBlock, secondClone].forEach((r) => {
311
311
  if (!r.nodes.length) {
312
312
  r.remove();
313
313
  }
314
314
  });
315
-
316
315
  if (!secondClone.parent) {
317
316
  return receivingBlock;
318
317
  }
319
-
320
318
  return secondClone;
321
319
  } else {
322
320
  receivingBlock.remove();
323
321
  return second;
324
322
  }
325
323
  }
324
+
326
325
  /**
327
326
  * @param {string[]} browsers
328
327
  * @param {Map<string, boolean>} compatibilityCache
329
- * @return {function(postcss.Rule)}
328
+ * @return {function(import('postcss').Rule)}
330
329
  */
331
-
332
-
333
330
  function selectorMerger(browsers, compatibilityCache) {
334
- /** @type {postcss.Rule} */
331
+ /** @type {import('postcss').Rule | null} */
335
332
  let cache = null;
336
333
  return function (rule) {
337
334
  // Prime the cache with the first rule, or alternately ensure that it is
@@ -339,71 +336,75 @@ function selectorMerger(browsers, compatibilityCache) {
339
336
  if (!cache || !canMerge(rule, cache, browsers, compatibilityCache)) {
340
337
  cache = rule;
341
338
  return;
342
- } // Ensure that we don't deduplicate the same rule; this is sometimes
339
+ }
340
+ // Ensure that we don't deduplicate the same rule; this is sometimes
343
341
  // caused by a partial merge
344
-
345
-
346
342
  if (cache === rule) {
347
343
  cache = rule;
348
344
  return;
349
- } // Parents merge: check if the rules have same parents, but not same parent nodes
345
+ }
350
346
 
347
+ // Parents merge: check if the rules have same parents, but not same parent nodes
348
+ mergeParents(cache, rule);
351
349
 
352
- mergeParents(cache, rule); // Merge when declarations are exactly equal
350
+ // Merge when declarations are exactly equal
353
351
  // e.g. h1 { color: red } h2 { color: red }
354
-
355
352
  if (sameDeclarationsAndOrder(getDecls(rule), getDecls(cache))) {
356
353
  rule.selector = joinSelectors(cache, rule);
357
354
  cache.remove();
358
355
  cache = rule;
359
356
  return;
360
- } // Merge when both selectors are exactly equal
357
+ }
358
+ // Merge when both selectors are exactly equal
361
359
  // e.g. a { color: blue } a { font-weight: bold }
362
-
363
-
364
360
  if (cache.selector === rule.selector) {
365
361
  const cached = getDecls(cache);
366
- rule.walk(decl => {
367
- if (~indexOfDeclaration(cached, decl)) {
368
- return decl.remove();
362
+ rule.walk((decl) => {
363
+ if (
364
+ ~indexOfDeclaration(
365
+ cached,
366
+ /** @type {import('postcss').Declaration} */ (decl)
367
+ )
368
+ ) {
369
+ decl.remove();
370
+ return;
369
371
  }
370
-
371
- cache.append(decl);
372
+ /** @type {import('postcss').Rule} */ (cache).append(decl);
372
373
  });
373
374
  rule.remove();
374
375
  return;
375
- } // Partial merge: check if the rule contains a subset of the last; if
376
+ }
377
+ // Partial merge: check if the rule contains a subset of the last; if
376
378
  // so create a joined selector with the subset, if smaller.
377
-
378
-
379
379
  cache = partialMerge(cache, rule);
380
380
  };
381
381
  }
382
-
382
+ /**
383
+ * @type {import('postcss').PluginCreator<void>}
384
+ * @return {import('postcss').Plugin}
385
+ */
383
386
  function pluginCreator() {
384
387
  return {
385
388
  postcssPlugin: 'postcss-merge-rules',
386
389
 
387
390
  prepare(result) {
391
+ /** @type {typeof result.opts & browserslist.Options} */
388
392
  const resultOpts = result.opts || {};
389
- const browsers = (0, _browserslist.default)(null, {
393
+ const browsers = browserslist(null, {
390
394
  stats: resultOpts.stats,
391
395
  path: __dirname,
392
- env: resultOpts.env
396
+ env: resultOpts.env,
393
397
  });
398
+
394
399
  const compatibilityCache = new Map();
395
400
  return {
396
401
  OnceExit(css) {
397
402
  css.walkRules(selectorMerger(browsers, compatibilityCache));
398
- }
399
-
403
+ },
400
404
  };
401
- }
402
-
405
+ },
403
406
  };
404
407
  }
405
408
 
406
409
  pluginCreator.postcss = true;
407
- var _default = pluginCreator;
408
- exports.default = _default;
409
- module.exports = exports.default;
410
+ module.exports = pluginCreator;
@@ -1,20 +1,9 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.ensureCompatibility = ensureCompatibility;
7
- exports.noVendor = noVendor;
8
- exports.pseudoElements = void 0;
9
- exports.sameVendor = sameVendor;
10
-
11
- var _caniuseApi = require("caniuse-api");
12
-
13
- var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser"));
14
-
15
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1
+ 'use strict';
2
+ const { isSupported } = require('caniuse-api');
3
+ const selectorParser = require('postcss-selector-parser');
16
4
 
17
5
  const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i;
6
+
18
7
  const cssSel2 = 'css-sel2';
19
8
  const cssSel3 = 'css-sel3';
20
9
  const cssGencontent = 'css-gencontent';
@@ -22,33 +11,50 @@ const cssFirstLetter = 'css-first-letter';
22
11
  const cssFirstLine = 'css-first-line';
23
12
  const cssInOutOfRange = 'css-in-out-of-range';
24
13
  const formValidation = 'form-validation';
25
- const vendorPrefix = /-(ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|wap|webkit|xv)-/;
14
+
15
+ const vendorPrefix =
16
+ /-(ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|wap|webkit|xv)-/;
17
+
18
+ const level2Sel = new Set(['=', '~=', '|=']);
19
+ const level3Sel = new Set(['^=', '$=', '*=']);
20
+
26
21
  /**
27
22
  * @param {string} selector
28
- * @return {string[]}
23
+ * @return {RegExpMatchArray | null}
29
24
  */
30
-
31
25
  function filterPrefixes(selector) {
32
26
  return selector.match(vendorPrefix);
33
- } // Internet Explorer use :-ms-input-placeholder.
34
- // Microsoft Edge use ::-ms-input-placeholder.
35
-
27
+ }
36
28
 
37
- const findMsInputPlaceholder = selector => ~selector.search(/-ms-input-placeholder/i);
29
+ /**
30
+ * Internet Explorer use :-ms-input-placeholder.
31
+ * Microsoft Edge use ::-ms-input-placeholder.
32
+ *
33
+ * @type {(selector: string) => number}
34
+ */
35
+ const findMsInputPlaceholder = (selector) =>
36
+ ~selector.search(/-ms-input-placeholder/i);
38
37
 
38
+ /**
39
+ * @param {string[]} selectorsA
40
+ * @param {string[]} selectorsB
41
+ * @return {boolean}
42
+ */
39
43
  function sameVendor(selectorsA, selectorsB) {
40
- let same = selectors => selectors.map(filterPrefixes).join();
41
-
42
- let findMsVendor = selectors => selectors.find(findMsInputPlaceholder);
43
-
44
- return same(selectorsA) === same(selectorsB) && !(findMsVendor(selectorsA) && findMsVendor(selectorsB));
44
+ /** @type {(selectors: string[]) => string} */
45
+ let same = (selectors) => selectors.map(filterPrefixes).join();
46
+ /** @type {(selectors: string[]) => string | undefined} */
47
+ let findMsVendor = (selectors) => selectors.find(findMsInputPlaceholder);
48
+ return (
49
+ same(selectorsA) === same(selectorsB) &&
50
+ !(findMsVendor(selectorsA) && findMsVendor(selectorsB))
51
+ );
45
52
  }
53
+
46
54
  /**
47
55
  * @param {string} selector
48
56
  * @return {boolean}
49
57
  */
50
-
51
-
52
58
  function noVendor(selector) {
53
59
  return !vendorPrefix.test(selector);
54
60
  }
@@ -104,109 +110,110 @@ const pseudoElements = {
104
110
  '::placeholder': 'css-placeholder',
105
111
  '::selection': 'css-selection',
106
112
  ':valid': formValidation,
107
- ':visited': cssSel2
113
+ ':visited': cssSel2,
108
114
  };
109
- exports.pseudoElements = pseudoElements;
110
115
 
116
+ /**
117
+ * @param {string} selector
118
+ * @return {boolean}
119
+ */
111
120
  function isCssMixin(selector) {
112
121
  return selector[selector.length - 1] === ':';
113
122
  }
114
123
 
124
+ /**
125
+ * @param {string} selector
126
+ * @return {boolean}
127
+ */
115
128
  function isHostPseudoClass(selector) {
116
129
  return selector.includes(':host');
117
130
  }
118
131
 
119
- const isSupportedCache = new Map(); // Move to util in future
132
+ const isSupportedCache = new Map();
120
133
 
134
+ // Move to util in future
135
+ /**
136
+ * @param {string} feature
137
+ * @param {string[] | undefined} browsers
138
+ */
121
139
  function isSupportedCached(feature, browsers) {
122
- const key = JSON.stringify({
123
- feature,
124
- browsers
125
- });
140
+ const key = JSON.stringify({ feature, browsers });
126
141
  let result = isSupportedCache.get(key);
127
142
 
128
143
  if (!result) {
129
- result = (0, _caniuseApi.isSupported)(feature, browsers);
144
+ result = isSupported(feature, /** @type {string[]} */ (browsers));
130
145
  isSupportedCache.set(key, result);
131
146
  }
132
147
 
133
148
  return result;
134
149
  }
135
150
 
151
+ /**
152
+ * @param {string[]} selectors
153
+ * @param{string[]=} browsers
154
+ * @param{Map<string,boolean>=} compatibilityCache
155
+ * @return {boolean}
156
+ */
136
157
  function ensureCompatibility(selectors, browsers, compatibilityCache) {
137
158
  // Should not merge mixins
138
159
  if (selectors.some(isCssMixin)) {
139
160
  return false;
140
- } // Should not merge :host selector https://github.com/angular/angular-cli/issues/18672
141
-
161
+ }
142
162
 
163
+ // Should not merge :host selector https://github.com/angular/angular-cli/issues/18672
143
164
  if (selectors.some(isHostPseudoClass)) {
144
165
  return false;
145
166
  }
146
-
147
- return selectors.every(selector => {
167
+ return selectors.every((selector) => {
148
168
  if (simpleSelectorRe.test(selector)) {
149
169
  return true;
150
170
  }
151
-
152
171
  if (compatibilityCache && compatibilityCache.has(selector)) {
153
172
  return compatibilityCache.get(selector);
154
173
  }
155
-
156
174
  let compatible = true;
157
- (0, _postcssSelectorParser.default)(ast => {
158
- ast.walk(node => {
159
- const {
160
- type,
161
- value
162
- } = node;
163
-
175
+ selectorParser((ast) => {
176
+ ast.walk((node) => {
177
+ const { type, value } = node;
164
178
  if (type === 'pseudo') {
165
- const entry = pseudoElements[value];
166
-
179
+ const entry =
180
+ pseudoElements[/** @type {keyof pseudoElements} */ (value)];
167
181
  if (!entry && noVendor(value)) {
168
182
  compatible = false;
169
183
  }
170
-
171
184
  if (entry && compatible) {
172
185
  compatible = isSupportedCached(entry, browsers);
173
186
  }
174
187
  }
175
-
176
188
  if (type === 'combinator') {
177
- if (~value.indexOf('~')) {
189
+ if (value.includes('~')) {
178
190
  compatible = isSupportedCached(cssSel3, browsers);
179
191
  }
180
-
181
- if (~value.indexOf('>') || ~value.indexOf('+')) {
192
+ if (value.includes('>') || value.includes('+')) {
182
193
  compatible = isSupportedCached(cssSel2, browsers);
183
194
  }
184
195
  }
185
-
186
196
  if (type === 'attribute' && node.attribute) {
187
197
  // [foo]
188
198
  if (!node.operator) {
189
199
  compatible = isSupportedCached(cssSel2, browsers);
190
200
  }
191
-
192
201
  if (value) {
193
202
  // [foo="bar"], [foo~="bar"], [foo|="bar"]
194
- if (~['=', '~=', '|='].indexOf(node.operator)) {
203
+ if (level2Sel.has(/** @type {string} */ (node.operator))) {
195
204
  compatible = isSupportedCached(cssSel2, browsers);
196
- } // [foo^="bar"], [foo$="bar"], [foo*="bar"]
197
-
198
-
199
- if (~['^=', '$=', '*='].indexOf(node.operator)) {
205
+ }
206
+ // [foo^="bar"], [foo$="bar"], [foo*="bar"]
207
+ if (level3Sel.has(/** @type {string} */ (node.operator))) {
200
208
  compatible = isSupportedCached(cssSel3, browsers);
201
209
  }
202
- } // [foo="bar" i]
203
-
210
+ }
204
211
 
212
+ // [foo="bar" i]
205
213
  if (node.insensitive) {
206
214
  compatible = isSupportedCached('css-case-insensitive', browsers);
207
215
  }
208
216
  }
209
-
210
217
  if (!compatible) {
211
218
  // If this node was not compatible,
212
219
  // break out early from walking the rest
@@ -214,11 +221,11 @@ function ensureCompatibility(selectors, browsers, compatibilityCache) {
214
221
  }
215
222
  });
216
223
  }).processSync(selector);
217
-
218
224
  if (compatibilityCache) {
219
225
  compatibilityCache.set(selector, compatible);
220
226
  }
221
-
222
227
  return compatible;
223
228
  });
224
- }
229
+ }
230
+
231
+ module.exports = { sameVendor, noVendor, pseudoElements, ensureCompatibility };
@@ -0,0 +1,9 @@
1
+ export = pluginCreator;
2
+ /**
3
+ * @type {import('postcss').PluginCreator<void>}
4
+ * @return {import('postcss').Plugin}
5
+ */
6
+ declare function pluginCreator(): import('postcss').Plugin;
7
+ declare namespace pluginCreator {
8
+ const postcss: true;
9
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @param {string[]} selectorsA
3
+ * @param {string[]} selectorsB
4
+ * @return {boolean}
5
+ */
6
+ export function sameVendor(selectorsA: string[], selectorsB: string[]): boolean;
7
+ /**
8
+ * @param {string} selector
9
+ * @return {boolean}
10
+ */
11
+ export function noVendor(selector: string): boolean;
12
+ export const pseudoElements: {
13
+ ':active': string;
14
+ ':after': string;
15
+ ':any-link': string;
16
+ ':before': string;
17
+ ':checked': string;
18
+ ':default': string;
19
+ ':dir': string;
20
+ ':disabled': string;
21
+ ':empty': string;
22
+ ':enabled': string;
23
+ ':first-child': string;
24
+ ':first-letter': string;
25
+ ':first-line': string;
26
+ ':first-of-type': string;
27
+ ':focus': string;
28
+ ':focus-within': string;
29
+ ':focus-visible': string;
30
+ ':has': string;
31
+ ':hover': string;
32
+ ':in-range': string;
33
+ ':indeterminate': string;
34
+ ':invalid': string;
35
+ ':is': string;
36
+ ':lang': string;
37
+ ':last-child': string;
38
+ ':last-of-type': string;
39
+ ':link': string;
40
+ ':matches': string;
41
+ ':not': string;
42
+ ':nth-child': string;
43
+ ':nth-last-child': string;
44
+ ':nth-last-of-type': string;
45
+ ':nth-of-type': string;
46
+ ':only-child': string;
47
+ ':only-of-type': string;
48
+ ':optional': string;
49
+ ':out-of-range': string;
50
+ ':placeholder-shown': string;
51
+ ':required': string;
52
+ ':root': string;
53
+ ':target': string;
54
+ '::after': string;
55
+ '::backdrop': string;
56
+ '::before': string;
57
+ '::first-letter': string;
58
+ '::first-line': string;
59
+ '::marker': string;
60
+ '::placeholder': string;
61
+ '::selection': string;
62
+ ':valid': string;
63
+ ':visited': string;
64
+ };
65
+ /**
66
+ * @param {string[]} selectors
67
+ * @param{string[]=} browsers
68
+ * @param{Map<string,boolean>=} compatibilityCache
69
+ * @return {boolean}
70
+ */
71
+ export function ensureCompatibility(selectors: string[], browsers?: string[] | undefined, compatibilityCache?: Map<string, boolean> | undefined): boolean;