UNPKG

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