UNPKG

markuplint

Version:

An HTML linter for all markup developers

73 lines (72 loc) 3.72 kB
import { messageToString, font, name, invisibleSpace, space, pad, getWidth, xterm } from '@markuplint/cli-utils'; const commandName = name.toLowerCase(); const loggerError = font.red; const loggerWarning = xterm(208); /** * Formats lint results using the standard (detailed) reporter. * * Produces multi-line output that shows each violation's message with * surrounding source code context, highlighted error regions, and * line numbers. Clean files are reported as "passed" or "skipped" * unless `--problem-only` is set. * * @param results - The lint result information for a single file. * @param options - CLI options controlling color and problem-only output. * @returns An array of formatted output lines. */ export function standardReporter(results, options) { const sizes = { line: 0, col: 0, meg: 0, }; for (const violation of results.violations) { sizes.line = Math.max(sizes.line, violation.line.toString(10).length); sizes.col = Math.max(sizes.col, violation.col.toString(10).length); const meg = messageToString(violation.message, violation.reason); sizes.meg = Math.max(sizes.meg, getWidth(meg)); } const out = []; if (results.violations.length > 0) { const lines = results.sourceCode.split(/\r?\n/); for (const violation of results.violations) { const logger = violation.severity === 'error' ? loggerError : loggerWarning; const meg = messageToString(violation.message, violation.reason); const startLine = violation.line - 1; // Main message out.push(`<${commandName}> ${logger(`${violation.severity}: ${meg} (${violation.ruleId}) ${font.underline(`${results.filePath}:${violation.line}:${violation.col}`)}`)}`); // Previous line if (startLine > 0) { const prev = lines[startLine - 1] ?? ''; out.push(` ${font.cyan(pad(startLine, sizes.col, true))}: ${space(prev)}`); } // Current line const rawLines = violation.raw.split(/\r?\n/); const line = lines[startLine] ?? ''; for (const [i, rawLine] of rawLines.entries()) { const currentLine = lines[startLine + i] ?? ''; const beforeChars = i === 0 ? line.slice(0, Math.max(0, violation.col - 1)) : (rawLine.match(/^\s+/)?.[0] ?? ''); const codeChars = rawLine.trim(); const afterChars = currentLine.slice(Math.max(0, beforeChars.length + codeChars.length)); const lineNoChars = pad(violation.line + i, sizes.col, true); const lineNo = font.cyan(lineNoChars); const before = i === 0 ? space(beforeChars) : font.bgRed(space(beforeChars)); const code = font.bgRed(codeChars); const after = space(afterChars); out.push(` ${lineNo}: ${before}${code}${space(after)}`); if (!options.color) { out.push(` ${invisibleSpace(lineNoChars + ': ' + beforeChars)}${'^'.repeat(codeChars.length)}${invisibleSpace(afterChars)}`); } } // Next line const next = lines[startLine + rawLines.length] ?? ''; out.push(` ${font.cyan(pad(startLine + rawLines.length + 1, sizes.col, true))}: ${space(next)}`); } } else if (!options.problemOnly) { const message = results.status === 'skipped' ? 'skipped' : 'passed'; const color = results.status === 'skipped' ? font.yellow : font.green; out.push(`<${commandName}> ${color(message)} ${font.underline(results.filePath)}`); } return out; }