| import { resolveNestedSelector } from '@csstools/selector-resolve-nested'; |
| |
| import { isAtRule, isRule } from './typeGuards.mjs'; |
| import { atRuleRegexes } from './regexes.mjs'; |
| import getRuleSelector from './getRuleSelector.mjs'; |
| import isStandardSyntaxRule from './isStandardSyntaxRule.mjs'; |
| import parseSelector from './parseSelector.mjs'; |
| import selectorParser from 'postcss-selector-parser'; |
| |
| /** @import { Selector, Root as SelectorRoot } from 'postcss-selector-parser' */ |
| /** @import { Rule, Node } from 'postcss' */ |
| /** @import { PostcssResult } from 'stylelint' */ |
| |
| /** @type {WeakMap<Rule, Map<PostcssResult, Array<ResolvedSelector>>>} */ |
| const cache = new WeakMap(); |
| |
| /** |
| * Resolve nested selectors following the CSS nesting specification |
| * |
| * @typedef {{selector: Selector, resolvedSelectors: SelectorRoot, nested: boolean}} ResolvedSelector |
| * |
| * @param {Rule} rule |
| * @param {PostcssResult} result |
| * @returns {Array<ResolvedSelector>} |
| */ |
| export default function resolveNestedSelectorsForRule(rule, result) { |
| let ruleCache = cache.get(rule); |
| |
| if (ruleCache) { |
| const cachedResolved = ruleCache.get(result); |
| |
| if (cachedResolved) return cachedResolved; |
| } else { |
| ruleCache = new Map(); |
| cache.set(rule, ruleCache); |
| } |
| |
| const ownAST = parseSelector(getRuleSelector(rule), result, rule); |
| |
| if (!ownAST) return []; |
| |
| /** @type {SelectorRoot | undefined} */ |
| let ast = undefined; |
| |
| /** @type {Array<Node>} */ |
| const ancestors = []; |
| |
| { |
| /** @type {Node['parent']} */ |
| let parent = rule.parent; |
| |
| while (parent) { |
| ancestors.push(parent); |
| parent = parent.parent; |
| } |
| } |
| |
| ancestors.reverse(); |
| |
| for (const child of ancestors) { |
| if (isAtRule(child) && atRuleRegexes.scopeName.test(child.name)) { |
| ast = parseSelector(':where(:scope)', result, child); |
| continue; |
| } |
| |
| if (!isRule(child)) continue; |
| |
| if (!isStandardSyntaxRule(child)) return []; |
| |
| const childAST = parseSelector(getRuleSelector(child), result, child); |
| |
| if (!childAST) return []; |
| |
| if (ast) { |
| ast = resolveNestedSelector(childAST, ast); |
| } else { |
| ast = childAST; |
| } |
| } |
| |
| const resolved = ownAST.map((selector) => { |
| if (!ast) { |
| return { |
| selector, |
| resolvedSelectors: selectorParser.root({ |
| value: '', |
| nodes: [selector], |
| }), |
| nested: false, |
| }; |
| } |
| |
| return { |
| selector, |
| resolvedSelectors: resolveNestedSelector( |
| selectorParser.root({ |
| nodes: [selector], |
| value: '', |
| }), |
| ast, |
| ), |
| nested: true, |
| }; |
| }); |
| |
| ruleCache.set(result, resolved); |
| |
| return resolved; |
| } |