verboseFormatter.cjs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // NOTICE: This file is generated by Rollup. To modify it,
  2. // please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
  3. 'use strict';
  4. const picocolors = require('picocolors');
  5. const pluralize = require('../utils/pluralize.cjs');
  6. const stringFormatter = require('./stringFormatter.cjs');
  7. const terminalLink = require('./terminalLink.cjs');
  8. const { underline, red, yellow, dim, green } = picocolors;
  9. /** @import {Formatter, LintResult, RuleMeta, Severity, Warning} from 'stylelint' */
  10. /**
  11. * @type {Formatter}
  12. */
  13. function verboseFormatter(results, returnValue) {
  14. let output = stringFormatter(results, returnValue);
  15. if (output === '') {
  16. output = '\n';
  17. }
  18. const ignoredCount = results.filter((result) => result.ignored).length;
  19. const checkedDisplay = ignoredCount
  20. ? `${results.length - ignoredCount} of ${results.length}`
  21. : results.length;
  22. output += underline(`${checkedDisplay} ${pluralize('source', results.length)} checked\n`);
  23. for (const result of results) {
  24. let formatting = green;
  25. if (result.errored) {
  26. formatting = red;
  27. } else if (result.warnings.length) {
  28. formatting = yellow;
  29. } else if (result.ignored) {
  30. formatting = dim;
  31. }
  32. let sourceText = fileLink(result.source);
  33. if (result.ignored) {
  34. sourceText += ' (ignored)';
  35. }
  36. output += formatting(` ${sourceText}\n`);
  37. }
  38. const warnings = results.flatMap((r) => r.warnings);
  39. if (warnings.length === 0) {
  40. output += '\n0 problems found\n';
  41. } else {
  42. const warningsBySeverity = groupBy(warnings, (w) => w.severity);
  43. /**
  44. * @param {Severity} severity
  45. */
  46. const printProblems = (severity) => {
  47. const problems = warningsBySeverity[severity];
  48. if (problems === undefined) return;
  49. output += '\n';
  50. output += underline(`${problems.length} ${pluralize(severity, problems.length)} found\n`);
  51. const problemsByRule = groupBy(problems, (w) => w.rule);
  52. const metadata = returnValue.ruleMetadata;
  53. for (const [rule, list] of Object.entries(problemsByRule)) {
  54. const meta = metadata[rule] || {};
  55. let additional = [meta.fixable ? 'maybe fixable' : '', meta.deprecated ? 'deprecated' : '']
  56. .filter(Boolean)
  57. .join(', ');
  58. additional = additional ? ` (${additional})` : '';
  59. output += dim(` ${ruleLink(rule, meta)}: ${list.length}${additional}\n`);
  60. }
  61. };
  62. printProblems('error');
  63. printProblems('warning');
  64. }
  65. const fixedRules = getFixedRules(results);
  66. if (fixedRules.size) {
  67. let lines = '\n';
  68. let total = 0;
  69. for (const [name, count] of fixedRules) {
  70. const meta = returnValue.ruleMetadata[name];
  71. lines += dim(` ${ruleLink(name, meta)}: ${count}\n`);
  72. total += count;
  73. }
  74. output += '\n';
  75. output += underline(`${total} ${pluralize('problem', total)} fixed`);
  76. output += lines;
  77. }
  78. return `${output}\n`;
  79. }
  80. /**
  81. * @template {string} K
  82. * @param {Warning[]} array
  83. * @param {(w: Warning) => K} keyFn
  84. * @returns {Record<K, Warning[]>}
  85. * @todo replace by Object.groupBy once support for Node.js version 20 is dropped
  86. */
  87. function groupBy(array, keyFn) {
  88. /** @type {Record<string, Warning[]>} */
  89. const result = {};
  90. for (const item of array) {
  91. const key = keyFn(item);
  92. let warnings = result[key];
  93. if (warnings === undefined) {
  94. result[key] = warnings = [];
  95. }
  96. warnings.push(item);
  97. }
  98. return result;
  99. }
  100. /**
  101. * @param {string | undefined} source
  102. * @returns {string}
  103. */
  104. function fileLink(source) {
  105. if (!source || source.startsWith('<')) {
  106. return `${source}`;
  107. }
  108. return terminalLink(source, `file://${source}`);
  109. }
  110. /**
  111. * @param {string} rule
  112. * @param {Partial<RuleMeta> | undefined} metadata
  113. * @returns {string}
  114. */
  115. function ruleLink(rule, metadata) {
  116. if (metadata && metadata.url) {
  117. return terminalLink(rule, metadata.url);
  118. }
  119. return rule;
  120. }
  121. /** @param {LintResult[]} results */
  122. function getFixedRules(results) {
  123. /** @type {Map<string, number>} */
  124. const rules = new Map();
  125. for (const { _postcssResult } of results) {
  126. if (!_postcssResult) continue; // CSS syntax error
  127. const {
  128. stylelint: { fixersData },
  129. } = _postcssResult;
  130. const entries = Object.entries(fixersData);
  131. for (const [ruleName, count] of entries) {
  132. if (count) rules.set(ruleName, count);
  133. }
  134. }
  135. return rules;
  136. }
  137. module.exports = verboseFormatter;