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
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
2
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
4
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
4
5
|
import { hasJavaBuildDescriptor } from "../../tool-policy.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 javac = createAvailabilityChecker("javac", ".exe");
|
|
8
|
-
function parseJavacOutput(raw, filePath) {
|
|
10
|
+
function parseJavacOutput(raw, filePath, cwd) {
|
|
9
11
|
const diagnostics = [];
|
|
10
12
|
const lines = raw.split(/\r?\n/);
|
|
13
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
11
14
|
for (const line of lines) {
|
|
12
15
|
const match = line.match(/^(.*?\.java):(\d+):\s+(error|warning):\s+(.+)$/i);
|
|
13
16
|
if (!match)
|
|
14
17
|
continue;
|
|
15
18
|
const [, reportedFile, lineStr, severityLabel, message] = match;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
// Is this reported diagnostic about the file we dispatched for? ONE seam
|
|
20
|
+
// answers that for every runner (#3278): resolve the tool's spelling against
|
|
21
|
+
// the cwd the tool RAN in — never `process.cwd()`, which is the extension's,
|
|
22
|
+
// not the runner's — and compare through `pathsEqual`, the repo's on-disk
|
|
23
|
+
// identity predicate. A bare `===` treats a spelling that differs only in
|
|
24
|
+
// case (the same file on Windows and on a case-folding POSIX mount) as a
|
|
25
|
+
// different file and drops every finding for the edited file (#209, #3277).
|
|
26
|
+
if (!pathsEqual(path.resolve(cwd, reportedFile.trim()), absTarget))
|
|
19
27
|
continue;
|
|
20
28
|
const severity = severityLabel.toLowerCase() === "error" ? "error" : "warning";
|
|
21
29
|
const lineNum = Number.parseInt(lineStr, 10) || 1;
|
|
@@ -66,24 +74,26 @@ const javacRunner = {
|
|
|
66
74
|
timeout: 30000,
|
|
67
75
|
});
|
|
68
76
|
const raw = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
const parsed = parseToolRun("javac", {
|
|
78
|
+
result,
|
|
79
|
+
output: raw,
|
|
80
|
+
// EXIT TABLE (javac 21 docs https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
81
|
+
exitCodes: { ran: [1, 2] },
|
|
82
|
+
}, (output) => parseJavacOutput(output, ctx.filePath, cwd));
|
|
83
|
+
if (parsed.skipped)
|
|
84
|
+
return parsed.skipped;
|
|
85
|
+
return finishParsedRun({
|
|
86
|
+
tool: "javac",
|
|
87
|
+
ctx,
|
|
88
|
+
result,
|
|
89
|
+
diagnostics: parsed.diagnostics,
|
|
90
|
+
classify: (diagnostics) => ({
|
|
91
|
+
status: diagnostics.some((d) => d.severity === "error")
|
|
92
|
+
? "failed"
|
|
93
|
+
: "succeeded",
|
|
77
94
|
semantic: "warning",
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
82
|
-
return {
|
|
83
|
-
status: hasErrors ? "failed" : "succeeded",
|
|
84
|
-
diagnostics,
|
|
85
|
-
semantic: "warning",
|
|
86
|
-
};
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
87
97
|
},
|
|
88
98
|
};
|
|
89
99
|
export default javacRunner;
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
2
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
4
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
4
5
|
import { getAutofixCapability, getLinterPolicyForCwd, } from "../../tool-policy.js";
|
|
5
6
|
import { PRIORITY } from "../priorities.js";
|
|
6
7
|
import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
|
|
8
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
7
9
|
const ktlint = createAvailabilityChecker("ktlint", ".exe");
|
|
10
|
+
// KtLint's exit contract, declared HERE rather than in the shared classifier:
|
|
11
|
+
// an admission is a property of this tool, and #3291 round 2 shipped it through
|
|
12
|
+
// a shared `parseToolRun` option that silently erased every OTHER runner's
|
|
13
|
+
// table. KtLint 1.8.0's CLI page documents the JSON reporter and a nonzero exit
|
|
14
|
+
// on violations, but does not enumerate numeric classes; 1, 2 and 3 are the
|
|
15
|
+
// statuses this runner observes carrying a valid reporter payload, and a valid
|
|
16
|
+
// payload is the evidence that analysis was reached. Any other nonzero status
|
|
17
|
+
// stays a rejected invocation.
|
|
18
|
+
const KTLINT_EXIT_CODES = { ran: [1, 2, 3] };
|
|
8
19
|
function normalizeKtlintResults(parsed) {
|
|
9
20
|
if (Array.isArray(parsed)) {
|
|
10
21
|
return parsed;
|
|
@@ -16,14 +27,19 @@ function normalizeKtlintResults(parsed) {
|
|
|
16
27
|
}
|
|
17
28
|
return null;
|
|
18
29
|
}
|
|
19
|
-
function parseKtlintOutput(raw, filePath) {
|
|
30
|
+
function parseKtlintOutput(raw, filePath, cwd) {
|
|
20
31
|
try {
|
|
21
32
|
const parsed = normalizeKtlintResults(JSON.parse(raw));
|
|
22
33
|
if (!parsed)
|
|
23
34
|
return null;
|
|
24
35
|
const autofix = getAutofixCapability("ktlint");
|
|
25
36
|
const diagnostics = [];
|
|
37
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
26
38
|
for (const result of parsed) {
|
|
39
|
+
// #3295: ktlint's JSON reporter is an array of FILES; `.editorconfig`
|
|
40
|
+
// globs put more than the argv in it.
|
|
41
|
+
if (result.file && !pathsEqual(path.resolve(cwd, result.file), absTarget))
|
|
42
|
+
continue;
|
|
27
43
|
for (const err of result.errors ?? []) {
|
|
28
44
|
diagnostics.push({
|
|
29
45
|
id: `ktlint-${err.ruleId}-${err.line}-${err.col}`,
|
|
@@ -47,12 +63,6 @@ function parseKtlintOutput(raw, filePath) {
|
|
|
47
63
|
return null;
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
|
-
function firstOutputLine(result) {
|
|
51
|
-
return (result.stderr || result.stdout || "")
|
|
52
|
-
.trim()
|
|
53
|
-
.split(/\r?\n/, 1)[0]
|
|
54
|
-
.slice(0, 200);
|
|
55
|
-
}
|
|
56
66
|
const ktlintRunner = {
|
|
57
67
|
id: "ktlint",
|
|
58
68
|
appliesTo: ["kotlin"],
|
|
@@ -78,59 +88,23 @@ const ktlintRunner = {
|
|
|
78
88
|
cwd,
|
|
79
89
|
timeout: 30000,
|
|
80
90
|
});
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
semantic: "warning",
|
|
99
|
-
tool: "ktlint",
|
|
100
|
-
fixable: false,
|
|
101
|
-
autoFixAvailable: false,
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
semantic: "warning",
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
if (diagnostics.length === 0) {
|
|
108
|
-
if (result.status && result.status !== 0) {
|
|
109
|
-
return {
|
|
110
|
-
status: "failed",
|
|
111
|
-
diagnostics: [
|
|
112
|
-
{
|
|
113
|
-
id: "ktlint-nonzero-no-diagnostics",
|
|
114
|
-
message: firstOutputLine(result) ||
|
|
115
|
-
"ktlint exited non-zero without JSON diagnostics",
|
|
116
|
-
filePath: ctx.filePath,
|
|
117
|
-
severity: "warning",
|
|
118
|
-
semantic: "warning",
|
|
119
|
-
tool: "ktlint",
|
|
120
|
-
fixable: false,
|
|
121
|
-
autoFixAvailable: false,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
semantic: "warning",
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
status: result.status && result.status !== 0 ? "failed" : "succeeded",
|
|
131
|
-
diagnostics,
|
|
132
|
-
semantic: "warning",
|
|
133
|
-
};
|
|
91
|
+
// `output` is what the classifier judges AND what the parser reads: ktlint
|
|
92
|
+
// writes its reporter payload to stdout and its errors to stderr, so both
|
|
93
|
+
// are one wire here. The exit table rides in this same per-runner input,
|
|
94
|
+
// never in a shared option (#3291 r3).
|
|
95
|
+
const run = parseToolRun("ktlint", {
|
|
96
|
+
result,
|
|
97
|
+
output: `${result.stdout ?? ""}\n${result.stderr ?? ""}`,
|
|
98
|
+
exitCodes: KTLINT_EXIT_CODES,
|
|
99
|
+
}, (output) => parseKtlintOutput(output, ctx.filePath, cwd) ?? []);
|
|
100
|
+
if (run.skipped)
|
|
101
|
+
return run.skipped;
|
|
102
|
+
return finishParsedRun({
|
|
103
|
+
tool: "ktlint",
|
|
104
|
+
ctx,
|
|
105
|
+
result,
|
|
106
|
+
diagnostics: run.diagnostics,
|
|
107
|
+
});
|
|
134
108
|
},
|
|
135
109
|
};
|
|
136
110
|
export default ktlintRunner;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { logExtension } from "../../extension-log.js";
|
|
15
15
|
import { getLspCapableKinds } from "../../language-policy.js";
|
|
16
|
+
import { exceedsLspSyncLimits } from "../../lsp/content-limits.js";
|
|
16
17
|
import { touchCoverageGap } from "../../lsp/diagnostic-binding.js";
|
|
17
18
|
import { getLSPService } from "../../lsp/index.js";
|
|
18
19
|
import { LSP_SERVERS } from "../../lsp/server.js";
|
|
@@ -25,8 +26,6 @@ import { enabledAuxiliaryLspServerIds, retagAuxiliaryDiagnostics, } from "../aux
|
|
|
25
26
|
import { readFileContent } from "./utils.js";
|
|
26
27
|
import { tryWarmAttachedCodeActions, tryWarmAttachedDiagnostics, } from "../../warm-attach.js";
|
|
27
28
|
import { contentHash, WARM_CODE_ACTION_LOOKUP_LIMIT } from "../../mcp/ipc.js";
|
|
28
|
-
const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
|
|
29
|
-
const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
|
|
30
29
|
const LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
|
|
31
30
|
// Diagnostics-wait cap for the dispatch lsp-runner. Bounded so a slow LSP
|
|
32
31
|
// (typescript-language-server on large monorepos has been observed >7 s)
|
|
@@ -133,9 +132,11 @@ const lspRunner = {
|
|
|
133
132
|
if (!content) {
|
|
134
133
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
135
134
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
// #3405 r2: was an inline second copy of the pipeline's byte/line
|
|
136
|
+
// predicate. One module owns the bound now (M3406-1's root cause was that
|
|
137
|
+
// a third writer could be added with neither copy); the SKIP is still this
|
|
138
|
+
// runner's own policy.
|
|
139
|
+
if (exceedsLspSyncLimits(content).tooLarge) {
|
|
139
140
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
140
141
|
}
|
|
141
142
|
// Cross-cutting auxiliary scanners (opengrep, …) attach alongside the
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
1
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
2
4
|
import { logRunnerAdvisoryOnce, resolveRunnerCwd } from "../../tool-cwd.js";
|
|
3
5
|
import { getLinterPolicyForCwd, markdownlintConfigArgs, } from "../../tool-policy.js";
|
|
@@ -10,7 +12,7 @@ const markdownlint = createAvailabilityChecker("markdownlint-cli2", ".cmd", [
|
|
|
10
12
|
"--no-globs",
|
|
11
13
|
"-",
|
|
12
14
|
]);
|
|
13
|
-
// markdownlint-cli2
|
|
15
|
+
// EXIT TABLE (markdownlint-cli2 0.17 README https://github.com/DavidAnson/markdownlint-cli2#exit-codes): 0 = no
|
|
14
16
|
// findings, 1 = lint findings, 2 = unexpected error (unreadable config, bad
|
|
15
17
|
// glob, crash). Only 2 is a rejected invocation.
|
|
16
18
|
const MARKDOWNLINT_EXIT_CODES = { ran: [1] };
|
|
@@ -59,18 +61,23 @@ const MARKDOWNLINT_FIXABLE_RULES = new Set([
|
|
|
59
61
|
// 2. some rules carry MULTIPLE slash-separated names (e.g.
|
|
60
62
|
// `MD041/first-line-heading/first-line-h1`).
|
|
61
63
|
// The severity token is optional so older/relative-path output still parses.
|
|
62
|
-
function parseMarkdownlintOutput(raw, filePath) {
|
|
64
|
+
function parseMarkdownlintOutput(raw, filePath, cwd) {
|
|
63
65
|
const diagnostics = [];
|
|
66
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
64
67
|
for (const line of raw.split(/\r?\n/)) {
|
|
65
68
|
if (!line.trim())
|
|
66
69
|
continue;
|
|
67
70
|
// Rule code is MD### followed by one or more slash-joined names. Use a
|
|
68
71
|
// single char class (`[\w/-]+`) rather than a nested quantifier
|
|
69
72
|
// (`(?:/[\w-]+)+`) so there's no super-linear backtracking (S5852).
|
|
70
|
-
const match = line.match(
|
|
73
|
+
const match = line.match(/^(.*?):(\d+)(?::(\d+))?\s+(?:error|warning)?\s*(MD\d+\/[\w/-]+)\s+(.+)$/);
|
|
71
74
|
if (!match)
|
|
72
75
|
continue;
|
|
73
|
-
const [, lineNum, col, ruleCode, message] = match;
|
|
76
|
+
const [, reported, lineNum, col, ruleCode, message] = match;
|
|
77
|
+
// #3295: the leading `.*?` this parser discarded IS markdownlint-cli2's
|
|
78
|
+
// reported path, and its config globs can widen the run past the argv.
|
|
79
|
+
if (reported && !pathsEqual(path.resolve(cwd, reported), absTarget))
|
|
80
|
+
continue;
|
|
74
81
|
const ruleName = ruleCode.split("/")[0];
|
|
75
82
|
const fixable = MARKDOWNLINT_FIXABLE_RULES.has(ruleName);
|
|
76
83
|
diagnostics.push({
|
|
@@ -144,7 +151,7 @@ const markdownlintRunner = {
|
|
|
144
151
|
const raw = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
145
152
|
// #1948: one seam for both gates — the tool produced nothing, and the
|
|
146
153
|
// tool produced something the parser could not read.
|
|
147
|
-
const run = parseToolRun("markdownlint", { result, output: raw, exitCodes: MARKDOWNLINT_EXIT_CODES }, (out) => parseMarkdownlintOutput(out, ctx.filePath));
|
|
154
|
+
const run = parseToolRun("markdownlint", { result, output: raw, exitCodes: MARKDOWNLINT_EXIT_CODES }, (out) => parseMarkdownlintOutput(out, ctx.filePath, cwd));
|
|
148
155
|
if (run.skipped)
|
|
149
156
|
return run.skipped;
|
|
150
157
|
const diagnostics = run.diagnostics;
|
|
@@ -14,7 +14,8 @@ const mypy = createAvailabilityChecker("mypy", "");
|
|
|
14
14
|
// [syntax]`). A bad flag or unreadable config also exits 2, but writes its
|
|
15
15
|
// message to STDERR and leaves stdout empty.
|
|
16
16
|
//
|
|
17
|
-
//
|
|
17
|
+
// EXIT TABLE (mypy 1.11 docs https://mypy.readthedocs.io/en/stable/command_line.html): 0 clean; 1 findings; 2 findings/error; other
|
|
18
|
+
// nonzero rejected. The exit code cannot separate the two, and the STREAM does:
|
|
18
19
|
// judges "nothing to parse" on stdout alone, while the parser still reads both.
|
|
19
20
|
// The table's remaining job is to reject any OTHER nonzero status.
|
|
20
21
|
const MYPY_EXIT_CODES = { ran: [1, 2] };
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Requires: oxlint (npm install -g oxlint)
|
|
8
8
|
*/
|
|
9
|
+
import * as path from "node:path";
|
|
9
10
|
import { findLocalBinUpwards } from "../../package-manager.js";
|
|
11
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
10
12
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
11
13
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
12
14
|
import { truncatedByOutputCap } from "../../spawn-output-cap.js";
|
|
@@ -111,7 +113,12 @@ const oxlintRunner = {
|
|
|
111
113
|
// force a status-0 lookalike away from clean. Neither escapes this runner.
|
|
112
114
|
// parseToolRun checks process failures before invoking this parser, so
|
|
113
115
|
// spawn/timeout/killed/signal precedence remains owned by #1994.
|
|
114
|
-
const parsedRun = parseToolRun("oxlint", {
|
|
116
|
+
const parsedRun = parseToolRun("oxlint", {
|
|
117
|
+
result,
|
|
118
|
+
output: parsedOutput,
|
|
119
|
+
// EXIT TABLE (oxlint 0.8 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
120
|
+
exitCodes: { ran: [1, 2] },
|
|
121
|
+
}, () => {
|
|
115
122
|
if (noFilesDecision.kind === "expected-no-files") {
|
|
116
123
|
return [OXLINT_NO_FILES];
|
|
117
124
|
}
|
|
@@ -119,9 +126,9 @@ const oxlintRunner = {
|
|
|
119
126
|
result.status === 0) {
|
|
120
127
|
return [OXLINT_NO_FILES_UNCONFIRMED];
|
|
121
128
|
}
|
|
122
|
-
let parsed = parseOxlintJson(stdout, ctx.filePath);
|
|
129
|
+
let parsed = parseOxlintJson(stdout, ctx.filePath, cwd);
|
|
123
130
|
if (parsed.length === 0 && stdout.length > 0) {
|
|
124
|
-
parsed = parseOxlintUnix(parsedOutput, ctx.filePath);
|
|
131
|
+
parsed = parseOxlintUnix(parsedOutput, ctx.filePath, cwd);
|
|
125
132
|
}
|
|
126
133
|
return parsed;
|
|
127
134
|
}, { parseOutput: parsedOutput });
|
|
@@ -334,7 +341,7 @@ function extractOxlintRule(code) {
|
|
|
334
341
|
return code;
|
|
335
342
|
return code.slice(open + 1, close);
|
|
336
343
|
}
|
|
337
|
-
function parseOxlintJson(raw, filePath) {
|
|
344
|
+
function parseOxlintJson(raw, filePath, cwd) {
|
|
338
345
|
const trimmed = raw.trim();
|
|
339
346
|
if (!trimmed.startsWith("{"))
|
|
340
347
|
return [];
|
|
@@ -348,7 +355,11 @@ function parseOxlintJson(raw, filePath) {
|
|
|
348
355
|
if (!Array.isArray(parsed.diagnostics))
|
|
349
356
|
return [];
|
|
350
357
|
const diagnostics = [];
|
|
358
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
351
359
|
for (const d of parsed.diagnostics) {
|
|
360
|
+
if (!d.filename || !pathsEqual(path.resolve(cwd, d.filename), absTarget)) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
352
363
|
const rule = extractOxlintRule(d.code);
|
|
353
364
|
const label = d.labels?.[0]?.span;
|
|
354
365
|
const lineNum = label?.line ?? 1;
|
|
@@ -374,13 +385,18 @@ function parseOxlintJson(raw, filePath) {
|
|
|
374
385
|
}
|
|
375
386
|
return diagnostics;
|
|
376
387
|
}
|
|
377
|
-
function parseOxlintUnix(raw, filePath) {
|
|
388
|
+
function parseOxlintUnix(raw, filePath, cwd) {
|
|
378
389
|
const diagnostics = [];
|
|
390
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
379
391
|
for (const line of raw.split("\n")) {
|
|
380
392
|
// Parse: file:line:column: message (rule)
|
|
381
393
|
const match = line.match(/^(.+):(\d+):(\d+):\s*(.+?)\s*\(([^)]+)\)$/);
|
|
382
394
|
if (match) {
|
|
383
|
-
const [,
|
|
395
|
+
const [, reportedPath, lineStr, _col, message, rule] = match;
|
|
396
|
+
if (!reportedPath || !lineStr || !message || !rule)
|
|
397
|
+
continue;
|
|
398
|
+
if (!pathsEqual(path.resolve(cwd, reportedPath), absTarget))
|
|
399
|
+
continue;
|
|
384
400
|
diagnostics.push({
|
|
385
401
|
id: `oxlint-${rule}-${lineStr}`,
|
|
386
402
|
message: `${message} (${rule})`,
|
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
2
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
4
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
4
5
|
import { PRIORITY } from "../priorities.js";
|
|
5
6
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
7
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
8
|
+
// PHP's `-l` exit contract, declared HERE rather than in the shared classifier
|
|
9
|
+
// (#3291 r3): an admission is a property of this tool. The manual documents
|
|
10
|
+
// `-l` as syntax-check-only with a nonzero return on failure; the captured
|
|
11
|
+
// PHP 8.3.32 wire exits 1 in the usual CLI path and 255 (the shell reading of
|
|
12
|
+
// PHP's documented -1) for the parse-error wire. Both are completed analyses
|
|
13
|
+
// whose output must reach the parser; any other nonzero status stays a
|
|
14
|
+
// rejected invocation.
|
|
15
|
+
const PHP_LINT_EXIT_CODES = { ran: [1, 255] };
|
|
6
16
|
const php = createAvailabilityChecker("php", ".exe");
|
|
7
|
-
function parsePhpLintOutput(raw, filePath) {
|
|
17
|
+
function parsePhpLintOutput(raw, filePath, cwd) {
|
|
8
18
|
const output = raw.trim();
|
|
9
|
-
if (!output)
|
|
19
|
+
if (!output || !/(?:PHP )?Parse error:/i.test(output))
|
|
10
20
|
return [];
|
|
11
21
|
const lineMatch = output.match(/on line (\d+)/i);
|
|
12
|
-
const messageMatch = output.match(/PHP Parse error:\s*(.+?)(?:\s+in\s
|
|
13
|
-
output.match(/Parse error:\s*(.+?)(?:\s+in\s
|
|
22
|
+
const messageMatch = output.match(/PHP Parse error:\s*(.+?)(?:\s+in\s+(.+?)\s+on line \d+)?$/im) ??
|
|
23
|
+
output.match(/Parse error:\s*(.+?)(?:\s+in\s+(.+?)\s+on line \d+)?$/im);
|
|
24
|
+
// #3295: PHP names the file it could not parse in the same sentence. `php -l`
|
|
25
|
+
// follows no includes today, so this drops nothing under our argv — it pins
|
|
26
|
+
// the attribution the parser was asserting without asking.
|
|
27
|
+
const reported = messageMatch?.[2]?.trim();
|
|
28
|
+
if (reported &&
|
|
29
|
+
!pathsEqual(path.resolve(cwd, reported), path.resolve(cwd, filePath)))
|
|
30
|
+
return [];
|
|
14
31
|
return [
|
|
15
32
|
{
|
|
16
33
|
id: `php-lint:${lineMatch?.[1] ?? "1"}`,
|
|
@@ -45,18 +62,24 @@ const phpLintRunner = {
|
|
|
45
62
|
timeout: 15000,
|
|
46
63
|
cwd,
|
|
47
64
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
// `output` is what the classifier judges AND what the parser reads: the
|
|
66
|
+
// parse error arrives on stderr, so both streams are one wire here. The
|
|
67
|
+
// exit table rides in this same per-runner input, never in a shared
|
|
68
|
+
// option (#3291 r3).
|
|
69
|
+
const run = parseToolRun("php-lint", {
|
|
70
|
+
result,
|
|
71
|
+
output: `${result.stdout ?? ""}\n${result.stderr ?? ""}`,
|
|
72
|
+
exitCodes: PHP_LINT_EXIT_CODES,
|
|
73
|
+
}, (output) => parsePhpLintOutput(output, ctx.filePath, cwd));
|
|
74
|
+
if (run.skipped)
|
|
75
|
+
return run.skipped;
|
|
76
|
+
return finishParsedRun({
|
|
77
|
+
tool: "php-lint",
|
|
78
|
+
ctx,
|
|
79
|
+
result,
|
|
80
|
+
diagnostics: run.diagnostics,
|
|
81
|
+
classify: () => ({ status: "failed", semantic: "blocking" }),
|
|
82
|
+
});
|
|
60
83
|
},
|
|
61
84
|
};
|
|
62
85
|
export default phpLintRunner;
|
|
@@ -6,7 +6,7 @@ import { PRIORITY } from "../priorities.js";
|
|
|
6
6
|
import { createAvailabilityChecker, resolveVendorToolCommand, } from "./utils/runner-helpers.js";
|
|
7
7
|
import { parseToolRun } from "./utils/tool-failure.js";
|
|
8
8
|
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
9
|
-
//
|
|
9
|
+
// EXIT TABLE (PHPStan 1.12 docs https://phpstan.org/user-guide/command-line-usage): 0 = no errors, 1 = errors found, 2 = a
|
|
10
10
|
// fatal/internal error that stopped the analysis. Only 1 is a run that carries
|
|
11
11
|
// findings.
|
|
12
12
|
const PHPSTAN_EXIT_CODES = { ran: [1] };
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Supports bundle exec (preferred in Bundler projects).
|
|
9
9
|
*/
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
10
12
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
11
13
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
12
14
|
import { getAutofixCapability, getLinterPolicyForCwd, getRubocopCommand, } from "../../tool-policy.js";
|
|
13
15
|
import { PRIORITY } from "../priorities.js";
|
|
14
16
|
import { resolveCommandArgsWithInstallFallback } from "./utils/runner-helpers.js";
|
|
17
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
15
18
|
const SEVERITY_MAP = {
|
|
16
19
|
fatal: "error",
|
|
17
20
|
error: "error",
|
|
@@ -19,12 +22,17 @@ const SEVERITY_MAP = {
|
|
|
19
22
|
convention: "warning",
|
|
20
23
|
refactor: "info",
|
|
21
24
|
};
|
|
22
|
-
function parseRubocopJson(raw, filePath) {
|
|
25
|
+
function parseRubocopJson(raw, filePath, cwd) {
|
|
23
26
|
try {
|
|
24
27
|
const output = JSON.parse(raw);
|
|
25
28
|
const autofix = getAutofixCapability("rubocop");
|
|
26
29
|
const diagnostics = [];
|
|
30
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
27
31
|
for (const file of output.files) {
|
|
32
|
+
// #3295: `.rubocop.yml` `Include:`/`inherit_from` can widen the run past
|
|
33
|
+
// the argv, and each result carries its own `path`.
|
|
34
|
+
if (file.path && !pathsEqual(path.resolve(cwd, file.path), absTarget))
|
|
35
|
+
continue;
|
|
28
36
|
for (const offense of file.offenses) {
|
|
29
37
|
const severity = SEVERITY_MAP[offense.severity] ?? "warning";
|
|
30
38
|
diagnostics.push({
|
|
@@ -68,23 +76,24 @@ const rubocopRunner = {
|
|
|
68
76
|
const { cmd, args } = resolved;
|
|
69
77
|
// Lint only — no auto-correct (formatter handles that)
|
|
70
78
|
const result = await safeSpawnAsync(cmd, [...args, "--format", "json", "--no-color", ctx.filePath], { timeout: 30000, cwd });
|
|
71
|
-
// rubocop
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
// EXIT TABLE (RuboCop 1.66 docs https://docs.rubocop.org/rubocop/usage/basic_usage.html): 0 = clean, 1 = offenses/findings, 2 = fatal/error or rejected
|
|
80
|
+
// invocation. A nonzero exit with valid JSON remains findings; a nonzero
|
|
81
|
+
// exit with empty or unparsable JSON is never clean (#1816).
|
|
82
|
+
const run = parseToolRun("rubocop", {
|
|
83
|
+
result,
|
|
84
|
+
// Classify both streams so stderr-only failures reach the parse-error
|
|
85
|
+
// path, while parseOutput keeps RuboCop's native JSON parser on stdout.
|
|
86
|
+
output: `${result.stdout}${result.stderr}`,
|
|
87
|
+
exitCodes: { ran: [1, 2] },
|
|
88
|
+
}, (output) => parseRubocopJson(output, ctx.filePath, cwd), { parseOutput: result.stdout });
|
|
89
|
+
if (run.skipped)
|
|
90
|
+
return run.skipped;
|
|
91
|
+
return finishParsedRun({
|
|
92
|
+
tool: "rubocop",
|
|
93
|
+
ctx,
|
|
94
|
+
result,
|
|
95
|
+
diagnostics: run.diagnostics,
|
|
96
|
+
});
|
|
88
97
|
},
|
|
89
98
|
};
|
|
90
99
|
export default rubocopRunner;
|
|
@@ -10,6 +10,7 @@ import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
|
10
10
|
import { stripAnsi } from "../../sanitize.js";
|
|
11
11
|
import { getAutofixCapability, getLinterPolicyForCwd, ruffConfigArgs, } from "../../tool-policy.js";
|
|
12
12
|
import { PRIORITY } from "../priorities.js";
|
|
13
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
13
14
|
import { parseRuffOutput } from "./utils/diagnostic-parsers.js";
|
|
14
15
|
import { createAvailabilityChecker, resolveAvailableOrInstall, resolveRunnerCwd, } from "./utils/runner-helpers.js";
|
|
15
16
|
const ruff = createAvailabilityChecker("ruff", ".exe");
|
|
@@ -65,25 +66,23 @@ const ruffRunner = {
|
|
|
65
66
|
// Step 1: Capture diagnostics (before fixing) — teaching signal for the agent
|
|
66
67
|
const checkResult = await safeSpawnAsync(cmd, ["check", "--output-format", "json", ...configArgs, ctx.filePath], { cwd, timeout: 30000 });
|
|
67
68
|
const raw = stripAnsi(checkResult.stdout + checkResult.stderr);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
semantic: hasErrors ? "blocking" : "warning",
|
|
86
|
-
};
|
|
69
|
+
// EXIT TABLE (Ruff 0.6 docs https://docs.astral.sh/ruff/linter/): 0 = clean, 1 = findings, 2 = findings/error or
|
|
70
|
+
// tool error. A nonzero exit with valid findings stays findings; empty or
|
|
71
|
+
// unparsable output must never become clean (#1816).
|
|
72
|
+
const run = parseToolRun("ruff", { result: checkResult, output: raw, exitCodes: { ran: [1, 2] } }, (output) => {
|
|
73
|
+
const diagnostics = parseRuffJson(output, ctx.filePath);
|
|
74
|
+
return diagnostics.length > 0
|
|
75
|
+
? diagnostics
|
|
76
|
+
: parseRuffOutput(output, ctx.filePath, cwd);
|
|
77
|
+
});
|
|
78
|
+
if (run.skipped)
|
|
79
|
+
return run.skipped;
|
|
80
|
+
return finishParsedRun({
|
|
81
|
+
tool: "ruff",
|
|
82
|
+
ctx,
|
|
83
|
+
result: checkResult,
|
|
84
|
+
diagnostics: run.diagnostics,
|
|
85
|
+
});
|
|
87
86
|
},
|
|
88
87
|
};
|
|
89
88
|
export default ruffRunner;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Runs `cargo clippy` for Rust files to catch common mistakes.
|
|
5
5
|
*/
|
|
6
6
|
import { isAbsolute, resolve } from "node:path";
|
|
7
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
7
8
|
import { rustClient } from "../../rust-client.js";
|
|
8
9
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
9
10
|
import { stripAnsi } from "../../sanitize.js";
|
|
@@ -125,8 +126,12 @@ const rustClippyRunner = {
|
|
|
125
126
|
// a crate-mate's pre-existing diagnostic must NOT fail the edited file's
|
|
126
127
|
// turn. Filter to the edited file like golangci-lint — if the edited file
|
|
127
128
|
// is clean, this turn succeeds even when siblings carry warnings/errors.
|
|
129
|
+
// #3278: one seam for reported-path attribution — see javac.ts. This
|
|
130
|
+
// member already had the right BASE (`parseClippyOutput` resolves
|
|
131
|
+
// `span.file` against `cargoDir`); what it lacked was the on-disk
|
|
132
|
+
// identity predicate.
|
|
128
133
|
const absEdited = resolve(ctx.filePath);
|
|
129
|
-
const diagnostics = allDiagnostics.filter((d) => resolve(d.filePath)
|
|
134
|
+
const diagnostics = allDiagnostics.filter((d) => pathsEqual(resolve(cargoDir, d.filePath), absEdited));
|
|
130
135
|
const hasErrors = diagnostics.some((d) => d.semantic === "blocking");
|
|
131
136
|
return {
|
|
132
137
|
status: hasErrors ? "failed" : "succeeded",
|