import { type CssNode, clone, find, List, type Rule } from 'css-tree'; import { isPartInlinable } from './is-part-inlinable.js'; /** * Split a rule into inlinable and non-inlinable parts. Caller must only pass rules * for which isRuleInlinable(rule) is false. Returns clones so the original stylesheet is never mutated. * * @returns inlinablePart: rule with only inlinable block children, or null if none. * nonInlinablePart: rule with only non-inlinable block children, or null if none. */ export function splitMixedRule(rule: Rule): { inlinablePart: Rule | null; nonInlinablePart: Rule | null; } { // If the selector itself contains pseudo-selectors, every declaration in the // block only applies in that pseudo context — splitting would incorrectly // inline them as base styles. Return the whole rule as non-inlinable. const selectorHasPseudo = rule.prelude !== null && find( rule.prelude, (node) => node.type === 'PseudoClassSelector' || node.type === 'PseudoElementSelector', ) !== null; if (selectorHasPseudo) { return { inlinablePart: null, nonInlinablePart: clone(rule) as Rule }; } const ruleCloneInlinable = clone(rule) as Rule; const ruleCloneNonInlinable = clone(rule) as Rule; const inlinableParts: CssNode[] = []; const nonInlinableParts: CssNode[] = []; for (const part of ruleCloneInlinable.block.children.toArray()) { if (isPartInlinable(part)) { inlinableParts.push(part); } else { nonInlinableParts.push(part); } } const inlinablePart = inlinableParts.length > 0 ? { ...ruleCloneInlinable, block: { type: 'Block' as const, children: new List().fromArray(inlinableParts), }, } : null; const nonInlinablePart = nonInlinableParts.length > 0 ? { ...ruleCloneNonInlinable, block: { type: 'Block' as const, children: new List().fromArray(nonInlinableParts), }, } : null; return { inlinablePart, nonInlinablePart }; }