.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / rules / selector-class-pattern / index.js
1 "use strict";
2
3 const _ = require("lodash");
4 const isKeyframeSelector = require("../../utils/isKeyframeSelector");
5 const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
6 const isStandardSyntaxSelector = require("../../utils/isStandardSyntaxSelector");
7 const parseSelector = require("../../utils/parseSelector");
8 const report = require("../../utils/report");
9 const resolveNestedSelector = require("postcss-resolve-nested-selector");
10 const ruleMessages = require("../../utils/ruleMessages");
11 const validateOptions = require("../../utils/validateOptions");
12
13 const ruleName = "selector-class-pattern";
14
15 const messages = ruleMessages(ruleName, {
16   expected: selectorValue =>
17     `Expected class selector ".${selectorValue}" to match specified pattern`
18 });
19
20 const rule = function(pattern, options) {
21   return (root, result) => {
22     const validOptions = validateOptions(
23       result,
24       ruleName,
25       {
26         actual: pattern,
27         possible: [_.isRegExp, _.isString]
28       },
29       {
30         actual: options,
31         possible: {
32           resolveNestedSelectors: _.isBoolean
33         },
34         optional: true
35       }
36     );
37     if (!validOptions) {
38       return;
39     }
40
41     const shouldResolveNestedSelectors = _.get(
42       options,
43       "resolveNestedSelectors"
44     );
45     const normalizedPattern = _.isString(pattern)
46       ? new RegExp(pattern)
47       : pattern;
48
49     root.walkRules(rule => {
50       const selector = rule.selector;
51       const selectors = rule.selectors;
52
53       if (!isStandardSyntaxRule(rule)) {
54         return;
55       }
56       if (!isStandardSyntaxSelector(selector)) {
57         return;
58       }
59       if (selectors.some(s => isKeyframeSelector(s))) {
60         return;
61       }
62
63       // Only bother resolving selectors that have an interpolating &
64       if (shouldResolveNestedSelectors && hasInterpolatingAmpersand(selector)) {
65         resolveNestedSelector(selector, rule).forEach(selector => {
66           if (!isStandardSyntaxSelector(selector)) {
67             return;
68           }
69
70           parseSelector(selector, result, rule, s => checkSelector(s, rule));
71         });
72       } else {
73         parseSelector(selector, result, rule, s => checkSelector(s, rule));
74       }
75     });
76
77     function checkSelector(fullSelector, rule) {
78       fullSelector.walkClasses(classNode => {
79         const value = classNode.value;
80         const sourceIndex = classNode.sourceIndex;
81
82         if (normalizedPattern.test(value)) {
83           return;
84         }
85         report({
86           result,
87           ruleName,
88           message: messages.expected(value),
89           node: rule,
90           index: sourceIndex
91         });
92       });
93     }
94   };
95 };
96
97 // An "interpolating ampersand" means an "&" used to interpolate
98 // within another simple selector, rather than an "&" that
99 // stands on its own as a simple selector
100 function hasInterpolatingAmpersand(selector) {
101   for (let i = 0, l = selector.length; i < l; i++) {
102     if (selector[i] !== "&") {
103       continue;
104     }
105     if (!_.isUndefined(selector[i - 1]) && !isCombinator(selector[i - 1])) {
106       return true;
107     }
108     if (!_.isUndefined(selector[i + 1]) && !isCombinator(selector[i + 1])) {
109       return true;
110     }
111   }
112   return false;
113 }
114
115 function isCombinator(x) {
116   return /[\s+>~]/.test(x);
117 }
118
119 rule.ruleName = ruleName;
120 rule.messages = messages;
121 module.exports = rule;