3 const _ = require("lodash");
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";
12 /*:: type disabledRangeObject = {
13 [ruleName: string]: Array<{
19 // Run it like a plugin ...
20 module.exports = function(
23 ) /*: postcss$result*/ {
24 result.stylelint = result.stylelint || {};
26 // Most of the functions below work via side effects mutating
28 const disabledRanges /*: disabledRangeObject*/ = {
31 result.stylelint.disabledRanges = disabledRanges;
32 root.walkComments(checkComment);
36 function processDisableLineCommand(comment /*: postcss$comment*/) {
37 getCommandRules(disableLineCommand, comment.text).forEach(ruleName => {
38 disableLine(comment.source.start.line, ruleName, comment);
42 function processDisableNextLineCommand(comment /*: postcss$comment*/) {
43 getCommandRules(disableNextLineCommand, comment.text).forEach(ruleName => {
44 disableLine(comment.source.start.line + 1, ruleName, comment);
50 ruleName /*: string*/,
51 comment /*: postcss$comment*/
53 if (ruleIsDisabled(ALL_RULES)) {
54 throw comment.error("All rules have already been disabled", {
58 if (ruleIsDisabled(ruleName)) {
59 throw comment.error(`"${ruleName}" has already been disabled`, {
63 if (ruleName === ALL_RULES) {
64 Object.keys(disabledRanges).forEach(disabledRuleName => {
65 startDisabledRange(line, disabledRuleName);
66 endDisabledRange(line, disabledRuleName);
69 startDisabledRange(line, ruleName);
70 endDisabledRange(line, ruleName);
74 function processDisableCommand(comment /*: postcss$comment*/) {
75 getCommandRules(disableCommand, comment.text).forEach(ruleToDisable => {
76 if (ruleToDisable === ALL_RULES) {
77 if (ruleIsDisabled(ALL_RULES)) {
78 throw comment.error("All rules have already been disabled", {
82 Object.keys(disabledRanges).forEach(ruleName => {
83 startDisabledRange(comment.source.start.line, ruleName);
88 if (ruleIsDisabled(ruleToDisable)) {
89 throw comment.error(`"${ruleToDisable}" has already been disabled`, {
93 startDisabledRange(comment.source.start.line, ruleToDisable);
97 function processEnableCommand(comment /*: postcss$comment*/) {
98 getCommandRules(enableCommand, comment.text).forEach(ruleToEnable => {
99 if (ruleToEnable === ALL_RULES) {
101 _.values(disabledRanges).every(
102 ranges => _.isEmpty(ranges) || !!_.last(ranges.end)
105 throw comment.error("No rules have been disabled", {
109 Object.keys(disabledRanges).forEach(ruleName => {
110 if (!_.get(_.last(disabledRanges[ruleName]), "end")) {
111 endDisabledRange(comment.source.end.line, ruleName);
118 ruleIsDisabled(ALL_RULES) &&
119 disabledRanges[ruleToEnable] === undefined
121 // Get a starting point from the where all rules were disabled
122 if (!disabledRanges[ruleToEnable]) {
123 disabledRanges[ruleToEnable] = _.cloneDeep(disabledRanges.all);
125 disabledRanges[ruleToEnable].push(
126 _.clone(_.last(disabledRanges[ALL_RULES]))
129 endDisabledRange(comment.source.end.line, ruleToEnable);
133 if (ruleIsDisabled(ruleToEnable)) {
134 endDisabledRange(comment.source.end.line, ruleToEnable);
138 throw comment.error(`"${ruleToEnable}" has not been disabled`, {
144 function checkComment(comment /*: postcss$comment*/) {
145 const text = comment.text;
147 // Ignore comments that are not relevant commands
149 if (text.indexOf(COMMAND_PREFIX) !== 0) {
153 if (text.indexOf(disableLineCommand) === 0) {
154 processDisableLineCommand(comment);
155 } else if (text.indexOf(disableNextLineCommand) === 0) {
156 processDisableNextLineCommand(comment);
157 } else if (text.indexOf(disableCommand) === 0) {
158 processDisableCommand(comment);
159 } else if (text.indexOf(enableCommand) === 0) {
160 processEnableCommand(comment);
164 function getCommandRules(
165 command /*: string*/,
166 fullText /*: string*/
167 ) /*: Array<string>*/ {
168 const rules = _.compact(fullText.slice(command.length).split(",")).map(r =>
171 if (_.isEmpty(rules)) {
177 function startDisabledRange(line /*: number*/, ruleName /*: string*/) {
178 const rangeObj = { start: line };
179 ensureRuleRanges(ruleName);
180 disabledRanges[ruleName].push(rangeObj);
183 function endDisabledRange(line /*: number*/, ruleName /*: string*/) {
184 const lastRangeForRule = _.last(disabledRanges[ruleName]);
185 if (!lastRangeForRule) {
188 // Add an `end` prop to the last range of that rule
189 lastRangeForRule.end = line;
192 function ensureRuleRanges(ruleName /*: string*/) {
193 if (!disabledRanges[ruleName]) {
194 disabledRanges[ruleName] = _.cloneDeep(disabledRanges.all);
198 function ruleIsDisabled(ruleName /*: string*/) /*: boolean*/ {
199 if (disabledRanges[ruleName] === undefined) return false;
200 if (_.last(disabledRanges[ruleName]) === undefined) return false;
201 if (_.get(_.last(disabledRanges[ruleName]), "end") === undefined)