UNPKG

2.65 kBJavaScriptView Raw
1'use strict';
2
3const isNonNegativeInteger = require('../../utils/isNonNegativeInteger');
4const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
5const parseSelector = require('../../utils/parseSelector');
6const report = require('../../utils/report');
7const resolvedNestedSelector = require('postcss-resolve-nested-selector');
8const ruleMessages = require('../../utils/ruleMessages');
9const selectorParser = require('postcss-selector-parser');
10const validateOptions = require('../../utils/validateOptions');
11
12const ruleName = 'selector-max-universal';
13
14const messages = ruleMessages(ruleName, {
15 expected: (selector, max) =>
16 `Expected "${selector}" to have no more than ${max} universal ${
17 max === 1 ? 'selector' : 'selectors'
18 }`,
19});
20
21const meta = {
22 url: 'https://stylelint.io/user-guide/rules/list/selector-max-universal',
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 * @param {import('postcss-selector-parser').Container<unknown>} selectorNode
39 * @param {import('postcss').Rule} ruleNode
40 */
41 function checkSelector(selectorNode, ruleNode) {
42 const count = selectorNode.reduce((total, childNode) => {
43 // Only traverse inside actual selectors
44 // All logical combinations will be resolved as nested selector in `postcss-resolve-nested-selector`
45 if (childNode.type === 'selector') {
46 checkSelector(childNode, ruleNode);
47 }
48
49 if (childNode.type === 'universal') total += 1;
50
51 return total;
52 }, 0);
53
54 if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
55 const selector = selectorNode.toString();
56
57 report({
58 ruleName,
59 result,
60 node: ruleNode,
61 message: messages.expected(selector, primary),
62 word: selector,
63 });
64 }
65 }
66
67 root.walkRules((ruleNode) => {
68 if (!isStandardSyntaxRule(ruleNode)) {
69 return;
70 }
71
72 /** @type {string[]} */
73 const selectors = [];
74
75 selectorParser()
76 .astSync(ruleNode.selector)
77 .walk((node) => {
78 if (node.type === 'selector') {
79 selectors.push(String(node).trim());
80 }
81 });
82
83 for (const selector of selectors) {
84 for (const resolvedSelector of resolvedNestedSelector(selector, ruleNode)) {
85 parseSelector(resolvedSelector, result, ruleNode, (container) =>
86 checkSelector(container, ruleNode),
87 );
88 }
89 }
90 });
91 };
92};
93
94rule.ruleName = ruleName;
95rule.messages = messages;
96rule.meta = meta;
97module.exports = rule;