/* @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*/ { 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; } };