index.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. 'use strict';
  2. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  3. const functionArgumentsSearch = require('../../utils/functionArgumentsSearch');
  4. const isStandardSyntaxUrl = require('../../utils/isStandardSyntaxUrl');
  5. const optionsMatches = require('../../utils/optionsMatches');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const validateOptions = require('../../utils/validateOptions');
  9. const ruleName = 'function-url-quotes';
  10. const messages = ruleMessages(ruleName, {
  11. expected: (functionName) => `Expected quotes around "${functionName}" function argument`,
  12. rejected: (functionName) => `Unexpected quotes around "${functionName}" function argument`,
  13. });
  14. const meta = {
  15. url: 'https://stylelint.io/user-guide/rules/list/function-url-quotes',
  16. };
  17. /** @type {import('stylelint').Rule} */
  18. const rule = (primary, secondaryOptions) => {
  19. return (root, result) => {
  20. const validOptions = validateOptions(
  21. result,
  22. ruleName,
  23. {
  24. actual: primary,
  25. possible: ['always', 'never'],
  26. },
  27. {
  28. actual: secondaryOptions,
  29. possible: {
  30. except: ['empty'],
  31. },
  32. optional: true,
  33. },
  34. );
  35. if (!validOptions) {
  36. return;
  37. }
  38. root.walkAtRules(checkAtRuleParams);
  39. root.walkDecls(checkDeclParams);
  40. /**
  41. * @param {import('postcss').Declaration} decl
  42. */
  43. function checkDeclParams(decl) {
  44. functionArgumentsSearch(decl.toString().toLowerCase(), 'url', (args, index) => {
  45. checkArgs(args, decl, index, 'url');
  46. });
  47. }
  48. /**
  49. * @param {import('postcss').AtRule} atRule
  50. */
  51. function checkAtRuleParams(atRule) {
  52. const atRuleParamsLowerCase = atRule.params.toLowerCase();
  53. functionArgumentsSearch(atRuleParamsLowerCase, 'url', (args, index) => {
  54. checkArgs(args, atRule, index + atRuleParamIndex(atRule), 'url');
  55. });
  56. functionArgumentsSearch(atRuleParamsLowerCase, 'url-prefix', (args, index) => {
  57. checkArgs(args, atRule, index + atRuleParamIndex(atRule), 'url-prefix');
  58. });
  59. functionArgumentsSearch(atRuleParamsLowerCase, 'domain', (args, index) => {
  60. checkArgs(args, atRule, index + atRuleParamIndex(atRule), 'domain');
  61. });
  62. }
  63. /**
  64. * @param {string} args
  65. * @param {import('postcss').Node} node
  66. * @param {number} index
  67. * @param {string} functionName
  68. */
  69. function checkArgs(args, node, index, functionName) {
  70. let shouldHasQuotes = primary === 'always';
  71. const leftTrimmedArgs = args.trimStart();
  72. if (!isStandardSyntaxUrl(leftTrimmedArgs)) {
  73. return;
  74. }
  75. const complaintIndex = index + args.length - leftTrimmedArgs.length;
  76. const complaintEndIndex = index + args.length;
  77. const hasQuotes = leftTrimmedArgs.startsWith("'") || leftTrimmedArgs.startsWith('"');
  78. const trimmedArg = args.trim();
  79. const isEmptyArgument = ['', "''", '""'].includes(trimmedArg);
  80. if (optionsMatches(secondaryOptions, 'except', 'empty') && isEmptyArgument) {
  81. shouldHasQuotes = !shouldHasQuotes;
  82. }
  83. if (shouldHasQuotes) {
  84. if (hasQuotes) {
  85. return;
  86. }
  87. complain(messages.expected(functionName), node, complaintIndex, complaintEndIndex);
  88. } else {
  89. if (!hasQuotes) {
  90. return;
  91. }
  92. complain(messages.rejected(functionName), node, complaintIndex, complaintEndIndex);
  93. }
  94. }
  95. /**
  96. * @param {string} message
  97. * @param {import('postcss').Node} node
  98. * @param {number} index
  99. * @param {number} endIndex
  100. */
  101. function complain(message, node, index, endIndex) {
  102. report({
  103. message,
  104. node,
  105. index,
  106. endIndex,
  107. result,
  108. ruleName,
  109. });
  110. }
  111. };
  112. };
  113. rule.ruleName = ruleName;
  114. rule.messages = messages;
  115. rule.meta = meta;
  116. module.exports = rule;