UNPKG

6.17 kBJavaScriptView Raw
1/* @flow */
2"use strict";
3
4const _ = require("lodash");
5
6const COMMAND_PREFIX = "stylelint-";
7const disableCommand = COMMAND_PREFIX + "disable";
8const enableCommand = COMMAND_PREFIX + "enable";
9const disableLineCommand = COMMAND_PREFIX + "disable-line";
10const disableNextLineCommand = COMMAND_PREFIX + "disable-next-line";
11const ALL_RULES = "all";
12
13/*:: type disabledRangeObject = {
14 [ruleName: string]: Array<{
15 start: number,
16 end?: number,
17 }>
18}*/
19
20// Run it like a plugin ...
21module.exports = function(
22 root /*: Object*/,
23 result /*: Object*/
24) /*: postcss$result*/ {
25 result.stylelint = result.stylelint || {};
26
27 // Most of the functions below work via side effects mutating
28 // this object
29 const disabledRanges /*: disabledRangeObject*/ = {
30 all: []
31 };
32
33 result.stylelint.disabledRanges = disabledRanges;
34 root.walkComments(checkComment);
35
36 return result;
37
38 function processDisableLineCommand(comment /*: postcss$comment*/) {
39 getCommandRules(disableLineCommand, comment.text).forEach(ruleName => {
40 disableLine(comment.source.start.line, ruleName, comment);
41 });
42 }
43
44 function processDisableNextLineCommand(comment /*: postcss$comment*/) {
45 getCommandRules(disableNextLineCommand, comment.text).forEach(ruleName => {
46 disableLine(comment.source.start.line + 1, ruleName, comment);
47 });
48 }
49
50 function disableLine(
51 line /*: number*/,
52 ruleName /*: string*/,
53 comment /*: postcss$comment*/
54 ) {
55 if (ruleIsDisabled(ALL_RULES)) {
56 throw comment.error("All rules have already been disabled", {
57 plugin: "stylelint"
58 });
59 }
60
61 if (ruleIsDisabled(ruleName)) {
62 throw comment.error(`"${ruleName}" has already been disabled`, {
63 plugin: "stylelint"
64 });
65 }
66
67 if (ruleName === ALL_RULES) {
68 Object.keys(disabledRanges).forEach(disabledRuleName => {
69 startDisabledRange(line, disabledRuleName);
70 endDisabledRange(line, disabledRuleName);
71 });
72 } else {
73 startDisabledRange(line, ruleName);
74 endDisabledRange(line, ruleName);
75 }
76 }
77
78 function processDisableCommand(comment /*: postcss$comment*/) {
79 getCommandRules(disableCommand, comment.text).forEach(ruleToDisable => {
80 if (ruleToDisable === ALL_RULES) {
81 if (ruleIsDisabled(ALL_RULES)) {
82 throw comment.error("All rules have already been disabled", {
83 plugin: "stylelint"
84 });
85 }
86
87 Object.keys(disabledRanges).forEach(ruleName => {
88 startDisabledRange(comment.source.start.line, ruleName);
89 });
90
91 return;
92 }
93
94 if (ruleIsDisabled(ruleToDisable)) {
95 throw comment.error(`"${ruleToDisable}" has already been disabled`, {
96 plugin: "stylelint"
97 });
98 }
99
100 startDisabledRange(comment.source.start.line, ruleToDisable);
101 });
102 }
103
104 function processEnableCommand(comment /*: postcss$comment*/) {
105 getCommandRules(enableCommand, comment.text).forEach(ruleToEnable => {
106 if (ruleToEnable === ALL_RULES) {
107 if (
108 _.values(disabledRanges).every(
109 ranges => _.isEmpty(ranges) || !!_.last(ranges.end)
110 )
111 ) {
112 throw comment.error("No rules have been disabled", {
113 plugin: "stylelint"
114 });
115 }
116
117 Object.keys(disabledRanges).forEach(ruleName => {
118 if (!_.get(_.last(disabledRanges[ruleName]), "end")) {
119 endDisabledRange(comment.source.end.line, ruleName);
120 }
121 });
122
123 return;
124 }
125
126 if (
127 ruleIsDisabled(ALL_RULES) &&
128 disabledRanges[ruleToEnable] === undefined
129 ) {
130 // Get a starting point from the where all rules were disabled
131 if (!disabledRanges[ruleToEnable]) {
132 disabledRanges[ruleToEnable] = _.cloneDeep(disabledRanges.all);
133 } else {
134 disabledRanges[ruleToEnable].push(
135 _.clone(_.last(disabledRanges[ALL_RULES]))
136 );
137 }
138
139 endDisabledRange(comment.source.end.line, ruleToEnable);
140
141 return;
142 }
143
144 if (ruleIsDisabled(ruleToEnable)) {
145 endDisabledRange(comment.source.end.line, ruleToEnable);
146
147 return;
148 }
149
150 throw comment.error(`"${ruleToEnable}" has not been disabled`, {
151 plugin: "stylelint"
152 });
153 });
154 }
155
156 function checkComment(comment /*: postcss$comment*/) {
157 const text = comment.text;
158
159 // Ignore comments that are not relevant commands
160
161 if (text.indexOf(COMMAND_PREFIX) !== 0) {
162 return result;
163 }
164
165 if (text.indexOf(disableLineCommand) === 0) {
166 processDisableLineCommand(comment);
167 } else if (text.indexOf(disableNextLineCommand) === 0) {
168 processDisableNextLineCommand(comment);
169 } else if (text.indexOf(disableCommand) === 0) {
170 processDisableCommand(comment);
171 } else if (text.indexOf(enableCommand) === 0) {
172 processEnableCommand(comment);
173 }
174 }
175
176 function getCommandRules(
177 command /*: string*/,
178 fullText /*: string*/
179 ) /*: Array<string>*/ {
180 const rules = _.compact(fullText.slice(command.length).split(",")).map(r =>
181 r.trim()
182 );
183
184 if (_.isEmpty(rules)) {
185 return [ALL_RULES];
186 }
187
188 return rules;
189 }
190
191 function startDisabledRange(line /*: number*/, ruleName /*: string*/) {
192 const rangeObj = { start: line };
193
194 ensureRuleRanges(ruleName);
195 disabledRanges[ruleName].push(rangeObj);
196 }
197
198 function endDisabledRange(line /*: number*/, ruleName /*: string*/) {
199 const lastRangeForRule = _.last(disabledRanges[ruleName]);
200
201 if (!lastRangeForRule) {
202 return;
203 }
204
205 // Add an `end` prop to the last range of that rule
206 lastRangeForRule.end = line;
207 }
208
209 function ensureRuleRanges(ruleName /*: string*/) {
210 if (!disabledRanges[ruleName]) {
211 disabledRanges[ruleName] = _.cloneDeep(disabledRanges.all);
212 }
213 }
214
215 function ruleIsDisabled(ruleName /*: string*/) /*: boolean*/ {
216 if (disabledRanges[ruleName] === undefined) return false;
217
218 if (_.last(disabledRanges[ruleName]) === undefined) return false;
219
220 if (_.get(_.last(disabledRanges[ruleName]), "end") === undefined)
221 return true;
222
223 return false;
224 }
225};