1 | 'use strict';
|
2 |
|
3 | const isStandardSyntaxComment = require('./utils/isStandardSyntaxComment');
|
4 |
|
5 | const COMMAND_PREFIX = 'stylelint-';
|
6 | const disableCommand = `${COMMAND_PREFIX}disable`;
|
7 | const enableCommand = `${COMMAND_PREFIX}enable`;
|
8 | const disableLineCommand = `${COMMAND_PREFIX}disable-line`;
|
9 | const disableNextLineCommand = `${COMMAND_PREFIX}disable-next-line`;
|
10 | const ALL_RULES = 'all';
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function createDisableRange(comment, start, strictStart, description, end, strictEnd) {
|
28 | return {
|
29 | comment,
|
30 | start,
|
31 | end: end || undefined,
|
32 | strictStart,
|
33 | strictEnd: typeof strictEnd === 'boolean' ? strictEnd : undefined,
|
34 | description,
|
35 | };
|
36 | }
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | module.exports = function assignDisabledRanges(root, result) {
|
45 | result.stylelint = result.stylelint || {
|
46 | disabledRanges: {},
|
47 | ruleSeverities: {},
|
48 | customMessages: {},
|
49 | };
|
50 |
|
51 | |
52 |
|
53 |
|
54 |
|
55 | const disabledRanges = {
|
56 | all: [],
|
57 | };
|
58 |
|
59 | result.stylelint.disabledRanges = disabledRanges;
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | let inlineEnd;
|
66 |
|
67 | root.walkComments((comment) => {
|
68 | if (inlineEnd) {
|
69 |
|
70 | if (inlineEnd === comment) inlineEnd = null;
|
71 |
|
72 | return;
|
73 | }
|
74 |
|
75 | const nextComment = comment.next();
|
76 |
|
77 |
|
78 | if (
|
79 | !(
|
80 | !isStandardSyntaxComment(comment) &&
|
81 | isStylelintCommand(comment) &&
|
82 | nextComment &&
|
83 | nextComment.type === 'comment' &&
|
84 | (comment.text.includes('--') || nextComment.text.startsWith('--'))
|
85 | )
|
86 | ) {
|
87 | checkComment(comment);
|
88 |
|
89 | return;
|
90 | }
|
91 |
|
92 | let lastLine = (comment.source && comment.source.end && comment.source.end.line) || 0;
|
93 | const fullComment = comment.clone();
|
94 |
|
95 | let current = nextComment;
|
96 |
|
97 | while (!isStandardSyntaxComment(current) && !isStylelintCommand(current)) {
|
98 | const currentLine = (current.source && current.source.end && current.source.end.line) || 0;
|
99 |
|
100 | if (lastLine + 1 !== currentLine) break;
|
101 |
|
102 | fullComment.text += `\n${current.text}`;
|
103 |
|
104 | if (fullComment.source && current.source) {
|
105 | fullComment.source.end = current.source.end;
|
106 | }
|
107 |
|
108 | inlineEnd = current;
|
109 | const next = current.next();
|
110 |
|
111 | if (!next || next.type !== 'comment') break;
|
112 |
|
113 | current = next;
|
114 | lastLine = currentLine;
|
115 | }
|
116 |
|
117 | checkComment(fullComment);
|
118 | });
|
119 |
|
120 | return result;
|
121 |
|
122 | |
123 |
|
124 |
|
125 | function isStylelintCommand(comment) {
|
126 | return comment.text.startsWith(disableCommand) || comment.text.startsWith(enableCommand);
|
127 | }
|
128 |
|
129 | |
130 |
|
131 |
|
132 | function processDisableLineCommand(comment) {
|
133 | if (comment.source && comment.source.start) {
|
134 | const line = comment.source.start.line;
|
135 | const description = getDescription(comment.text);
|
136 |
|
137 | for (const ruleName of getCommandRules(disableLineCommand, comment.text)) {
|
138 | disableLine(comment, line, ruleName, description);
|
139 | }
|
140 | }
|
141 | }
|
142 |
|
143 | |
144 |
|
145 |
|
146 | function processDisableNextLineCommand(comment) {
|
147 | if (comment.source && comment.source.end) {
|
148 | const line = comment.source.end.line;
|
149 | const description = getDescription(comment.text);
|
150 |
|
151 | for (const ruleName of getCommandRules(disableNextLineCommand, comment.text)) {
|
152 | disableLine(comment, line + 1, ruleName, description);
|
153 | }
|
154 | }
|
155 | }
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | function disableLine(comment, line, ruleName, description) {
|
164 | if (ruleIsDisabled(ALL_RULES)) {
|
165 | throw comment.error('All rules have already been disabled', {
|
166 | plugin: 'stylelint',
|
167 | });
|
168 | }
|
169 |
|
170 | if (ruleName === ALL_RULES) {
|
171 | for (const disabledRuleName of Object.keys(disabledRanges)) {
|
172 | if (ruleIsDisabled(disabledRuleName)) continue;
|
173 |
|
174 | const strict = disabledRuleName === ALL_RULES;
|
175 |
|
176 | startDisabledRange(comment, line, disabledRuleName, strict, description);
|
177 | endDisabledRange(line, disabledRuleName, strict);
|
178 | }
|
179 | } else {
|
180 | if (ruleIsDisabled(ruleName)) {
|
181 | throw comment.error(`"${ruleName}" has already been disabled`, {
|
182 | plugin: 'stylelint',
|
183 | });
|
184 | }
|
185 |
|
186 | startDisabledRange(comment, line, ruleName, true, description);
|
187 | endDisabledRange(line, ruleName, true);
|
188 | }
|
189 | }
|
190 |
|
191 | |
192 |
|
193 |
|
194 | function processDisableCommand(comment) {
|
195 | const description = getDescription(comment.text);
|
196 |
|
197 | for (const ruleToDisable of getCommandRules(disableCommand, comment.text)) {
|
198 | const isAllRules = ruleToDisable === ALL_RULES;
|
199 |
|
200 | if (ruleIsDisabled(ruleToDisable)) {
|
201 | throw comment.error(
|
202 | isAllRules
|
203 | ? 'All rules have already been disabled'
|
204 | : `"${ruleToDisable}" has already been disabled`,
|
205 | {
|
206 | plugin: 'stylelint',
|
207 | },
|
208 | );
|
209 | }
|
210 |
|
211 | if (comment.source && comment.source.start) {
|
212 | const line = comment.source.start.line;
|
213 |
|
214 | if (isAllRules) {
|
215 | for (const ruleName of Object.keys(disabledRanges)) {
|
216 | startDisabledRange(comment, line, ruleName, ruleName === ALL_RULES, description);
|
217 | }
|
218 | } else {
|
219 | startDisabledRange(comment, line, ruleToDisable, true, description);
|
220 | }
|
221 | }
|
222 | }
|
223 | }
|
224 |
|
225 | |
226 |
|
227 |
|
228 | function processEnableCommand(comment) {
|
229 | for (const ruleToEnable of getCommandRules(enableCommand, comment.text)) {
|
230 |
|
231 |
|
232 | const endLine = (
|
233 | comment.source && comment.source.end && comment.source.end.line
|
234 | );
|
235 |
|
236 | if (ruleToEnable === ALL_RULES) {
|
237 | if (
|
238 | Object.values(disabledRanges).every(
|
239 | (ranges) => ranges.length === 0 || typeof ranges[ranges.length - 1].end === 'number',
|
240 | )
|
241 | ) {
|
242 | throw comment.error('No rules have been disabled', {
|
243 | plugin: 'stylelint',
|
244 | });
|
245 | }
|
246 |
|
247 | for (const [ruleName, ranges] of Object.entries(disabledRanges)) {
|
248 | const lastRange = ranges[ranges.length - 1];
|
249 |
|
250 | if (!lastRange || !lastRange.end) {
|
251 | endDisabledRange(endLine, ruleName, ruleName === ALL_RULES);
|
252 | }
|
253 | }
|
254 |
|
255 | continue;
|
256 | }
|
257 |
|
258 | if (ruleIsDisabled(ALL_RULES) && disabledRanges[ruleToEnable] === undefined) {
|
259 |
|
260 | if (!disabledRanges[ruleToEnable]) {
|
261 | disabledRanges[ruleToEnable] = disabledRanges.all.map(({ start, end, description }) =>
|
262 | createDisableRange(comment, start, false, description, end, false),
|
263 | );
|
264 | } else {
|
265 | const ranges = disabledRanges[ALL_RULES];
|
266 | const range = ranges ? ranges[ranges.length - 1] : null;
|
267 |
|
268 | if (range) {
|
269 | disabledRanges[ruleToEnable].push({ ...range });
|
270 | }
|
271 | }
|
272 |
|
273 | endDisabledRange(endLine, ruleToEnable, true);
|
274 |
|
275 | continue;
|
276 | }
|
277 |
|
278 | if (ruleIsDisabled(ruleToEnable)) {
|
279 | endDisabledRange(endLine, ruleToEnable, true);
|
280 |
|
281 | continue;
|
282 | }
|
283 |
|
284 | throw comment.error(`"${ruleToEnable}" has not been disabled`, {
|
285 | plugin: 'stylelint',
|
286 | });
|
287 | }
|
288 | }
|
289 |
|
290 | |
291 |
|
292 |
|
293 | function checkComment(comment) {
|
294 | const text = comment.text;
|
295 |
|
296 |
|
297 |
|
298 | if (text.indexOf(COMMAND_PREFIX) !== 0) {
|
299 | return result;
|
300 | }
|
301 |
|
302 | if (text.startsWith(disableLineCommand)) {
|
303 | processDisableLineCommand(comment);
|
304 | } else if (text.startsWith(disableNextLineCommand)) {
|
305 | processDisableNextLineCommand(comment);
|
306 | } else if (text.startsWith(disableCommand)) {
|
307 | processDisableCommand(comment);
|
308 | } else if (text.startsWith(enableCommand)) {
|
309 | processEnableCommand(comment);
|
310 | }
|
311 | }
|
312 |
|
313 | |
314 |
|
315 |
|
316 |
|
317 |
|
318 | function getCommandRules(command, fullText) {
|
319 | const rules = fullText
|
320 | .slice(command.length)
|
321 | .split(/\s-{2,}\s/u)[0]
|
322 | .trim()
|
323 | .split(',')
|
324 | .filter(Boolean)
|
325 | .map((r) => r.trim());
|
326 |
|
327 | if (rules.length === 0) {
|
328 | return [ALL_RULES];
|
329 | }
|
330 |
|
331 | return rules;
|
332 | }
|
333 |
|
334 | |
335 |
|
336 |
|
337 |
|
338 | function getDescription(fullText) {
|
339 | const descriptionStart = fullText.indexOf('--');
|
340 |
|
341 | if (descriptionStart === -1) return;
|
342 |
|
343 | return fullText.slice(descriptionStart + 2).trim();
|
344 | }
|
345 |
|
346 | |
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | function startDisabledRange(comment, line, ruleName, strict, description) {
|
354 | const rangeObj = createDisableRange(comment, line, strict, description);
|
355 |
|
356 | ensureRuleRanges(ruleName);
|
357 | disabledRanges[ruleName].push(rangeObj);
|
358 | }
|
359 |
|
360 | |
361 |
|
362 |
|
363 |
|
364 |
|
365 | function endDisabledRange(line, ruleName, strict) {
|
366 | const ranges = disabledRanges[ruleName];
|
367 | const lastRangeForRule = ranges ? ranges[ranges.length - 1] : null;
|
368 |
|
369 | if (!lastRangeForRule) {
|
370 | return;
|
371 | }
|
372 |
|
373 |
|
374 | lastRangeForRule.end = line;
|
375 | lastRangeForRule.strictEnd = strict;
|
376 | }
|
377 |
|
378 | |
379 |
|
380 |
|
381 | function ensureRuleRanges(ruleName) {
|
382 | if (!disabledRanges[ruleName]) {
|
383 | disabledRanges[ruleName] = disabledRanges.all.map(({ comment, start, end, description }) =>
|
384 | createDisableRange(comment, start, false, description, end, false),
|
385 | );
|
386 | }
|
387 | }
|
388 |
|
389 | |
390 |
|
391 |
|
392 |
|
393 | function ruleIsDisabled(ruleName) {
|
394 | const ranges = disabledRanges[ruleName];
|
395 |
|
396 | if (!ranges) return false;
|
397 |
|
398 | const lastRange = ranges[ranges.length - 1];
|
399 |
|
400 | if (!lastRange) return false;
|
401 |
|
402 | if (!lastRange.end) return true;
|
403 |
|
404 | return false;
|
405 | }
|
406 | };
|