UNPKG

4.64 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
5const parseSelector = require('../../utils/parseSelector');
6const report = require('../../utils/report');
7const ruleMessages = require('../../utils/ruleMessages');
8const styleSearch = require('style-search');
9const validateOptions = require('../../utils/validateOptions');
10
11const ruleName = 'selector-attribute-brackets-space-inside';
12
13const messages = ruleMessages(ruleName, {
14 expectedOpening: 'Expected single space after "["',
15 rejectedOpening: 'Unexpected whitespace after "["',
16 expectedClosing: 'Expected single space before "]"',
17 rejectedClosing: 'Unexpected whitespace before "]"',
18});
19
20function rule(expectation, options, context) {
21 return (root, result) => {
22 const validOptions = validateOptions(result, ruleName, {
23 actual: expectation,
24 possible: ['always', 'never'],
25 });
26
27 if (!validOptions) {
28 return;
29 }
30
31 root.walkRules((rule) => {
32 if (!isStandardSyntaxRule(rule)) {
33 return;
34 }
35
36 if (!rule.selector.includes('[')) {
37 return;
38 }
39
40 const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector;
41
42 let hasFixed;
43 const fixedSelector = parseSelector(selector, result, rule, (selectorTree) => {
44 selectorTree.walkAttributes((attributeNode) => {
45 const attributeSelectorString = attributeNode.toString();
46
47 styleSearch({ source: attributeSelectorString, target: '[' }, (match) => {
48 const nextCharIsSpace = attributeSelectorString[match.startIndex + 1] === ' ';
49 const index = attributeNode.sourceIndex + match.startIndex + 1;
50
51 if (nextCharIsSpace && expectation === 'never') {
52 if (context.fix) {
53 hasFixed = true;
54 fixBefore(attributeNode);
55
56 return;
57 }
58
59 complain(messages.rejectedOpening, index);
60 }
61
62 if (!nextCharIsSpace && expectation === 'always') {
63 if (context.fix) {
64 hasFixed = true;
65 fixBefore(attributeNode);
66
67 return;
68 }
69
70 complain(messages.expectedOpening, index);
71 }
72 });
73
74 styleSearch({ source: attributeSelectorString, target: ']' }, (match) => {
75 const prevCharIsSpace = attributeSelectorString[match.startIndex - 1] === ' ';
76 const index = attributeNode.sourceIndex + match.startIndex - 1;
77
78 if (prevCharIsSpace && expectation === 'never') {
79 if (context.fix) {
80 hasFixed = true;
81 fixAfter(attributeNode);
82
83 return;
84 }
85
86 complain(messages.rejectedClosing, index);
87 }
88
89 if (!prevCharIsSpace && expectation === 'always') {
90 if (context.fix) {
91 hasFixed = true;
92 fixAfter(attributeNode);
93
94 return;
95 }
96
97 complain(messages.expectedClosing, index);
98 }
99 });
100 });
101 });
102
103 if (hasFixed) {
104 if (!rule.raws.selector) {
105 rule.selector = fixedSelector;
106 } else {
107 rule.raws.selector.raw = fixedSelector;
108 }
109 }
110
111 function complain(message, index) {
112 report({
113 message,
114 index,
115 result,
116 ruleName,
117 node: rule,
118 });
119 }
120 });
121 };
122
123 function fixBefore(attributeNode) {
124 const rawAttrBefore = _.get(attributeNode, 'raws.spaces.attribute.before');
125 const { attrBefore, setAttrBefore } = rawAttrBefore
126 ? {
127 attrBefore: rawAttrBefore,
128 setAttrBefore(fixed) {
129 attributeNode.raws.spaces.attribute.before = fixed;
130 },
131 }
132 : {
133 attrBefore: _.get(attributeNode, 'spaces.attribute.before', ''),
134 setAttrBefore(fixed) {
135 _.set(attributeNode, 'spaces.attribute.before', fixed);
136 },
137 };
138
139 if (expectation === 'always') {
140 setAttrBefore(attrBefore.replace(/^\s*/, ' '));
141 } else if (expectation === 'never') {
142 setAttrBefore(attrBefore.replace(/^\s*/, ''));
143 }
144 }
145
146 function fixAfter(attributeNode) {
147 let key;
148
149 if (attributeNode.operator) {
150 if (attributeNode.insensitive) {
151 key = 'insensitive';
152 } else {
153 key = 'value';
154 }
155 } else {
156 key = 'attribute';
157 }
158
159 const rawAfter = _.get(attributeNode, `raws.spaces.${key}.after`);
160 const { after, setAfter } = rawAfter
161 ? {
162 after: rawAfter,
163 setAfter(fixed) {
164 attributeNode.raws.spaces[key].after = fixed;
165 },
166 }
167 : {
168 after: _.get(attributeNode, `spaces.${key}.after`, ''),
169 setAfter(fixed) {
170 _.set(attributeNode, `spaces.${key}.after`, fixed);
171 },
172 };
173
174 if (expectation === 'always') {
175 setAfter(after.replace(/\s*$/, ' '));
176 } else if (expectation === 'never') {
177 setAfter(after.replace(/\s*$/, ''));
178 }
179 }
180}
181
182rule.ruleName = ruleName;
183rule.messages = messages;
184module.exports = rule;