@paretools/go 0.8.5 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/formatters.d.ts +25 -9
- package/dist/lib/formatters.d.ts.map +1 -1
- package/dist/lib/formatters.js +208 -29
- package/dist/lib/formatters.js.map +1 -1
- package/dist/lib/go-runner.d.ts +1 -1
- package/dist/lib/go-runner.d.ts.map +1 -1
- package/dist/lib/go-runner.js +3 -3
- package/dist/lib/go-runner.js.map +1 -1
- package/dist/lib/parsers.d.ts +45 -10
- package/dist/lib/parsers.d.ts.map +1 -1
- package/dist/lib/parsers.js +490 -33
- package/dist/lib/parsers.js.map +1 -1
- package/dist/schemas/index.d.ts +148 -4
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +96 -5
- package/dist/schemas/index.js.map +1 -1
- package/dist/tools/build.d.ts.map +1 -1
- package/dist/tools/build.js +61 -2
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/env.d.ts.map +1 -1
- package/dist/tools/env.js +14 -4
- package/dist/tools/env.js.map +1 -1
- package/dist/tools/fmt.d.ts.map +1 -1
- package/dist/tools/fmt.js +26 -3
- package/dist/tools/fmt.js.map +1 -1
- package/dist/tools/generate.d.ts.map +1 -1
- package/dist/tools/generate.js +54 -2
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/get.d.ts.map +1 -1
- package/dist/tools/get.js +34 -3
- package/dist/tools/get.js.map +1 -1
- package/dist/tools/golangci-lint.d.ts.map +1 -1
- package/dist/tools/golangci-lint.js +144 -4
- package/dist/tools/golangci-lint.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +66 -5
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/mod-tidy.d.ts.map +1 -1
- package/dist/tools/mod-tidy.js +69 -4
- package/dist/tools/mod-tidy.js.map +1 -1
- package/dist/tools/run.d.ts.map +1 -1
- package/dist/tools/run.js +59 -6
- package/dist/tools/run.js.map +1 -1
- package/dist/tools/test.d.ts.map +1 -1
- package/dist/tools/test.js +86 -2
- package/dist/tools/test.js.map +1 -1
- package/dist/tools/vet.d.ts.map +1 -1
- package/dist/tools/vet.js +54 -4
- package/dist/tools/vet.js.map +1 -1
- package/package.json +2 -2
package/dist/lib/parsers.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
const GO_ERROR_RE = /^(.+?\.go):(\d+)(?::(\d+))?: (.+)$/;
|
|
2
|
+
/**
|
|
3
|
+
* Regex to detect non-file build error lines. These are package-level or linker
|
|
4
|
+
* errors that don't match the standard file:line:col format.
|
|
5
|
+
* Matches: "package X is not in GOROOT", linker errors, build constraint errors, etc.
|
|
6
|
+
* Ignores: blank lines, "# package" headers (captured separately as context).
|
|
7
|
+
*/
|
|
8
|
+
const GO_NON_FILE_ERROR_PATTERNS = [
|
|
9
|
+
/^package .+ is not in (?:GOROOT|GOPATH|std)/,
|
|
10
|
+
/^can(?:not|'t) (?:find|load) package/,
|
|
11
|
+
/^no required module provides package/,
|
|
12
|
+
/^build constraints exclude all Go files/,
|
|
13
|
+
/^imports .+ is not in/,
|
|
14
|
+
// Linker errors
|
|
15
|
+
/^(?:\/[^\s]+\/)?ld[.:]/,
|
|
16
|
+
/^(?:#\s+command-line-arguments|link:)/,
|
|
17
|
+
/undefined reference to/,
|
|
18
|
+
/multiple definition of/,
|
|
19
|
+
// CGO errors
|
|
20
|
+
/^cgo: /,
|
|
21
|
+
// Module errors
|
|
22
|
+
/^go: /,
|
|
23
|
+
];
|
|
2
24
|
/** Parses `go build` stderr output into structured error data with file locations. */
|
|
3
25
|
export function parseGoBuildOutput(stdout, stderr, exitCode) {
|
|
4
26
|
const output = stdout + "\n" + stderr;
|
|
5
27
|
const lines = output.split("\n");
|
|
6
28
|
const errors = [];
|
|
29
|
+
const rawErrors = [];
|
|
7
30
|
for (const line of lines) {
|
|
8
|
-
const
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed)
|
|
33
|
+
continue;
|
|
34
|
+
const match = trimmed.match(GO_ERROR_RE);
|
|
9
35
|
if (match) {
|
|
10
36
|
errors.push({
|
|
11
37
|
file: match[1],
|
|
@@ -13,12 +39,22 @@ export function parseGoBuildOutput(stdout, stderr, exitCode) {
|
|
|
13
39
|
column: match[3] ? parseInt(match[3], 10) : undefined,
|
|
14
40
|
message: match[4],
|
|
15
41
|
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Only capture non-file errors when the build actually failed
|
|
45
|
+
if (exitCode !== 0) {
|
|
46
|
+
const isNonFileError = GO_NON_FILE_ERROR_PATTERNS.some((re) => re.test(trimmed));
|
|
47
|
+
if (isNonFileError) {
|
|
48
|
+
rawErrors.push(trimmed);
|
|
49
|
+
}
|
|
16
50
|
}
|
|
17
51
|
}
|
|
52
|
+
const total = errors.length + rawErrors.length;
|
|
18
53
|
return {
|
|
19
54
|
success: exitCode === 0,
|
|
20
55
|
errors,
|
|
21
|
-
|
|
56
|
+
rawErrors: rawErrors.length > 0 ? rawErrors : undefined,
|
|
57
|
+
total,
|
|
22
58
|
};
|
|
23
59
|
}
|
|
24
60
|
/**
|
|
@@ -28,7 +64,13 @@ export function parseGoBuildOutput(stdout, stderr, exitCode) {
|
|
|
28
64
|
*/
|
|
29
65
|
export function parseGoTestJson(stdout, exitCode) {
|
|
30
66
|
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
67
|
+
// Collect output lines per test (keyed by package/test)
|
|
68
|
+
const testOutputMap = new Map();
|
|
69
|
+
// Collect output lines per package (for package-level output)
|
|
70
|
+
const pkgOutputMap = new Map();
|
|
31
71
|
const testMap = new Map();
|
|
72
|
+
// Track package-level failures (where event.Test is undefined)
|
|
73
|
+
const pkgFailures = new Map();
|
|
32
74
|
for (const line of lines) {
|
|
33
75
|
let event;
|
|
34
76
|
try {
|
|
@@ -37,18 +79,62 @@ export function parseGoTestJson(stdout, exitCode) {
|
|
|
37
79
|
catch {
|
|
38
80
|
continue;
|
|
39
81
|
}
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
82
|
+
if (event.Test) {
|
|
83
|
+
const key = `${event.Package}/${event.Test}`;
|
|
84
|
+
// Collect output lines for tests (#51)
|
|
85
|
+
if (event.Action === "output" && event.Output) {
|
|
86
|
+
if (!testOutputMap.has(key)) {
|
|
87
|
+
testOutputMap.set(key, []);
|
|
88
|
+
}
|
|
89
|
+
testOutputMap.get(key).push(event.Output);
|
|
90
|
+
}
|
|
91
|
+
if (event.Action === "pass" || event.Action === "fail" || event.Action === "skip") {
|
|
92
|
+
testMap.set(key, {
|
|
93
|
+
package: event.Package,
|
|
94
|
+
name: event.Test,
|
|
95
|
+
status: event.Action,
|
|
96
|
+
elapsed: event.Elapsed,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Package-level events (no Test field) (#52)
|
|
102
|
+
if (event.Action === "output" && event.Output) {
|
|
103
|
+
if (!pkgOutputMap.has(event.Package)) {
|
|
104
|
+
pkgOutputMap.set(event.Package, []);
|
|
105
|
+
}
|
|
106
|
+
pkgOutputMap.get(event.Package).push(event.Output);
|
|
107
|
+
}
|
|
108
|
+
if (event.Action === "fail") {
|
|
109
|
+
pkgFailures.set(event.Package, {
|
|
110
|
+
package: event.Package,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
50
113
|
}
|
|
51
114
|
}
|
|
115
|
+
// Attach output to failed tests (#51)
|
|
116
|
+
for (const [key, entry] of testMap) {
|
|
117
|
+
if (entry.status === "fail") {
|
|
118
|
+
const outputLines = testOutputMap.get(key);
|
|
119
|
+
if (outputLines && outputLines.length > 0) {
|
|
120
|
+
entry.output = outputLines.join("").trimEnd();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Attach package-level output to package failures (#52)
|
|
125
|
+
for (const [pkg, failure] of pkgFailures) {
|
|
126
|
+
const outputLines = pkgOutputMap.get(pkg);
|
|
127
|
+
if (outputLines && outputLines.length > 0) {
|
|
128
|
+
failure.output = outputLines.join("").trimEnd();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Filter out package failures that have corresponding test-level results
|
|
132
|
+
// (a package "fail" event with tests means the package summary, not a build failure)
|
|
133
|
+
const pkgWithTests = new Set();
|
|
134
|
+
for (const entry of testMap.values()) {
|
|
135
|
+
pkgWithTests.add(entry.package);
|
|
136
|
+
}
|
|
137
|
+
const packageFailures = Array.from(pkgFailures.values()).filter((f) => !pkgWithTests.has(f.package));
|
|
52
138
|
const tests = Array.from(testMap.values());
|
|
53
139
|
const passed = tests.filter((t) => t.status === "pass").length;
|
|
54
140
|
const failed = tests.filter((t) => t.status === "fail").length;
|
|
@@ -56,14 +142,119 @@ export function parseGoTestJson(stdout, exitCode) {
|
|
|
56
142
|
return {
|
|
57
143
|
success: exitCode === 0,
|
|
58
144
|
tests,
|
|
145
|
+
packageFailures: packageFailures.length > 0 ? packageFailures : undefined,
|
|
59
146
|
total: tests.length,
|
|
60
147
|
passed,
|
|
61
148
|
failed,
|
|
62
149
|
skipped,
|
|
63
150
|
};
|
|
64
151
|
}
|
|
65
|
-
/**
|
|
66
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Parses `go vet -json` output into structured diagnostics.
|
|
154
|
+
*
|
|
155
|
+
* The `-json` flag produces output grouped by package. Each package entry is a JSON
|
|
156
|
+
* object keyed by package import path, containing analyzer results:
|
|
157
|
+
* ```json
|
|
158
|
+
* {
|
|
159
|
+
* "<package>": {
|
|
160
|
+
* "<analyzer>": {
|
|
161
|
+
* "posn": "file.go:10:5",
|
|
162
|
+
* "message": "..."
|
|
163
|
+
* }
|
|
164
|
+
* // or an array of diagnostics per analyzer
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* Falls back to text parsing if JSON parsing fails (e.g., older Go versions).
|
|
170
|
+
*/
|
|
171
|
+
export function parseGoVetOutput(stdout, stderr, exitCode) {
|
|
172
|
+
// Try JSON parsing first (go vet -json)
|
|
173
|
+
const jsonResult = tryParseGoVetJson(stdout, stderr);
|
|
174
|
+
if (jsonResult) {
|
|
175
|
+
return { ...jsonResult, success: exitCode === 0 };
|
|
176
|
+
}
|
|
177
|
+
// Fallback: text parsing
|
|
178
|
+
return parseGoVetText(stdout, stderr, exitCode);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Attempt to parse `go vet -json` structured output.
|
|
182
|
+
* Returns null if the output doesn't look like JSON.
|
|
183
|
+
*
|
|
184
|
+
* go vet -json emits one JSON object per package on stdout/stderr.
|
|
185
|
+
* The structure is: { "<package-path>": { "<analyzer>": <diagnostic-or-array> } }
|
|
186
|
+
* Each diagnostic has: { posn: "file:line:col", message: "..." }
|
|
187
|
+
*/
|
|
188
|
+
function tryParseGoVetJson(stdout, stderr) {
|
|
189
|
+
// go vet -json writes JSON to stdout; stderr may have non-JSON messages
|
|
190
|
+
const combined = (stdout + "\n" + stderr).trim();
|
|
191
|
+
if (!combined || !combined.includes("{"))
|
|
192
|
+
return null;
|
|
193
|
+
const diagnostics = [];
|
|
194
|
+
// go vet -json outputs concatenated JSON objects (one per package)
|
|
195
|
+
const chunks = splitJsonObjects(combined);
|
|
196
|
+
if (chunks.length === 0)
|
|
197
|
+
return null;
|
|
198
|
+
let parsedAny = false;
|
|
199
|
+
for (const chunk of chunks) {
|
|
200
|
+
let pkgObj;
|
|
201
|
+
try {
|
|
202
|
+
pkgObj = JSON.parse(chunk);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
// Each top-level key is a package path
|
|
208
|
+
for (const [, analyzerMap] of Object.entries(pkgObj)) {
|
|
209
|
+
if (typeof analyzerMap !== "object" || analyzerMap === null)
|
|
210
|
+
continue;
|
|
211
|
+
// Each key in the analyzerMap is an analyzer name
|
|
212
|
+
for (const [analyzerName, diagData] of Object.entries(analyzerMap)) {
|
|
213
|
+
const diagItems = Array.isArray(diagData) ? diagData : [diagData];
|
|
214
|
+
for (const item of diagItems) {
|
|
215
|
+
if (typeof item !== "object" || item === null)
|
|
216
|
+
continue;
|
|
217
|
+
const diag = item;
|
|
218
|
+
if (!diag.posn || !diag.message)
|
|
219
|
+
continue;
|
|
220
|
+
parsedAny = true;
|
|
221
|
+
const parsed = parseVetPosn(diag.posn);
|
|
222
|
+
diagnostics.push({
|
|
223
|
+
file: parsed.file,
|
|
224
|
+
line: parsed.line,
|
|
225
|
+
column: parsed.column,
|
|
226
|
+
message: diag.message,
|
|
227
|
+
analyzer: analyzerName,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!parsedAny)
|
|
234
|
+
return null;
|
|
235
|
+
return { diagnostics, total: diagnostics.length };
|
|
236
|
+
}
|
|
237
|
+
/** Parse a posn string like "file.go:10:5" or "file.go:10" into components. */
|
|
238
|
+
function parseVetPosn(posn) {
|
|
239
|
+
// posn format: "file.go:line:col" or "file.go:line"
|
|
240
|
+
const parts = posn.split(":");
|
|
241
|
+
if (parts.length >= 3) {
|
|
242
|
+
return {
|
|
243
|
+
file: parts.slice(0, -2).join(":"),
|
|
244
|
+
line: parseInt(parts[parts.length - 2], 10) || 0,
|
|
245
|
+
column: parseInt(parts[parts.length - 1], 10) || undefined,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (parts.length === 2) {
|
|
249
|
+
return {
|
|
250
|
+
file: parts[0],
|
|
251
|
+
line: parseInt(parts[1], 10) || 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return { file: posn, line: 0 };
|
|
255
|
+
}
|
|
256
|
+
/** Fallback text parser for go vet output (non-JSON mode). */
|
|
257
|
+
function parseGoVetText(stdout, stderr, exitCode) {
|
|
67
258
|
const output = stdout + "\n" + stderr;
|
|
68
259
|
const lines = output.split("\n");
|
|
69
260
|
const diagnostics = [];
|
|
@@ -78,7 +269,7 @@ export function parseGoVetOutput(stdout, stderr) {
|
|
|
78
269
|
});
|
|
79
270
|
}
|
|
80
271
|
}
|
|
81
|
-
return { diagnostics, total: diagnostics.length };
|
|
272
|
+
return { success: exitCode === 0, diagnostics, total: diagnostics.length };
|
|
82
273
|
}
|
|
83
274
|
/** Parses `go run` output into structured result with stdout, stderr, and exit code. */
|
|
84
275
|
export function parseGoRunOutput(stdout, stderr, exitCode) {
|
|
@@ -89,13 +280,39 @@ export function parseGoRunOutput(stdout, stderr, exitCode) {
|
|
|
89
280
|
success: exitCode === 0,
|
|
90
281
|
};
|
|
91
282
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Parses `go mod tidy` output into structured result with success status, summary,
|
|
285
|
+
* and whether changes were made.
|
|
286
|
+
*
|
|
287
|
+
* @param goModHashBefore - MD5/hash of go.mod before tidy (optional, for change detection)
|
|
288
|
+
* @param goModHashAfter - MD5/hash of go.mod after tidy (optional, for change detection)
|
|
289
|
+
* @param goSumHashBefore - MD5/hash of go.sum before tidy (optional, for change detection)
|
|
290
|
+
* @param goSumHashAfter - MD5/hash of go.sum after tidy (optional, for change detection)
|
|
291
|
+
*/
|
|
292
|
+
export function parseGoModTidyOutput(stdout, stderr, exitCode, goModHashBefore, goModHashAfter, goSumHashBefore, goSumHashAfter) {
|
|
94
293
|
if (exitCode === 0) {
|
|
95
294
|
const combined = (stdout + "\n" + stderr).trim();
|
|
295
|
+
// Determine whether changes were made
|
|
296
|
+
let madeChanges;
|
|
297
|
+
if (goModHashBefore !== undefined && goModHashAfter !== undefined) {
|
|
298
|
+
const modChanged = goModHashBefore !== goModHashAfter;
|
|
299
|
+
const sumChanged = goSumHashBefore !== undefined &&
|
|
300
|
+
goSumHashAfter !== undefined &&
|
|
301
|
+
goSumHashBefore !== goSumHashAfter;
|
|
302
|
+
madeChanges = modChanged || sumChanged;
|
|
303
|
+
}
|
|
304
|
+
else if (combined) {
|
|
305
|
+
// If we have output (e.g., "go: downloading..."), changes were likely made
|
|
306
|
+
madeChanges = true;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// No output and no hashes: assume no changes
|
|
310
|
+
madeChanges = false;
|
|
311
|
+
}
|
|
96
312
|
return {
|
|
97
313
|
success: true,
|
|
98
314
|
summary: combined || "go.mod and go.sum are already tidy.",
|
|
315
|
+
madeChanges,
|
|
99
316
|
};
|
|
100
317
|
}
|
|
101
318
|
const combined = (stderr + "\n" + stdout).trim();
|
|
@@ -104,51 +321,147 @@ export function parseGoModTidyOutput(stdout, stderr, exitCode) {
|
|
|
104
321
|
summary: combined || "go mod tidy failed.",
|
|
105
322
|
};
|
|
106
323
|
}
|
|
107
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* Regex for gofmt stderr parse errors.
|
|
326
|
+
* Matches patterns like: "file.go:10:5: expected ..." or "file.go:10: expected ..."
|
|
327
|
+
*/
|
|
328
|
+
const GOFMT_ERROR_RE = /^(.+?):(\d+)(?::(\d+))?: (.+)$/;
|
|
329
|
+
/** Parses `gofmt -l` or `gofmt -l -w` output into structured result with file list. */
|
|
108
330
|
export function parseGoFmtOutput(stdout, stderr, exitCode, checkMode) {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
331
|
+
// Both check mode (-l) and fix mode (-l -w) list changed files on stdout.
|
|
332
|
+
// The -l flag causes gofmt to print filenames, -w causes it to also rewrite them.
|
|
111
333
|
// stderr may contain error messages.
|
|
112
|
-
const
|
|
113
|
-
const files = output
|
|
334
|
+
const files = stdout
|
|
114
335
|
.split("\n")
|
|
115
336
|
.map((f) => f.trim())
|
|
116
337
|
.filter(Boolean);
|
|
117
338
|
const hasErrors = exitCode !== 0 || (checkMode && files.length > 0);
|
|
339
|
+
// Parse stderr for parse errors (Gap #151)
|
|
340
|
+
const parseErrors = [];
|
|
341
|
+
if (stderr) {
|
|
342
|
+
const stderrLines = stderr.split("\n");
|
|
343
|
+
for (const line of stderrLines) {
|
|
344
|
+
const trimmed = line.trim();
|
|
345
|
+
if (!trimmed)
|
|
346
|
+
continue;
|
|
347
|
+
const match = trimmed.match(GOFMT_ERROR_RE);
|
|
348
|
+
if (match) {
|
|
349
|
+
parseErrors.push({
|
|
350
|
+
file: match[1],
|
|
351
|
+
line: parseInt(match[2], 10),
|
|
352
|
+
column: match[3] ? parseInt(match[3], 10) : undefined,
|
|
353
|
+
message: match[4],
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
118
358
|
return {
|
|
119
359
|
success: !hasErrors,
|
|
120
360
|
filesChanged: files.length,
|
|
121
361
|
files,
|
|
362
|
+
parseErrors: parseErrors.length > 0 ? parseErrors : undefined,
|
|
122
363
|
};
|
|
123
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Regex for go generate -v output: prints package name as it's processed.
|
|
367
|
+
* Format: "mypackage" (just package name on stderr when -v is used)
|
|
368
|
+
*
|
|
369
|
+
* Regex for go generate -x output: prints the command being executed.
|
|
370
|
+
* Format on stderr: "cd /path/to/pkg; <command>" or just the command
|
|
371
|
+
*
|
|
372
|
+
* Combined regex for go generate verbose output lines indicating per-directive status.
|
|
373
|
+
* Patterns:
|
|
374
|
+
* - `<file>:<line>: running "<command>"...` (from -v, not always present)
|
|
375
|
+
* - `<file>.go:<line>: <command>` (from stderr with -x)
|
|
376
|
+
* - `cd /path; <command>` (from -x)
|
|
377
|
+
*/
|
|
378
|
+
const GO_GENERATE_DIRECTIVE_RE = /^(.+\.go):(\d+): running "(.+?)"$/;
|
|
379
|
+
const GO_GENERATE_X_FILE_RE = /^(.+\.go):(\d+): (.+)$/;
|
|
124
380
|
/** Parses `go generate` output into structured result with success status and output text. */
|
|
125
381
|
export function parseGoGenerateOutput(stdout, stderr, exitCode) {
|
|
126
382
|
const combined = (stdout + "\n" + stderr).trim();
|
|
383
|
+
// Parse per-directive output from -v/-x flags (Gap #152)
|
|
384
|
+
const directives = [];
|
|
385
|
+
const allLines = combined.split("\n");
|
|
386
|
+
for (const line of allLines) {
|
|
387
|
+
const trimmed = line.trim();
|
|
388
|
+
if (!trimmed)
|
|
389
|
+
continue;
|
|
390
|
+
// Match "file.go:10: running "command""
|
|
391
|
+
const verboseMatch = trimmed.match(GO_GENERATE_DIRECTIVE_RE);
|
|
392
|
+
if (verboseMatch) {
|
|
393
|
+
directives.push({
|
|
394
|
+
file: verboseMatch[1],
|
|
395
|
+
line: parseInt(verboseMatch[2], 10),
|
|
396
|
+
command: verboseMatch[3],
|
|
397
|
+
status: exitCode === 0 ? "completed" : "running",
|
|
398
|
+
});
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
// Match "file.go:10: <command>" from -x output
|
|
402
|
+
const xMatch = trimmed.match(GO_GENERATE_X_FILE_RE);
|
|
403
|
+
if (xMatch) {
|
|
404
|
+
// Avoid matching error messages that look like file:line: patterns
|
|
405
|
+
const command = xMatch[3];
|
|
406
|
+
// Skip lines that look like error messages (contain "exec:", "not found", etc.)
|
|
407
|
+
if (!command.startsWith("running ") &&
|
|
408
|
+
!command.includes("executable file not found") &&
|
|
409
|
+
!command.startsWith("bad flag syntax")) {
|
|
410
|
+
directives.push({
|
|
411
|
+
file: xMatch[1],
|
|
412
|
+
line: parseInt(xMatch[2], 10),
|
|
413
|
+
command: command,
|
|
414
|
+
status: exitCode === 0 ? "completed" : undefined,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
127
419
|
return {
|
|
128
420
|
success: exitCode === 0,
|
|
129
421
|
output: combined,
|
|
422
|
+
directives: directives.length > 0 ? directives : undefined,
|
|
130
423
|
};
|
|
131
424
|
}
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
425
|
+
/**
|
|
426
|
+
* Parses `go env -json` output into structured result with environment variables and key fields.
|
|
427
|
+
* When specific vars are queried, the result only contains those vars from the JSON output.
|
|
428
|
+
* The queriedVars parameter is used to ensure compact mode includes the queried variables.
|
|
429
|
+
*/
|
|
430
|
+
export function parseGoEnvOutput(stdout, _queriedVars) {
|
|
431
|
+
let vars;
|
|
432
|
+
try {
|
|
433
|
+
vars = JSON.parse(stdout || "{}");
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
vars: undefined,
|
|
439
|
+
goroot: "",
|
|
440
|
+
gopath: "",
|
|
441
|
+
goversion: "",
|
|
442
|
+
goos: "",
|
|
443
|
+
goarch: "",
|
|
444
|
+
};
|
|
445
|
+
}
|
|
135
446
|
return {
|
|
447
|
+
success: true,
|
|
136
448
|
vars,
|
|
137
449
|
goroot: vars.GOROOT ?? "",
|
|
138
450
|
gopath: vars.GOPATH ?? "",
|
|
139
451
|
goversion: vars.GOVERSION ?? "",
|
|
140
452
|
goos: vars.GOOS ?? "",
|
|
141
453
|
goarch: vars.GOARCH ?? "",
|
|
454
|
+
// Store queriedVars for compact mode usage (handled in formatter)
|
|
142
455
|
};
|
|
143
456
|
}
|
|
144
457
|
/**
|
|
145
458
|
* Parses `go list -json` output into structured result with package list and total count.
|
|
146
459
|
* go list -json emits concatenated JSON objects (JSONL), one per package.
|
|
147
460
|
*/
|
|
148
|
-
export function parseGoListOutput(stdout) {
|
|
461
|
+
export function parseGoListOutput(stdout, exitCode) {
|
|
149
462
|
const packages = [];
|
|
150
463
|
if (!stdout.trim()) {
|
|
151
|
-
return { packages, total: 0 };
|
|
464
|
+
return { success: exitCode === 0, packages, total: 0 };
|
|
152
465
|
}
|
|
153
466
|
// go list -json outputs concatenated JSON objects separated by newlines.
|
|
154
467
|
// Split by detecting `}\n{` boundary and parse each object individually.
|
|
@@ -156,18 +469,54 @@ export function parseGoListOutput(stdout) {
|
|
|
156
469
|
for (const chunk of chunks) {
|
|
157
470
|
try {
|
|
158
471
|
const pkg = JSON.parse(chunk);
|
|
159
|
-
|
|
472
|
+
const entry = {
|
|
160
473
|
dir: pkg.Dir ?? "",
|
|
161
474
|
importPath: pkg.ImportPath ?? "",
|
|
162
475
|
name: pkg.Name ?? "",
|
|
163
476
|
goFiles: pkg.GoFiles,
|
|
477
|
+
testGoFiles: pkg.TestGoFiles,
|
|
478
|
+
imports: pkg.Imports,
|
|
479
|
+
};
|
|
480
|
+
// Capture Error field (Gap #155)
|
|
481
|
+
if (pkg.Error && typeof pkg.Error === "object" && pkg.Error.Err) {
|
|
482
|
+
entry.error = { err: String(pkg.Error.Err) };
|
|
483
|
+
}
|
|
484
|
+
packages.push(entry);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// skip malformed JSON chunks
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { success: exitCode === 0, packages, total: packages.length };
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Parses `go list -json -m` output into structured result with module list and total count.
|
|
494
|
+
* go list -json -m emits concatenated JSON objects, one per module.
|
|
495
|
+
*/
|
|
496
|
+
export function parseGoListModulesOutput(stdout, exitCode) {
|
|
497
|
+
const modules = [];
|
|
498
|
+
if (!stdout.trim()) {
|
|
499
|
+
return { success: exitCode === 0, modules, total: 0 };
|
|
500
|
+
}
|
|
501
|
+
const chunks = splitJsonObjects(stdout);
|
|
502
|
+
for (const chunk of chunks) {
|
|
503
|
+
try {
|
|
504
|
+
const mod = JSON.parse(chunk);
|
|
505
|
+
modules.push({
|
|
506
|
+
path: mod.Path ?? "",
|
|
507
|
+
version: mod.Version || undefined,
|
|
508
|
+
dir: mod.Dir || undefined,
|
|
509
|
+
goMod: mod.GoMod || undefined,
|
|
510
|
+
goVersion: mod.GoVersion || undefined,
|
|
511
|
+
main: mod.Main || undefined,
|
|
512
|
+
indirect: mod.Indirect || undefined,
|
|
164
513
|
});
|
|
165
514
|
}
|
|
166
515
|
catch {
|
|
167
516
|
// skip malformed JSON chunks
|
|
168
517
|
}
|
|
169
518
|
}
|
|
170
|
-
return {
|
|
519
|
+
return { success: exitCode === 0, modules, total: modules.length };
|
|
171
520
|
}
|
|
172
521
|
/** Splits concatenated JSON objects from go list -json output. */
|
|
173
522
|
function splitJsonObjects(text) {
|
|
@@ -211,7 +560,7 @@ export function parseGolangciLintJson(stdout, _exitCode) {
|
|
|
211
560
|
const issues = parsed.Issues ?? [];
|
|
212
561
|
for (const issue of issues) {
|
|
213
562
|
const severity = mapSeverity(issue.Severity);
|
|
214
|
-
|
|
563
|
+
const diag = {
|
|
215
564
|
file: issue.Pos?.Filename ?? "",
|
|
216
565
|
line: issue.Pos?.Line ?? 0,
|
|
217
566
|
column: issue.Pos?.Column || undefined,
|
|
@@ -219,7 +568,35 @@ export function parseGolangciLintJson(stdout, _exitCode) {
|
|
|
219
568
|
severity,
|
|
220
569
|
message: issue.Text ?? "",
|
|
221
570
|
sourceLine: issue.SourceLines?.[0] || undefined,
|
|
222
|
-
}
|
|
571
|
+
};
|
|
572
|
+
// Capture Replacement/fix data (Gap #154)
|
|
573
|
+
if (issue.Replacement) {
|
|
574
|
+
const replacement = issue.Replacement;
|
|
575
|
+
if (replacement.NeedOnlyDelete || replacement.NewLines !== undefined) {
|
|
576
|
+
const fix = {
|
|
577
|
+
text: replacement.NeedOnlyDelete ? "" : (replacement.NewLines ?? []).join("\n"),
|
|
578
|
+
};
|
|
579
|
+
// Include range if inline fix info is present
|
|
580
|
+
if (replacement.Inline) {
|
|
581
|
+
fix.range = {
|
|
582
|
+
start: {
|
|
583
|
+
line: replacement.Inline.StartLine ?? issue.Pos?.Line ?? 0,
|
|
584
|
+
column: replacement.Inline.StartCol,
|
|
585
|
+
},
|
|
586
|
+
end: {
|
|
587
|
+
line: replacement.Inline.EndLine ?? issue.Pos?.Line ?? 0,
|
|
588
|
+
column: replacement.Inline.EndCol,
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
diag.fix = fix;
|
|
593
|
+
}
|
|
594
|
+
else if (typeof replacement.NewLines === "undefined" && !replacement.NeedOnlyDelete) {
|
|
595
|
+
// Some replacements only have NewLines without inline info
|
|
596
|
+
// If Replacement exists but has no expected structure, still try to capture it
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
diagnostics.push(diag);
|
|
223
600
|
}
|
|
224
601
|
const errors = diagnostics.filter((d) => d.severity === "error").length;
|
|
225
602
|
const warnings = diagnostics.filter((d) => d.severity === "warning").length;
|
|
@@ -249,12 +626,92 @@ function mapSeverity(severity) {
|
|
|
249
626
|
return "info";
|
|
250
627
|
return "warning";
|
|
251
628
|
}
|
|
252
|
-
/**
|
|
253
|
-
|
|
629
|
+
/**
|
|
630
|
+
* Regex patterns for go get output lines that indicate version resolution.
|
|
631
|
+
* Matches lines like:
|
|
632
|
+
* go: upgraded golang.org/x/text v0.3.7 => v0.14.0
|
|
633
|
+
* go: added github.com/pkg/errors v0.9.1
|
|
634
|
+
* go: downgraded golang.org/x/net v0.10.0 => v0.8.0
|
|
635
|
+
*/
|
|
636
|
+
const GO_GET_UPGRADED_RE = /^go: (?:upgraded|downgraded)\s+(\S+)\s+(\S+)\s+=>\s+(\S+)$/;
|
|
637
|
+
const GO_GET_ADDED_RE = /^go: added\s+(\S+)\s+(\S+)$/;
|
|
638
|
+
/**
|
|
639
|
+
* Regex for go get error lines per package.
|
|
640
|
+
* Examples:
|
|
641
|
+
* go: module github.com/nonexistent/pkg: no matching versions for query "latest"
|
|
642
|
+
* go: github.com/some/pkg@v1.0.0: verifying module: ...
|
|
643
|
+
*/
|
|
644
|
+
const GO_GET_ERROR_RE = /^go: (?:module\s+)?(\S+?)(?:@(\S+))?:\s+(.+)$/;
|
|
645
|
+
/** Parses `go get` output into structured result with success status, output text, resolved packages, and per-package status. */
|
|
646
|
+
export function parseGoGetOutput(stdout, stderr, exitCode, requestedPackages) {
|
|
254
647
|
const combined = (stdout + "\n" + stderr).trim();
|
|
648
|
+
const resolvedPackages = [];
|
|
649
|
+
// Per-package status tracking (Gap #153)
|
|
650
|
+
const packageStatuses = new Map();
|
|
651
|
+
const lines = combined.split("\n");
|
|
652
|
+
for (const line of lines) {
|
|
653
|
+
const trimmed = line.trim();
|
|
654
|
+
// Match "go: upgraded <pkg> <old> => <new>" or "go: downgraded <pkg> <old> => <new>"
|
|
655
|
+
const upgradeMatch = trimmed.match(GO_GET_UPGRADED_RE);
|
|
656
|
+
if (upgradeMatch) {
|
|
657
|
+
resolvedPackages.push({
|
|
658
|
+
package: upgradeMatch[1],
|
|
659
|
+
previousVersion: upgradeMatch[2],
|
|
660
|
+
newVersion: upgradeMatch[3],
|
|
661
|
+
});
|
|
662
|
+
packageStatuses.set(upgradeMatch[1], {
|
|
663
|
+
path: upgradeMatch[1],
|
|
664
|
+
version: upgradeMatch[3],
|
|
665
|
+
});
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
// Match "go: added <pkg> <version>"
|
|
669
|
+
const addedMatch = trimmed.match(GO_GET_ADDED_RE);
|
|
670
|
+
if (addedMatch) {
|
|
671
|
+
resolvedPackages.push({
|
|
672
|
+
package: addedMatch[1],
|
|
673
|
+
newVersion: addedMatch[2],
|
|
674
|
+
});
|
|
675
|
+
packageStatuses.set(addedMatch[1], {
|
|
676
|
+
path: addedMatch[1],
|
|
677
|
+
version: addedMatch[2],
|
|
678
|
+
});
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
// Match per-package error lines (Gap #153)
|
|
682
|
+
const errorMatch = trimmed.match(GO_GET_ERROR_RE);
|
|
683
|
+
if (errorMatch) {
|
|
684
|
+
const pkgPath = errorMatch[1];
|
|
685
|
+
const errMsg = errorMatch[3];
|
|
686
|
+
// Skip "downloading" lines which are progress, not errors
|
|
687
|
+
if (!errMsg.startsWith("downloading ")) {
|
|
688
|
+
packageStatuses.set(pkgPath, {
|
|
689
|
+
path: pkgPath,
|
|
690
|
+
version: errorMatch[2] || undefined,
|
|
691
|
+
error: errMsg,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// If we have requestedPackages but some aren't in statuses, add them as successful (no error, no explicit status)
|
|
697
|
+
if (requestedPackages) {
|
|
698
|
+
for (const reqPkg of requestedPackages) {
|
|
699
|
+
// Strip version suffix from request (e.g., "github.com/pkg/errors@latest" -> "github.com/pkg/errors")
|
|
700
|
+
const pkgPath = reqPkg.replace(/@.*$/, "");
|
|
701
|
+
if (!packageStatuses.has(pkgPath)) {
|
|
702
|
+
// If the overall command succeeded, mark as successful
|
|
703
|
+
if (exitCode === 0) {
|
|
704
|
+
packageStatuses.set(pkgPath, { path: pkgPath });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const packages = packageStatuses.size > 0 ? Array.from(packageStatuses.values()) : undefined;
|
|
255
710
|
return {
|
|
256
711
|
success: exitCode === 0,
|
|
257
712
|
output: combined || undefined,
|
|
713
|
+
resolvedPackages: resolvedPackages.length > 0 ? resolvedPackages : undefined,
|
|
714
|
+
packages,
|
|
258
715
|
};
|
|
259
716
|
}
|
|
260
717
|
//# sourceMappingURL=parsers.js.map
|