|
## 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..<count:
|
|
parts.add(self.formatError(errors[i], source))
|
|
|
|
result = parts.join("\n")
|
|
|
|
if self.maxErrors > 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
|