import { canonicalJson, hash64Hex, type FactLocation, } from "../facts/index.js"; import { normalizeFinding, normalizeViolation, type Finding, type Violation, } from "../schemas/index.js"; import { byRuleId } from "./codes.js"; export interface BridgeViolationInput { violation: Violation; canonicalRuleId?: string; validator?: string; attributes?: Record; } export function bridgeSourceViolation(input: BridgeViolationInput): { violation: Violation; finding: Finding; } { const ruleId = input.canonicalRuleId ?? input.violation.rule; const code = byRuleId.get(ruleId); const violation = normalizeViolation({ ...input.violation, code: input.violation.code ?? code?.code, helpUrl: input.violation.helpUrl ?? code?.explainUrl, }); const location = locationFromViolation(violation); const fingerprint = hash64Hex( canonicalJson({ ruleId, legacyRule: violation.rule, nodeId: violation.nodeId, nodeType: violation.nodeType, prop: violation.prop, path: violation.path, filePath: violation.filePath, line: violation.line, column: violation.column, rawValue: violation.rawValue, message: violation.message, }), ); return { violation, finding: normalizeFinding({ ruleId, ruleVersion: "1.0.0", severity: violation.severity, level: violation.level, code: code?.code, helpUrl: code?.explainUrl, message: violation.message, fingerprint, location, evidence: [ { factId: [ "source", input.validator ?? "validator", ruleId, violation.filePath ?? violation.nodeId, violation.line ?? 0, violation.column ?? 0, violation.prop ?? violation.path ?? "", ].join(":"), fact: { kind: "source_violation", location, }, }, ], attributes: { source: "source-validator", validator: input.validator, legacyRule: violation.rule === ruleId ? undefined : violation.rule, nodeId: violation.nodeId, nodeType: violation.nodeType, prop: violation.prop, path: violation.path, rawValue: violation.rawValue, legacySuggestion: violation.suggestion, ...input.attributes, }, }), }; } export function bridgeSourceViolations( violations: Violation[], options: Omit = {}, ): { violations: Violation[]; findings: Finding[] } { const bridged = violations.map((violation) => bridgeSourceViolation({ ...options, violation }), ); return { violations: bridged.map((entry) => entry.violation), findings: bridged.map((entry) => entry.finding), }; } function locationFromViolation(violation: Violation): FactLocation { return { file: violation.filePath ?? "", line: violation.line ?? 0, column: violation.column ?? 0, }; }