UNPKG

3.07 kBJavaScriptView Raw
1'use strict';
2
3const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
4const parseSelector = require('../../utils/parseSelector');
5const punctuationSets = require('../../reference/punctuationSets');
6const report = require('../../utils/report');
7const ruleMessages = require('../../utils/ruleMessages');
8const validateOptions = require('../../utils/validateOptions');
9
10const ruleName = 'selector-descendant-combinator-no-non-space';
11
12const messages = ruleMessages(ruleName, {
13 rejected: (nonSpaceCharacter) => `Unexpected "${nonSpaceCharacter}"`,
14});
15
16function rule(expectation, options, context) {
17 return (root, result) => {
18 const validOptions = validateOptions(result, ruleName, {
19 actual: expectation,
20 });
21
22 if (!validOptions) {
23 return;
24 }
25
26 root.walkRules((rule) => {
27 if (!isStandardSyntaxRule(rule)) {
28 return;
29 }
30
31 let hasFixed = false;
32 const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector;
33
34 const fixedSelector = parseSelector(selector, result, rule, (fullSelector) => {
35 fullSelector.walkCombinators((combinatorNode) => {
36 if (!isActuallyCombinator(combinatorNode)) {
37 return;
38 }
39
40 const value = combinatorNode.value;
41
42 if (punctuationSets.nonSpaceCombinators.has(value)) {
43 return;
44 }
45
46 if (
47 value.includes(' ') ||
48 value.includes('\t') ||
49 value.includes('\n') ||
50 value.includes('\r')
51 ) {
52 if (context.fix && /^\s+$/.test(value)) {
53 hasFixed = true;
54 combinatorNode.value = ' ';
55
56 return;
57 }
58
59 report({
60 result,
61 ruleName,
62 message: messages.rejected(value),
63 node: rule,
64 index: combinatorNode.sourceIndex,
65 });
66 }
67 });
68 });
69
70 if (hasFixed) {
71 if (!rule.raws.selector) {
72 rule.selector = fixedSelector;
73 } else {
74 rule.raws.selector.raw = fixedSelector;
75 }
76 }
77 });
78 };
79}
80
81rule.ruleName = ruleName;
82rule.messages = messages;
83module.exports = rule;
84
85/**
86 * Check whether is actually a combinator.
87 * @param {Node} combinatorNode The combinator node
88 * @returns {boolean} `true` if is actually a combinator.
89 */
90function isActuallyCombinator(combinatorNode) {
91 // `.foo /*comment*/, .bar`
92 // ^^
93 // If include comments, this spaces is a combinator, but it is not combinators.
94 if (!/^\s+$/.test(combinatorNode.value)) {
95 return true;
96 }
97
98 let next = combinatorNode.next();
99
100 while (skipTest(next)) {
101 next = next.next();
102 }
103
104 if (isNonTarget(next)) {
105 return false;
106 }
107
108 let prev = combinatorNode.prev();
109
110 while (skipTest(prev)) {
111 prev = prev.prev();
112 }
113
114 if (isNonTarget(prev)) {
115 return false;
116 }
117
118 return true;
119
120 function skipTest(node) {
121 if (!node) {
122 return false;
123 }
124
125 if (node.type === 'comment') {
126 return true;
127 }
128
129 if (node.type === 'combinator' && /^\s+$/.test(node.value)) {
130 return true;
131 }
132
133 return false;
134 }
135
136 function isNonTarget(node) {
137 if (!node) {
138 return true;
139 }
140
141 if (node.type === 'combinator' && !/^\s+$/.test(node.value)) {
142 return true;
143 }
144
145 return false;
146 }
147}