UNPKG

2.67 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const _ = require('lodash');
6const isContextFunctionalPseudoClass = require('../../utils/isContextFunctionalPseudoClass');
7const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
8const optionsMatches = require('../../utils/optionsMatches');
9const parseSelector = require('../../utils/parseSelector');
10const report = require('../../utils/report');
11const resolvedNestedSelector = require('postcss-resolve-nested-selector');
12const ruleMessages = require('../../utils/ruleMessages');
13const validateOptions = require('../../utils/validateOptions');
14
15const ruleName = 'selector-max-id';
16
17const messages = ruleMessages(ruleName, {
18 expected: (selector, max) =>
19 `Expected "${selector}" to have no more than ${max} ID ${max === 1 ? 'selector' : 'selectors'}`,
20});
21
22function rule(max, options) {
23 return (root, result) => {
24 const validOptions = validateOptions(
25 result,
26 ruleName,
27 {
28 actual: max,
29 possible: [
30 function (max) {
31 return typeof max === 'number' && max >= 0;
32 },
33 ],
34 },
35 {
36 actual: options,
37 possible: {
38 ignoreContextFunctionalPseudoClasses: [_.isString, _.isRegExp],
39 },
40 optional: true,
41 },
42 );
43
44 if (!validOptions) {
45 return;
46 }
47
48 function checkSelector(selectorNode, ruleNode) {
49 const count = selectorNode.reduce((total, childNode) => {
50 // Only traverse inside actual selectors and context functional pseudo-classes that are not part of ignored functional pseudo-classes
51 if (
52 childNode.type === 'selector' ||
53 (isContextFunctionalPseudoClass(childNode) &&
54 !isIgnoredContextFunctionalPseudoClass(childNode, options))
55 ) {
56 checkSelector(childNode, ruleNode);
57 }
58
59 return (total += childNode.type === 'id' ? 1 : 0);
60 }, 0);
61
62 if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > max) {
63 report({
64 ruleName,
65 result,
66 node: ruleNode,
67 message: messages.expected(selectorNode, max),
68 word: selectorNode,
69 });
70 }
71 }
72
73 function isIgnoredContextFunctionalPseudoClass(node, options) {
74 return (
75 node.type === 'pseudo' &&
76 optionsMatches(options, 'ignoreContextFunctionalPseudoClasses', node.value)
77 );
78 }
79
80 root.walkRules((ruleNode) => {
81 if (!isStandardSyntaxRule(ruleNode)) {
82 return;
83 }
84
85 ruleNode.selectors.forEach((selector) => {
86 resolvedNestedSelector(selector, ruleNode).forEach((resolvedSelector) => {
87 parseSelector(resolvedSelector, result, ruleNode, (container) =>
88 checkSelector(container, ruleNode),
89 );
90 });
91 });
92 });
93 };
94}
95
96rule.ruleName = ruleName;
97rule.messages = messages;
98module.exports = rule;