UNPKG

stylelint

Version:

A mighty CSS linter that helps you avoid errors and enforce conventions.

110 lines (89 loc) 2.8 kB
import { parse, walk } from 'css-tree'; import { aNPlusBNotationPseudoClasses, aNPlusBOfSNotationPseudoClasses, } from '../../reference/selectors.mjs'; import { assert } from '../../utils/validateTypes.mjs'; import getRuleSelector from '../../utils/getRuleSelector.mjs'; import isStandardSyntaxRule from '../../utils/isStandardSyntaxRule.mjs'; import { mayIncludeRegexes } from '../../utils/regexes.mjs'; import report from '../../utils/report.mjs'; import ruleMessages from '../../utils/ruleMessages.mjs'; import validateOptions from '../../utils/validateOptions.mjs'; const ruleName = 'selector-anb-no-unmatchable'; const messages = ruleMessages(ruleName, { rejected: (pseudoClass) => `Unmatchable An+B selector "${pseudoClass}"`, }); const meta = { url: 'https://stylelint.io/user-guide/rules/selector-anb-no-unmatchable', }; function isUnmatchableNth(/** @type {import('css-tree').AnPlusB} */ nth) { const { a, b } = nth; if (a !== null && a !== '0' && a !== '-0') { return false; } if (b !== null && b !== '0' && b !== '-0') { return false; } return true; } /** @type {import('stylelint').CoreRules[ruleName]} */ const rule = (primary) => { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: primary }); if (!validOptions) { return; } root.walkRules(mayIncludeRegexes.aNPlusBNotationPseudoClass, (ruleNode) => { if (!isStandardSyntaxRule(ruleNode)) return; const ruleSelector = getRuleSelector(ruleNode); let cssTreeSelectors; try { cssTreeSelectors = parse(ruleSelector, { context: 'selectorList', positions: true, }); } catch { return; } walk(cssTreeSelectors, { visit: 'PseudoClassSelector', enter(pseudoClassSelector) { if ( !aNPlusBNotationPseudoClasses.has(pseudoClassSelector.name) && !aNPlusBOfSNotationPseudoClasses.has(pseudoClassSelector.name) ) { return; } if (pseudoClassSelector.children === null) { return; } pseudoClassSelector.children.forEach((child) => { if (child.type !== 'Nth' || child.nth.type !== 'AnPlusB') { return; } // `loc` is expected to be present when calling `parse()` with `{ positions: true }` assert(pseudoClassSelector.loc); const index = pseudoClassSelector.loc.start.offset; const endIndex = pseudoClassSelector.loc.end.offset; if (isUnmatchableNth(child.nth)) { report({ message: messages.rejected, messageArgs: [ruleSelector.slice(index, endIndex)], node: ruleNode, index, endIndex, result, ruleName, }); } }); }, }); }); }; }; rule.ruleName = ruleName; rule.messages = messages; rule.meta = meta; export default rule;