4
0

tapFormatter.mjs 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { isNumber } from '../utils/validateTypes.mjs';
  2. import preprocessWarnings from './preprocessWarnings.mjs';
  3. /** @import {Warning, Formatter} from 'stylelint' */
  4. /** @typedef {Array<Omit<Warning, 'rule'>>} Warnings */
  5. /** @type {Formatter} */
  6. export default function tapFormatter(results) {
  7. const lines = ['TAP version 14', `1..${results.length}`];
  8. for (const [index, result] of results.entries()) {
  9. preprocessWarnings(result);
  10. // SKIP indicates "that a test was not run, or if it was,
  11. // that its success or failure is being temporarily ignored."
  12. // skipped tests must not be treated as test failures though
  13. // if a file is ignored, errored is currently always false
  14. const ignored = result.ignored ? ' # SKIP ignored' : '';
  15. const error = result.errored ? 'not ' : '';
  16. lines.push(`${error}ok ${index + 1} - ${getEscapedSource(result.source)}${ignored}`);
  17. if (error) {
  18. lines.push(' ---');
  19. /** @type {Record<string, Warnings>} */
  20. const rules = {};
  21. for (const { rule, ...rest } of result.warnings) {
  22. const name = rule || 'unknown';
  23. const warnings = (rules[name] ??= []);
  24. warnings.push(rest);
  25. }
  26. for (const [ruleName, warnings] of Object.entries(rules)) {
  27. lines.push(` ${ruleName}:`);
  28. for (const { text, severity, line, column, endLine, endColumn } of warnings) {
  29. lines.push(
  30. ` - message: ${JSON.stringify(text)}`,
  31. ` severity: ${severity}`,
  32. ` line: ${line}`,
  33. ` column: ${column}`,
  34. );
  35. if (isNumber(endLine)) lines.push(` endLine: ${endLine}`);
  36. if (isNumber(endColumn)) lines.push(` endColumn: ${endColumn}`);
  37. }
  38. }
  39. lines.push(' ...');
  40. }
  41. }
  42. lines.push('');
  43. return lines.join('\n');
  44. }
  45. /**
  46. * Apply TAP escaping to the source that is part of the test point description
  47. * @see {@link https://testanything.org/tap-version-14-specification.html#escaping TAP 14 specification}
  48. * @param {string} [source]
  49. */
  50. function getEscapedSource(source) {
  51. if (!source) return source;
  52. let output = '';
  53. for (const char of source) {
  54. switch (char.codePointAt(0)) {
  55. case 0x005c: // `\`
  56. case 0x0023: // `#`
  57. output += '\\';
  58. output += char;
  59. break;
  60. default:
  61. output += char;
  62. break;
  63. }
  64. }
  65. return output;
  66. }