| /* @flow */ |
| "use strict"; |
| |
| const _ = require("lodash"); |
| |
| const COMMAND_PREFIX = "stylelint-"; |
| const disableCommand = COMMAND_PREFIX + "disable"; |
| const enableCommand = COMMAND_PREFIX + "enable"; |
| const disableLineCommand = COMMAND_PREFIX + "disable-line"; |
| const disableNextLineCommand = COMMAND_PREFIX + "disable-next-line"; |
| const ALL_RULES = "all"; |
| |
| /*:: type disabledRangeObject = { |
| [ruleName: string]: Array<{ |
| start: number, |
| end?: number, |
| }> |
| }*/ |
| |
| // Run it like a plugin ... |
| module.exports = function( |
| root /*: Object*/, |
| result /*: Object*/ |
| ) /*: postcss$result*/ { |
| result.stylelint = result.stylelint || {}; |
| |
| // Most of the functions below work via side effects mutating |
| // this object |
| const disabledRanges /*: disabledRangeObject*/ = { |
| all: [] |
| }; |
| |
| result.stylelint.disabledRanges = disabledRanges; |
| root.walkComments(checkComment); |
| |
| return result; |
| |
| function processDisableLineCommand(comment /*: postcss$comment*/) { |
| getCommandRules(disableLineCommand, comment.text).forEach(ruleName => { |
| disableLine(comment.source.start.line, ruleName, comment); |
| }); |
| } |
| |
| function processDisableNextLineCommand(comment /*: postcss$comment*/) { |
| getCommandRules(disableNextLineCommand, comment.text).forEach(ruleName => { |
| disableLine(comment.source.start.line + 1, ruleName, comment); |
| }); |
| } |
| |
| function disableLine( |
| line /*: number*/, |
| ruleName /*: string*/, |
| comment /*: postcss$comment*/ |
| ) { |
| if (ruleIsDisabled(ALL_RULES)) { |
| throw comment.error("All rules have already been disabled", { |
| plugin: "stylelint" |
| }); |
| } |
| |
| if (ruleIsDisabled(ruleName)) { |
| throw comment.error(`"${ruleName}" has already been disabled`, { |
| plugin: "stylelint" |
| }); |
| } |
| |
| if (ruleName === ALL_RULES) { |
| Object.keys(disabledRanges).forEach(disabledRuleName => { |
| startDisabledRange(line, disabledRuleName); |
| endDisabledRange(line, disabledRuleName); |
| }); |
| } else { |
| startDisabledRange(line, ruleName); |
| endDisabledRange(line, ruleName); |
| } |
| } |
| |
| function processDisableCommand(comment /*: postcss$comment*/) { |
| getCommandRules(disableCommand, comment.text).forEach(ruleToDisable => { |
| if (ruleToDisable === ALL_RULES) { |
| if (ruleIsDisabled(ALL_RULES)) { |
| throw comment.error("All rules have already been disabled", { |
| plugin: "stylelint" |
| }); |
| } |
| |
| Object.keys(disabledRanges).forEach(ruleName => { |
| startDisabledRange(comment.source.start.line, ruleName); |
| }); |
| |
| return; |
| } |
| |
| if (ruleIsDisabled(ruleToDisable)) { |
| throw comment.error(`"${ruleToDisable}" has already been disabled`, { |
| plugin: "stylelint" |
| }); |
| } |
| |
| startDisabledRange(comment.source.start.line, ruleToDisable); |
| }); |
| } |
| |
| function processEnableCommand(comment /*: postcss$comment*/) { |
| getCommandRules(enableCommand, comment.text).forEach(ruleToEnable => { |
| if (ruleToEnable === ALL_RULES) { |
| if ( |
| _.values(disabledRanges).every( |
| ranges => _.isEmpty(ranges) || !!_.last(ranges.end) |
| ) |
| ) { |
| throw comment.error("No rules have been disabled", { |
| plugin: "stylelint" |
| }); |
| } |
| |
| Object.keys(disabledRanges).forEach(ruleName => { |
| if (!_.get(_.last(disabledRanges[ruleName]), "end")) { |
| endDisabledRange(comment.source.end.line, ruleName); |
| } |
| }); |
| |
| return; |
| } |
| |
| if ( |
| ruleIsDisabled(ALL_RULES) && |
| disabledRanges[ruleToEnable] === undefined |
| ) { |
| // Get a starting point from the where all rules were disabled |
| if (!disabledRanges[ruleToEnable]) { |
| disabledRanges[ruleToEnable] = _.cloneDeep(disabledRanges.all); |
| } else { |
| disabledRanges[ruleToEnable].push( |
| _.clone(_.last(disabledRanges[ALL_RULES])) |
| ); |
| } |
| |
| endDisabledRange(comment.source.end.line, ruleToEnable); |
| |
| return; |
| } |
| |
| if (ruleIsDisabled(ruleToEnable)) { |
| endDisabledRange(comment.source.end.line, ruleToEnable); |
| |
| return; |
| } |
| |
| throw comment.error(`"${ruleToEnable}" has not been disabled`, { |
| plugin: "stylelint" |
| }); |
| }); |
| } |
| |
| function checkComment(comment /*: postcss$comment*/) { |
| const text = comment.text; |
| |
| // Ignore comments that are not relevant commands |
| |
| if (text.indexOf(COMMAND_PREFIX) !== 0) { |
| return result; |
| } |
| |
| if (text.indexOf(disableLineCommand) === 0) { |
| processDisableLineCommand(comment); |
| } else if (text.indexOf(disableNextLineCommand) === 0) { |
| processDisableNextLineCommand(comment); |
| } else if (text.indexOf(disableCommand) === 0) { |
| processDisableCommand(comment); |
| } else if (text.indexOf(enableCommand) === 0) { |
| processEnableCommand(comment); |
| } |
| } |
| |
| function getCommandRules( |
| command /*: string*/, |
| fullText /*: string*/ |
| ) /*: Array<string>*/ { |
| const rules = _.compact(fullText.slice(command.length).split(",")).map(r => |
| r.trim() |
| ); |
| |
| if (_.isEmpty(rules)) { |
| return [ALL_RULES]; |
| } |
| |
| return rules; |
| } |
| |
| function startDisabledRange(line /*: number*/, ruleName /*: string*/) { |
| const rangeObj = { start: line }; |
| |
| ensureRuleRanges(ruleName); |
| disabledRanges[ruleName].push(rangeObj); |
| } |
| |
| function endDisabledRange(line /*: number*/, ruleName /*: string*/) { |
| const lastRangeForRule = _.last(disabledRanges[ruleName]); |
| |
| if (!lastRangeForRule) { |
| return; |
| } |
| |
| // Add an `end` prop to the last range of that rule |
| lastRangeForRule.end = line; |
| } |
| |
| function ensureRuleRanges(ruleName /*: string*/) { |
| if (!disabledRanges[ruleName]) { |
| disabledRanges[ruleName] = _.cloneDeep(disabledRanges.all); |
| } |
| } |
| |
| function ruleIsDisabled(ruleName /*: string*/) /*: boolean*/ { |
| if (disabledRanges[ruleName] === undefined) return false; |
| |
| if (_.last(disabledRanges[ruleName]) === undefined) return false; |
| |
| if (_.get(_.last(disabledRanges[ruleName]), "end") === undefined) |
| return true; |
| |
| return false; |
| } |
| }; |