## Error reporting and formatting for Validatrix. ## ## Provides consistent error formatting across all language validators, ## including human-readable and machine-readable (JSON) output. import std/[strformat, json, tables] import ../core/types # --------------------------------------------------------------------------- # Error formatter # --------------------------------------------------------------------------- type ErrorFormatter* = object ## Formats validation errors consistently. includeSourceContext*: bool includeColorHint*: bool maxErrors*: int proc newErrorFormatter*( srcCtx: bool = true, colorHint: bool = false, maxErr: int = 0 ): ErrorFormatter = ## Create a new error formatter. result.includeSourceContext = srcCtx result.includeColorHint = colorHint result.maxErrors = maxErr # --------------------------------------------------------------------------- # Format single error as human-readable string # --------------------------------------------------------------------------- proc formatError*(self: ErrorFormatter, err: ValidationError, source: string = ""): string = ## Format a single validation error as a human-readable string. let posStr = &"L{err.position.line}:{err.position.column}" let codeStr = &"[{err.code}]" let sevStr = &"({$err.severity})" let msgStr = err.message result = &" {codeStr} {sevStr} {posStr}: {msgStr}" if self.includeSourceContext and source.len > 0 and err.position.line > 0: let lines = source.splitLines() let lineIdx = err.position.line - 1 if lineIdx >= 0 and lineIdx < lines.len: let sourceLine = lines[lineIdx] result.add("\n" & " | " & sourceLine) if err.position.column > 0: let caretCol = max(err.position.column - 1, 0) let caret = " ".repeat(caretCol) & "^" result.add("\n" & " | " & caret) if self.includeColorHint and err.hint.len > 0: result.add("\n" & " hint: " & err.hint) # --------------------------------------------------------------------------- # Format all errors # --------------------------------------------------------------------------- proc formatAllErrors*(self: ErrorFormatter, errors: seq[ValidationError], source: string = ""): string = ## Format all errors as a human-readable multi-line string. if errors.len == 0: return " No errors found." var parts: seq[string] = @[] let count = if self.maxErrors > 0: min(errors.len, self.maxErrors) else: errors.len for i in 0.. 0 and errors.len > self.maxErrors: result.add(&"\n ... and {errors.len - self.maxErrors} more errors (use maxErrors=0 to show all)") # --------------------------------------------------------------------------- # Format validation result as human-readable report # --------------------------------------------------------------------------- proc formatReport*(vr: ValidationResult, source: string = ""): string = ## Format the complete validation result as a human-readable report. let formatter = newErrorFormatter(srcCtx = true, colorHint = true) var lines: seq[string] = @[] lines.add(&"=== Validation Report ===") lines.add(&" Flavor: {$vr.flavor}") lines.add(&" Detection: {$vr.detectionMethod}") lines.add(&" Valid: {vr.valid}") lines.add(&" Errors: {vr.errors.len}") lines.add(&" Warnings: {vr.warnings}") lines.add(&" Duration: {vr.durationMs:.2f}ms") lines.add(&" Lines of code: {vr.moduleInfo.linesOfCode}") lines.add(&" Tokens: {vr.moduleInfo.totalTokens}") lines.add("") if vr.errors.len > 0: lines.add(" --- Findings ---") for err in vr.errors: lines.add(formatter.formatError(err, source)) else: lines.add(" No issues found.") lines.add("") lines.add(" --- Summary ---") if vr.valid: lines.add(" VALID - source passed all checks.") else: lines.add(" INVALID - source has issues that need attention.") if vr.debugOutput.len > 0: lines.add("") lines.add(" --- Debug Output ---") for line in vr.debugOutput.splitLines(): lines.add(" " & line) result = lines.join("\n") # --------------------------------------------------------------------------- # JSON formatting (comprehensive) # --------------------------------------------------------------------------- proc toJsonReport*(vr: ValidationResult): JsonNode = ## Full validation result as JSON (same as result.toJson()). vr.toJson() proc toJsonReportString*(vr: ValidationResult, pretty: bool = true): string = ## Validation result as JSON string. let j = vr.toJson() if pretty: result = j.pretty() else: result = $j # --------------------------------------------------------------------------- # Error counting / categorization # --------------------------------------------------------------------------- proc countBySeverity*(errors: seq[ValidationError]): Table[string, int] = ## Count errors by severity. result = initTable[string, int]() for err in errors: let key = $err.severity result.mgetOrPut(key, 0).inc proc countByCode*(errors: seq[ValidationError]): Table[string, int] = ## Count errors by error code. result = initTable[string, int]() for err in errors: result.mgetOrPut(err.code, 0).inc proc filterBySeverity*(errors: seq[ValidationError], minSeverity: ErrorSeverity): seq[ValidationError] = ## Filter errors by minimum severity level. let severities: array[4, ErrorSeverity] = [esInfo, esWarning, esError, esCritical] var minIdx = 0 for i, s in severities: if s == minSeverity: minIdx = i break for err in errors: var errIdx = 0 for i, s in severities: if s == err.severity: errIdx = i break if errIdx >= minIdx: result.add(err) # --------------------------------------------------------------------------- # Error severity ordering for filtering # --------------------------------------------------------------------------- proc `>`*(a, b: ErrorSeverity): bool = ## Compare severities: critical > error > warning > info. const order: array[4, int] = [0, 1, 2, 3] order[ord(a)] > order[ord(b)] proc `<`*(a, b: ErrorSeverity): bool = const order: array[4, int] = [0, 1, 2, 3] order[ord(a)] < order[ord(b)] proc `<=`*(a, b: ErrorSeverity): bool = a < b or a == b proc `>=`*(a, b: ErrorSeverity): bool = a > b or a == b