3 const _ = require("lodash");
4 const isKeyframeSelector = require("../../utils/isKeyframeSelector");
5 const isOnlyWhitespace = require("../../utils/isOnlyWhitespace");
6 const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
7 const isStandardSyntaxSelector = require("../../utils/isStandardSyntaxSelector");
8 const optionsMatches = require("../../utils/optionsMatches");
9 const parseSelector = require("../../utils/parseSelector");
10 const report = require("../../utils/report");
11 const resolvedNestedSelector = require("postcss-resolve-nested-selector");
12 const ruleMessages = require("../../utils/ruleMessages");
13 const validateOptions = require("../../utils/validateOptions");
15 const ruleName = "selector-max-type";
17 const messages = ruleMessages(ruleName, {
18 expected: (selector, max) =>
19 `Expected "${selector}" to have no more than ${max} type ${
20 max === 1 ? "selector" : "selectors"
24 function rule(max, options) {
25 return (root, result) => {
26 const validOptions = validateOptions(
32 return typeof max === "number" && max >= 0;
38 ignore: ["descendant", "child", "compounded"],
39 ignoreTypes: [_.isString]
48 const ignoreDescendant = optionsMatches(options, "ignore", "descendant");
49 const ignoreChild = optionsMatches(options, "ignore", "child");
50 const ignoreCompounded = optionsMatches(options, "ignore", "compounded");
52 function checkSelector(selectorNode, ruleNode) {
53 const count = selectorNode.reduce((total, childNode) => {
54 // Only traverse inside actual selectors and :not()
55 if (childNode.type === "selector" || childNode.value === ":not") {
56 checkSelector(childNode, ruleNode);
59 if (optionsMatches(options, "ignoreTypes", childNode.value)) {
63 if (ignoreDescendant && hasDescendantCombinatorBefore(childNode)) {
67 if (ignoreChild && hasChildCombinatorBefore(childNode)) {
71 if (ignoreCompounded && hasCompoundSelector(childNode)) {
75 return (total += childNode.type === "tag" ? 1 : 0);
79 selectorNode.type !== "root" &&
80 selectorNode.type !== "pseudo" &&
87 message: messages.expected(selectorNode, max),
93 root.walkRules(ruleNode => {
94 const selector = ruleNode.selector,
95 selectors = ruleNode.selectors;
97 if (!isStandardSyntaxRule(ruleNode)) {
100 if (!isStandardSyntaxSelector(selector)) {
103 if (selectors.some(s => isKeyframeSelector(s))) {
108 node => ["rule", "atrule"].indexOf(node.type) !== -1
111 // Skip unresolved nested selectors
115 ruleNode.selectors.forEach(selector => {
116 resolvedNestedSelector(selector, ruleNode).forEach(resolvedSelector => {
117 if (!isStandardSyntaxSelector(resolvedSelector)) {
120 parseSelector(resolvedSelector, result, ruleNode, container =>
121 checkSelector(container, ruleNode)
129 function hasDescendantCombinatorBefore(node) {
130 const nodeIndex = node.parent.nodes.indexOf(node);
131 return node.parent.nodes.slice(0, nodeIndex).some(isDescendantCombinator);
134 function hasChildCombinatorBefore(node) {
135 const nodeIndex = node.parent.nodes.indexOf(node);
136 return node.parent.nodes.slice(0, nodeIndex).some(isChildCombinator);
139 function hasCompoundSelector(node) {
140 if (node.prev() && !isCombinator(node.prev())) {
143 if (node.next() && !isCombinator(node.next())) {
149 function isCombinator(node) {
150 if (!node) return false;
151 return _.get(node, "type") === "combinator";
154 function isDescendantCombinator(node) {
155 if (!node) return false;
156 return isCombinator(node) && isOnlyWhitespace(node.value);
159 function isChildCombinator(node) {
160 if (!node) return false;
161 return isCombinator(node) && node.value.includes(">");
164 rule.ruleName = ruleName;
165 rule.messages = messages;
166 module.exports = rule;