verboseFormatter.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use strict';
  2. const stringFormatter = require('./stringFormatter');
  3. const { underline, red, yellow, dim, green } = require('picocolors');
  4. const terminalLink = require('./terminalLink');
  5. /** @typedef {import('stylelint').Formatter} Formatter */
  6. /** @typedef {import('stylelint').LintResult} LintResult */
  7. /** @typedef {import('stylelint').Warning} Warning */
  8. /**
  9. * @type {Formatter}
  10. */
  11. module.exports = function (results, returnValue) {
  12. let output = stringFormatter(results, returnValue);
  13. if (output === '') {
  14. output = '\n';
  15. }
  16. const sourceWord = results.length > 1 ? 'sources' : 'source';
  17. const ignoredCount = results.filter((result) => result.ignored).length;
  18. const checkedDisplay = ignoredCount
  19. ? `${results.length - ignoredCount} of ${results.length}`
  20. : results.length;
  21. output += underline(`${checkedDisplay} ${sourceWord} checked\n`);
  22. for (const result of results) {
  23. let formatting = green;
  24. if (result.errored) {
  25. formatting = red;
  26. } else if (result.warnings.length) {
  27. formatting = yellow;
  28. } else if (result.ignored) {
  29. formatting = dim;
  30. }
  31. let sourceText = fileLink(result.source);
  32. if (result.ignored) {
  33. sourceText += ' (ignored)';
  34. }
  35. output += formatting(` ${sourceText}\n`);
  36. }
  37. const warnings = results.flatMap((r) => r.warnings);
  38. const warningsBySeverity = groupBy(warnings, (w) => w.severity);
  39. const problemWord = warnings.length === 1 ? 'problem' : 'problems';
  40. output += underline(`\n${warnings.length} ${problemWord} found\n`);
  41. const metadata = ruleMetadata(results);
  42. for (const [severityLevel, warningList] of Object.entries(warningsBySeverity)) {
  43. const warningsByRule = groupBy(warningList, (w) => w.rule);
  44. output += ` severity level "${severityLevel}": ${warningList.length}\n`;
  45. for (const [rule, list] of Object.entries(warningsByRule)) {
  46. output += dim(` ${ruleLink(rule, metadata[rule])}: ${list.length}\n`);
  47. }
  48. }
  49. return `${output}\n`;
  50. };
  51. /**
  52. * @param {Warning[]} array
  53. * @param {(w: Warning) => string} keyFn
  54. * @returns {Record<string, Warning[]>}
  55. */
  56. function groupBy(array, keyFn) {
  57. /** @type {Record<string, Warning[]>} */
  58. const result = {};
  59. for (const item of array) {
  60. const key = keyFn(item);
  61. if (!(key in result)) {
  62. result[key] = [];
  63. }
  64. result[key].push(item);
  65. }
  66. return result;
  67. }
  68. /**
  69. * @param {string | undefined} source
  70. * @returns {string}
  71. */
  72. function fileLink(source) {
  73. if (!source || source.startsWith('<')) {
  74. return `${source}`;
  75. }
  76. return terminalLink(source, `file://${source}`);
  77. }
  78. /**
  79. * @param {LintResult[]} results
  80. * @returns {Record<string, { url?: string }>}
  81. */
  82. function ruleMetadata(results) {
  83. const firstResult = results[0];
  84. return (
  85. (firstResult &&
  86. firstResult._postcssResult &&
  87. firstResult._postcssResult.stylelint &&
  88. firstResult._postcssResult.stylelint.ruleMetadata) ||
  89. {}
  90. );
  91. }
  92. /**
  93. * @param {string} rule
  94. * @param {{ url?: string } | undefined} metadata
  95. * @returns {string}
  96. */
  97. function ruleLink(rule, metadata) {
  98. if (metadata && metadata.url) {
  99. return terminalLink(rule, metadata.url);
  100. }
  101. return rule;
  102. }