@herbertgao/pi-extensions 2026.9.14 → 2026.9.16
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/README.md +8 -3
- package/THIRD_PARTY_NOTICES.md +28 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -6
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +2 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +16 -4
- package/node_modules/pi-jev-auto-mode/CHANGELOG.md +8 -0
- package/node_modules/pi-jev-auto-mode/README.md +7 -0
- package/node_modules/pi-jev-auto-mode/package.json +1 -1
- package/node_modules/pi-jev-auto-mode/src/extension.ts +21 -2
- package/node_modules/pi-jev-auto-mode/src/records.ts +68 -2
- package/node_modules/pi-jev-auto-mode/src/settings.ts +22 -0
- package/node_modules/pi-jev-auto-mode/src/ui.ts +10 -0
- package/node_modules/pi-lens/CHANGELOG.md +498 -0
- package/node_modules/pi-lens/README.md +45 -2
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +23 -7
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +187 -51
- package/node_modules/pi-lens/dist/clients/cache-manager.js +64 -5
- package/node_modules/pi-lens/dist/clients/child-unref.js +17 -8
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +237 -9
- package/node_modules/pi-lens/dist/clients/config-resolve.js +7 -1
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +33 -3
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +87 -64
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -3
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +118 -1
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +23 -4
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +14 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +26 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +38 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +32 -49
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +16 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +25 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +28 -47
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +27 -22
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +31 -46
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +29 -46
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +91 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +16 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +38 -23
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +27 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +31 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +34 -60
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +6 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +12 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +22 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +39 -16
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +27 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +18 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +6 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +11 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +10 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +11 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +12 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +30 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +15 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +12 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +27 -39
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +13 -3
- package/node_modules/pi-lens/dist/clients/effective-config.js +3 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +42 -11
- package/node_modules/pi-lens/dist/clients/formatters.js +49 -11
- package/node_modules/pi-lens/dist/clients/git-guard.js +247 -34
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +121 -25
- package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +186 -0
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +19 -6
- package/node_modules/pi-lens/dist/clients/installer/index.js +281 -32
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +66 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +139 -11
- package/node_modules/pi-lens/dist/clients/lsp/config.js +9 -7
- package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +41 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +23 -2
- package/node_modules/pi-lens/dist/clients/lsp/server.js +106 -25
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +14 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +105 -16
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +232 -44
- package/node_modules/pi-lens/dist/clients/mcp/review.js +53 -7
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +11 -6
- package/node_modules/pi-lens/dist/clients/pipeline.js +121 -66
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +11 -3
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +34 -26
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +16 -10
- package/node_modules/pi-lens/dist/clients/ruff-client.js +31 -4
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +38 -4
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +16 -5
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +27 -12
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +447 -280
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +256 -65
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +84 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +119 -58
- package/node_modules/pi-lens/dist/clients/turn-end/lane.js +51 -0
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +112 -0
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +204 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +59 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +145 -14
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +57 -3
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -13
- package/node_modules/pi-lens/dist/index.js +5695 -3733
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +4 -0
- package/node_modules/pi-lens/dist/mcp/server.js +33 -21
- package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +43 -16
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +118 -7
- package/node_modules/pi-lens/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/node_modules/pi-lens/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/node_modules/pi-lens/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/node_modules/pi-lens/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/node_modules/pi-lens/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/node_modules/pi-lens/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/node_modules/pi-lens/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/node_modules/pi-lens/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/node_modules/pi-lens/docs/adr/0009-reported-path-attribution.md +114 -0
- package/node_modules/pi-lens/docs/adr/README.md +26 -0
- package/node_modules/pi-lens/docs/agent-tools.md +18 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +4 -3
- package/node_modules/pi-lens/docs/configuration.md +55 -1
- package/node_modules/pi-lens/docs/engineering-principles.md +202 -0
- package/node_modules/pi-lens/docs/environment-variables.md +61 -4
- package/node_modules/pi-lens/docs/features.md +2 -2
- package/node_modules/pi-lens/docs/globalconfig.md +6 -1
- package/node_modules/pi-lens/docs/language-coverage.md +17 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +98 -31
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +57 -3
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +9 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +16 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +9 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +2 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +21 -11
- package/node_modules/pi-lens/docs/settings.md +12 -3
- package/node_modules/pi-lens/docs/subagent-compat.md +1 -1
- package/node_modules/pi-lens/package.json +5 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/node_modules/pi-lens/scripts/install-selftest.mjs +21 -17
- package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +29 -0
- package/node_modules/pi-mcp-adapter/OAUTH.md +369 -0
- package/node_modules/pi-mcp-adapter/README.md +12 -5
- package/node_modules/pi-mcp-adapter/config.ts +31 -8
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +33 -8
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +15 -2
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +27 -2
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +6 -4
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +2 -2
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +10 -3
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +37 -20
- package/node_modules/pi-mcp-adapter/init.ts +14 -15
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +16 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +27 -2
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +3 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -3
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +99 -6
- package/node_modules/pi-mcp-adapter/types.ts +2 -2
- package/node_modules/pi-mcp-adapter/ui-session.ts +60 -27
- package/node_modules/pi-mcp-adapter/utils.ts +11 -3
- package/node_modules/pi-multi-account/CHANGELOG.md +6 -0
- package/node_modules/pi-multi-account/README.md +15 -1
- package/node_modules/pi-multi-account/index.ts +295 -41
- package/node_modules/pi-multi-account/package.json +1 -1
- package/node_modules/pi-next-cue/.github/workflows/publish.yml +20 -0
- package/node_modules/pi-next-cue/LICENSE +21 -0
- package/node_modules/pi-next-cue/README.md +81 -0
- package/node_modules/pi-next-cue/index.ts +358 -0
- package/node_modules/pi-next-cue/package.json +28 -0
- package/package.json +10 -7
|
@@ -1,28 +1,108 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
1
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
4
|
+
import { stripAnsi } from "../../sanitize.js";
|
|
2
5
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
3
6
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
4
7
|
import { PRIORITY } from "../priorities.js";
|
|
5
8
|
const gleam = createAvailabilityChecker("gleam", ".exe");
|
|
6
|
-
|
|
9
|
+
/**
|
|
10
|
+
* gleam renders every located diagnostic through
|
|
11
|
+
* `codespan_reporting::term::emit` (v1.18.1
|
|
12
|
+
* `compiler-core/src/diagnostic.rs:92-133`, unchanged since v1.6.3), so its
|
|
13
|
+
* location line is codespan's LOCUS line, not a bare `path:line:col`:
|
|
14
|
+
*
|
|
15
|
+
* ```text
|
|
16
|
+
* error: Type mismatch
|
|
17
|
+
* ┌─ /abs/proj/src/app.gleam:1:8
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* `<outer gutter spaces> ┌─ <name>:<line>:<column>` —
|
|
21
|
+
* codespan-reporting 0.13.1 `src/term/renderer.rs:379-396` (the gutter, then
|
|
22
|
+
* `chars().snippet_start`, then one space, then the locus) and `:869-878`
|
|
23
|
+
* (`{name}:{line_number}:{column_number}`), with `snippet_start` = `┌─` in the
|
|
24
|
+
* default box-drawing set (`src/term/config.rs:419-429`, reached because gleam
|
|
25
|
+
* passes `Config::default()`, whose display style is Rich).
|
|
26
|
+
*
|
|
27
|
+
* `name` is `location.path` verbatim (`diagnostic.rs:92-94`,
|
|
28
|
+
* `files.add(main_location_path, …)`), and the CLI builds that path by walking
|
|
29
|
+
* UP from the absolute `std::env::current_dir()` to the nearest `gleam.toml`
|
|
30
|
+
* (`compiler-cli/src/fs.rs:32-62`) and joining `src`/`test`
|
|
31
|
+
* (`compiler-core/src/paths.rs:42-48`) before enumerating the directory — so it
|
|
32
|
+
* is ABSOLUTE, with host-native separators. Resolving it against the cwd the
|
|
33
|
+
* tool ran in is therefore a no-op today and the ADR 0009 shape for the
|
|
34
|
+
* hypothetical relative spelling.
|
|
35
|
+
*
|
|
36
|
+
* Capturing the path REQUIRES matching the gutter: the pre-#3285 regex
|
|
37
|
+
* `/^(.+?):(\d+):(\d+)$/` captured ` ┌─ /abs/proj/src/app.gleam`, gutter
|
|
38
|
+
* included, and only an `endsWith` compare tolerated that. An equality
|
|
39
|
+
* predicate (the seam every other runner now uses) drops every gleam diagnostic
|
|
40
|
+
* unless the gutter is out of the capture — which is why PR #3284 reverted its
|
|
41
|
+
* own fold here and filed #3285.
|
|
42
|
+
*/
|
|
43
|
+
const GLEAM_LOCUS = /^\s*┌─\s+(.+?):(\d+):(\d+)$/;
|
|
44
|
+
const GLEAM_HEADER = /^\s*(error|warning):\s*(.+?)\s*$/;
|
|
45
|
+
const GLEAM_LABEL = /^\s*│\s*\^+\s*(.*?)\s*$/;
|
|
46
|
+
/**
|
|
47
|
+
* codespan writes ONE title line per diagnostic and then everything that
|
|
48
|
+
* belongs to it — locus, borders, snippet, labels, notes — before the next
|
|
49
|
+
* title (`codespan-reporting` 0.13.1 `src/term/renderer.rs:141-210`). Splitting
|
|
50
|
+
* on the title is therefore the diagnostic boundary, and it is what keeps an
|
|
51
|
+
* adjacent diagnostic's label out of its predecessor's message. Lines before
|
|
52
|
+
* the first title are gleam's own progress output (`Compiling demo`), which
|
|
53
|
+
* belongs to no diagnostic.
|
|
54
|
+
*/
|
|
55
|
+
function gleamBlocks(lines) {
|
|
56
|
+
const blocks = [];
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const header = line.match(GLEAM_HEADER);
|
|
59
|
+
if (header)
|
|
60
|
+
blocks.push({ header, body: [] });
|
|
61
|
+
else
|
|
62
|
+
blocks.at(-1)?.body.push(line);
|
|
63
|
+
}
|
|
64
|
+
return blocks;
|
|
65
|
+
}
|
|
66
|
+
function gleamMessage(title, body) {
|
|
67
|
+
const labels = body.flatMap((line) => line.match(GLEAM_LABEL)?.[1]?.trim() || []);
|
|
68
|
+
return labels.length > 0 ? `${title} — ${labels.join(" ")}` : title;
|
|
69
|
+
}
|
|
70
|
+
function parseGleamOutput(raw, filePath, cwd) {
|
|
7
71
|
const diagnostics = [];
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
72
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
73
|
+
// `stripAnsi` (the seam `ruff.ts`, `go-vet.ts` and `rust-clippy.ts` already
|
|
74
|
+
// use): gleam colours the gutter whenever `FORCE_COLOR` is set non-empty,
|
|
75
|
+
// whatever stderr is (`compiler-cli/src/cli.rs:194-207` — `ColorChoice::Never`
|
|
76
|
+
// only when it is neither forced nor a terminal), and the SGR bytes sit
|
|
77
|
+
// between the line start and `┌─`. The old suffix compare never saw the
|
|
78
|
+
// prefix; an anchored capture would drop every diagnostic in that
|
|
79
|
+
// environment.
|
|
80
|
+
const lines = stripAnsi(raw).split(/\r?\n/);
|
|
81
|
+
for (const { header, body } of gleamBlocks(lines)) {
|
|
82
|
+
const severity = header[1] === "warning" ? "warning" : "error";
|
|
83
|
+
const title = `${header[1]}: ${header[2]}`;
|
|
84
|
+
// A Gleam diagnostic with NO locus describes the whole project: `check`
|
|
85
|
+
// receives no file argument (`Diagnostic::write_title`, no codespan emit).
|
|
86
|
+
// Leave it for the nonzero-without-diagnostics fallback instead of
|
|
87
|
+
// charging it to whichever file happened to trigger dispatch — ADR 0009:
|
|
88
|
+
// without a reported path there is no evidence it is about this file.
|
|
89
|
+
const location = body
|
|
90
|
+
.find((line) => GLEAM_LOCUS.test(line))
|
|
91
|
+
?.match(GLEAM_LOCUS);
|
|
11
92
|
if (!location)
|
|
12
93
|
continue;
|
|
13
94
|
const [, sourcePath, lineStr, colStr] = location;
|
|
14
|
-
|
|
95
|
+
// #3278: one seam for reported-path attribution — see javac.ts.
|
|
96
|
+
if (!pathsEqual(path.resolve(cwd, sourcePath.trim()), absTarget))
|
|
15
97
|
continue;
|
|
16
|
-
}
|
|
17
|
-
const message = lines.slice(i + 1).find((line) => line.trim().length > 0);
|
|
18
98
|
diagnostics.push({
|
|
19
99
|
id: `gleam-check-${lineStr}-${colStr}`,
|
|
20
|
-
message:
|
|
100
|
+
message: gleamMessage(title, body),
|
|
21
101
|
filePath,
|
|
22
102
|
line: Number.parseInt(lineStr, 10) || 1,
|
|
23
103
|
column: Number.parseInt(colStr, 10) || 1,
|
|
24
|
-
severity
|
|
25
|
-
semantic: "blocking",
|
|
104
|
+
severity,
|
|
105
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
26
106
|
tool: "gleam",
|
|
27
107
|
rule: "gleam-check",
|
|
28
108
|
fixable: false,
|
|
@@ -54,7 +134,7 @@ const gleamCheckRunner = {
|
|
|
54
134
|
if (result.error && !result.stdout && !result.stderr) {
|
|
55
135
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
56
136
|
}
|
|
57
|
-
const diagnostics = parseGleamOutput(`${result.stderr || ""}\n${result.stdout || ""}`, ctx.filePath);
|
|
137
|
+
const diagnostics = parseGleamOutput(`${result.stderr || ""}\n${result.stdout || ""}`, ctx.filePath, cwd);
|
|
58
138
|
if (diagnostics.length === 0) {
|
|
59
139
|
if (result.status && result.status !== 0) {
|
|
60
140
|
return {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { relative, resolve, sep, posix } from "node:path";
|
|
7
7
|
import { goClient } from "../../go-client.js";
|
|
8
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
8
9
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
9
10
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
10
11
|
import { stripAnsi } from "../../sanitize.js";
|
|
@@ -71,15 +72,28 @@ const goVetRunner = {
|
|
|
71
72
|
// cwd before comparing, so go's path FORM (a leading `./` for the
|
|
72
73
|
// module-root package, an absolute path, `.` segments) can't cause the
|
|
73
74
|
// edited file's OWN diagnostics to be silently dropped.
|
|
75
|
+
//
|
|
76
|
+
// #3277: resolving is not enough — the two sides are SPELLED by
|
|
77
|
+
// different actors. `ctx.filePath` is `normalizeMapKey`-canonical
|
|
78
|
+
// (#2016) while go prints its own spelling: `cmd/go` rewrites the
|
|
79
|
+
// package-dir prefix to a cwd-relative one only when the rewrite
|
|
80
|
+
// matches byte-for-byte (`replacePrefix`), even though the check that
|
|
81
|
+
// authorized it (`filepath.Rel`) compares case-insensitively on
|
|
82
|
+
// Windows, so a differently-cased ABSOLUTE path reaches this filter.
|
|
83
|
+
// A bare `===` then drops every line for the edited file and the run
|
|
84
|
+
// is reported clean — the #209 defect. `pathsEqual` asks the
|
|
85
|
+
// filesystem instead of a platform list, so it is also right on a
|
|
86
|
+
// case-folding POSIX mount. It does NOT resolve symlinks on POSIX
|
|
87
|
+
// (`clients/path-utils.ts:181`); nothing here relies on that.
|
|
74
88
|
const absTarget = resolve(ctx.filePath);
|
|
75
89
|
const relevant = raw
|
|
76
90
|
.split("\n")
|
|
77
91
|
.filter((line) => {
|
|
78
92
|
const m = line.match(/^(.+?):(\d+):(\d+):\s*(.+)/);
|
|
79
|
-
return m != null && resolve(cwd, m[1].trim())
|
|
93
|
+
return m != null && pathsEqual(resolve(cwd, m[1].trim()), absTarget);
|
|
80
94
|
})
|
|
81
95
|
.join("\n");
|
|
82
|
-
const diagnostics = parseGoVetOutput(relevant, ctx.filePath);
|
|
96
|
+
const diagnostics = parseGoVetOutput(relevant, ctx.filePath, cwd);
|
|
83
97
|
// Edited file clean → succeeded: a sibling-file error no longer flags
|
|
84
98
|
// the edited file's turn (it surfaces when that file is itself edited).
|
|
85
99
|
return diagnostics.length > 0
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
14
|
+
import { pathsEqual } from "../../path-utils.js";
|
|
14
15
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
15
16
|
import { getLinterPolicyForCwd, hasGolangciConfig } from "../../tool-policy.js";
|
|
16
17
|
import { PRIORITY } from "../priorities.js";
|
|
17
18
|
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
19
|
+
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
18
20
|
const golangci = createAvailabilityChecker("golangci-lint", ".exe");
|
|
19
21
|
/**
|
|
20
22
|
* Summarize a golangci-lint Replacement into a single-line fixSuggestion.
|
|
@@ -39,13 +41,21 @@ function describeReplacement(replacement) {
|
|
|
39
41
|
}
|
|
40
42
|
return "Apply golangci-lint suggested fix";
|
|
41
43
|
}
|
|
42
|
-
function parseGolangciJson(raw, filePath) {
|
|
44
|
+
function parseGolangciJson(raw, filePath, cwd) {
|
|
43
45
|
try {
|
|
44
46
|
const output = JSON.parse(raw);
|
|
45
47
|
if (!output.Issues)
|
|
46
48
|
return [];
|
|
47
|
-
const absFile = path.resolve(filePath);
|
|
48
|
-
|
|
49
|
+
const absFile = path.resolve(cwd, filePath);
|
|
50
|
+
// #3278: one seam for reported-path attribution — see javac.ts. The base
|
|
51
|
+
// matters here more than anywhere else in the family: golangci-lint's
|
|
52
|
+
// `PathPrettifier` OVERWRITES `Pos.Filename` with `filepath.Rel(basePath,
|
|
53
|
+
// …)` before the JSON printer sees it (v1.64.8,
|
|
54
|
+
// `pkg/result/processors/path_prettifier.go:31` +
|
|
55
|
+
// `path_relativity.go:43`), so the spelling is relative to the child's
|
|
56
|
+
// base path — the `cwd` we spawned it in — and `path.resolve` with no
|
|
57
|
+
// base resolved it against the EXTENSION's cwd instead.
|
|
58
|
+
return output.Issues.filter((issue) => pathsEqual(path.resolve(cwd, issue.Pos.Filename), absFile)).map((issue) => {
|
|
49
59
|
const severity = issue.Severity === "error" ? "error" : "warning";
|
|
50
60
|
// golangci-lint's --out-format=json emits a Replacement object per
|
|
51
61
|
// issue when `golangci-lint run --fix` would deterministically
|
|
@@ -95,26 +105,31 @@ const golangciRunner = {
|
|
|
95
105
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
96
106
|
// Run on the specific file. golangci-lint accepts file paths directly.
|
|
97
107
|
const result = await safeSpawnAsync(cmd, ["run", "--out-format=json", ctx.filePath], { timeout: 60000, cwd });
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
// Exit table: 0 is clean-or-findings; 1 means issues, 3 failure,
|
|
109
|
+
// 4 timeout, and 5 missing config. Every nonzero code is a ran outcome
|
|
110
|
+
// whose parser decides: JSON issues remain findings, while emitted
|
|
111
|
+
// non-JSON text becomes a parse-error diagnostic and no output stays
|
|
112
|
+
// skipped. Keep stderr as the parse input when stdout is absent so tool
|
|
113
|
+
// errors cannot be mistaken for a clean file.
|
|
114
|
+
const raw = (result.stdout ?? "").length > 0
|
|
115
|
+
? (result.stdout ?? "")
|
|
116
|
+
: result.status !== 0
|
|
117
|
+
? result.stderr || ""
|
|
118
|
+
: "";
|
|
119
|
+
const parsed = parseToolRun("golangci-lint", {
|
|
120
|
+
result,
|
|
121
|
+
output: raw,
|
|
122
|
+
// EXIT TABLE (golangci-lint 1.60 docs https://golangci-lint.run/docs/welcome/quick-start/): 0 clean; 1 findings; 2 error; 3 error; 4 error; 5 error; other nonzero rejected.
|
|
123
|
+
exitCodes: { ran: [1, 2, 3, 4, 5] },
|
|
124
|
+
}, (raw) => parseGolangciJson(raw, ctx.filePath, cwd));
|
|
125
|
+
if (parsed.skipped)
|
|
126
|
+
return parsed.skipped;
|
|
127
|
+
return finishParsedRun({
|
|
128
|
+
tool: "golangci-lint",
|
|
129
|
+
ctx,
|
|
130
|
+
result,
|
|
131
|
+
diagnostics: parsed.diagnostics,
|
|
132
|
+
});
|
|
118
133
|
},
|
|
119
134
|
};
|
|
120
135
|
export default golangciRunner;
|
|
@@ -1,4 +1,5 @@
|
|
|
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 { getLinterPolicyForCwd } from "../../tool-policy.js";
|
|
@@ -7,25 +8,32 @@ import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } fro
|
|
|
7
8
|
import { parseToolRun } from "./utils/tool-failure.js";
|
|
8
9
|
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
9
10
|
const hadolint = createAvailabilityChecker("hadolint", ".exe");
|
|
10
|
-
function parseHadolintOutput(raw, filePath) {
|
|
11
|
+
function parseHadolintOutput(raw, filePath, cwd) {
|
|
11
12
|
try {
|
|
12
13
|
const parsed = JSON.parse(raw);
|
|
13
14
|
if (!Array.isArray(parsed))
|
|
14
15
|
return [];
|
|
15
|
-
|
|
16
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
17
|
+
return parsed.flatMap((item) => {
|
|
18
|
+
// #3295: hadolint takes a LIST of Dockerfiles and names each finding's
|
|
19
|
+
// own `file`.
|
|
20
|
+
if (item.file && !pathsEqual(path.resolve(cwd, item.file), absTarget))
|
|
21
|
+
return [];
|
|
16
22
|
const severity = item.level === "error" ? "error" : "warning";
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
id: `hadolint-${item.code}-${item.line}`,
|
|
26
|
+
message: `[${item.code}] ${item.message}`,
|
|
27
|
+
filePath,
|
|
28
|
+
line: item.line,
|
|
29
|
+
column: item.column ?? 1,
|
|
30
|
+
severity,
|
|
31
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
32
|
+
tool: "hadolint",
|
|
33
|
+
rule: item.code,
|
|
34
|
+
fixable: false,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
29
37
|
});
|
|
30
38
|
}
|
|
31
39
|
catch {
|
|
@@ -57,7 +65,11 @@ const hadolintRunner = {
|
|
|
57
65
|
// #1948: hadolint runs with `--no-fail`, so it exits 0 even when it finds
|
|
58
66
|
// something. A nonzero exit therefore means hadolint itself failed, and
|
|
59
67
|
// zero parsed diagnostics out of whatever it printed is a parser break.
|
|
60
|
-
const run = parseToolRun("hadolint", {
|
|
68
|
+
const run = parseToolRun("hadolint", {
|
|
69
|
+
result,
|
|
70
|
+
// EXIT TABLE (Hadolint 2.12 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
71
|
+
exitCodes: { ran: [1, 2] },
|
|
72
|
+
}, (out) => parseHadolintOutput(out, ctx.filePath, cwd));
|
|
61
73
|
if (run.skipped)
|
|
62
74
|
return run.skipped;
|
|
63
75
|
const diagnostics = run.diagnostics;
|
|
@@ -1,4 +1,5 @@
|
|
|
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 { getLinterPolicyForCwd } from "../../tool-policy.js";
|
|
@@ -15,19 +16,22 @@ const HTMLHINT_RULES = {
|
|
|
15
16
|
"spec-char-escape": true,
|
|
16
17
|
"id-unique": true,
|
|
17
18
|
};
|
|
18
|
-
function parseHtmlhintOutput(raw, filePath) {
|
|
19
|
+
function parseHtmlhintOutput(raw, filePath, cwd) {
|
|
19
20
|
const diagnostics = [];
|
|
21
|
+
const absTarget = path.resolve(cwd, filePath);
|
|
20
22
|
// unix format: "file:line:col: message [severity/rule]"
|
|
21
|
-
const lineRe =
|
|
23
|
+
const lineRe = /^(.+?):(\d+):(\d+): (.+?) \[(error|warning)\/([^\]]+)\]/;
|
|
22
24
|
for (const line of raw.split("\n")) {
|
|
23
25
|
const match = line.match(lineRe);
|
|
24
26
|
if (!match)
|
|
25
27
|
continue;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
28
|
+
if (!pathsEqual(path.resolve(cwd, match[1]), absTarget))
|
|
29
|
+
continue;
|
|
30
|
+
const lineNum = parseInt(match[2], 10);
|
|
31
|
+
const col = parseInt(match[3], 10);
|
|
32
|
+
const message = match[4].trim();
|
|
33
|
+
const level = match[5];
|
|
34
|
+
const rule = match[6].trim();
|
|
31
35
|
const severity = level === "error" ? "error" : "warning";
|
|
32
36
|
diagnostics.push({
|
|
33
37
|
id: `htmlhint-${rule}-${lineNum}`,
|
|
@@ -86,7 +90,12 @@ const htmlhintRunner = {
|
|
|
86
90
|
// #1948: htmlhint exits 1 when it finds errors and prints them in `unix`
|
|
87
91
|
// format on stdout. Zero parsed out of a nonzero exit is a parser break.
|
|
88
92
|
const output = result.stdout || result.stderr || "";
|
|
89
|
-
const run = parseToolRun("htmlhint", {
|
|
93
|
+
const run = parseToolRun("htmlhint", {
|
|
94
|
+
result,
|
|
95
|
+
output,
|
|
96
|
+
// EXIT TABLE (HTMLHint 1.1 measured fixture): 0 clean; 1 findings; 2 error; other nonzero rejected.
|
|
97
|
+
exitCodes: { ran: [1, 2] },
|
|
98
|
+
}, (out) => parseHtmlhintOutput(out, ctx.filePath, cwd));
|
|
90
99
|
if (run.skipped)
|
|
91
100
|
return run.skipped;
|
|
92
101
|
const diagnostics = run.diagnostics;
|
|
@@ -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
|