blob: 68a0d7fae477e9b144b5cb3bf434cd59d03b41a8 [file] [edit]
import { isConflictingProp } from './propertyRelations.js';
/** @import {Declaration} from 'postcss' */
/**
* @param {Declaration} a
* @param {Declaration} b
* @return {boolean}
*/
function declarationIsEqual(a, b) {
return (
a.important === b.important && a.prop === b.prop && a.value === b.value
);
}
/**
* @param {Declaration[]} array
* @param {Declaration} decl
* @return {number}
*/
export function indexOfDeclaration(array, decl) {
return array.findIndex((d) => declarationIsEqual(d, decl));
}
/**
* @param {Declaration[]} a
* @param {Declaration[]} b
* @param {boolean} [not=false]
* @return {Declaration[]}
*/
export function intersect(a, b, not) {
return a.filter((c) => {
const index = indexOfDeclaration(b, c) !== -1;
return not ? !index : index;
});
}
/**
* @param {Declaration[]} a
* @param {Declaration[]} b
* @return {boolean}
*/
export function sameDeclarationsAndOrder(a, b) {
if (a.length !== b.length) {
return false;
}
return a.every((d, index) => declarationIsEqual(d, b[index]));
}
/**
* @param {Declaration} candidate
* @param {number} candidateIndex
* @param {Declaration[]} hoistCandidates
* @param {Declaration[]} earlierRuleDeclarations
* @return {boolean}
*/
function hoistingPreservesOverrideOrder(
candidate,
candidateIndex,
hoistCandidates,
earlierRuleDeclarations
) {
const indexInEarlierRule = indexOfDeclaration(
earlierRuleDeclarations,
candidate
);
const overridesInEarlierRule = earlierRuleDeclarations
.slice(indexInEarlierRule + 1)
.filter((d) => isConflictingProp(d.prop, candidate.prop));
if (overridesInEarlierRule.length === 0) {
return true;
}
const overridesAmongCandidates = hoistCandidates
.slice(candidateIndex + 1)
.filter((d) => isConflictingProp(d.prop, candidate.prop));
if (overridesInEarlierRule.length !== overridesAmongCandidates.length) {
return false;
}
return overridesInEarlierRule.every((d, index) =>
declarationIsEqual(d, overridesAmongCandidates[index])
);
}
/**
* @param {Declaration} candidate
* @param {Declaration[]} laterDeclarations
* @param {Set<number>} claimedIndices
* @return {boolean}
*/
function claimMatchInLaterRule(candidate, laterDeclarations, claimedIndices) {
const matchIndex = laterDeclarations.findIndex(
(d, index) =>
!claimedIndices.has(index) && isConflictingProp(d.prop, candidate.prop)
);
if (matchIndex === -1) {
return false;
}
if (!declarationIsEqual(laterDeclarations[matchIndex], candidate)) {
return false;
}
if (
candidate.prop.toLowerCase() !== 'direction' &&
candidate.prop.toLowerCase() !== 'unicode-bidi' &&
laterDeclarations.some(
(declaration) => declaration.prop.toLowerCase() === 'all'
)
) {
return false;
}
claimedIndices.add(matchIndex);
return true;
}
/**
* @param {Declaration[]} hoistCandidates
* @param {Declaration[]} earlierRuleDeclarations
* @param {Declaration[]} laterRuleDeclarations
* @return {{intersection: Declaration[], claimedIndices: Set<number>}}
*/
export function filterRuleIntersections(
hoistCandidates,
earlierRuleDeclarations,
laterRuleDeclarations
) {
let remainingCandidates = hoistCandidates;
for (;;) {
const claimedIndices = new Set();
const survivors = remainingCandidates.filter(
(candidate, candidateIndex) =>
hoistingPreservesOverrideOrder(
candidate,
candidateIndex,
remainingCandidates,
earlierRuleDeclarations
) &&
claimMatchInLaterRule(candidate, laterRuleDeclarations, claimedIndices)
);
if (
survivors.length === remainingCandidates.length ||
survivors.length === 0
) {
return { intersection: survivors, claimedIndices };
}
remainingCandidates = survivors;
}
}