.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / rules / selector-max-specificity / index.js
1 "use strict";
2
3 const _ = require("lodash");
4 const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
5 const isStandardSyntaxSelector = require("../../utils/isStandardSyntaxSelector");
6 const optionsMatches = require("../../utils/optionsMatches");
7 const parseSelector = require("../../utils/parseSelector");
8 const report = require("../../utils/report");
9 const resolvedNestedSelector = require("postcss-resolve-nested-selector");
10 const ruleMessages = require("../../utils/ruleMessages");
11 const specificity = require("specificity");
12 const validateOptions = require("../../utils/validateOptions");
13
14 const ruleName = "selector-max-specificity";
15
16 const messages = ruleMessages(ruleName, {
17   expected: (selector, specificity) =>
18     `Expected "${selector}" to have a specificity no more than "${specificity}"`
19 });
20
21 // Return an array representation of zero specificity. We need a new array each time so that it can mutated
22 const zeroSpecificity = () => [0, 0, 0, 0];
23
24 // Calculate the sum of given array of specificity arrays
25 const specificitySum = specificities => {
26   const sum = zeroSpecificity();
27   specificities.forEach(specificityArray => {
28     specificityArray.forEach((value, i) => {
29       sum[i] += value;
30     });
31   });
32   return sum;
33 };
34
35 const rule = function(max, options) {
36   return (root, result) => {
37     const validOptions = validateOptions(
38       result,
39       ruleName,
40       {
41         actual: max,
42         possible: [
43           function(max) {
44             // Check that the max specificity is in the form "a,b,c"
45             const pattern = new RegExp("^\\d+,\\d+,\\d+$");
46             return pattern.test(max);
47           }
48         ]
49       },
50       {
51         actual: options,
52         possible: {
53           ignoreSelectors: [_.isString]
54         },
55         optional: true
56       }
57     );
58     if (!validOptions) {
59       return;
60     }
61
62     // Calculate the specificity of a simple selector (type, attribute, class, id, or pseudos's own value)
63     const simpleSpecificity = selector => {
64       if (optionsMatches(options, "ignoreSelectors", selector)) {
65         return zeroSpecificity();
66       }
67       return specificity.calculate(selector)[0].specificityArray;
68     };
69
70     // Calculate the the specificity of the most specific direct child
71     const maxChildSpecificity = node =>
72       node.reduce((max, child) => {
73         const childSpecificity = nodeSpecificity(child); // eslint-disable-line no-use-before-define
74         return specificity.compare(childSpecificity, max) === 1
75           ? childSpecificity
76           : max;
77       }, zeroSpecificity());
78
79     // Calculate the specificity of a pseudo selector including own value and children
80     const pseudoSpecificity = node => {
81       // `node.toString()` includes children which should be processed separately,
82       // so use `node.value` instead
83       const ownValue = node.value;
84       const ownSpecificity =
85         ownValue === ":not" || ownValue === ":matches"
86           ? // :not and :matches don't add specificity themselves, but their children do
87             zeroSpecificity()
88           : simpleSpecificity(ownValue);
89
90       return specificitySum([ownSpecificity, maxChildSpecificity(node)]);
91     };
92
93     // Calculate the specificity of a node parsed by `postcss-selector-parser`
94     const nodeSpecificity = node => {
95       switch (node.type) {
96         case "attribute":
97         case "class":
98         case "id":
99         case "tag":
100           return simpleSpecificity(node.toString());
101         case "pseudo":
102           return pseudoSpecificity(node);
103         case "selector":
104           // Calculate the sum of all the direct children
105           return specificitySum(node.map(nodeSpecificity));
106         default:
107           return zeroSpecificity();
108       }
109     };
110
111     const maxSpecificityArray = ("0," + max).split(",").map(parseFloat);
112     root.walkRules(rule => {
113       if (!isStandardSyntaxRule(rule)) {
114         return;
115       }
116       if (!isStandardSyntaxSelector(rule.selector)) {
117         return;
118       }
119       // Using rule.selectors gets us each selector in the eventuality we have a comma separated set
120       rule.selectors.forEach(selector => {
121         resolvedNestedSelector(selector, rule).forEach(resolvedSelector => {
122           try {
123             // Skip non-standard syntax selectors
124             if (!isStandardSyntaxSelector(resolvedSelector)) {
125               return;
126             }
127             parseSelector(resolvedSelector, result, rule, selectorTree => {
128               // Check if the selector specificity exceeds the allowed maximum
129               if (
130                 specificity.compare(
131                   maxChildSpecificity(selectorTree),
132                   maxSpecificityArray
133                 ) === 1
134               ) {
135                 report({
136                   ruleName,
137                   result,
138                   node: rule,
139                   message: messages.expected(resolvedSelector, max),
140                   word: selector
141                 });
142               }
143             });
144           } catch (e) {
145             result.warn("Cannot parse selector", {
146               node: rule,
147               stylelintType: "parseError"
148             });
149           }
150         });
151       });
152     });
153   };
154 };
155
156 rule.ruleName = ruleName;
157 rule.messages = messages;
158 module.exports = rule;