.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / rules / selector-max-type / index.js
1 "use strict";
2
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");
14
15 const ruleName = "selector-max-type";
16
17 const messages = ruleMessages(ruleName, {
18   expected: (selector, max) =>
19     `Expected "${selector}" to have no more than ${max} type ${
20       max === 1 ? "selector" : "selectors"
21     }`
22 });
23
24 function rule(max, options) {
25   return (root, result) => {
26     const validOptions = validateOptions(
27       result,
28       ruleName,
29       {
30         actual: max,
31         possible(max) {
32           return typeof max === "number" && max >= 0;
33         }
34       },
35       {
36         actual: options,
37         possible: {
38           ignore: ["descendant", "child", "compounded"],
39           ignoreTypes: [_.isString]
40         },
41         optional: true
42       }
43     );
44     if (!validOptions) {
45       return;
46     }
47
48     const ignoreDescendant = optionsMatches(options, "ignore", "descendant");
49     const ignoreChild = optionsMatches(options, "ignore", "child");
50     const ignoreCompounded = optionsMatches(options, "ignore", "compounded");
51
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);
57         }
58
59         if (optionsMatches(options, "ignoreTypes", childNode.value)) {
60           return total;
61         }
62
63         if (ignoreDescendant && hasDescendantCombinatorBefore(childNode)) {
64           return total;
65         }
66
67         if (ignoreChild && hasChildCombinatorBefore(childNode)) {
68           return total;
69         }
70
71         if (ignoreCompounded && hasCompoundSelector(childNode)) {
72           return total;
73         }
74
75         return (total += childNode.type === "tag" ? 1 : 0);
76       }, 0);
77
78       if (
79         selectorNode.type !== "root" &&
80         selectorNode.type !== "pseudo" &&
81         count > max
82       ) {
83         report({
84           ruleName,
85           result,
86           node: ruleNode,
87           message: messages.expected(selectorNode, max),
88           word: selectorNode
89         });
90       }
91     }
92
93     root.walkRules(ruleNode => {
94       const selector = ruleNode.selector,
95         selectors = ruleNode.selectors;
96
97       if (!isStandardSyntaxRule(ruleNode)) {
98         return;
99       }
100       if (!isStandardSyntaxSelector(selector)) {
101         return;
102       }
103       if (selectors.some(s => isKeyframeSelector(s))) {
104         return;
105       }
106       if (
107         ruleNode.nodes.some(
108           node => ["rule", "atrule"].indexOf(node.type) !== -1
109         )
110       ) {
111         // Skip unresolved nested selectors
112         return;
113       }
114
115       ruleNode.selectors.forEach(selector => {
116         resolvedNestedSelector(selector, ruleNode).forEach(resolvedSelector => {
117           if (!isStandardSyntaxSelector(resolvedSelector)) {
118             return;
119           }
120           parseSelector(resolvedSelector, result, ruleNode, container =>
121             checkSelector(container, ruleNode)
122           );
123         });
124       });
125     });
126   };
127 }
128
129 function hasDescendantCombinatorBefore(node) {
130   const nodeIndex = node.parent.nodes.indexOf(node);
131   return node.parent.nodes.slice(0, nodeIndex).some(isDescendantCombinator);
132 }
133
134 function hasChildCombinatorBefore(node) {
135   const nodeIndex = node.parent.nodes.indexOf(node);
136   return node.parent.nodes.slice(0, nodeIndex).some(isChildCombinator);
137 }
138
139 function hasCompoundSelector(node) {
140   if (node.prev() && !isCombinator(node.prev())) {
141     return true;
142   }
143   if (node.next() && !isCombinator(node.next())) {
144     return true;
145   }
146   return false;
147 }
148
149 function isCombinator(node) {
150   if (!node) return false;
151   return _.get(node, "type") === "combinator";
152 }
153
154 function isDescendantCombinator(node) {
155   if (!node) return false;
156   return isCombinator(node) && isOnlyWhitespace(node.value);
157 }
158
159 function isChildCombinator(node) {
160   if (!node) return false;
161   return isCombinator(node) && node.value.includes(">");
162 }
163
164 rule.ruleName = ruleName;
165 rule.messages = messages;
166 module.exports = rule;