| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 | 'use strict';const isStandardSyntaxComment = require('./utils/isStandardSyntaxComment');const {	DISABLE_COMMAND,	DISABLE_LINE_COMMAND,	DISABLE_NEXT_LINE_COMMAND,	ENABLE_COMMAND,	extractStylelintCommand,	isStylelintCommand,} = require('./utils/stylelintCommand');const { assert, assertNumber, assertString } = require('./utils/validateTypes');const ALL_RULES = 'all';/** @typedef {import('postcss').Comment} PostcssComment *//** @typedef {import('postcss').Root} PostcssRoot *//** @typedef {import('postcss').Document} PostcssDocument *//** @typedef {import('stylelint').PostcssResult} PostcssResult *//** @typedef {import('stylelint').DisabledRangeObject} DisabledRangeObject *//** @typedef {import('stylelint').DisabledRange} DisabledRange *//** * @param {PostcssComment} comment * @param {number} start * @param {boolean} strictStart * @param {string|undefined} description * @param {number} [end] * @param {boolean} [strictEnd] * @returns {DisabledRange} */function createDisableRange(comment, start, strictStart, description, end, strictEnd) {	return {		comment,		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} */module.exports = function assignDisabledRanges(root, result) {	result.stylelint = result.stylelint || {		disabledRanges: {},		ruleSeverities: {},		customMessages: {},		ruleMetadata: {},	};	/**	 * Most of the functions below work via side effects mutating this object	 * @type {DisabledRangeObject & { all: DisabledRange[] }}	 */	const disabledRanges = {		[ALL_RULES]: [],	};	result.stylelint.disabledRanges = disabledRanges;	// Work around postcss/postcss-scss#109 by merging adjacent `//` comments	// into a single node before passing to `checkComment`.	/** @type {PostcssComment?} */	let inlineEnd;	root.walkComments((comment) => {		if (inlineEnd) {			// Ignore comments already processed by grouping with a previous one.			if (inlineEnd === comment) inlineEnd = null;			return;		}		const nextComment = comment.next();		// If any of these conditions are not met, do not merge comments.		if (			!(				!isStandardSyntaxComment(comment) &&				isStylelintCommand(comment) &&				nextComment &&				nextComment.type === 'comment' &&				(comment.text.includes('--') || nextComment.text.startsWith('--'))			)		) {			checkComment(comment);			return;		}		let lastLine = (comment.source && comment.source.end && comment.source.end.line) || 0;		const fullComment = comment.clone();		let current = nextComment;		while (!isStandardSyntaxComment(current) && !isStylelintCommand(current)) {			const currentLine = (current.source && current.source.end && 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 || next.type !== 'comment') break;			current = next;			lastLine = currentLine;		}		checkComment(fullComment);	});	return result;	/**	 * @param {PostcssComment} comment	 */	function processDisableLineCommand(comment) {		if (comment.source && comment.source.start) {			const line = comment.source.start.line;			const description = getDescription(comment.text);			for (const ruleName of getCommandRules(DISABLE_LINE_COMMAND, comment.text)) {				disableLine(comment, line, ruleName, description);			}		}	}	/**	 * @param {PostcssComment} comment	 */	function processDisableNextLineCommand(comment) {		if (comment.source && comment.source.end) {			const line = comment.source.end.line;			const description = getDescription(comment.text);			for (const ruleName of getCommandRules(DISABLE_NEXT_LINE_COMMAND, comment.text)) {				disableLine(comment, line + 1, ruleName, description);			}		}	}	/**	 * @param {PostcssComment} comment	 * @param {number} line	 * @param {string} ruleName	 * @param {string|undefined} description	 */	function disableLine(comment, line, ruleName, description) {		if (ruleIsDisabled(ALL_RULES)) {			throw comment.error('All rules have already been disabled', {				plugin: 'stylelint',			});		}		if (ruleName === ALL_RULES) {			for (const disabledRuleName of Object.keys(disabledRanges)) {				if (ruleIsDisabled(disabledRuleName)) continue;				const strict = disabledRuleName === ALL_RULES;				startDisabledRange(comment, line, disabledRuleName, strict, description);				endDisabledRange(line, disabledRuleName, strict);			}		} else {			if (ruleIsDisabled(ruleName)) {				throw comment.error(`"${ruleName}" has already been disabled`, {					plugin: 'stylelint',				});			}			startDisabledRange(comment, line, ruleName, true, description);			endDisabledRange(line, ruleName, true);		}	}	/**	 * @param {PostcssComment} comment	 */	function processDisableCommand(comment) {		const description = getDescription(comment.text);		for (const ruleToDisable of getCommandRules(DISABLE_COMMAND, comment.text)) {			const isAllRules = ruleToDisable === ALL_RULES;			if (ruleIsDisabled(ruleToDisable)) {				throw comment.error(					isAllRules						? 'All rules have already been disabled'						: `"${ruleToDisable}" has already been disabled`,					{						plugin: 'stylelint',					},				);			}			if (comment.source && comment.source.start) {				const line = comment.source.start.line;				if (isAllRules) {					for (const ruleName of Object.keys(disabledRanges)) {						startDisabledRange(comment, line, ruleName, ruleName === ALL_RULES, description);					}				} else {					startDisabledRange(comment, line, ruleToDisable, true, description);				}			}		}	}	/**	 * @param {PostcssComment} comment	 */	function processEnableCommand(comment) {		for (const ruleToEnable of getCommandRules(ENABLE_COMMAND, comment.text)) {			// need fallback if endLine will be undefined			const endLine = comment.source && comment.source.end && comment.source.end.line;			assertNumber(endLine);			if (ruleToEnable === ALL_RULES) {				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 comment.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 === ALL_RULES);					}				}				continue;			}			if (ruleIsDisabled(ALL_RULES) && disabledRanges[ruleToEnable] === undefined) {				// Get a starting point from the where all rules were disabled				disabledRanges[ruleToEnable] = disabledRanges[ALL_RULES].map(					({ start, end, description }) =>						createDisableRange(comment, start, false, description, end, false),				);				endDisabledRange(endLine, ruleToEnable, true);				continue;			}			if (ruleIsDisabled(ruleToEnable)) {				endDisabledRange(endLine, ruleToEnable, true);				continue;			}			throw comment.error(`"${ruleToEnable}" has not been disabled`, {				plugin: 'stylelint',			});		}	}	/**	 * @param {PostcssComment} comment	 */	function checkComment(comment) {		// Ignore comments that are not relevant commands		if (!isStylelintCommand(comment)) {			return;		}		switch (extractStylelintCommand(comment)) {			case DISABLE_LINE_COMMAND:				processDisableLineCommand(comment);				break;			case DISABLE_NEXT_LINE_COMMAND:				processDisableNextLineCommand(comment);				break;			case DISABLE_COMMAND:				processDisableCommand(comment);				break;			case ENABLE_COMMAND:				processEnableCommand(comment);				break;		}	}	/**	 * @param {string} command	 * @param {string} fullText	 * @returns {string[]}	 */	function getCommandRules(command, fullText) {		// Allow for description (f.e. /* stylelint-disable a, b -- Description */).		const splitted = fullText.slice(command.length).split(/\s-{2,}\s/u)[0];		assertString(splitted);		const rules = splitted			.trim()			.split(',')			.filter(Boolean)			.map((r) => r.trim());		if (rules.length === 0) {			return [ALL_RULES];		}		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 {PostcssComment} comment	 * @param {number} line	 * @param {string} ruleName	 * @param {boolean} strict	 * @param {string|undefined} description	 */	function startDisabledRange(comment, line, ruleName, strict, description) {		const rangeObj = createDisableRange(comment, line, strict, description);		ensureRuleRanges(ruleName);		const range = disabledRanges[ruleName];		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] = disabledRanges[ALL_RULES].map(				({ comment, start, end, description }) =>					createDisableRange(comment, 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;	}};
 |