index.cjs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // NOTICE: This file is generated by Rollup. To modify it,
  2. // please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
  3. 'use strict';
  4. const nodeFieldIndices = require('../../utils/nodeFieldIndices.cjs');
  5. const functionArgumentsSearch = require('../../utils/functionArgumentsSearch.cjs');
  6. const getAtRuleParams = require('../../utils/getAtRuleParams.cjs');
  7. const getDeclarationValue = require('../../utils/getDeclarationValue.cjs');
  8. const isStandardSyntaxDeclaration = require('../../utils/isStandardSyntaxDeclaration.cjs');
  9. const isStandardSyntaxUrl = require('../../utils/isStandardSyntaxUrl.cjs');
  10. const optionsMatches = require('../../utils/optionsMatches.cjs');
  11. const report = require('../../utils/report.cjs');
  12. const ruleMessages = require('../../utils/ruleMessages.cjs');
  13. const validateOptions = require('../../utils/validateOptions.cjs');
  14. const ruleName = 'function-url-quotes';
  15. const messages = ruleMessages(ruleName, {
  16. expected: (functionName) => `Expected quotes around "${functionName}" function argument`,
  17. rejected: (functionName) => `Unexpected quotes around "${functionName}" function argument`,
  18. });
  19. const meta = {
  20. url: 'https://stylelint.io/user-guide/rules/function-url-quotes',
  21. fixable: true,
  22. };
  23. const URL_FUNC_REGEX = /url\(/i;
  24. /** @import { Problem, CoreRules } from 'stylelint' */
  25. /** @import { FunctionNode, ParsedValue } from 'postcss-value-parser' */
  26. /** @import { AtRule, Declaration } from 'postcss' */
  27. /** @type {CoreRules[ruleName]} */
  28. const rule = (primary, secondaryOptions) => {
  29. return (root, result) => {
  30. const validOptions = validateOptions(
  31. result,
  32. ruleName,
  33. {
  34. actual: primary,
  35. possible: ['always', 'never'],
  36. },
  37. {
  38. actual: secondaryOptions,
  39. possible: {
  40. except: ['empty'],
  41. },
  42. optional: true,
  43. },
  44. );
  45. if (!validOptions) {
  46. return;
  47. }
  48. const exceptEmpty = optionsMatches(secondaryOptions, 'except', 'empty');
  49. const emptyArgumentPatterns = new Set(['', "''", '""']);
  50. root.walkAtRules(checkAtRuleParams);
  51. root.walkDecls(checkDeclParams);
  52. /**
  53. * @param {Declaration|AtRule} node
  54. * @param {FunctionNode} fn
  55. * @param {keyof messages} messageType
  56. * @param {ParsedValue} parsedValue
  57. */
  58. function getFix(node, fn, messageType, parsedValue) {
  59. return () => {
  60. switch (messageType) {
  61. case 'expected':
  62. addQuotes(fn);
  63. break;
  64. case 'rejected':
  65. removeQuotes(fn);
  66. break;
  67. }
  68. switch (node.type) {
  69. case 'decl':
  70. node.value = parsedValue.toString();
  71. break;
  72. case 'atrule':
  73. node.params = parsedValue.toString();
  74. break;
  75. }
  76. };
  77. }
  78. /**
  79. * @param {AtRule|Declaration} node
  80. * @param {string} source
  81. * @param {number} startIndex
  82. */
  83. function complain(node, source, startIndex) {
  84. functionArgumentsSearch(source, /^url$/i, (args, index, funcNode, parsedValue) => {
  85. const object = checkArgs(args, startIndex + index);
  86. if (object) {
  87. const { messageType, ...rest } = object;
  88. const message = messages[messageType];
  89. const messageArgs = [funcNode.value.toLowerCase()];
  90. const fix = getFix(node, funcNode, messageType, parsedValue);
  91. report({
  92. ...rest,
  93. node,
  94. fix: { apply: fix, node },
  95. message,
  96. messageArgs,
  97. result,
  98. ruleName,
  99. });
  100. }
  101. });
  102. }
  103. /** @param {Declaration} decl */
  104. function checkDeclParams(decl) {
  105. if (!URL_FUNC_REGEX.test(decl.value)) return;
  106. if (!isStandardSyntaxDeclaration(decl)) return;
  107. const value = getDeclarationValue(decl);
  108. const startIndex = nodeFieldIndices.declarationValueIndex(decl);
  109. complain(decl, value, startIndex);
  110. }
  111. /** @param {AtRule} atRule */
  112. function checkAtRuleParams(atRule) {
  113. const params = getAtRuleParams(atRule);
  114. const startIndex = nodeFieldIndices.atRuleParamIndex(atRule);
  115. complain(atRule, params, startIndex);
  116. }
  117. /** @param {FunctionNode} funcNode */
  118. function addQuotes(funcNode) {
  119. for (const argNode of funcNode.nodes) {
  120. if (argNode.type === 'word') {
  121. argNode.value = `"${argNode.value}"`;
  122. }
  123. }
  124. }
  125. /** @param {FunctionNode} funcNode */
  126. function removeQuotes(funcNode) {
  127. for (const argNode of funcNode.nodes) {
  128. if (argNode.type === 'string') {
  129. // NOTE: We can ignore this error because the test passes.
  130. // @ts-expect-error -- TS2322: Type '"word"' is not assignable to type '"string"'.
  131. argNode.type = 'word';
  132. }
  133. }
  134. }
  135. /**
  136. * @param {string} args
  137. * @param {number} index
  138. * @returns {Pick<Problem, 'index' | 'endIndex'> & { messageType: keyof messages } | undefined}
  139. */
  140. function checkArgs(args, index) {
  141. const leftTrimmedArgs = args.trimStart();
  142. if (!isStandardSyntaxUrl(leftTrimmedArgs)) return;
  143. let expectQuotes = primary === 'always';
  144. if (exceptEmpty && emptyArgumentPatterns.has(args.trim())) {
  145. expectQuotes = !expectQuotes;
  146. }
  147. const hasQuotes = leftTrimmedArgs.startsWith("'") || leftTrimmedArgs.startsWith('"');
  148. if (expectQuotes && hasQuotes) return;
  149. if (!expectQuotes && !hasQuotes) return;
  150. const messageType = expectQuotes ? 'expected' : 'rejected';
  151. const reportIndex = index + args.length - leftTrimmedArgs.length;
  152. const reportEndIndex = index + args.length;
  153. return {
  154. messageType,
  155. index: reportIndex,
  156. endIndex: reportEndIndex,
  157. };
  158. }
  159. };
  160. };
  161. rule.ruleName = ruleName;
  162. rule.messages = messages;
  163. rule.meta = meta;
  164. module.exports = rule;