UNPKG

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