postcss-merge-rules 8.0.2 → 8.0.4

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/src/index.js CHANGED
@@ -10,6 +10,7 @@ const {
10
10
  sameVendor,
11
11
  noVendor,
12
12
  } = require('./lib/ensureCompatibility');
13
+ const { isConflictingProp } = require('./lib/propertyRelations.js');
13
14
  /** @import {Declaration, Rule} from 'postcss' */
14
15
  /**
15
16
  * @param {Declaration} a
@@ -105,10 +106,7 @@ function canMerge(
105
106
  return false;
106
107
  }
107
108
 
108
- const parent = sameParent(
109
- /** @type {any} */ (ruleA),
110
- /** @type {any} */ (ruleB)
111
- );
109
+ const parent = sameParent(ruleA, ruleB);
112
110
  if (
113
111
  parent &&
114
112
  ruleA.parent &&
@@ -200,83 +198,6 @@ function ruleLength(...rules) {
200
198
  return rules.map((r) => (r.nodes.length ? String(r) : '')).join('').length;
201
199
  }
202
200
 
203
- /**
204
- * @param {string} prop
205
- * @return {{prefix: string?, base:string?, rest:string[]}}
206
- */
207
- function splitProp(prop) {
208
- // Treat vendor prefixed properties as if they were unprefixed;
209
- // moving them when combined with non-prefixed properties can
210
- // cause issues. e.g. moving -webkit-background-clip when there
211
- // is a background shorthand definition.
212
-
213
- const parts = prop.split('-');
214
- if (prop[0] !== '-') {
215
- return {
216
- prefix: '',
217
- base: parts[0],
218
- rest: parts.slice(1),
219
- };
220
- }
221
- // Don't split css variables
222
- if (prop[1] === '-') {
223
- return {
224
- prefix: null,
225
- base: null,
226
- rest: [prop],
227
- };
228
- }
229
- // Found prefix
230
- return {
231
- prefix: parts[1],
232
- base: parts[2],
233
- rest: parts.slice(3),
234
- };
235
- }
236
-
237
- /**
238
- * @param {string} propA
239
- * @param {string} propB
240
- * @return {boolean}
241
- */
242
- function isConflictingProp(propA, propB) {
243
- if (propA === propB) {
244
- // Same specificity
245
- return true;
246
- }
247
- const a = splitProp(propA);
248
- const b = splitProp(propB);
249
- // Don't resort css variables
250
- if (!a.base && !b.base) {
251
- return true;
252
- }
253
-
254
- // Different base and none is `place`;
255
- if (a.base !== b.base && a.base !== 'place' && b.base !== 'place') {
256
- return false;
257
- }
258
-
259
- // Conflict if rest-count mismatches
260
- if (a.rest.length !== b.rest.length) {
261
- return true;
262
- }
263
-
264
- /* Do not merge conflicting border properties */
265
- if (a.base === 'border') {
266
- const allRestProps = new Set([...a.rest, ...b.rest]);
267
- if (
268
- allRestProps.has('image') ||
269
- allRestProps.has('width') ||
270
- allRestProps.has('color') ||
271
- allRestProps.has('style')
272
- ) {
273
- return true;
274
- }
275
- }
276
- // Conflict if rest parameters are equal (same but unprefixed)
277
- return a.rest.every((s, index) => b.rest[index] === s);
278
- }
279
-
280
201
  /**
281
202
  * @param {Rule} first
282
203
  * @param {Rule} second
@@ -300,45 +221,48 @@ function mergeParents(first, second) {
300
221
  return true;
301
222
  }
302
223
 
224
+ /**
225
+ * @param {Rule} second
226
+ * @return {Rule | null}
227
+ */
228
+ function getNextRule(second) {
229
+ let nextRule = second.next();
230
+ if (!nextRule) {
231
+ // Grab next cousin
232
+ const parentSibling =
233
+ /** @type {import('postcss').Container | undefined} */ (
234
+ /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
235
+ second.parent
236
+ ).next()
237
+ );
238
+ nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
239
+ }
240
+ return nextRule?.type === 'rule' ? nextRule : null;
241
+ }
242
+
303
243
  /**
304
244
  * @param {Rule} first
305
245
  * @param {Rule} second
246
+ * @param {Declaration[]} intersection
306
247
  * @param {string[]} browsers
307
248
  * @param {Map<string, boolean>} compatibilityCache
308
249
  * @param {WeakSet<Rule>} ruleCache
309
250
  * @param {WeakMap<Rule, RuleMeta>} ruleMeta
310
- * @return {Rule} mergedRule
251
+ * @return {{first: Rule, second: Rule, intersection: Declaration[]}}
311
252
  */
