UNPKG

2.57 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-attribute';
16
17const messages = ruleMessages(ruleName, {
18 expected: (selector, max) =>
19 `Expected "${selector}" to have no more than ${max} attribute ${
20 max === 1 ? 'selector' : 'selectors'
21 }`,
22});
23
24function rule(max, options) {
25 return (root, result) => {
26 const validOptions = validateOptions(
27 result,
28 ruleName,
29 {
30 actual: max,
31 possible: [
32 function (max) {
33 return typeof max === 'number' && max >= 0;
34 },
35 ],
36 },
37 {
38 actual: options,
39 possible: {
40 ignoreAttributes: [_.isString, _.isRegExp],
41 },
42 optional: true,
43 },
44 );
45
46 if (!validOptions) {
47 return;
48 }
49
50 function checkSelector(selectorNode, ruleNode) {
51 const count = selectorNode.reduce((total, childNode) => {
52 // Only traverse inside actual selectors and context functional pseudo-classes
53 if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
54 checkSelector(childNode, ruleNode);
55 }
56
57 if (childNode.type !== 'attribute') {
58 // Not an attribute node -> ignore
59 return total;
60 }
61
62 if (optionsMatches(options, 'ignoreAttributes', childNode.attribute)) {
63 // it's an attribute that is supposed to be ignored
64 return total;
65 }
66
67 return (total += 1);
68 }, 0);
69
70 if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > max) {
71 report({
72 ruleName,
73 result,
74 node: ruleNode,
75 message: messages.expected(selectorNode, max),
76 word: selectorNode,
77 });
78 }
79 }
80
81 root.walkRules((ruleNode) => {
82 if (!isStandardSyntaxRule(ruleNode)) {
83 return;
84 }
85
86 ruleNode.selectors.forEach((selector) => {
87 resolvedNestedSelector(selector, ruleNode).forEach((resolvedSelector) => {
88 parseSelector(resolvedSelector, result, ruleNode, (container) =>
89 checkSelector(container, ruleNode),
90 );
91 });
92 });
93 });
94 };
95}
96
97rule.ruleName = ruleName;
98rule.messages = messages;
99module.exports = rule;