diff --git a/src/harness/check-engine/output-parsers-extra.test.ts b/src/harness/check-engine/output-parsers-extra.test.ts index 9d048881..826999a8 100644 --- a/src/harness/check-engine/output-parsers-extra.test.ts +++ b/src/harness/check-engine/output-parsers-extra.test.ts @@ -456,6 +456,21 @@ describe("parseCargoJson", () => { }); describe("parseGolangciLintJson", () => { + it("keeps issues when a text trailer follows the JSON object", () => { + const payload = JSON.stringify({ + Issues: [ + { + FromLinter: "govet", + Text: 'brace in text { and }', + Pos: { Filename: "a.go", Line: 4, Column: 1 }, + }, + ], + }); + const results = parseGolangciLintJson(`${payload}\n1834 issues:\n`); + expect(results).toHaveLength(1); + expect(nonNull(results[0])).toMatchObject({ file: "a.go", line: 4, ruleId: "govet" }); + }); + it("reads file/line from issue.Pos when present", () => { const payload = JSON.stringify({ Issues: [{ FromLinter: "govet", Text: "bad", Pos: { Filename: "a.go", Line: 10, Column: 2 } }], diff --git a/src/harness/check-engine/output-parsers-extra.ts b/src/harness/check-engine/output-parsers-extra.ts index a83ce8a0..f9308833 100644 --- a/src/harness/check-engine/output-parsers-extra.ts +++ b/src/harness/check-engine/output-parsers-extra.ts @@ -365,9 +365,47 @@ function parseGolangciIssue(value: unknown): GolangciIssue | null { }; } +/** + * golangci-lint v2 can write the JSON report and then a text trailer + * (`N issues:`) on the same stdout. `JSON.parse` rejects that trailer. + * Take the first JSON object and ignore the rest. + */ +function firstJsonObject(output: string): string | null { + const start = output.indexOf("{"); + if (start < 0) return null; + let depth = 0; + let inString = false; + let escape = false; + for (let i = start; i < output.length; i++) { + const ch = output[i]; + if (inString) { + if (escape) { + escape = false; + continue; + } + if (ch === "\\") { + escape = true; + continue; + } + if (ch === '"') inString = false; + continue; + } + if (ch === '"') { + inString = true; + continue; + } + if (ch === "{") depth++; + else if (ch === "}") { + depth--; + if (depth === 0) return output.slice(start, i + 1); + } + } + return null; +} + export function parseGolangciLintJson(output: string): CheckResult[] { try { - const parsed = JSON.parse(output); + const parsed = JSON.parse(firstJsonObject(output) ?? output); if (!isJsonObject(parsed)) return []; const issues = parsed.Issues; if (!Array.isArray(issues)) return [];