312
- function partialMerge(
253
+ function mergeWithNextRule(
313
254
  first,
314
255
  second,
256
+ intersection,
315
257
  browsers,
316
258
  compatibilityCache,
317
259
  ruleCache,
318
260
  ruleMeta
319
261
  ) {
320
- if (ruleMeta) {
321
- flush(first, ruleMeta);
322
- }
323
- const metaFirst = getMeta(first, ruleMeta);
324
- const metaSecond = getMeta(second, ruleMeta);
325
- let intersection = intersect(metaFirst.declarations, metaSecond.declarations);
326
- if (intersection.length === 0) {
327
- return second;
328
- }
329
- let nextRule = second.next();
330
- if (!nextRule) {
331
- // Grab next cousin
332
- /** @type {any} */
333
- const parentSibling =
334
- /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
335
- second.parent
336
- ).next();
337
- nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
338
- }
262
+ const nextRule = getNextRule(second);
339
263
  if (
340
- nextRule?.type === 'rule' &&
341
- canMerge(
264
+ !nextRule ||
265
+ !canMerge(
342
266
  second,
343
267
  nextRule,
344
268
  browsers,
@@ -347,76 +271,164 @@ function partialMerge(
347
271
  ruleMeta
348
272
  )
349
273
  ) {
350
- const metaNext = getMeta(nextRule, ruleMeta);
351
- const nextIntersection = intersect(
352
- metaSecond.declarations,
353
- metaNext.declarations
354
- );
355
- if (nextIntersection.length > intersection.length) {
356
- mergeParents(second, nextRule);
357
- first = second;
358
- second = nextRule;
359
- intersection = nextIntersection;
360
- }
274
+ return { first, second, intersection };
361
275
  }
362
276
 
363
- const metaFirstActual = getMeta(first, ruleMeta);
364
- const metaSecondActual = getMeta(second, ruleMeta);
365
- const firstDecls = [...metaFirstActual.declarations];
366
- const secondDecls = [...metaSecondActual.declarations];
367
-
368
- // Filter out intersections with later conflicts in First
369
- intersection = intersection.filter((decl, intersectIndex) => {
370
- const indexOfDecl = indexOfDeclaration(firstDecls, decl);
371
- const nextConflictInFirst = firstDecls
372
- .slice(indexOfDecl + 1)
373
- .filter((d) => isConflictingProp(d.prop, decl.prop));
374
- if (nextConflictInFirst.length === 0) {
375
- return true;
376
- }
377
- const nextConflictInIntersection = intersection
378
- .slice(intersectIndex + 1)
379
- .filter((d) => isConflictingProp(d.prop, decl.prop));
380
- if (nextConflictInFirst.length !== nextConflictInIntersection.length) {
381
- return false;
382
- }
383
- return nextConflictInFirst.every((d, index) =>
384
- declarationIsEqual(d, nextConflictInIntersection[index])
385
- );
386
- });
277
+ const nextIntersection = intersect(
278
+ getMeta(second, ruleMeta).declarations,
279
+ getMeta(nextRule, ruleMeta).declarations
280
+ );
281
+ if (nextIntersection.length <= intersection.length) {
282
+ return { first, second, intersection };
283
+ }
387
284
 
388
- // Filter out intersections with previous conflicts in Second
389
- intersection = intersection.filter((decl) => {
390
- const nextConflictIndex = secondDecls.findIndex((d) =>
391
- isConflictingProp(d.prop, decl.prop)
285
+ mergeParents(second, nextRule);
286
+ return { first: second, second: nextRule, intersection: nextIntersection };
287
+ }
288
+
289
+ /**
290
+ * Returns true if hoisting `candidate` cannot reverse the cascade: every
291
+ * declaration that might override it is itself being
292
+ * hoisted, in the same relative order, so declarations move together.
293
+ *
294
+ * @param {Declaration} candidate
295
+ * @param {number} candidateIndex
296
+ * @param {Declaration[]} hoistCandidates
297
+ * @param {Declaration[]} earlierRuleDeclarations
298
+ * @return {boolean}
299
+ */
300
+ function hoistingPreservesOverrideOrder(
301
+ candidate,
302
+ candidateIndex,
303
+ hoistCandidates,
304
+ earlierRuleDeclarations
305
+ ) {
306
+ const indexInEarlierRule = indexOfDeclaration(
307
+ earlierRuleDeclarations,
308
+ candidate
309
+ );
310
+ const overridesInEarlierRule = earlierRuleDeclarations
311
+ .slice(indexInEarlierRule + 1)
312
+ .filter((d) => isConflictingProp(d.prop, candidate.prop));
313
+ if (overridesInEarlierRule.length === 0) {
314
+ return true;
315
+ }
316
+ const overridesAmongCandidates = hoistCandidates
317
+ .slice(candidateIndex + 1)
318
+ .filter((d) => isConflictingProp(d.prop, candidate.prop));
319
+ if (overridesInEarlierRule.length !== overridesAmongCandidates.length) {
320
+ return false;
321
+ }
322
+ return overridesInEarlierRule.every((d, index) =>
323
+ declarationIsEqual(d, overridesAmongCandidates[index])
324
+ );
325
+ }
326
+
327
+ /**
328
+ * True if the later rule has an unclaimed declaration equal to `candidate`.
329
+ * Records its index in `claimedIndices`, so no other candidate can match the
330
+ * same declaration. Always false when the later rule contains `all`,
331
+ * since `all` resets everything except `direction`/`unicode-bidi`.
332
+ *
333
+ * @param {Declaration} candidate
334
+ * @param {Declaration[]} laterDeclarations
335
+ * @param {Set<number>} claimedIndices
336
+ * @return {boolean}
337
+ */
338
+ function claimMatchInLaterRule(candidate, laterDeclarations, claimedIndices) {
339
+ const matchIndex = laterDeclarations.findIndex(
340
+ (d, index) =>
341
+ !claimedIndices.has(index) && isConflictingProp(d.prop, candidate.prop)
342
+ );
343
+ if (matchIndex === -1) {
344
+ return false;
345
+ }
346
+ if (!declarationIsEqual(laterDeclarations[matchIndex], candidate)) {
347
+ return false;
348
+ }
349
+ if (
350
+ candidate.prop.toLowerCase() !== 'direction' &&
351
+ candidate.prop.toLowerCase() !== 'unicode-bidi' &&
352
+ laterDeclarations.some(
353
+ (declaration) => declaration.prop.toLowerCase() === 'all'
354
+ )
355
+ ) {
356
+ return false;
357
+ }
358
+ claimedIndices.add(matchIndex);
359
+ return true;
360
+ }
361
+
362
+ /**
363
+ * Narrows the declarations shared by two adjacent rules down to those that can
364
+ * safely move into a merged rule, along with the indices of the later rule's
365
+ * declarations they claim.
366
+ *
367
+ * The merged rule is emitted after the earlier rule's remaining declarations,
368
+ * so hoisting a declaration that a later declaration in the earlier rule
369
+ * overrides would reverse the cascade and make the previously dead value
370
+ * effective again, unless that overriding declaration is hoisted too, so both
371
+ * move together and keep their relative order.
372
+ *
373
+ * Dropping a candidate can invalidate one that was only admitted because it
374
+ * travelled with it, so the pass repeats until the surviving set stops
375
+ * shrinking.
376
+ *
377
+ * @param {Declaration[]} hoistCandidates
378
+ * @param {Declaration[]} earlierRuleDeclarations
379
+ * @param {Declaration[]} laterRuleDeclarations
380
+ * @return {{intersection: Declaration[], claimedIndices: Set<number>}}
381
+ */
382
+ function filterRuleIntersections(
383
+ hoistCandidates,
384
+ earlierRuleDeclarations,
385
+ laterRuleDeclarations
386
+ ) {
387
+ let remainingCandidates = hoistCandidates;
388
+ for (;;) {
389
+ // A candidate rejected by the override check never claims a match.
390
+ const claimedIndices = new Set();
391
+ const survivors = remainingCandidates.filter(
392
+ (candidate, candidateIndex) =>
393
+ hoistingPreservesOverrideOrder(
394
+ candidate,
395
+ candidateIndex,
396
+ remainingCandidates,
397
+ earlierRuleDeclarations
398
+ ) &&
399
+ claimMatchInLaterRule(candidate, laterRuleDeclarations, claimedIndices)
392
400
  );
393
- if (nextConflictIndex === -1) {
394
- return false;
395
- }
396
- if (!declarationIsEqual(secondDecls[nextConflictIndex], decl)) {
397
- return false;
398
- }
399
401
  if (
400
- decl.prop.toLowerCase() !== 'direction' &&
401
- decl.prop.toLowerCase() !== 'unicode-bidi' &&
402
- secondDecls.some(
403
- (declaration) => declaration.prop.toLowerCase() === 'all'
404
- )
402
+ survivors.length === remainingCandidates.length ||
403
+ survivors.length === 0
405
404
  ) {
406
- return false;
405
+ return { intersection: survivors, claimedIndices };
407
406
  }
408
- secondDecls.splice(nextConflictIndex, 1);
409
- return true;
410
- });
411
-
412
- if (intersection.length === 0) {
413
- // Nothing to merge
414
- return second;
407
+ remainingCandidates = survivors;
415
408
  }
409
+ }
416
410
 
411
+ /**
412
+ * @param {Rule} first
413
+ * @param {Rule} second
414
+ * @param {Declaration[]} intersection
415
+ * @param {Set<number>} claimedIndices Positions of the declarations the
416
+ * intersection claimed in `second`, which the merged rule replaces.
417
+ * @param {WeakSet<Rule>} ruleCache
418
+ * @param {WeakMap<Rule, RuleMeta>} ruleMeta
419
+ * @return {Rule}
420
+ */
421
+ function buildMergedRule(
422
+ first,
423
+ second,
424
+ intersection,
425
+ claimedIndices,
426
+ ruleCache,
427
+ ruleMeta
428
+ ) {
417
429
  const receivingBlock = second.clone();
418
- const firstSelectors = metaFirstActual.selectors;
419
- const secondSelectors = metaSecondActual.selectors;
430
+ const firstSelectors = getMeta(first, ruleMeta).selectors;
431
+ const secondSelectors = getMeta(second, ruleMeta).selectors;
420
432
 
421
433
  receivingBlock.selector = [...firstSelectors, ...secondSelectors].join();
422
434
  receivingBlock.nodes = [];
@@ -451,7 +463,15 @@ function partialMerge(
451
463
  }
452
464
  )
453
465
  );
454
- secondClone.walkDecls(moveDecl((decl) => decl.remove()));
466
+ // Remove exactly the declarations that were claimed: matching by value would
467
+ // also delete a duplicate that re-asserts the value later in the rule, after
468
+ // an overriding declaration that stays behind.
469
+ let laterIndex = 0;
470
+ secondClone.walkDecls((decl) => {
471
+ if (claimedIndices.has(laterIndex++)) {
472
+ decl.remove();
473
+ }
474
+ });
455
475
 
456
476
  // Ensure original rules are flushed for accurate length comparison
457
477
  if (ruleMeta) {
@@ -464,11 +484,11 @@ function partialMerge(
464
484
  if (merged < original) {
465
485
  first.replaceWith(firstClone);
466
486
  second.replaceWith(secondClone);
467
- [firstClone, receivingBlock, secondClone].forEach((r) => {
487
+ for (const r of [firstClone, receivingBlock, secondClone]) {
468
488
  if (r.nodes.length === 0) {
469
489
  r.remove();
470
490
  }
471
- });
491
+ }
472
492
  if (!secondClone.parent) {
473
493
  ruleCache?.add(receivingBlock);
474
494
  return receivingBlock;
@@ -478,10 +498,76 @@ function partialMerge(
478
498
  ruleMeta?.delete(first);
479
499
  ruleMeta?.delete(second);
480
500
  return secondClone;
481
- } else {
482
- receivingBlock.remove();
501
+ }
502
+
503
+ receivingBlock.remove();
504
+ return second;
505
+ }
506
+
507
+ /**
508
+ * @param {Rule} first
509
+ * @param {Rule} second
510
+ * @param {string[]} browsers
511
+ * @param {Map<string, boolean>} compatibilityCache
512
+ * @param {WeakSet<Rule>} ruleCache
513
+ * @param {WeakMap<Rule, RuleMeta>} ruleMeta
514
+ * @return {Rule} mergedRule
515
+ */
516
+ function partialMerge(
517
+ first,
518
+ second,
519
+ browsers,
520
+ compatibilityCache,
521
+ ruleCache,
522
+ ruleMeta
523
+ ) {
524
+ if (ruleMeta) {
525
+ flush(first, ruleMeta);
526
+ }
527
+ const metaFirst = getMeta(first, ruleMeta);
528
+ const metaSecond = getMeta(second, ruleMeta);
529
+ let intersection = intersect(metaFirst.declarations, metaSecond.declarations);
530
+ if (intersection.length === 0) {
483
531
  return second;
484
532
  }
533
+ const mergedNext = mergeWithNextRule(
534
+ first,
535
+ second,
536
+ intersection,
537
+ browsers,
538
+ compatibilityCache,
539
+ ruleCache,
540
+ ruleMeta
541
+ );
542
+ const mergedFirst = mergedNext.first;
543
+ const mergedSecond = mergedNext.second;
544
+ intersection = mergedNext.intersection;
545
+
546
+ const metaFirstActual = getMeta(mergedFirst, ruleMeta);
547
+ const metaSecondActual = getMeta(mergedSecond, ruleMeta);
548
+ const earlierRuleDeclarations = [...metaFirstActual.declarations];
549
+ const laterRuleDeclarations = [...metaSecondActual.declarations];
550
+
551
+ const filtered = filterRuleIntersections(
552
+ intersection,
553
+ earlierRuleDeclarations,
554
+ laterRuleDeclarations
555
+ );
556
+ intersection = filtered.intersection;
557
+
558
+ if (intersection.length === 0) {
559
+ // Nothing to merge
560
+ return mergedSecond;
561
+ }
562
+
563
+ return buildMergedRule(
564
+ mergedFirst,
565
+ mergedSecond,
566
+ intersection,
567
+ filtered.claimedIndices,
568
+ ruleCache,
569
+ ruleMeta
570
+ );
485
571
  }
486
572
 
487
573
  /**
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ const data = require('../data/propertyGroups.json');
4
+
5
+ const vendorPrefixRegex = /^-\w+-/;
6
+ /**
7
+ * @param {string} prop
8
+ * @return {string}
9
+ */
10
+ function vendorUnprefixed(prop) {
11
+ return prop.replace(vendorPrefixRegex, '');
12
+ }
13
+
14
+ // The generated file is JSON, so its maps arrive as plain objects. Property
15
+ // names come from the stylesheet, and `constructor` or `toString` are things a
16
+ // declaration can be called, so never index those objects directly.
17
+ const knownProperties = new Set(data.properties);
18
+ const aliases = new Map(Object.entries(data.aliases));
19
+ const shorthands = new Map(Object.entries(data.shorthands));
20
+ const logicalGroups = new Map(Object.entries(data.logicalGroups));
21
+ const flowRelative = new Set(data.flowRelative);
22
+
23
+ const ALL = 'all';
24
+
25
+ /**
26
+ * An alias target, or the candidate itself if it's a known property outright.
27
+ *
28
+ * @param {string} candidate
29
+ * @return {string|undefined}
30
+ */
31
+ function lookupProperty(candidate) {
32
+ const alias = aliases.get(candidate);
33
+ if (alias !== undefined) {
34
+ return alias;
35
+ }
36
+ return knownProperties.has(candidate) ? candidate : undefined;
37
+ }
38
+
39
+ /**
40
+ * Resolves a property to the name the generated data knows it by: vendor
41
+ * prefixed spellings collapse onto the property they alias, and a prefix we
42
+ * have no data for is dropped, since moving `-webkit-background-clip` past a
43
+ * `background` shorthand is as unsafe as moving the unprefixed property. Webref
44
+ * lists some prefixed spellings, like `-webkit-user-select`, as properties in
45
+ * their own right with no alias back to the unprefixed one, so the unprefixed
46
+ * spelling is always tried first, not just when the prefixed one is unknown.
47
+ *
48
+ * @param {string} name Lowercased property name.
49
+ * @return {{name: string, known: boolean}}
50
+ */
51
+ function resolveProperty(name) {
52
+ if (name.startsWith('-')) {
53
+ const resolved = lookupProperty(vendorUnprefixed(name));
54
+ if (resolved !== undefined) {
55
+ return { name: resolved, known: true };
56
+ }
57
+ }
58
+ const resolved = lookupProperty(name);
59
+ return resolved !== undefined
60
+ ? { name: resolved, known: true }
61
+ : { name, known: false };
62
+ }
63
+
64
+ /**
65
+ * The longhands a property sets. A longhand sets only itself.
66
+ *
67
+ * @param {string} name
68
+ * @return {string[]}
69
+ */
70
+ function longhandsOf(name) {
71
+ return shorthands.get(name) ?? [name];
72
+ }
73
+
74
+ /**
75
+ * True if two longhands can be the same physical property. Beyond being the
76
+ * same property, that happens between the flow-relative and the physical
77
+ * members of a logical property group, since `margin-inline-start` is
78
+ * `margin-top` under a vertical writing mode. Two physical members, or two
79
+ * flow-relative ones, always address different sides.
80
+ *
81
+ * @param {string} a
82
+ * @param {string} b
83
+ * @return {boolean}
84
+ */
85
+ function isSameLonghand(a, b) {
86
+ if (a === b) {
87
+ return true;
88
+ }
89
+ const group = logicalGroups.get(a);
90
+ return (
91
+ group !== undefined &&
92
+ group === logicalGroups.get(b) &&
93
+ flowRelative.has(a) !== flowRelative.has(b)
94
+ );
95
+ }
96
+
97
+ /**
98
+ * The name-based approximation the plugin relied on before it had property
99
+ * data: two properties interact when they share their leading segment and
100
+ * their remaining segments either match or differ in number. `place` is
101
+ * treated as a wildcard leading segment, since `place-content` expands to
102
+ * `align-content`/`justify-content` and the like. Reached only for vendor
103
+ * extensions no spec describes, such as `-webkit-box-direction`.
104
+ *
105
+ * @param {string} nameA
106
+ * @param {string} nameB
107
+ * @return {boolean}
108
+ */
109
+ function conflictingSegments(nameA, nameB) {
110
+ const a = vendorUnprefixed(nameA).split('-');
111
+ const b = vendorUnprefixed(nameB).split('-');
112
+ if (a[0] !== b[0] && a[0] !== 'place' && b[0] !== 'place') {
113
+ return false;
114
+ }
115
+ if (a.length !== b.length) {
116
+ return true;
117
+ }
118
+ return a.every((segment, index) => b[index] === segment);
119
+ }
120
+
121
+ /**
122
+ * True if declarations of `propA` and `propB` can set the same underlying
123
+ * property, so that reordering them within a rule can change what the rule
124
+ * computes to. The relation is symmetric: a shorthand setting a longhand and a
125
+ * longhand overriding part of a shorthand are the same conflict seen from
126
+ * either end.
127
+ *
128
+ * @param {string} propA
129
+ * @param {string} propB
130
+ * @return {boolean}
131
+ */
132
+ function isConflictingProp(propA, propB) {
133
+ if (propA === propB) {
134
+ return true;
135
+ }
136
+ // Nothing sets a custom property except itself, and custom properties are
137
+ // case-sensitive, so this must run before the names are lowercased below.
138
+ if (propA.startsWith('--') || propB.startsWith('--')) {
139
+ return false;
140
+ }
141
+ const nameA = propA.toLowerCase();
142
+ const nameB = propB.toLowerCase();
143
+ if (nameA === nameB) {
144
+ return true;
145
+ }
146
+ if (nameA === ALL || nameB === ALL) {
147
+ const other = nameA === ALL ? nameB : nameA;
148
+ return other !== 'direction' && other !== 'unicode-bidi';
149
+ }
150
+ const a = resolveProperty(nameA);
151
+ const b = resolveProperty(nameB);
152
+ if (a.name === b.name) {
153
+ return true;
154
+ }
155
+ if (!a.known || !b.known) {
156
+ // A vendor extension the data says nothing about. The shorthand relations
157
+ // that would settle it are exactly what is missing, so fall back to
158
+ // comparing the names.
159
+ return conflictingSegments(a.name, b.name);
160
+ }
161
+ for (const longhandA of longhandsOf(a.name)) {
162
+ for (const longhandB of longhandsOf(b.name)) {
163
+ if (isSameLonghand(longhandA, longhandB)) {
164
+ return true;
165
+ }
166
+ }
167
+ }
168
+ return false;
169
+ }
170
+
171
+ module.exports = { isConflictingProp };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;AAII,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAQxC,OAAQ,KAAA,EAAC,WAAW,EAAO,MAAM,SAAS,CAAA;AAmD3C,YAAkB,QAAQ,GAC1B;;;;IAAqB,SAAS,EAAnB,MAAM,EAAE,CACnB;;;;IAA0B,YAAY,EAA3B,WAAW,EAAE,CACxB;;;;IAAoB,KAAK,EAAd,OAAO,CACpB;CAAA,CAAA;AAygBE,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAAqD,OAAO,GAAlD,mBAAmB,GAAG,mBAAmB,CAAS"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;AAII,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AASxC,OAAQ,KAAA,EAAC,WAAW,EAAO,MAAM,SAAS,CAAA;AAmD3C,YAAkB,QAAQ,GAC1B;;;;IAAqB,SAAS,EAAnB,MAAM,EAAE,CACnB;;;;IAA0B,YAAY,EAA3B,WAAW,EAAE,CACxB;;;;IAAoB,KAAK,EAAd,OAAO,CACpB;CAAA,CAAA;AA8lBE,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAAqD,OAAO,GAAlD,mBAAmB,GAAG,mBAAmB,CAAS"}
@@ -0,0 +1,17 @@
1
+ declare const _exports: {
2
+ isConflictingProp: typeof isConflictingProp;
3
+ };
4
+ export = _exports;
5
+ /**
6
+ * True if declarations of `propA` and `propB` can set the same underlying
7
+ * property, so that reordering them within a rule can change what the rule
8
+ * computes to. The relation is symmetric: a shorthand setting a longhand and a
9
+ * longhand overriding part of a shorthand are the same conflict seen from
10
+ * either end.
11
+ *
12
+ * @param {string} propA
13
+ * @param {string} propB
14
+ * @return {boolean}
15
+ */
16
+ declare function isConflictingProp(propA: string, propB: string): boolean;
17
+ //# sourceMappingURL=propertyRelations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propertyRelations.d.ts","sourceRoot":"","sources":["../../src/lib/propertyRelations.js"],"names":[],"mappings":";;;;AAwHA;;;;;;;;;;GAUG;AACH,iBAAS,iBAAiB,CAAC,KAAK,EAJrB,MAIqB,EAAE,KAAK,EAH5B,MAG4B,GAF3B,OAAO,CAuClB"}