pi-lens 4.2.0 → 4.3.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/CHANGELOG.md +543 -0
- package/README.md +45 -2
- package/dist/clients/actionable-warnings.js +44 -16
- package/dist/clients/advisory-provenance.js +187 -51
- package/dist/clients/cache-manager.js +64 -5
- package/dist/clients/cascade-format.js +24 -0
- package/dist/clients/child-unref.js +17 -8
- package/dist/clients/config-diagnostic-codes.js +2 -0
- package/dist/clients/config-locations.js +237 -9
- package/dist/clients/config-resolve.js +7 -1
- package/dist/clients/degradation-ledger.js +33 -3
- package/dist/clients/dependency-checker.js +87 -64
- package/dist/clients/diagnostic-dispositions.js +39 -3
- package/dist/clients/dispatch/finding-policy.js +118 -1
- package/dist/clients/dispatch/indent-detect.js +8 -2
- package/dist/clients/dispatch/inline-suppressions.js +23 -4
- package/dist/clients/dispatch/rule-ignores.js +14 -7
- package/dist/clients/dispatch/runners/actionlint.js +26 -28
- package/dist/clients/dispatch/runners/biome-check.js +38 -25
- package/dist/clients/dispatch/runners/cpp-check.js +32 -49
- package/dist/clients/dispatch/runners/credo.js +16 -14
- package/dist/clients/dispatch/runners/cue-vet.js +25 -7
- package/dist/clients/dispatch/runners/dart-analyze.js +28 -47
- package/dist/clients/dispatch/runners/detekt.js +11 -4
- package/dist/clients/dispatch/runners/dotnet-build.js +27 -22
- package/dist/clients/dispatch/runners/elixir-check.js +31 -46
- package/dist/clients/dispatch/runners/eslint.js +29 -46
- package/dist/clients/dispatch/runners/gleam-check.js +91 -11
- package/dist/clients/dispatch/runners/go-vet.js +16 -2
- package/dist/clients/dispatch/runners/golangci-lint.js +38 -23
- package/dist/clients/dispatch/runners/hadolint.js +27 -15
- package/dist/clients/dispatch/runners/htmlhint.js +17 -8
- package/dist/clients/dispatch/runners/javac.js +31 -21
- package/dist/clients/dispatch/runners/ktlint.js +34 -60
- package/dist/clients/dispatch/runners/lsp.js +6 -5
- package/dist/clients/dispatch/runners/markdownlint.js +12 -5
- package/dist/clients/dispatch/runners/mypy.js +2 -1
- package/dist/clients/dispatch/runners/oxlint.js +22 -6
- package/dist/clients/dispatch/runners/php-lint.js +39 -16
- package/dist/clients/dispatch/runners/phpstan.js +1 -1
- package/dist/clients/dispatch/runners/rubocop.js +27 -18
- package/dist/clients/dispatch/runners/ruff.js +18 -19
- package/dist/clients/dispatch/runners/rust-clippy.js +6 -1
- package/dist/clients/dispatch/runners/shellcheck.js +6 -2
- package/dist/clients/dispatch/runners/spellcheck.js +10 -3
- package/dist/clients/dispatch/runners/sqlfluff.js +11 -2
- package/dist/clients/dispatch/runners/stylelint.js +10 -2
- package/dist/clients/dispatch/runners/swiftlint.js +11 -2
- package/dist/clients/dispatch/runners/taplo.js +12 -6
- package/dist/clients/dispatch/runners/tflint.js +30 -15
- package/dist/clients/dispatch/runners/tree-sitter.js +23 -0
- package/dist/clients/dispatch/runners/trivy-config.js +8 -2
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
- package/dist/clients/dispatch/runners/vale.js +15 -3
- package/dist/clients/dispatch/runners/yamllint.js +12 -2
- package/dist/clients/dispatch/runners/zig-check.js +27 -39
- package/dist/clients/dispatch/suppress-writer.js +13 -3
- package/dist/clients/effective-config.js +3 -1
- package/dist/clients/extension-log.js +48 -1
- package/dist/clients/finding-delivery-gate.js +53 -23
- package/dist/clients/formatters.js +49 -11
- package/dist/clients/git-guard.js +247 -34
- package/dist/clients/gradle-ktfmt-style.js +121 -25
- package/dist/clients/inline-blocker-dispositions.js +186 -0
- package/dist/clients/install-diagnostics.js +19 -6
- package/dist/clients/installer/index.js +281 -32
- package/dist/clients/jscpd-client.js +8 -0
- package/dist/clients/latency-logger.js +8 -0
- package/dist/clients/lens-config.js +66 -8
- package/dist/clients/lsp/cascade-tier.js +3 -0
- package/dist/clients/lsp/client.js +139 -11
- package/dist/clients/lsp/config.js +9 -7
- package/dist/clients/lsp/content-limits.js +41 -0
- package/dist/clients/lsp/index.js +42 -8
- package/dist/clients/lsp/launch.js +23 -2
- package/dist/clients/lsp/server.js +106 -25
- package/dist/clients/lsp/sync-kind.js +14 -0
- package/dist/clients/lsp/wait-policy/strategies.js +105 -16
- package/dist/clients/mcp/analyze.js +9 -2
- package/dist/clients/mcp/ipc.js +232 -44
- package/dist/clients/mcp/review.js +53 -7
- package/dist/clients/opengrep-client.js +11 -6
- package/dist/clients/path-utils.js +51 -9
- package/dist/clients/persistent-reverify.js +271 -0
- package/dist/clients/pipeline.js +136 -67
- package/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
- package/dist/clients/project-lens-config.js +11 -3
- package/dist/clients/review-graph/builder.js +34 -26
- package/dist/clients/review-graph/git-identity.js +16 -10
- package/dist/clients/ruff-client.js +31 -4
- package/dist/clients/runtime-coordinator.js +38 -4
- package/dist/clients/runtime-tool-call.js +16 -5
- package/dist/clients/runtime-tool-result.js +27 -12
- package/dist/clients/runtime-turn.js +571 -287
- package/dist/clients/safe-spawn.js +256 -65
- package/dist/clients/skills-resolver.js +38 -2
- package/dist/clients/spawn-output-cap.js +84 -1
- package/dist/clients/tree-sitter-client.js +119 -58
- package/dist/clients/turn-end/lane.js +51 -0
- package/dist/clients/turn-end/lanes/govulncheck.js +112 -0
- package/dist/clients/turn-end/lanes/secrets.js +204 -0
- package/dist/clients/warm-attach.js +59 -0
- package/dist/clients/widget-state.js +294 -17
- package/dist/clients/workspace-topology.js +57 -3
- package/dist/clients/zizmor-config.js +26 -13
- package/dist/index.js +6342 -3995
- package/dist/mcp/analyze-cli.js +4 -0
- package/dist/mcp/server.js +33 -21
- package/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/dist/tools/lens-diagnostic-mark.js +14 -2
- package/dist/tools/lens-diagnostics.js +127 -33
- package/dist/tools/lsp-diagnostics.js +150 -12
- package/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/docs/adr/0009-reported-path-attribution.md +114 -0
- package/docs/adr/README.md +26 -0
- package/docs/agent-tools.md +18 -1
- package/docs/ast-grep_rules_catalog.md +4 -3
- package/docs/configuration.md +55 -1
- package/docs/engineering-principles.md +202 -0
- package/docs/environment-variables.md +61 -4
- package/docs/features.md +2 -2
- package/docs/globalconfig.md +6 -1
- package/docs/language-coverage.md +17 -0
- package/docs/lsp-capability-matrix.md +98 -31
- package/docs/pi-lens-fixer.md +57 -3
- package/docs/pi-lens-investigator.md +9 -0
- package/docs/pi-lens-reviewer.md +16 -0
- package/docs/pi-lens-subagent.md +9 -0
- package/docs/public-api-stability.md +1 -0
- package/docs/release-qa-baseline.md +2 -0
- package/docs/servercapabilities.md +21 -11
- package/docs/settings.md +12 -3
- package/docs/subagent-compat.md +1 -1
- package/package.json +5 -3
- package/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/scripts/install-selftest.mjs +21 -17
- package/scripts/lib/web-tree-sitter-dir.mjs +123 -0
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* Autofix is handled earlier by the post-write pipeline to avoid
|
|
6
6
|
* mutating files mid-dispatch after LSP sync has already happened.
|
|
7
7
|
*/
|
|
8
|
+
import * as path from "node:path";
|
|
8
9
|
import { incrementDegradationCount } from "../../degradation-ledger.js";
|
|
10
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
9
11
|
import { mapWithConcurrency } from "../../dependency-checker.js";
|
|
10
12
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
11
13
|
import { biomeConfigArgs, getAutofixCapability, getJstsLintPolicyForCwd, } from "../../tool-policy.js";
|
|
@@ -50,35 +52,46 @@ export function biomeRuleNameFromCategory(category) {
|
|
|
50
52
|
const parts = category.split("/");
|
|
51
53
|
return parts[parts.length - 1] || undefined;
|
|
52
54
|
}
|
|
53
|
-
export function parseBiomeJson(raw, filePath, fixKindByRule) {
|
|
55
|
+
export function parseBiomeJson(raw, filePath, cwd, fixKindByRule) {
|
|
54
56
|
try {
|
|
55
57
|
const result = JSON.parse(raw);
|
|
56
58
|
const diagnostics = result.diagnostics || [];
|
|
57
59
|
const autofix = getAutofixCapability("biome");
|
|
60
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
58
61
|
return {
|
|
59
|
-
diagnostics: diagnostics.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
diagnostics: diagnostics.flatMap((d) => {
|
|
63
|
+
// #3295: `location.path` is biome's own name for the file each
|
|
64
|
+
// diagnostic is about; `biome.json` `files.includes` can widen the
|
|
65
|
+
// run past the argv.
|
|
66
|
+
if (d.location?.path &&
|
|
67
|
+
!pathsEqual(path.resolve(cwd, d.location.path), absTarget))
|
|
68
|
+
return [];
|
|
69
|
+
{
|
|
70
|
+
const ruleName = biomeRuleNameFromCategory(d.category);
|
|
71
|
+
const fixKind = ruleName ? fixKindByRule?.get(ruleName) : undefined;
|
|
72
|
+
const isFixable = fixKind === "safe" || fixKind === "unsafe";
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
id: `biome:${d.category}:${d.location.start.line}`,
|
|
76
|
+
message: d.message,
|
|
77
|
+
filePath,
|
|
78
|
+
line: d.location.start.line,
|
|
79
|
+
column: d.location.start.column,
|
|
80
|
+
severity: normalizeBiomeSeverity(d.severity),
|
|
81
|
+
semantic: d.severity === "error" ? "blocking" : "warning",
|
|
82
|
+
tool: "biome",
|
|
83
|
+
rule: d.category,
|
|
84
|
+
fixable: isFixable,
|
|
85
|
+
// Mirrors `biomeClient.fixFileAsync`'s own `lint --write` call
|
|
86
|
+
// (no `--unsafe`, clients/biome-client.ts): the pipeline only
|
|
87
|
+
// ever applies SAFE fixes, so only "safe" earns autoFixAvailable.
|
|
88
|
+
autoFixAvailable: fixKind === "safe" && (autofix?.safePipelineAutofix ?? false),
|
|
89
|
+
fixKind: isFixable && autofix?.fixKind !== "none"
|
|
90
|
+
? autofix?.fixKind
|
|
91
|
+
: undefined,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
}
|
|
82
95
|
}),
|
|
83
96
|
};
|
|
84
97
|
}
|
|
@@ -248,7 +261,7 @@ const biomeCheckJsonRunner = {
|
|
|
248
261
|
fixKindByRule = await resolveBiomeFixKinds(cmd, cwd, categories);
|
|
249
262
|
}
|
|
250
263
|
const parsed = checkResult.status === 0 || checkResult.status === 1
|
|
251
|
-
? parseBiomeJson(checkResult.stdout || "", ctx.filePath, fixKindByRule)
|
|
264
|
+
? parseBiomeJson(checkResult.stdout || "", ctx.filePath, cwd, fixKindByRule)
|
|
252
265
|
: { diagnostics: [] };
|
|
253
266
|
if (parsed.parseError) {
|
|
254
267
|
const raw = checkResult.stdout || checkResult.stderr || "";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
3
4
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
4
5
|
import { probeToolAsync } from "../../tool-probe.js";
|
|
5
6
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
6
7
|
import { PRIORITY } from "../priorities.js";
|
|
7
8
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
9
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
8
10
|
// Per-compiler availability checkers — each one caches per-cwd and
|
|
9
11
|
// dedupes concurrent --version probes, the same protections every other
|
|
10
12
|
// runner gets via createAvailabilityChecker. cpp-check previously
|
|
@@ -112,16 +114,16 @@ async function resolveCompiler(absPath, cwd) {
|
|
|
112
114
|
}
|
|
113
115
|
return undefined;
|
|
114
116
|
}
|
|
115
|
-
function parseGccLikeOutput(raw, filePath) {
|
|
117
|
+
function parseGccLikeOutput(raw, filePath, cwd) {
|
|
116
118
|
const diagnostics = [];
|
|
119
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
117
120
|
for (const line of raw.split(/\r?\n/)) {
|
|
118
121
|
const match = line.match(/^(.*?):(\d+):(?:(\d+):)?\s*(fatal error|error|warning|note):\s+(.+)$/i);
|
|
119
122
|
if (!match)
|
|
120
123
|
continue;
|
|
121
124
|
const [, sourcePath, lineStr, colStr, severityLabel, message] = match;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (resolvedSource !== resolvedTarget)
|
|
125
|
+
// #3278: one seam for reported-path attribution — see javac.ts.
|
|
126
|
+
if (!pathsEqual(path.resolve(cwd, sourcePath.trim()), absTarget))
|
|
125
127
|
continue;
|
|
126
128
|
const severity = severityLabel.toLowerCase().includes("error")
|
|
127
129
|
? "error"
|
|
@@ -143,16 +145,16 @@ function parseGccLikeOutput(raw, filePath) {
|
|
|
143
145
|
}
|
|
144
146
|
return diagnostics;
|
|
145
147
|
}
|
|
146
|
-
function parseMsvcOutput(raw, filePath) {
|
|
148
|
+
function parseMsvcOutput(raw, filePath, cwd) {
|
|
147
149
|
const diagnostics = [];
|
|
150
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
148
151
|
for (const line of raw.split(/\r?\n/)) {
|
|
149
152
|
const match = line.match(/^(.*)\((\d+)(?:,(\d+))?\):\s*(fatal error|error|warning)\s+([A-Z]+\d+):\s+(.+)$/i);
|
|
150
153
|
if (!match)
|
|
151
154
|
continue;
|
|
152
155
|
const [, sourcePath, lineStr, colStr, severityLabel, rule, message] = match;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (resolvedSource !== resolvedTarget)
|
|
156
|
+
// #3278: one seam for reported-path attribution — see javac.ts.
|
|
157
|
+
if (!pathsEqual(path.resolve(cwd, sourcePath.trim()), absTarget))
|
|
156
158
|
continue;
|
|
157
159
|
const severity = severityLabel.toLowerCase().includes("error")
|
|
158
160
|
? "error"
|
|
@@ -173,9 +175,6 @@ function parseMsvcOutput(raw, filePath) {
|
|
|
173
175
|
}
|
|
174
176
|
return diagnostics;
|
|
175
177
|
}
|
|
176
|
-
function firstOutputLine(raw) {
|
|
177
|
-
return raw.trim().split(/\r?\n/, 1)[0]?.slice(0, 200) ?? "";
|
|
178
|
-
}
|
|
179
178
|
const cppCheckRunner = {
|
|
180
179
|
id: "cpp-check",
|
|
181
180
|
appliesTo: ["cxx"],
|
|
@@ -193,44 +192,28 @@ const cppCheckRunner = {
|
|
|
193
192
|
timeout: 30000,
|
|
194
193
|
});
|
|
195
194
|
const raw = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
status: "succeeded",
|
|
222
|
-
diagnostics: [],
|
|
223
|
-
semantic: "none",
|
|
224
|
-
rawOutput: raw,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
228
|
-
return {
|
|
229
|
-
status: hasErrors ? "failed" : "succeeded",
|
|
230
|
-
diagnostics,
|
|
231
|
-
semantic: "warning",
|
|
232
|
-
rawOutput: raw,
|
|
233
|
-
};
|
|
195
|
+
const parsed = parseToolRun("cpp-check", {
|
|
196
|
+
result,
|
|
197
|
+
output: raw,
|
|
198
|
+
// EXIT TABLE (gcc/clang 13 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
199
|
+
exitCodes: { ran: [1, 2] },
|
|
200
|
+
}, (output) => compiler.flavor === "msvc"
|
|
201
|
+
? parseMsvcOutput(output, ctx.filePath, cwd)
|
|
202
|
+
: parseGccLikeOutput(output, ctx.filePath, cwd));
|
|
203
|
+
if (parsed.skipped)
|
|
204
|
+
return parsed.skipped;
|
|
205
|
+
return finishParsedRun({
|
|
206
|
+
tool: "cpp-check",
|
|
207
|
+
ctx,
|
|
208
|
+
result,
|
|
209
|
+
diagnostics: parsed.diagnostics,
|
|
210
|
+
classify: (diagnostics) => ({
|
|
211
|
+
status: diagnostics.some((d) => d.severity === "error")
|
|
212
|
+
? "failed"
|
|
213
|
+
: "succeeded",
|
|
214
|
+
semantic: "warning",
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
234
217
|
},
|
|
235
218
|
};
|
|
236
219
|
export default cppCheckRunner;
|
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
4
4
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
5
5
|
import { PRIORITY } from "../priorities.js";
|
|
6
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
6
7
|
import { createCwdCachedProbe } from "./utils/runner-helpers.js";
|
|
7
8
|
const CREDO_PROBE_BUDGET_MS = 10_000;
|
|
8
9
|
// Per-cwd cached `mix credo --version` probe (#120), governed by the shared
|
|
@@ -61,20 +62,21 @@ const credoRunner = {
|
|
|
61
62
|
}
|
|
62
63
|
const absPath = path.resolve(cwd, ctx.filePath);
|
|
63
64
|
const result = await safeSpawnAsync("mix", ["credo", "--format", "json", "--strict", absPath], { timeout: 30000, cwd });
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
66
|
+
const parsed = parseToolRun("credo", {
|
|
67
|
+
result,
|
|
68
|
+
output,
|
|
69
|
+
// EXIT TABLE (Credo 1.7 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
70
|
+
exitCodes: { ran: [1, 2] },
|
|
71
|
+
}, (raw) => parseCredoJson(raw, ctx.filePath, cwd));
|
|
72
|
+
if (parsed.skipped)
|
|
73
|
+
return parsed.skipped;
|
|
74
|
+
return finishParsedRun({
|
|
75
|
+
tool: "credo",
|
|
76
|
+
ctx,
|
|
77
|
+
result,
|
|
78
|
+
diagnostics: parsed.diagnostics,
|
|
79
|
+
});
|
|
78
80
|
},
|
|
79
81
|
};
|
|
80
82
|
export default credoRunner;
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
import * as fs from "node:fs";
|
|
105
105
|
import * as path from "node:path";
|
|
106
106
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
107
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
107
108
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
108
109
|
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
109
110
|
import { spawnFailedWithNoOutput } from "./utils/spawn-outcome.js";
|
|
@@ -205,10 +206,24 @@ export function hasPackageClause(content) {
|
|
|
205
206
|
}
|
|
206
207
|
return false;
|
|
207
208
|
}
|
|
208
|
-
/**
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Is this reported location about the file we dispatched for? ONE seam answers
|
|
211
|
+
* that for every runner (#3278): resolve the tool's spelling against the cwd
|
|
212
|
+
* the tool RAN in and compare through `pathsEqual`.
|
|
213
|
+
*
|
|
214
|
+
* `cue` prints every position relative to its own cwd and prefixes a `./` when
|
|
215
|
+
* the result does not already start with a dot (`cue/errors/errors.go:586-596`
|
|
216
|
+
* at v0.11.0: `filepath.Rel(cfg.Cwd, s)`), which is why `.\bad.cue`,
|
|
217
|
+
* `./bad.cue` and `bad.cue` all name the vetted file — `pathsEqual` folds the
|
|
218
|
+
* separator and the `.` segment, so all three still match.
|
|
219
|
+
*
|
|
220
|
+
* What the basename compare this replaces could not do is tell those apart
|
|
221
|
+
* from `./sub/bad.cue` or `../lib/bad.cue`: an imported package's file that
|
|
222
|
+
* merely SHARES the touched file's basename was reported as the touched file's
|
|
223
|
+
* own failure (the over-merge direction, #3278 sub-shape C).
|
|
224
|
+
*/
|
|
225
|
+
function locationMatchesFile(location, absTarget, vetCwd) {
|
|
226
|
+
return pathsEqual(path.resolve(vetCwd, location.file), absTarget);
|
|
212
227
|
}
|
|
213
228
|
function toDiagnostic(id, message, fileName, line, column) {
|
|
214
229
|
return {
|
|
@@ -252,16 +267,19 @@ function toDiagnostic(id, message, fileName, line, column) {
|
|
|
252
267
|
* whole-output fallback message is used instead of one row per parsed
|
|
253
268
|
* header.
|
|
254
269
|
*/
|
|
255
|
-
export function filterToTouchedFile(errors, fileName) {
|
|
270
|
+
export function filterToTouchedFile(errors, fileName, vetCwd) {
|
|
256
271
|
if (errors.length === 0)
|
|
257
272
|
return undefined;
|
|
273
|
+
// `cue vet` always runs in the touched file's own directory, so the vet cwd
|
|
274
|
+
// joined with the bare file name IS the dispatched file.
|
|
275
|
+
const absTarget = path.resolve(vetCwd, fileName);
|
|
258
276
|
const withLocation = errors.filter((error) => error.locations.length > 0);
|
|
259
277
|
const unattributable = errors.filter((error) => error.locations.length === 0);
|
|
260
278
|
if (withLocation.length === 0)
|
|
261
279
|
return undefined;
|
|
262
280
|
const diagnostics = [];
|
|
263
281
|
for (const error of withLocation) {
|
|
264
|
-
const match = error.locations.find((location) => locationMatchesFile(location,
|
|
282
|
+
const match = error.locations.find((location) => locationMatchesFile(location, absTarget, vetCwd));
|
|
265
283
|
if (!match)
|
|
266
284
|
continue; // sibling-only finding — surfaces when that file is touched
|
|
267
285
|
diagnostics.push(toDiagnostic(`cue-vet-${diagnostics.length + 1}-${match.line}`, error.message, fileName, match.line, match.column));
|
|
@@ -364,7 +382,7 @@ const cueVetRunner = {
|
|
|
364
382
|
// the directory-scoped path — one code path, and a single-file run
|
|
365
383
|
// that somehow fails with no location at all still gets the same
|
|
366
384
|
// never-silently-clean `undefined` fallback as the directory path.
|
|
367
|
-
const filtered = filterToTouchedFile(errors, fileName);
|
|
385
|
+
const filtered = filterToTouchedFile(errors, fileName, fileDir);
|
|
368
386
|
if (filtered === undefined) {
|
|
369
387
|
// Nonzero exit, some output, but nothing in it could be attributed to
|
|
370
388
|
// ANY file — a real failure must not present as zero findings.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
3
4
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
4
5
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
6
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
5
7
|
import { PRIORITY } from "../priorities.js";
|
|
6
8
|
const dart = createAvailabilityChecker("dart", ".exe");
|
|
7
9
|
const flutter = createAvailabilityChecker("flutter", ".bat");
|
|
@@ -94,8 +96,9 @@ const DART_FIXABLE_RULES = new Set([
|
|
|
94
96
|
]);
|
|
95
97
|
// dart analyze --format=machine output:
|
|
96
98
|
// severity|type|code|file|line|col|length|message
|
|
97
|
-
function parseDartMachineOutput(raw, filePath) {
|
|
99
|
+
function parseDartMachineOutput(raw, filePath, cwd) {
|
|
98
100
|
const diagnostics = [];
|
|
101
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
99
102
|
for (const line of raw.split(/\r?\n/)) {
|
|
100
103
|
if (!line.trim())
|
|
101
104
|
continue;
|
|
@@ -106,14 +109,16 @@ function parseDartMachineOutput(raw, filePath) {
|
|
|
106
109
|
const message = messageParts.join("|").trim();
|
|
107
110
|
const lineNum = parseInt(lineStr, 10);
|
|
108
111
|
const colNum = parseInt(colStr, 10);
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
// #3278: one seam for reported-path attribution — see javac.ts.
|
|
113
|
+
// The `endsWith` outer arm this replaces was inert on the target (a
|
|
114
|
+
// string ends with itself) and an over-merge risk everywhere else: any
|
|
115
|
+
// reported path whose TAIL spelled the absolute target attached to it.
|
|
116
|
+
// `file` is `parts[3]`, which `tsconfig.strict-indexed.json`
|
|
117
|
+
// (`noUncheckedIndexedAccess`) types as `string | undefined` even behind the
|
|
118
|
+
// `parts.length < 8` guard above — the strictness ratchet pins that, so the
|
|
119
|
+
// optional chain stays.
|
|
120
|
+
if (!pathsEqual(path.resolve(cwd, file?.trim() ?? ""), absTarget))
|
|
121
|
+
continue;
|
|
117
122
|
const severity = severityStr?.trim().toLowerCase() === "error" ? "error" : "warning";
|
|
118
123
|
const ruleId = code?.trim() ?? "dart";
|
|
119
124
|
const fixable = DART_FIXABLE_RULES.has(ruleId);
|
|
@@ -136,12 +141,6 @@ function parseDartMachineOutput(raw, filePath) {
|
|
|
136
141
|
}
|
|
137
142
|
return diagnostics;
|
|
138
143
|
}
|
|
139
|
-
function firstOutputLine(result) {
|
|
140
|
-
return `${result.stderr || ""}\n${result.stdout || ""}`
|
|
141
|
-
.trim()
|
|
142
|
-
.split(/\r?\n/, 1)[0]
|
|
143
|
-
.slice(0, 200);
|
|
144
|
-
}
|
|
145
144
|
const dartAnalyzeRunner = {
|
|
146
145
|
id: "dart-analyze",
|
|
147
146
|
appliesTo: ["dart"],
|
|
@@ -162,40 +161,22 @@ const dartAnalyzeRunner = {
|
|
|
162
161
|
? ["analyze", "--format=machine", absPath]
|
|
163
162
|
: ["analyze", "--machine", absPath];
|
|
164
163
|
const result = await safeSpawnAsync(cmd, args, { cwd, timeout: 30000 });
|
|
165
|
-
if (result.error && !result.stdout && !result.stderr) {
|
|
166
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
167
|
-
}
|
|
168
164
|
// dart analyze writes diagnostics to stderr in machine format
|
|
169
165
|
const raw = (result.stderr || "") + (result.stdout || "");
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
rule: "dart-analyze",
|
|
185
|
-
fixable: false,
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
semantic: "warning",
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
192
|
-
}
|
|
193
|
-
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
194
|
-
return {
|
|
195
|
-
status: hasErrors ? "failed" : "succeeded",
|
|
196
|
-
diagnostics,
|
|
197
|
-
semantic: hasErrors ? "blocking" : "warning",
|
|
198
|
-
};
|
|
166
|
+
const parsed = parseToolRun("dart-analyze", {
|
|
167
|
+
result,
|
|
168
|
+
output: raw,
|
|
169
|
+
// EXIT TABLE (Dart 3.5 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
170
|
+
exitCodes: { ran: [1, 2] },
|
|
171
|
+
}, (out) => parseDartMachineOutput(out, ctx.filePath, cwd));
|
|
172
|
+
if (parsed.skipped)
|
|
173
|
+
return parsed.skipped;
|
|
174
|
+
return finishParsedRun({
|
|
175
|
+
tool: "dart-analyze",
|
|
176
|
+
ctx,
|
|
177
|
+
result,
|
|
178
|
+
diagnostics: parsed.diagnostics,
|
|
179
|
+
});
|
|
199
180
|
},
|
|
200
181
|
};
|
|
201
182
|
export default dartAnalyzeRunner;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
4
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
4
5
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
5
6
|
import { getLinterPolicyForCwd } from "../../tool-policy.js";
|
|
6
7
|
import { PRIORITY } from "../priorities.js";
|
|
@@ -91,13 +92,14 @@ export function findDetektConfig(cwd) {
|
|
|
91
92
|
return undefined;
|
|
92
93
|
}
|
|
93
94
|
// detekt text output: /path/file.kt:10:5: error: Message [RuleId]
|
|
94
|
-
function parseDetektOutput(raw, filePath) {
|
|
95
|
+
function parseDetektOutput(raw, filePath, cwd) {
|
|
95
96
|
const diagnostics = [];
|
|
96
97
|
const pattern = /^(.+?):(\d+):(\d+): (error|warning): (.+?)(?:\s+\[([^\]]+)\])?$/gm;
|
|
97
|
-
const absTarget = path.resolve(filePath);
|
|
98
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
98
99
|
for (const match of raw.matchAll(pattern)) {
|
|
99
100
|
const [, file, lineStr, colStr, level, message, rule] = match;
|
|
100
|
-
|
|
101
|
+
// #3278: one seam for reported-path attribution — see javac.ts.
|
|
102
|
+
if (!pathsEqual(path.resolve(cwd, file.trim()), absTarget))
|
|
101
103
|
continue;
|
|
102
104
|
const severity = level === "error" ? "error" : "warning";
|
|
103
105
|
const lineNum = Number.parseInt(lineStr, 10);
|
|
@@ -150,7 +152,12 @@ const detektRunner = {
|
|
|
150
152
|
// read the same concatenated string. A nonzero exit that yields zero
|
|
151
153
|
// findings out of real output is a parser break, not clean Kotlin.
|
|
152
154
|
const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
153
|
-
const run = parseToolRun("detekt", {
|
|
155
|
+
const run = parseToolRun("detekt", {
|
|
156
|
+
result,
|
|
157
|
+
output: raw,
|
|
158
|
+
// EXIT TABLE (detekt 1.23 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
159
|
+
exitCodes: { ran: [1, 2] },
|
|
160
|
+
}, (out) => parseDetektOutput(out, ctx.filePath, cwd));
|
|
154
161
|
if (run.skipped)
|
|
155
162
|
return run.skipped;
|
|
156
163
|
const diagnostics = run.diagnostics;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
4
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
4
5
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
5
6
|
import { PRIORITY } from "../priorities.js";
|
|
6
7
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
8
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
7
9
|
const dotnet = createAvailabilityChecker("dotnet", ".exe");
|
|
8
10
|
function findProjectTarget(cwd) {
|
|
9
11
|
const entries = fs.readdirSync(cwd);
|
|
@@ -83,16 +85,16 @@ function parseDotnetDiagnosticLine(line) {
|
|
|
83
85
|
}
|
|
84
86
|
return { reportedFile, lineStr, colStr, severityLabel, rule, message };
|
|
85
87
|
}
|
|
86
|
-
function parseDotnetBuildOutput(raw, filePath) {
|
|
88
|
+
function parseDotnetBuildOutput(raw, filePath, cwd) {
|
|
87
89
|
const diagnostics = [];
|
|
90
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
88
91
|
for (const line of raw.split(/\r?\n/)) {
|
|
89
92
|
const parsed = parseDotnetDiagnosticLine(line);
|
|
90
93
|
if (!parsed)
|
|
91
94
|
continue;
|
|
92
95
|
const { reportedFile, lineStr, colStr, severityLabel, rule, message } = parsed;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (resolvedReported !== resolvedTarget)
|
|
96
|
+
// #3278: one seam for reported-path attribution — see javac.ts.
|
|
97
|
+
if (!pathsEqual(path.resolve(cwd, reportedFile), absTarget))
|
|
96
98
|
continue;
|
|
97
99
|
const severity = severityLabel.toLowerCase() === "error" ? "error" : "warning";
|
|
98
100
|
const lineNum = Number.parseInt(lineStr, 10) || 1;
|
|
@@ -136,24 +138,27 @@ const dotnetBuildRunner = {
|
|
|
136
138
|
timeout: 60000,
|
|
137
139
|
});
|
|
138
140
|
const raw = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
141
|
+
const parsed = parseToolRun("dotnet-build", {
|
|
142
|
+
result,
|
|
143
|
+
output: raw,
|
|
144
|
+
// EXIT TABLE (.NET SDK 8 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
145
|
+
exitCodes: { ran: [1, 2] },
|
|
146
|
+
}, (output) => parseDotnetBuildOutput(output, ctx.filePath, cwd));
|
|
147
|
+
if (parsed.skipped)
|
|
148
|
+
return parsed.skipped;
|
|
149
|
+
return finishParsedRun({
|
|
150
|
+
tool: "dotnet-build",
|
|
151
|
+
ctx,
|
|
152
|
+
result,
|
|
153
|
+
diagnostics: parsed.diagnostics,
|
|
154
|
+
classify: (diagnostics) => {
|
|
155
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
156
|
+
return {
|
|
157
|
+
status: hasErrors ? "failed" : "succeeded",
|
|
158
|
+
semantic: hasErrors ? "blocking" : "warning",
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
});
|
|
157
162
|
},
|
|
158
163
|
};
|
|
159
164
|
export default dotnetBuildRunner;
|