report.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use strict';
  2. /**
  3. * Report a problem.
  4. *
  5. * This function accounts for `disabledRanges` attached to the result.
  6. * That is, if the reported problem is within a disabledRange,
  7. * it is ignored. Otherwise, it is attached to the result as a
  8. * postcss warning.
  9. *
  10. * It also accounts for the rule's severity.
  11. *
  12. * You *must* pass *either* a node or a line number.
  13. *
  14. * @type {typeof import('stylelint').utils.report}
  15. */
  16. module.exports = function report(problem) {
  17. const { ruleName, result, message, messageArgs, line, node, index, endIndex, word, severity } =
  18. problem;
  19. result.stylelint = result.stylelint || {
  20. ruleSeverities: {},
  21. customMessages: {},
  22. ruleMetadata: {},
  23. };
  24. const ruleSeverity =
  25. severity || (result.stylelint.ruleSeverities && result.stylelint.ruleSeverities[ruleName]);
  26. // In quiet mode, mere warnings are ignored
  27. if (result.stylelint.quiet && ruleSeverity !== 'error') {
  28. return;
  29. }
  30. const { start } = (node && node.rangeBy({ index, endIndex })) || {};
  31. // If a line is not passed, use the node.rangeBy method to get the
  32. // line number that the complaint pertains to
  33. const startLine = line || (start && start.line);
  34. if (!startLine) {
  35. throw new Error('You must pass either a node or a line number');
  36. }
  37. const { ignoreDisables } = result.stylelint.config || {};
  38. if (result.stylelint.disabledRanges) {
  39. const ranges =
  40. result.stylelint.disabledRanges[ruleName] || result.stylelint.disabledRanges.all || [];
  41. for (const range of ranges) {
  42. if (
  43. // If the problem is within a disabledRange,
  44. // and that disabledRange's rules include this one,
  45. // do not register a warning
  46. range.start <= startLine &&
  47. (range.end === undefined || range.end >= startLine) &&
  48. (!range.rules || range.rules.includes(ruleName))
  49. ) {
  50. // Collect disabled warnings
  51. // Used to report `needlessDisables` in subsequent processing.
  52. const disabledWarnings =
  53. result.stylelint.disabledWarnings || (result.stylelint.disabledWarnings = []);
  54. disabledWarnings.push({
  55. rule: ruleName,
  56. line: startLine,
  57. });
  58. if (!ignoreDisables) {
  59. return;
  60. }
  61. break;
  62. }
  63. }
  64. }
  65. if (!result.stylelint.stylelintError && ruleSeverity === 'error') {
  66. result.stylelint.stylelintError = true;
  67. }
  68. if (!result.stylelint.stylelintWarning && ruleSeverity === 'warning') {
  69. result.stylelint.stylelintWarning = true;
  70. }
  71. /** @type {import('stylelint').WarningOptions} */
  72. const warningProperties = {
  73. severity: ruleSeverity,
  74. rule: ruleName,
  75. };
  76. if (node) {
  77. warningProperties.node = node;
  78. }
  79. if (problem.start) {
  80. warningProperties.start = problem.start;
  81. } else if (index) {
  82. warningProperties.index = index;
  83. }
  84. if (problem.end) {
  85. warningProperties.end = problem.end;
  86. } else if (endIndex) {
  87. warningProperties.endIndex = endIndex;
  88. }
  89. if (word) {
  90. warningProperties.word = word;
  91. }
  92. const { customMessages } = result.stylelint;
  93. const warningMessage = buildWarningMessage(
  94. (customMessages && customMessages[ruleName]) || message,
  95. messageArgs,
  96. );
  97. result.warn(warningMessage, warningProperties);
  98. };
  99. /**
  100. * @param {import('stylelint').RuleMessage} message
  101. * @param {import('stylelint').Problem['messageArgs']} messageArgs
  102. * @returns {string}
  103. */
  104. function buildWarningMessage(message, messageArgs) {
  105. const args = messageArgs || [];
  106. if (typeof message === 'string') {
  107. return printfLike(message, ...args);
  108. }
  109. return message(...args);
  110. }
  111. /**
  112. * @param {string} format
  113. * @param {Array<unknown>} args
  114. * @returns {string}
  115. */
  116. function printfLike(format, ...args) {
  117. return args.reduce((/** @type {string} */ result, arg) => {
  118. return result.replace(/%[ds]/, String(arg));
  119. }, format);
  120. }