UNPKG

2.79 kBJavaScriptView Raw
1'use strict';
2
3const isContextFunctionalPseudoClass = require('../../utils/isContextFunctionalPseudoClass');
4const isNonNegativeInteger = require('../../utils/isNonNegativeInteger');
5const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
6const parseSelector = require('../../utils/parseSelector');
7const report = require('../../utils/report');
8const resolvedNestedSelector = require('postcss-resolve-nested-selector');
9const ruleMessages = require('../../utils/ruleMessages');
10const validateOptions = require('../../utils/validateOptions');
11
12const ruleName = 'selector-max-compound-selectors';
13
14const messages = ruleMessages(ruleName, {
15 expected: (selector, max) =>
16 `Expected "${selector}" to have no more than ${max} compound ${
17 max === 1 ? 'selector' : 'selectors'
18 }`,
19});
20
21const meta = {
22 url: 'https://stylelint.io/user-guide/rules/list/selector-max-compound-selectors',
23};
24
25/** @type {import('stylelint').Rule} */
26const rule = (primary) => {
27 return (root, result) => {
28 const validOptions = validateOptions(result, ruleName, {
29 actual: primary,
30 possible: isNonNegativeInteger,
31 });
32
33 if (!validOptions) {
34 return;
35 }
36
37 /**
38 * Finds actual selectors in selectorNode object and checks them.
39 *
40 * @param {import('postcss-selector-parser').Container<unknown>} selectorNode
41 * @param {import('postcss').Rule} ruleNode
42 */
43 function checkSelector(selectorNode, ruleNode) {
44 let compoundCount = 1;
45
46 selectorNode.each((childNode) => {
47 // Only traverse inside actual selectors and context functional pseudo-classes
48 if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
49 checkSelector(childNode, ruleNode);
50 }
51
52 // Compound selectors are separated by combinators, so increase count when meeting one
53 if (childNode.type === 'combinator') {
54 compoundCount++;
55 }
56 });
57
58 if (
59 selectorNode.type !== 'root' &&
60 selectorNode.type !== 'pseudo' &&
61 compoundCount > primary
62 ) {
63 const selector = selectorNode.toString();
64
65 report({
66 ruleName,
67 result,
68 node: ruleNode,
69 message: messages.expected(selector, primary),
70 word: selector,
71 });
72 }
73 }
74
75 root.walkRules((ruleNode) => {
76 if (!isStandardSyntaxRule(ruleNode)) {
77 return;
78 }
79
80 // Using `.selectors` gets us each selector if there is a comma separated set
81 for (const selector of ruleNode.selectors) {
82 for (const resolvedSelector of resolvedNestedSelector(selector, ruleNode)) {
83 // Process each resolved selector with `checkSelector` via postcss-selector-parser
84 parseSelector(resolvedSelector, result, ruleNode, (s) => checkSelector(s, ruleNode));
85 }
86 }
87 });
88 };
89};
90
91rule.ruleName = ruleName;
92rule.messages = messages;
93rule.meta = meta;
94module.exports = rule;