// NOTICE: This file is generated by Rollup. To modify it, // please instead edit the ESM counterpart and rebuild with Rollup (npm run build). 'use strict'; const constants = require('./constants.cjs'); const configurationComment = require('./utils/configurationComment.cjs'); const validateTypes = require('./utils/validateTypes.cjs'); const typeGuards = require('./utils/typeGuards.cjs'); const isStandardSyntaxComment = require('./utils/isStandardSyntaxComment.cjs'); const cssTokenizer = require('@csstools/css-tokenizer'); /** @import {Node as PostcssNode, Comment as PostcssComment, Document as PostcssDocument, Root as PostcssRoot, Source as PostcssSource} from 'postcss' */ /** @import {DisabledRange, DisabledRangeObject, PostcssResult} from 'stylelint' */ /** @typedef {Pick} Source */ /** * @param {PostcssNode} node * @param {number} start * @param {boolean} strictStart * @param {string|undefined} description * @param {number} [end] * @param {boolean} [strictEnd] * @returns {DisabledRange} */ function createDisableRange(node, start, strictStart, description, end, strictEnd) { return { node, start, end: end || undefined, strictStart, strictEnd: typeof strictEnd === 'boolean' ? strictEnd : undefined, description, }; } /** * Run it like a PostCSS plugin * @param {PostcssRoot | PostcssDocument} root * @param {PostcssResult} result * @returns {PostcssResult} */ function assignDisabledRanges(root, result) { /** * Most of the functions below work via side effects mutating this object * @type {DisabledRangeObject} */ const disabledRanges = result.stylelint.disabledRanges; /** @type {DisabledRange[]} */ const disabledRangesAll = []; disabledRanges[constants.RULE_NAME_ALL] = disabledRangesAll; // Work around postcss/postcss-scss#109 by merging adjacent `//` comments // into a single node before passing to `checkComment`. /** @type {PostcssComment?} */ let inlineEnd; const configurationComment$1 = result.stylelint.config?.configurationComment; root.walk((node) => { if (typeGuards.isComment(node)) { if (inlineEnd) { // Ignore comments already processed by grouping with a previous one. if (inlineEnd === node) inlineEnd = null; return; } const nextComment = node.next(); // If any of these conditions are not met, do not merge comments. if ( !( !isStandardSyntaxComment(node) && configurationComment.isConfigurationComment(node.text, configurationComment$1) && nextComment && typeGuards.isComment(nextComment) && (node.text.includes('--') || nextComment.text.startsWith('--')) ) ) { checkComment(node, node.source, node.text); return; } let lastLine = node.source?.end?.line ?? 0; const fullComment = node.clone(); let current = nextComment; while ( !isStandardSyntaxComment(current) && !configurationComment.isConfigurationComment(current.text, configurationComment$1) ) { const currentLine = current.source?.end?.line ?? 0; if (lastLine + 1 !== currentLine) break; fullComment.text += `\n${current.text}`; if (fullComment.source && current.source) { fullComment.source.end = current.source.end; } inlineEnd = current; const next = current.next(); if (!next || !typeGuards.isComment(next)) break; current = next; lastLine = currentLine; } checkComment(fullComment, fullComment.source, fullComment.text); } if (typeGuards.isRule(node)) { let offset = 0; const selector = node.raws?.selector?.raw; checkCommentsInNode(node, selector, offset); offset += selector?.length ?? node.selector.length; const between = node.raws?.between; checkCommentsInNode(node, between, offset); } if (typeGuards.isAtRule(node)) { let offset = node.name.length + 1; // `@` + name const afterName = node.raws?.afterName; checkCommentsInNode(node, afterName, offset); offset += afterName?.length ?? 0; const params = node.raws?.params?.raw; checkCommentsInNode(node, params, offset); offset += params?.length ?? node.params.length; const between = node.raws?.between; checkCommentsInNode(node, between, offset); } if (typeGuards.isDeclaration(node)) { let offset = node.prop.length; const between = node.raws?.between; checkCommentsInNode(node, between, offset); offset += between?.length ?? 0; const value = node.raws?.value?.raw; checkCommentsInNode(node, value, offset); } }); return result; /** * @param {PostcssNode} node * @param {Source} source * @param {string} text */ function processDisableLineCommand(node, source, text) { if (source.start) { const line = source.start.line; const description = getDescription(text); for (const ruleName of getCommandRules(configurationComment.DISABLE_LINE_COMMAND, text)) { disableLine(node, line, ruleName, description); } } } /** * @param {PostcssNode} node * @param {Source} source * @param {string} text */ function processDisableNextLineCommand(node, source, text) { if (source.end) { const line = source.end.line; const description = getDescription(text); for (const ruleName of getCommandRules(configurationComment.DISABLE_NEXT_LINE_COMMAND, text)) { disableLine(node, line + 1, ruleName, description); } } } /** * @param {PostcssNode} node * @param {number} line * @param {string} ruleName * @param {string|undefined} description */ function disableLine(node, line, ruleName, description) { if (ruleIsDisabled(constants.RULE_NAME_ALL)) { throw node.error('All rules have already been disabled', { plugin: 'stylelint', }); } if (ruleName === constants.RULE_NAME_ALL) { for (const disabledRuleName of Object.keys(disabledRanges)) { if (ruleIsDisabled(disabledRuleName)) continue; const strict = disabledRuleName === constants.RULE_NAME_ALL; startDisabledRange(node, line, disabledRuleName, strict, description); endDisabledRange(line, disabledRuleName, strict); } } else { if (ruleIsDisabled(ruleName)) { throw node.error(`"${ruleName}" has already been disabled`, { plugin: 'stylelint', }); } startDisabledRange(node, line, ruleName, true, description); endDisabledRange(line, ruleName, true); } } /** * @param {PostcssNode} node * @param {Source} source * @param {string} text */ function processDisableCommand(node, source, text) { const description = getDescription(text); for (const ruleToDisable of getCommandRules(configurationComment.DISABLE_COMMAND, text)) { const isAllRules = ruleToDisable === constants.RULE_NAME_ALL; if (ruleIsDisabled(ruleToDisable)) { throw node.error( isAllRules ? 'All rules have already been disabled' : `"${ruleToDisable}" has already been disabled`, { plugin: 'stylelint', }, ); } if (source.start) { const line = source.start.line; if (isAllRules) { for (const ruleName of Object.keys(disabledRanges)) { startDisabledRange(node, line, ruleName, ruleName === constants.RULE_NAME_ALL, description); } } else { startDisabledRange(node, line, ruleToDisable, true, description); } } } } /** * @param {PostcssNode} node * @param {Source} source * @param {string} text */ function processEnableCommand(node, source, text) { for (const ruleToEnable of getCommandRules(configurationComment.ENABLE_COMMAND, text)) { // need fallback if endLine will be undefined const endLine = source.end?.line; validateTypes.assertNumber(endLine); if (ruleToEnable === constants.RULE_NAME_ALL) { if ( Object.values(disabledRanges).every((ranges) => { if (ranges.length === 0) return true; const lastRange = ranges[ranges.length - 1]; return lastRange && typeof lastRange.end === 'number'; }) ) { throw node.error('No rules have been disabled', { plugin: 'stylelint', }); } for (const [ruleName, ranges] of Object.entries(disabledRanges)) { const lastRange = ranges[ranges.length - 1]; if (!lastRange || !lastRange.end) { endDisabledRange(endLine, ruleName, ruleName === constants.RULE_NAME_ALL); } } continue; } if (ruleIsDisabled(constants.RULE_NAME_ALL) && disabledRanges[ruleToEnable] === undefined) { // Get a starting point from the where all rules were disabled disabledRanges[ruleToEnable] = disabledRangesAll.map(({ start, end, description }) => createDisableRange(node, start, false, description, end, false), ); endDisabledRange(endLine, ruleToEnable, true); continue; } if (ruleIsDisabled(ruleToEnable)) { endDisabledRange(endLine, ruleToEnable, true); continue; } throw node.error(`"${ruleToEnable}" has not been disabled`, { plugin: 'stylelint', }); } } /** * @param {PostcssNode} node * @param {string | undefined} part * @param {number} offset */ function checkCommentsInNode(node, part, offset) { if (!(part && part.includes('/*') && part.includes('*/'))) return; cssTokenizer.tokenize({ css: part }).forEach((token) => { if (!cssTokenizer.isTokenComment(token)) return; const [, text, start, end] = token; const source = node.rangeBy({ index: start + offset, endIndex: end + 1 + offset, }); checkComment(node, source, text.slice(2, -2).trim()); }); } /** * @param {PostcssNode} node * @param {Source | undefined} source * @param {string} text */ function checkComment(node, source, text) { if (!source) return; // Ignore comments that are not relevant commands if (!configurationComment.isConfigurationComment(text, configurationComment$1)) { return; } switch (configurationComment.extractConfigurationComment(text, configurationComment$1)) { case configurationComment.DISABLE_LINE_COMMAND: processDisableLineCommand(node, source, text); break; case configurationComment.DISABLE_NEXT_LINE_COMMAND: processDisableNextLineCommand(node, source, text); break; case configurationComment.DISABLE_COMMAND: processDisableCommand(node, source, text); break; case configurationComment.ENABLE_COMMAND: processEnableCommand(node, source, text); break; } } /** * @param {string} command * @param {string} fullText * @returns {string[]} */ function getCommandRules(command, fullText) { // Allow for description (f.e. /* stylelint-disable a, b -- Description */). const fullCommand = configurationComment.getConfigurationComment(command, configurationComment$1); const rulesText = fullText.slice(fullCommand.length).split(/\s-{2,}\s/u)[0]; validateTypes.assertString(rulesText); const rules = rulesText .trim() .split(',') .filter(Boolean) .map((r) => r.trim()); if (rules.length === 0) { return [constants.RULE_NAME_ALL]; } return rules; } /** * @param {string} fullText * @returns {string|undefined} */ function getDescription(fullText) { const descriptionStart = fullText.indexOf('--'); if (descriptionStart === -1) return; return fullText.slice(descriptionStart + 2).trim(); } /** * @param {PostcssNode} node * @param {number} line * @param {string} ruleName * @param {boolean} strict * @param {string|undefined} description */ function startDisabledRange(node, line, ruleName, strict, description) { const rangeObj = createDisableRange(node, line, strict, description); ensureRuleRanges(ruleName); const range = disabledRanges[ruleName]; validateTypes.assert(range); range.push(rangeObj); } /** * @param {number} line * @param {string} ruleName * @param {boolean} strict */ function endDisabledRange(line, ruleName, strict) { const ranges = disabledRanges[ruleName]; const lastRangeForRule = ranges ? ranges[ranges.length - 1] : null; if (!lastRangeForRule) { return; } // Add an `end` prop to the last range of that rule lastRangeForRule.end = line; lastRangeForRule.strictEnd = strict; } /** * @param {string} ruleName */ function ensureRuleRanges(ruleName) { if (!disabledRanges[ruleName]) { disabledRanges[ruleName] = disabledRangesAll.map(({ node, start, end, description }) => createDisableRange(node, start, false, description, end, false), ); } } /** * @param {string} ruleName * @returns {boolean} */ function ruleIsDisabled(ruleName) { const ranges = disabledRanges[ruleName]; if (!ranges) return false; const lastRange = ranges[ranges.length - 1]; if (!lastRange) return false; if (!lastRange.end) return true; return false; } } module.exports = assignDisabledRanges;