index.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. 'use strict';
  2. const declarationValueIndex = require('../../utils/declarationValueIndex');
  3. const findFontFamily = require('../../utils/findFontFamily');
  4. const keywordSets = require('../../reference/keywordSets');
  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/list/font-family-no-duplicate-names',
  16. };
  17. /**
  18. * @param {import('postcss-value-parser').Node} node
  19. */
  20. const isFamilyNameKeyword = (node) =>
  21. !('quote' in node) && keywordSets.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. if (isFamilyNameKeyword(fontFamilyNode)) {
  53. if (keywords.has(family.toLowerCase())) {
  54. complain(
  55. messages.rejected(family),
  56. declarationValueIndex(decl) + fontFamilyNode.sourceIndex,
  57. decl,
  58. );
  59. continue;
  60. }
  61. keywords.add(family);
  62. continue;
  63. }
  64. if (familyNames.has(family)) {
  65. complain(
  66. messages.rejected(family),
  67. declarationValueIndex(decl) + fontFamilyNode.sourceIndex,
  68. decl,
  69. );
  70. continue;
  71. }
  72. familyNames.add(family);
  73. }
  74. });
  75. /**
  76. * @param {string} message
  77. * @param {number} index
  78. * @param {import('postcss').Declaration} decl
  79. */
  80. function complain(message, index, decl) {
  81. report({
  82. result,
  83. ruleName,
  84. message,
  85. node: decl,
  86. index,
  87. });
  88. }
  89. };
  90. };
  91. rule.ruleName = ruleName;
  92. rule.messages = messages;
  93. rule.meta = meta;
  94. module.exports = rule;