index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. 'use strict';
  2. const declarationValueIndex = require('../../utils/declarationValueIndex');
  3. const findFontFamily = require('../../utils/findFontFamily');
  4. const { fontFamilyKeywords } = require('../../reference/keywords');
  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 { isRegExp, isString } = require('../../utils/validateTypes');
  10. const ruleName = 'font-family-no-duplicate-names';
  11. const messages = ruleMessages(ruleName, {
  12. rejected: (name) => `Unexpected duplicate name ${name}`,
  13. });
  14. const meta = {
  15. url: 'https://stylelint.io/user-guide/rules/font-family-no-duplicate-names',
  16. };
  17. /**
  18. * @param {import('postcss-value-parser').Node} node
  19. */
  20. const isFamilyNameKeyword = (node) =>
  21. !('quote' in node) && fontFamilyKeywords.has(node.value.toLowerCase());
  22. /** @type {import('stylelint').Rule} */
  23. const rule = (primary, secondaryOptions) => {
  24. return (root, result) => {
  25. const validOptions = validateOptions(
  26. result,
  27. ruleName,
  28. { actual: primary },
  29. {
  30. actual: secondaryOptions,
  31. possible: {
  32. ignoreFontFamilyNames: [isString, isRegExp],
  33. },
  34. optional: true,
  35. },
  36. );
  37. if (!validOptions) {
  38. return;
  39. }
  40. root.walkDecls(/^font(-family)?$/i, (decl) => {
  41. const keywords = new Set();
  42. const familyNames = new Set();
  43. const fontFamilies = findFontFamily(decl.value);
  44. if (fontFamilies.length === 0) {
  45. return;
  46. }
  47. for (const fontFamilyNode of fontFamilies) {
  48. const family = fontFamilyNode.value.trim();
  49. if (optionsMatches(secondaryOptions, 'ignoreFontFamilyNames', family)) {
  50. continue;
  51. }
  52. const rawFamily =
  53. 'quote' in fontFamilyNode ? fontFamilyNode.quote + family + fontFamilyNode.quote : family;
  54. if (isFamilyNameKeyword(fontFamilyNode)) {
  55. if (keywords.has(family.toLowerCase())) {
  56. complain(
  57. messages.rejected(family),
  58. declarationValueIndex(decl) + fontFamilyNode.sourceIndex,
  59. rawFamily.length,
  60. decl,
  61. );
  62. continue;
  63. }
  64. keywords.add(family);
  65. continue;
  66. }
  67. if (familyNames.has(family)) {
  68. complain(
  69. messages.rejected(family),
  70. declarationValueIndex(decl) + fontFamilyNode.sourceIndex,
  71. rawFamily.length,
  72. decl,
  73. );
  74. continue;
  75. }
  76. familyNames.add(family);
  77. }
  78. });
  79. /**
  80. * @param {string} message
  81. * @param {number} index
  82. * @param {number} length
  83. * @param {import('postcss').Declaration} decl
  84. */
  85. function complain(message, index, length, decl) {
  86. report({
  87. result,
  88. ruleName,
  89. message,
  90. node: decl,
  91. index,
  92. endIndex: index + length,
  93. });
  94. }
  95. };
  96. };
  97. rule.ruleName = ruleName;
  98. rule.messages = messages;
  99. rule.meta = meta;
  100. module.exports = rule;