UNPKG

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