| import { isNumber } from '../utils/validateTypes.mjs'; |
| import preprocessWarnings from './preprocessWarnings.mjs'; |
| |
| /** @import {Warning, Formatter} from 'stylelint' */ |
| /** @typedef {Array<Omit<Warning, 'rule'>>} Warnings */ |
| |
| /** @type {Formatter} */ |
| export default function tapFormatter(results) { |
| const lines = ['TAP version 14', `1..${results.length}`]; |
| |
| for (const [index, result] of results.entries()) { |
| preprocessWarnings(result); |
| // SKIP indicates "that a test was not run, or if it was, |
| // that its success or failure is being temporarily ignored." |
| // skipped tests must not be treated as test failures though |
| // if a file is ignored, errored is currently always false |
| const ignored = result.ignored ? ' # SKIP ignored' : ''; |
| const error = result.errored ? 'not ' : ''; |
| |
| lines.push(`${error}ok ${index + 1} - ${getEscapedSource(result.source)}${ignored}`); |
| |
| if (error) { |
| lines.push(' ---'); |
| |
| /** @type {Record<string, Warnings>} */ |
| const rules = {}; |
| |
| for (const { rule, ...rest } of result.warnings) { |
| const name = rule || 'unknown'; |
| const warnings = (rules[name] ??= []); |
| |
| warnings.push(rest); |
| } |
| |
| for (const [ruleName, warnings] of Object.entries(rules)) { |
| lines.push(` ${ruleName}:`); |
| |
| for (const { text, severity, line, column, endLine, endColumn } of warnings) { |
| lines.push( |
| ` - message: ${JSON.stringify(text)}`, |
| ` severity: ${severity}`, |
| ` line: ${line}`, |
| ` column: ${column}`, |
| ); |
| |
| if (isNumber(endLine)) lines.push(` endLine: ${endLine}`); |
| |
| if (isNumber(endColumn)) lines.push(` endColumn: ${endColumn}`); |
| } |
| } |
| |
| lines.push(' ...'); |
| } |
| } |
| |
| lines.push(''); |
| |
| return lines.join('\n'); |
| } |
| |
| /** |
| * Apply TAP escaping to the source that is part of the test point description |
| * @see {@link https://testanything.org/tap-version-14-specification.html#escaping TAP 14 specification} |
| * @param {string} [source] |
| */ |
| function getEscapedSource(source) { |
| if (!source) return source; |
| |
| let output = ''; |
| |
| for (const char of source) { |
| switch (char.codePointAt(0)) { |
| case 0x005c: // `\` |
| case 0x0023: // `#` |
| output += '\\'; |
| output += char; |
| break; |
| default: |
| output += char; |
| break; |
| } |
| } |
| |
| return output; |
| } |