| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 | 'use strict';const atRuleParamIndex = require('../../utils/atRuleParamIndex');const declarationValueIndex = require('../../utils/declarationValueIndex');const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');const parseSelector = require('../../utils/parseSelector');const report = require('../../utils/report');const ruleMessages = require('../../utils/ruleMessages');const validateOptions = require('../../utils/validateOptions');const valueParser = require('postcss-value-parser');const { isBoolean, assertString } = require('../../utils/validateTypes');const { isAtRule } = require('../../utils/typeGuards');const ruleName = 'string-quotes';const messages = ruleMessages(ruleName, {	expected: (q) => `Expected ${q} quotes`,});const meta = {	url: 'https://stylelint.io/user-guide/rules/string-quotes',	fixable: true,};const singleQuote = `'`;const doubleQuote = `"`;/** @type {import('stylelint').Rule} */const rule = (primary, secondaryOptions, context) => {	const correctQuote = primary === 'single' ? singleQuote : doubleQuote;	const erroneousQuote = primary === 'single' ? doubleQuote : singleQuote;	return (root, result) => {		const validOptions = validateOptions(			result,			ruleName,			{				actual: primary,				possible: ['single', 'double'],			},			{				actual: secondaryOptions,				possible: {					avoidEscape: [isBoolean],				},				optional: true,			},		);		if (!validOptions) {			return;		}		const avoidEscape =			secondaryOptions && secondaryOptions.avoidEscape !== undefined				? secondaryOptions.avoidEscape				: true;		root.walk((node) => {			switch (node.type) {				case 'atrule':					checkDeclOrAtRule(node, node.params, atRuleParamIndex);					break;				case 'decl':					checkDeclOrAtRule(node, node.value, declarationValueIndex);					break;				case 'rule':					checkRule(node);					break;			}		});		/**		 * @param {import('postcss').Rule} ruleNode		 * @returns {void}		 */		function checkRule(ruleNode) {			if (!isStandardSyntaxRule(ruleNode)) {				return;			}			if (!ruleNode.selector.includes('[') || !ruleNode.selector.includes('=')) {				return;			}			/** @type {number[]} */			const fixPositions = [];			parseSelector(ruleNode.selector, result, ruleNode, (selectorTree) => {				let selectorFixed = false;				selectorTree.walkAttributes((attributeNode) => {					if (!attributeNode.quoted) {						return;					}					if (attributeNode.quoteMark === correctQuote && avoidEscape) {						assertString(attributeNode.value);						const needsCorrectEscape = attributeNode.value.includes(correctQuote);						const needsOtherEscape = attributeNode.value.includes(erroneousQuote);						if (needsOtherEscape) {							return;						}						if (needsCorrectEscape) {							if (context.fix) {								selectorFixed = true;								attributeNode.quoteMark = erroneousQuote;							} else {								report({									message: messages.expected(primary === 'single' ? 'double' : primary),									node: ruleNode,									index: attributeNode.sourceIndex + attributeNode.offsetOf('value'),									result,									ruleName,								});							}						}					}					if (attributeNode.quoteMark === erroneousQuote) {						if (avoidEscape) {							assertString(attributeNode.value);							const needsCorrectEscape = attributeNode.value.includes(correctQuote);							const needsOtherEscape = attributeNode.value.includes(erroneousQuote);							if (needsOtherEscape) {								if (context.fix) {									selectorFixed = true;									attributeNode.quoteMark = correctQuote;								} else {									report({										message: messages.expected(primary),										node: ruleNode,										index: attributeNode.sourceIndex + attributeNode.offsetOf('value'),										result,										ruleName,									});								}								return;							}							if (needsCorrectEscape) {								return;							}						}						if (context.fix) {							selectorFixed = true;							attributeNode.quoteMark = correctQuote;						} else {							report({								message: messages.expected(primary),								node: ruleNode,								index: attributeNode.sourceIndex + attributeNode.offsetOf('value'),								result,								ruleName,							});						}					}				});				if (selectorFixed) {					ruleNode.selector = selectorTree.toString();				}			});			for (const fixIndex of fixPositions) {				ruleNode.selector = replaceQuote(ruleNode.selector, fixIndex, correctQuote);			}		}		/**		 * @template {import('postcss').AtRule | import('postcss').Declaration} T		 * @param {T} node		 * @param {string} value		 * @param {(node: T) => number} getIndex		 * @returns {void}		 */		function checkDeclOrAtRule(node, value, getIndex) {			/** @type {number[]} */			const fixPositions = [];			// Get out quickly if there are no erroneous quotes			if (!value.includes(erroneousQuote)) {				return;			}			if (isAtRule(node) && node.name === 'charset') {				// allow @charset rules to have double quotes, in spite of the configuration				// TODO: @charset should always use double-quotes, see https://github.com/stylelint/stylelint/issues/2788				return;			}			valueParser(value).walk((valueNode) => {				if (valueNode.type === 'string' && valueNode.quote === erroneousQuote) {					const needsEscape = valueNode.value.includes(correctQuote);					if (avoidEscape && needsEscape) {						// don't consider this an error						return;					}					const openIndex = valueNode.sourceIndex;					// we currently don't fix escapes					if (context.fix && !needsEscape) {						const closeIndex = openIndex + valueNode.value.length + erroneousQuote.length;						fixPositions.push(openIndex, closeIndex);					} else {						report({							message: messages.expected(primary),							node,							index: getIndex(node) + openIndex,							result,							ruleName,						});					}				}			});			for (const fixIndex of fixPositions) {				if (isAtRule(node)) {					node.params = replaceQuote(node.params, fixIndex, correctQuote);				} else {					node.value = replaceQuote(node.value, fixIndex, correctQuote);				}			}		}	};};/** * @param {string} string * @param {number} index * @param {string} replace * @returns {string} */function replaceQuote(string, index, replace) {	return string.substring(0, index) + replace + string.substring(index + replace.length);}rule.ruleName = ruleName;rule.messages = messages;rule.meta = meta;module.exports = rule;
 |