index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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 hasQuotes = leftTrimmedArgs.startsWith("'") || leftTrimmedArgs.startsWith('"');
  77. const trimmedArg = args.trim();
  78. const isEmptyArgument = ['', "''", '""'].includes(trimmedArg);
  79. if (optionsMatches(secondaryOptions, 'except', 'empty') && isEmptyArgument) {
  80. shouldHasQuotes = !shouldHasQuotes;
  81. }
  82. if (shouldHasQuotes) {
  83. if (hasQuotes) {
  84. return;
  85. }
  86. complain(messages.expected(functionName), node, complaintIndex);
  87. } else {
  88. if (!hasQuotes) {
  89. return;
  90. }
  91. complain(messages.rejected(functionName), node, complaintIndex);
  92. }
  93. }
  94. /**
  95. * @param {string} message
  96. * @param {import('postcss').Node} node
  97. * @param {number} index
  98. */
  99. function complain(message, node, index) {
  100. report({
  101. message,
  102. node,
  103. index,
  104. result,
  105. ruleName,
  106. });
  107. }
  108. };
  109. };
  110. rule.ruleName = ruleName;
  111. rule.messages = messages;
  112. rule.meta = meta;
  113. module.exports = rule;