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");
13 const ruleName = "selector-class-pattern";
15 const messages = ruleMessages(ruleName, {
16 expected: selectorValue =>
17 `Expected class selector ".${selectorValue}" to match specified pattern`
20 const rule = function(pattern, options) {
21 return (root, result) => {
22 const validOptions = validateOptions(
27 possible: [_.isRegExp, _.isString]
32 resolveNestedSelectors: _.isBoolean
41 const shouldResolveNestedSelectors = _.get(
43 "resolveNestedSelectors"
45 const normalizedPattern = _.isString(pattern)
49 root.walkRules(rule => {
50 const selector = rule.selector;
51 const selectors = rule.selectors;
53 if (!isStandardSyntaxRule(rule)) {
56 if (!isStandardSyntaxSelector(selector)) {
59 if (selectors.some(s => isKeyframeSelector(s))) {
63 // Only bother resolving selectors that have an interpolating &
64 if (shouldResolveNestedSelectors && hasInterpolatingAmpersand(selector)) {
65 resolveNestedSelector(selector, rule).forEach(selector => {
66 if (!isStandardSyntaxSelector(selector)) {
70 parseSelector(selector, result, rule, s => checkSelector(s, rule));
73 parseSelector(selector, result, rule, s => checkSelector(s, rule));
77 function checkSelector(fullSelector, rule) {
78 fullSelector.walkClasses(classNode => {
79 const value = classNode.value;
80 const sourceIndex = classNode.sourceIndex;
82 if (normalizedPattern.test(value)) {
88 message: messages.expected(value),
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] !== "&") {
105 if (!_.isUndefined(selector[i - 1]) && !isCombinator(selector[i - 1])) {
108 if (!_.isUndefined(selector[i + 1]) && !isCombinator(selector[i + 1])) {
115 function isCombinator(x) {
116 return /[\s+>~]/.test(x);
119 rule.ruleName = ruleName;
120 rule.messages = messages;
121 module.exports = rule;