postcss-merge-rules 5.0.6 → 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.6",
3
+ "version": "5.1.0",
4
4
  "description": "Merge CSS rules with PostCSS.",
5
+ "types": "types/index.d.ts",
5
6
  "main": "src/index.js",
6
7
  "files": [
7
8
  "LICENSE-MIT",
8
- "src"
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.2",
29
+ "cssnano-utils": "^3.1.0",
28
30
  "postcss-selector-parser": "^6.0.5"
29
31
  },
30
32
  "bugs": {
@@ -34,8 +36,9 @@
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.3"
41
+ "postcss-discard-comments": "^5.1.0"
39
42
  },
40
43
  "peerDependencies": {
41
44
  "postcss": "^8.2.15"
package/src/index.js CHANGED
@@ -8,8 +8,8 @@ const {
8
8
  } = require('./lib/ensureCompatibility');
9
9
 
10
10
  /**
11
- * @param {postcss.Declaration} a
12
- * @param {postcss.Declaration} b
11
+ * @param {import('postcss').Declaration} a
12
+ * @param {import('postcss').Declaration} b
13
13
  * @return {boolean}
14
14
  */
15
15
  function declarationIsEqual(a, b) {
@@ -19,8 +19,8 @@ function declarationIsEqual(a, b) {
19
19
  }
20
20
 
21
21
  /**
22
- * @param {postcss.Declaration[]} array
23
- * @param {postcss.Declaration} decl
22
+ * @param {import('postcss').Declaration[]} array
23
+ * @param {import('postcss').Declaration} decl
24
24
  * @return {number}
25
25
  */
26
26
  function indexOfDeclaration(array, decl) {
@@ -29,10 +29,10 @@ function indexOfDeclaration(array, decl) {
29
29
 
30
30
  /**
31
31
  * Returns filtered array of matched or unmatched declarations
32
- * @param {postcss.Declaration[]} a
33
- * @param {postcss.Declaration[]} b
32
+ * @param {import('postcss').Declaration[]} a
33
+ * @param {import('postcss').Declaration[]} b
34
34
  * @param {boolean} [not=false]
35
- * @return {postcss.Declaration[]}
35
+ * @return {import('postcss').Declaration[]}
36
36
  */
37
37
  function intersect(a, b, not) {
38
38
  return a.filter((c) => {
@@ -42,8 +42,8 @@ function intersect(a, b, not) {
42
42
  }
43
43
 
44
44
  /**
45
- * @param {postcss.Declaration[]} a
46
- * @param {postcss.Declaration[]} b
45
+ * @param {import('postcss').Declaration[]} a
46
+ * @param {import('postcss').Declaration[]} b
47
47
  * @return {boolean}
48
48
  */
49
49
  function sameDeclarationsAndOrder(a, b) {
@@ -54,8 +54,8 @@ function sameDeclarationsAndOrder(a, b) {
54
54
  }
55
55
 
56
56
  /**
57
- * @param {postcss.Rule} ruleA
58
- * @param {postcss.Rule} ruleB
57
+ * @param {import('postcss').Rule} ruleA
58
+ * @param {import('postcss').Rule} ruleB
59
59
  * @param {string[]=} browsers
60
60
  * @param {Map<string, boolean>=} compatibilityCache
61
61
  * @return {boolean}
@@ -70,8 +70,11 @@ function canMerge(ruleA, ruleB, browsers, compatibilityCache) {
70
70
  return false;
71
71
  }
72
72
 
73
- const parent = sameParent(ruleA, ruleB);
74
- const { name } = ruleA.parent;
73
+ const parent = sameParent(
74
+ /** @type {any} */ (ruleA),
75
+ /** @type {any} */ (ruleB)
76
+ );
77
+ const { name } = /** @type {any} */ (ruleA).parent;
75
78
  if (parent && name && name.includes('keyframes')) {
76
79
  return false;
77
80
  }
@@ -79,22 +82,29 @@ function canMerge(ruleA, ruleB, browsers, compatibilityCache) {
79
82
  }
80
83
 
81
84
  /**
82
- * @param {postcss.Rule} rule
83
- * @return {postcss.Declaration[]}
85
+ * @param {import('postcss').Rule} rule
86
+ * @return {import('postcss').Declaration[]}
84
87
  */
85
88
  function getDecls(rule) {
86
- return rule.nodes.filter((node) => node.type === 'decl');
89
+ return /** @type {import('postcss').Declaration[]} */ (
90
+ rule.nodes.filter((node) => node.type === 'decl')
91
+ );
87
92
  }
88
93
 
94
+ /** @type {(...rules: import('postcss').Rule[]) => string} */
89
95
  const joinSelectors = (...rules) => rules.map((s) => s.selector).join();
90
96
 
97
+ /**
98
+ * @param {...import('postcss').Rule} rules
99
+ * @return {number}
100
+ */
91
101
  function ruleLength(...rules) {
92
102
  return rules.map((r) => (r.nodes.length ? String(r) : '')).join('').length;
93
103
  }
94
104
 
95
105
  /**
96
106
  * @param {string} prop
97
- * @return {{prefix: string, base:string, rest:string[]}}
107
+ * @return {{prefix: string?, base:string?, rest:string[]}}
98
108
  */
99
109
  function splitProp(prop) {
100
110
  // Treat vendor prefixed properties as if they were unprefixed;
@@ -155,8 +165,8 @@ function isConflictingProp(propA, propB) {
155
165
  }
156
166
 
157
167
  /**
158
- * @param {postcss.Rule} first
159
- * @param {postcss.Rule} second
168
+ * @param {import('postcss').Rule} first
169
+ * @param {import('postcss').Rule} second
160
170
  * @return {boolean} merged
161
171
  */
162
172
  function mergeParents(first, second) {
@@ -178,9 +188,9 @@ function mergeParents(first, second) {
178
188
  }
179
189
 
180
190
  /**
181
- * @param {postcss.Rule} first
182
- * @param {postcss.Rule} second
183
- * @return {postcss.Rule} mergedRule
191
+ * @param {import('postcss').Rule} first
192
+ * @param {import('postcss').Rule} second
193
+ * @return {import('postcss').Rule} mergedRule
184
194
  */
185
195
  function partialMerge(first, second) {
186
196
  let intersection = intersect(getDecls(first), getDecls(second));
@@ -190,7 +200,11 @@ function partialMerge(first, second) {
190
200
  let nextRule = second.next();
191
201
  if (!nextRule) {
192
202
  // Grab next cousin
193
- const parentSibling = second.parent.next();
203
+ /** @type {any} */
204
+ const parentSibling =
205
+ /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
206
+ second.parent
207
+ ).next();
194
208
  nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
195
209
  }
196
210
  if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) {
@@ -262,14 +276,17 @@ function partialMerge(first, second) {
262
276
  receivingBlock.selector = joinSelectors(first, second);
263
277
  receivingBlock.nodes = [];
264
278
 
265
- second.parent.insertBefore(second, receivingBlock);
279
+ /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
280
+ second.parent
281
+ ).insertBefore(second, receivingBlock);
266
282
 
267
283
  const firstClone = first.clone();
268
284
  const secondClone = second.clone();
269
285
 
270
286
  /**
271
- * @param {function(postcss.Declaration):void} callback
272
- * @return {function(postcss.Declaration)}
287
+ * @param {function(import('postcss').Declaration):void} callback
288
+ * @this {import('postcss').Rule}
289
+ * @return {function(import('postcss').Declaration)}
273
290
  */
274
291
  function moveDecl(callback) {
275
292
  return (decl) => {
@@ -308,10 +325,10 @@ function partialMerge(first, second) {
308
325
  /**
309
326
  * @param {string[]} browsers
310
327
  * @param {Map<string, boolean>} compatibilityCache
311
- * @return {function(postcss.Rule)}
328
+ * @return {function(import('postcss').Rule)}
312
329
  */
313
330
  function selectorMerger(browsers, compatibilityCache) {
314
- /** @type {postcss.Rule} */
331
+ /** @type {import('postcss').Rule | null} */
315
332
  let cache = null;
316
333
  return function (rule) {
317
334
  // Prime the cache with the first rule, or alternately ensure that it is
@@ -343,10 +360,16 @@ function selectorMerger(browsers, compatibilityCache) {
343
360
  if (cache.selector === rule.selector) {
344
361
  const cached = getDecls(cache);
345
362
  rule.walk((decl) => {
346
- if (~indexOfDeclaration(cached, decl)) {
347
- return decl.remove();
363
+ if (
364
+ ~indexOfDeclaration(
365
+ cached,
366
+ /** @type {import('postcss').Declaration} */ (decl)
367
+ )
368
+ ) {
369
+ decl.remove();
370
+ return;
348
371
  }
349
- cache.append(decl);
372
+ /** @type {import('postcss').Rule} */ (cache).append(decl);
350
373
  });
351
374
  rule.remove();
352
375
  return;
@@ -356,12 +379,16 @@ function selectorMerger(browsers, compatibilityCache) {
356
379
  cache = partialMerge(cache, rule);
357
380
  };
358
381
  }
359
-
382
+ /**
383
+ * @type {import('postcss').PluginCreator<void>}
384
+ * @return {import('postcss').Plugin}
385
+ */
360
386
  function pluginCreator() {
361
387
  return {
362
388
  postcssPlugin: 'postcss-merge-rules',
363
389
 
364
390
  prepare(result) {
391
+ /** @type {typeof result.opts & browserslist.Options} */
365
392
  const resultOpts = result.opts || {};
366
393
  const browsers = browserslist(null, {
367
394
  stats: resultOpts.stats,
@@ -15,21 +15,35 @@ const formValidation = 'form-validation';
15
15
  const vendorPrefix =
16
16
  /-(ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|wap|webkit|xv)-/;
17
17
 
18
+ const level2Sel = new Set(['=', '~=', '|=']);
19
+ const level3Sel = new Set(['^=', '$=', '*=']);
20
+
18
21
  /**
19
22
  * @param {string} selector
20
- * @return {string[]}
23
+ * @return {RegExpMatchArray | null}
21
24
  */
22
25
  function filterPrefixes(selector) {
23
26
  return selector.match(vendorPrefix);
24
27
  }
25
28
 
26
- // Internet Explorer use :-ms-input-placeholder.
27
- // Microsoft Edge use ::-ms-input-placeholder.
29
+ /**
30
+ * Internet Explorer use :-ms-input-placeholder.
31
+ * Microsoft Edge use ::-ms-input-placeholder.
32
+ *
33
+ * @type {(selector: string) => number}
34
+ */
28
35
  const findMsInputPlaceholder = (selector) =>
29
36
  ~selector.search(/-ms-input-placeholder/i);
30
37
 
38
+ /**
39
+ * @param {string[]} selectorsA
40
+ * @param {string[]} selectorsB
41
+ * @return {boolean}
42
+ */
31
43
  function sameVendor(selectorsA, selectorsB) {
44
+ /** @type {(selectors: string[]) => string} */
32
45
  let same = (selectors) => selectors.map(filterPrefixes).join();
46
+ /** @type {(selectors: string[]) => string | undefined} */
33
47
  let findMsVendor = (selectors) => selectors.find(findMsInputPlaceholder);
34
48
  return (
35
49
  same(selectorsA) === same(selectorsB) &&
@@ -99,10 +113,18 @@ const pseudoElements = {
99
113
  ':visited': cssSel2,
100
114
  };
101
115
 
116
+ /**
117
+ * @param {string} selector
118
+ * @return {boolean}
119
+ */
102
120
  function isCssMixin(selector) {
103
121
  return selector[selector.length - 1] === ':';
104
122
  }
105
123
 
124
+ /**
125
+ * @param {string} selector
126
+ * @return {boolean}
127
+ */
106
128
  function isHostPseudoClass(selector) {
107
129
  return selector.includes(':host');
108
130
  }
@@ -110,18 +132,28 @@ function isHostPseudoClass(selector) {
110
132
  const isSupportedCache = new Map();
111
133
 
112
134
  // Move to util in future
135
+ /**
136
+ * @param {string} feature
137
+ * @param {string[] | undefined} browsers
138
+ */
113
139
  function isSupportedCached(feature, browsers) {
114
140
  const key = JSON.stringify({ feature, browsers });
115
141
  let result = isSupportedCache.get(key);
116
142
 
117
143
  if (!result) {
118
- result = isSupported(feature, browsers);
144
+ result = isSupported(feature, /** @type {string[]} */ (browsers));
119
145
  isSupportedCache.set(key, result);
120
146
  }
121
147
 
122
148
  return result;
123
149
  }
124
150
 
151
+ /**
152
+ * @param {string[]} selectors
153
+ * @param{string[]=} browsers
154
+ * @param{Map<string,boolean>=} compatibilityCache
155
+ * @return {boolean}
156
+ */
125
157
  function ensureCompatibility(selectors, browsers, compatibilityCache) {
126
158
  // Should not merge mixins
127
159
  if (selectors.some(isCssMixin)) {
@@ -144,7 +176,8 @@ function ensureCompatibility(selectors, browsers, compatibilityCache) {
144
176
  ast.walk((node) => {
145
177
  const { type, value } = node;
146
178
  if (type === 'pseudo') {
147
- const entry = pseudoElements[value];
179
+ const entry =
180
+ pseudoElements[/** @type {keyof pseudoElements} */ (value)];
148
181
  if (!entry && noVendor(value)) {
149
182
  compatible = false;
150
183
  }
@@ -165,14 +198,13 @@ function ensureCompatibility(selectors, browsers, compatibilityCache) {
165
198
  if (!node.operator) {
166
199
  compatible = isSupportedCached(cssSel2, browsers);
167
200
  }
168
-
169
201
  if (value) {
170
202
  // [foo="bar"], [foo~="bar"], [foo|="bar"]
171
- if (['=', '~=', '|='].includes(node.operator)) {
203
+ if (level2Sel.has(/** @type {string} */ (node.operator))) {
172
204
  compatible = isSupportedCached(cssSel2, browsers);
173
205
  }
174
206
  // [foo^="bar"], [foo$="bar"], [foo*="bar"]
175
- if (['^=', '$=', '*='].includes(node.operator)) {
207
+ if (level3Sel.has(/** @type {string} */ (node.operator))) {
176
208
  compatible = isSupportedCached(cssSel3, browsers);
177
209
  }
178
210
  }
@@ -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;