import * as vscode from 'vscode'; import type { LinterDiagnostic, Severity } from '../types'; import type { TranslatedDiagnostic, CustomRuleResult, AIFinding } from '../ai/schema'; export interface MergedReport { linterDiagnostics: LinterDiagnostic[]; customRuleDiagnostics: LinterDiagnostic[]; translatedDiagnostics: TranslatedDiagnostic[]; aiFindings: AIFinding[]; linterCount: number; customRuleCount: number; aiCount: number; errors: string[]; degraded: boolean; duration: number; filePath: string; language: string; adapterNames: string[]; fixableLinterIndices: number[]; fixableCustomIndices: number[]; customRuleFilterInfo?: { totalActive: number; injected: number; filteredOut: number; skippedRequestA: boolean; }; } interface MergeInput { staticDiagnostics: LinterDiagnostic[]; customRuleResults: CustomRuleResult[]; translatedDiagnostics: TranslatedDiagnostic[]; aiFindings: AIFinding[]; errors: string[]; degraded: boolean; startTime: number; filePath: string; language: string; adapterIds: string[]; customRuleFilterInfo?: { totalActive: number; injected: number; filteredOut: number; skippedRequestA: boolean; }; } const SEVERITY_RANK: Record = { error: 0, warning: 1, info: 2 }; const RULE_NAMESPACE_PREFIXES = ['eslint:', 'stylelint:', 'sqlfluff:', 'pmd:', 'custom:', 'method:']; function normalizeRuleId(id: string): string { let result = id.trim(); for (const p of RULE_NAMESPACE_PREFIXES) { if (result.startsWith(p)) { result = result.slice(p.length); break; } } const segments = result.split(/[:/]/); return segments[segments.length - 1]; } function findTranslation(pool: TranslatedDiagnostic[], ruleId: string): TranslatedDiagnostic | undefined { for (let i = 0; i < pool.length; i++) { if (pool[i].originalRuleId === ruleId) { return pool.splice(i, 1)[0]; } } const norm = normalizeRuleId(ruleId); for (let i = 0; i < pool.length; i++) { if (normalizeRuleId(pool[i].originalRuleId) === norm) { return pool.splice(i, 1)[0]; } } return undefined; } function sortBySeverityAndLine(items: T[], lineOf: (item: T) => number): T[] { return [...items].sort((a, b) => { const rankDiff = (SEVERITY_RANK[a.severity] ?? 3) - (SEVERITY_RANK[b.severity] ?? 3); if (rankDiff !== 0) { return rankDiff; } return lineOf(a) - lineOf(b); }); } export function mergeResults(input: MergeInput): MergedReport { const customRuleDiagnostics: LinterDiagnostic[] = sortBySeverityAndLine( input.customRuleResults.map(r => ({ severity: r.severity as Severity, ruleId: r.ruleId, message: r.message, range: new vscode.Range(Math.max(0, r.line - 1), 0, Math.max(0, r.line - 1), 1), })), d => d.range.start.line ); const translationPool = [...input.translatedDiagnostics]; const linterDiagnostics = sortBySeverityAndLine( input.staticDiagnostics.map(d => { const td = findTranslation(translationPool, d.ruleId); if (td) { return { ...d, message: td.translatedMessage, suggestion: td.translatedSuggestion || d.suggestion }; } return d; }), d => d.range.start.line ); const aiFindings = sortBySeverityAndLine( input.aiFindings.map(f => ({ ...f, line: Math.max(0, f.line - 1) })), f => f.line ); const linterCount = linterDiagnostics.length; const customRuleCount = customRuleDiagnostics.length; const aiCount = aiFindings.length; const fixableLinterIndices = linterDiagnostics .map((d, i) => (d.fix ? i : -1)) .filter(i => i !== -1); const fixableCustomIndices: number[] = []; return { linterDiagnostics, customRuleDiagnostics, translatedDiagnostics: input.translatedDiagnostics, aiFindings, linterCount, customRuleCount, aiCount, errors: input.errors, degraded: input.degraded, duration: Date.now() - input.startTime, filePath: input.filePath, language: input.language, adapterNames: input.adapterIds, fixableLinterIndices, fixableCustomIndices, customRuleFilterInfo: input.customRuleFilterInfo, }; }