@mrclrchtr/supi-code-intelligence 6.3.0 → 7.0.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/README.md +164 -82
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +770 -56
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +48 -12
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +50 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/package.json +1 -1
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +1 -2
- package/src/analysis/health/file-scope.ts +4 -1
- package/src/analysis/health/server-status.ts +1 -1
- package/src/analysis/relations/callees.ts +17 -8
- package/src/analysis/relations/implementations.ts +1 -2
- package/src/analysis/relations/references.ts +8 -4
- package/src/analysis/relations/types.ts +6 -30
- package/src/analysis/target/anchored.ts +9 -1
- package/src/analysis/target/symbol.ts +29 -11
- package/src/analysis/target/types.ts +14 -6
- package/src/api.ts +2 -0
- package/src/session/graph/collect.ts +18 -3
- package/src/session/health-refresh.ts +27 -11
- package/src/session/health-types.ts +21 -4
- package/src/session/health-workflow.ts +28 -22
- package/src/session/orientation-types.ts +5 -6
- package/src/session/orientation-workflow.ts +10 -2
- package/src/session/session.ts +12 -5
- package/src/session/target-workflow.ts +9 -6
- package/src/substrate/lsp/maintenance.ts +53 -47
- package/src/substrate/lsp/source-tracking.ts +252 -0
- package/src/tool/code_find/guidance.ts +3 -3
- package/src/tool/code_find/spec.ts +5 -4
- package/src/tool/code_find/tui.ts +7 -1
- package/src/tool/code_graph/details.ts +32 -0
- package/src/tool/code_graph/display.ts +54 -0
- package/src/tool/code_graph/format.ts +60 -0
- package/src/tool/code_graph/guidance.ts +2 -2
- package/src/tool/code_graph/markdown.ts +76 -198
- package/src/tool/code_graph/read-next.ts +65 -0
- package/src/tool/code_graph/result.ts +118 -83
- package/src/tool/code_graph/tui.ts +216 -54
- package/src/tool/code_health/guidance.ts +3 -5
- package/src/tool/code_health/markdown.ts +90 -57
- package/src/tool/code_health/refresh-outcome.ts +51 -0
- package/src/tool/code_health/refresh-status.ts +130 -43
- package/src/tool/code_health/scope.ts +78 -0
- package/src/tool/code_health/spec.ts +1 -1
- package/src/tool/code_health/tui.ts +29 -38
- package/src/tool/code_inspect/guidance.ts +5 -3
- package/src/tool/code_inspect/tui.ts +7 -1
- package/src/tool/code_orientation/guidance.ts +3 -4
- package/src/tool/code_orientation/result.ts +31 -8
- package/src/tool/code_orientation/tui.ts +26 -5
- package/src/tool/code_refactor_apply/guidance.ts +1 -1
- package/src/tool/code_refactor_apply/tui.ts +7 -3
- package/src/tool/code_refactor_plan/guidance.ts +3 -5
- package/src/tool/code_refactor_plan/tui.ts +7 -1
- package/src/tool/code_resolve/guidance.ts +3 -5
- package/src/tool/code_resolve/result.ts +13 -10
- package/src/tool/code_resolve/tui.ts +18 -1
- package/src/tool/guidance.ts +11 -9
- package/src/tool/infra/truncate.ts +39 -13
- package/src/tool/result/candidate-row.ts +27 -0
- package/src/tool/result/errors.ts +21 -6
- package/src/tool/result/types.ts +7 -3
- package/src/tool/schemas.ts +5 -0
- package/src/types/execution.ts +2 -0
- package/src/types/index.ts +2 -0
- package/src/ui/tui/candidate-legacy-body.ts +69 -0
- package/src/ui/tui/candidate-selection.ts +366 -0
- package/src/ui/tui/common.ts +26 -31
- package/src/ui/tui/execution-error.ts +92 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
// biome-ignore-all lint/style/noExcessiveLinesPerFile: TypeScript protocol validation and conversion stay together.
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type CodeRequestControl,
|
|
5
|
+
throwIfCodeRequestInterrupted,
|
|
6
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
7
|
+
import { fileToUri, uriToFile } from "@mrclrchtr/supi-core/path";
|
|
8
|
+
import type { Diagnostic, Position } from "../config/types.ts";
|
|
9
|
+
import { raceRequestControl } from "../session/readiness.ts";
|
|
10
|
+
import type {
|
|
11
|
+
DiagnosticPullRequest,
|
|
12
|
+
DiagnosticRequestAdapter,
|
|
13
|
+
DiagnosticRequestExecution,
|
|
14
|
+
DiagnosticRequestResult,
|
|
15
|
+
} from "./client-diagnostic-request.ts";
|
|
16
|
+
|
|
17
|
+
const TYPESCRIPT_REQUEST_METHOD = "typescript.tsserverRequest";
|
|
18
|
+
const EXECUTE_COMMAND_METHOD = "workspace/executeCommand";
|
|
19
|
+
const DIAGNOSTIC_COMMANDS = [
|
|
20
|
+
"syntacticDiagnosticsSync",
|
|
21
|
+
"semanticDiagnosticsSync",
|
|
22
|
+
"suggestionDiagnosticsSync",
|
|
23
|
+
] as const;
|
|
24
|
+
const MAX_POSITION = 2_147_483_647;
|
|
25
|
+
|
|
26
|
+
type OwnedRequest = {
|
|
27
|
+
result: Promise<unknown>;
|
|
28
|
+
settled: Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Dependencies needed by the private TypeScript diagnostic adapter. */
|
|
32
|
+
export interface TypeScriptDiagnosticAdapterOptions {
|
|
33
|
+
readonly fileTypes: readonly string[];
|
|
34
|
+
readonly cwd?: string;
|
|
35
|
+
readonly isSupportedRoute: () => boolean;
|
|
36
|
+
readonly hasCommand: () => boolean;
|
|
37
|
+
readonly getReady: () => Promise<void>;
|
|
38
|
+
readonly sendRequestOwned: (
|
|
39
|
+
method: string,
|
|
40
|
+
params: unknown,
|
|
41
|
+
options: { timeoutMs: number; deadline?: number; operationId?: string },
|
|
42
|
+
) => OwnedRequest;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build the tested TypeScript request adapter.
|
|
47
|
+
*
|
|
48
|
+
* The adapter uses the running language server's tsserver and sends all three
|
|
49
|
+
* synchronous diagnostic requests in order. It never starts a second compiler
|
|
50
|
+
* process and never treats an incomplete phase set as a clean result.
|
|
51
|
+
*/
|
|
52
|
+
export function createTypeScriptDiagnosticRequestAdapter(
|
|
53
|
+
options: TypeScriptDiagnosticAdapterOptions,
|
|
54
|
+
): DiagnosticRequestAdapter {
|
|
55
|
+
return {
|
|
56
|
+
supports: (uri) =>
|
|
57
|
+
options.isSupportedRoute() && options.hasCommand() && isSupportedFile(uri, options.fileTypes),
|
|
58
|
+
sourceFor: (uri) =>
|
|
59
|
+
options.isSupportedRoute() && options.hasCommand() && isSupportedFile(uri, options.fileTypes)
|
|
60
|
+
? "typescript"
|
|
61
|
+
: undefined,
|
|
62
|
+
collect: (request) => collectTypeScriptDiagnostics(request, options),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface TypeScriptDiagnosticCore {
|
|
67
|
+
readonly range: Diagnostic["range"];
|
|
68
|
+
readonly message: string;
|
|
69
|
+
readonly severity: NonNullable<Diagnostic["severity"]>;
|
|
70
|
+
readonly code?: number;
|
|
71
|
+
readonly source: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Normalize one validated TypeScript diagnostic body into an LSP diagnostic. */
|
|
75
|
+
export function normalizeTypeScriptDiagnostic(
|
|
76
|
+
value: unknown,
|
|
77
|
+
options: { cwd?: string } = {},
|
|
78
|
+
): Diagnostic | undefined {
|
|
79
|
+
if (!isRecord(value)) return undefined;
|
|
80
|
+
const core = readDiagnosticCore(value);
|
|
81
|
+
if (!core) return undefined;
|
|
82
|
+
if (!hasValidDiagnosticFlag(value, "reportsUnnecessary")) return undefined;
|
|
83
|
+
if (!hasValidDiagnosticFlag(value, "reportsDeprecated")) return undefined;
|
|
84
|
+
const relatedInformation = readRelatedInformation(value.relatedInformation, options.cwd);
|
|
85
|
+
if (value.relatedInformation !== undefined && relatedInformation === undefined) return undefined;
|
|
86
|
+
const tags = buildDiagnosticTags(value);
|
|
87
|
+
return {
|
|
88
|
+
...core,
|
|
89
|
+
...(relatedInformation !== undefined ? { relatedInformation } : {}),
|
|
90
|
+
...(tags !== undefined ? { tags } : {}),
|
|
91
|
+
} satisfies Diagnostic;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readDiagnosticCore(value: Record<string, unknown>): TypeScriptDiagnosticCore | undefined {
|
|
95
|
+
const linePosition = isLinePositionDiagnostic(value);
|
|
96
|
+
const range = readDiagnosticRange(value, linePosition);
|
|
97
|
+
const message = linePosition ? value.message : value.text;
|
|
98
|
+
const severity = readCategory(value.category);
|
|
99
|
+
const code = readDiagnosticCode(value.code, linePosition);
|
|
100
|
+
if (!range || typeof message !== "string" || severity === undefined || code === null) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (value.source !== undefined && typeof value.source !== "string") return undefined;
|
|
104
|
+
return {
|
|
105
|
+
range,
|
|
106
|
+
message,
|
|
107
|
+
severity,
|
|
108
|
+
...(code === undefined ? {} : { code }),
|
|
109
|
+
source:
|
|
110
|
+
typeof value.source === "string" && value.source.length > 0 ? value.source : "typescript",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readDiagnosticRange(
|
|
115
|
+
value: Record<string, unknown>,
|
|
116
|
+
linePosition: boolean,
|
|
117
|
+
): Diagnostic["range"] | undefined {
|
|
118
|
+
if (
|
|
119
|
+
linePosition &&
|
|
120
|
+
(!isInteger(value.start) ||
|
|
121
|
+
value.start < 0 ||
|
|
122
|
+
value.start > MAX_POSITION ||
|
|
123
|
+
!isInteger(value.length) ||
|
|
124
|
+
value.length < 0 ||
|
|
125
|
+
value.length > MAX_POSITION)
|
|
126
|
+
) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const start = readLocation(linePosition ? value.startLocation : value.start);
|
|
130
|
+
const end = readLocation(linePosition ? value.endLocation : value.end);
|
|
131
|
+
return start && end && comparePositions(start, end) <= 0 ? { start, end } : undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function readDiagnosticCode(value: unknown, linePosition: boolean): number | null | undefined {
|
|
135
|
+
if (value === undefined && !linePosition) return undefined;
|
|
136
|
+
if (!isInteger(value) || value < 0) return null;
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Normalize every diagnostic in one successful TypeScript phase body. */
|
|
141
|
+
export function normalizeTypeScriptDiagnostics(
|
|
142
|
+
value: unknown,
|
|
143
|
+
options: { cwd?: string } = {},
|
|
144
|
+
): Diagnostic[] | undefined {
|
|
145
|
+
if (!Array.isArray(value)) return undefined;
|
|
146
|
+
const diagnostics: Diagnostic[] = [];
|
|
147
|
+
for (const item of value) {
|
|
148
|
+
const diagnostic = normalizeTypeScriptDiagnostic(item, options);
|
|
149
|
+
if (diagnostic === undefined) return undefined;
|
|
150
|
+
diagnostics.push(diagnostic);
|
|
151
|
+
}
|
|
152
|
+
return diagnostics;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function collectTypeScriptDiagnostics(
|
|
156
|
+
request: DiagnosticPullRequest,
|
|
157
|
+
options: TypeScriptDiagnosticAdapterOptions,
|
|
158
|
+
): DiagnosticRequestExecution<DiagnosticRequestResult> {
|
|
159
|
+
let activeSettled = Promise.resolve();
|
|
160
|
+
const startedAt = Date.now();
|
|
161
|
+
const collectionDeadline = Number.isFinite(request.timeoutMs)
|
|
162
|
+
? startedAt + request.timeoutMs
|
|
163
|
+
: undefined;
|
|
164
|
+
|
|
165
|
+
const result = (async (): Promise<DiagnosticRequestResult> => {
|
|
166
|
+
const control: CodeRequestControl = {
|
|
167
|
+
signal: request.signal,
|
|
168
|
+
deadline: collectionDeadline,
|
|
169
|
+
};
|
|
170
|
+
throwIfCodeRequestInterrupted(control);
|
|
171
|
+
await raceRequestControl(options.getReady(), control);
|
|
172
|
+
throwIfCodeRequestInterrupted(control);
|
|
173
|
+
|
|
174
|
+
const diagnostics: Diagnostic[] = [];
|
|
175
|
+
for (const command of DIAGNOSTIC_COMMANDS) {
|
|
176
|
+
const phase = await collectTypeScriptPhase({
|
|
177
|
+
command,
|
|
178
|
+
request,
|
|
179
|
+
options,
|
|
180
|
+
control,
|
|
181
|
+
collectionDeadline,
|
|
182
|
+
startedAt,
|
|
183
|
+
setActiveSettled: (settled) => {
|
|
184
|
+
activeSettled = settled;
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
diagnostics.push(...phase);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
source: "typescript",
|
|
192
|
+
report: { kind: "full", items: diagnostics },
|
|
193
|
+
};
|
|
194
|
+
})();
|
|
195
|
+
|
|
196
|
+
// The result can reject at the owner deadline while the raw request is
|
|
197
|
+
// still outstanding. Keep the scheduler occupied until that request ends.
|
|
198
|
+
const settled = result
|
|
199
|
+
.then(
|
|
200
|
+
() => activeSettled,
|
|
201
|
+
() => activeSettled,
|
|
202
|
+
)
|
|
203
|
+
.then(() => undefined);
|
|
204
|
+
result.catch(() => {});
|
|
205
|
+
settled.catch(() => {});
|
|
206
|
+
return { result, settled };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface TypeScriptPhaseOptions {
|
|
210
|
+
readonly command: (typeof DIAGNOSTIC_COMMANDS)[number];
|
|
211
|
+
readonly request: DiagnosticPullRequest;
|
|
212
|
+
readonly options: TypeScriptDiagnosticAdapterOptions;
|
|
213
|
+
readonly control: CodeRequestControl;
|
|
214
|
+
readonly collectionDeadline: number | undefined;
|
|
215
|
+
readonly startedAt: number;
|
|
216
|
+
readonly setActiveSettled: (settled: Promise<void>) => void;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function collectTypeScriptPhase(options: TypeScriptPhaseOptions): Promise<Diagnostic[]> {
|
|
220
|
+
const { command, request, control, collectionDeadline, startedAt } = options;
|
|
221
|
+
throwIfCodeRequestInterrupted(control);
|
|
222
|
+
const remaining = remainingBudget(collectionDeadline, request.timeoutMs, startedAt);
|
|
223
|
+
if (remaining <= 0) {
|
|
224
|
+
throw new Error("TypeScript diagnostic request timed out.");
|
|
225
|
+
}
|
|
226
|
+
const execution = options.options.sendRequestOwned(
|
|
227
|
+
EXECUTE_COMMAND_METHOD,
|
|
228
|
+
{
|
|
229
|
+
command: TYPESCRIPT_REQUEST_METHOD,
|
|
230
|
+
arguments: [
|
|
231
|
+
command,
|
|
232
|
+
{ file: request.uri, includeLinePosition: true },
|
|
233
|
+
{
|
|
234
|
+
executionTarget: 0,
|
|
235
|
+
expectsResult: true,
|
|
236
|
+
isAsync: false,
|
|
237
|
+
lowPriority: true,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
timeoutMs: remaining,
|
|
243
|
+
deadline: collectionDeadline,
|
|
244
|
+
...(request.operationId !== undefined ? { operationId: request.operationId } : {}),
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
options.setActiveSettled(execution.settled);
|
|
248
|
+
const response = await raceRequestControl(execution.result, {
|
|
249
|
+
signal: request.signal,
|
|
250
|
+
deadline: collectionDeadline,
|
|
251
|
+
});
|
|
252
|
+
await execution.settled;
|
|
253
|
+
const body = readSuccessfulResponseBody(response);
|
|
254
|
+
if (body === undefined) {
|
|
255
|
+
throw new Error(`Invalid ${command} response from TypeScript language server.`);
|
|
256
|
+
}
|
|
257
|
+
const phase = normalizeTypeScriptDiagnostics(body, { cwd: options.options.cwd });
|
|
258
|
+
if (phase === undefined) {
|
|
259
|
+
throw new Error(`Invalid ${command} diagnostic body from TypeScript language server.`);
|
|
260
|
+
}
|
|
261
|
+
return phase;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function readSuccessfulResponseBody(value: unknown): unknown[] | undefined {
|
|
265
|
+
if (!isRecord(value) || value.type !== "response" || value.success !== true) return undefined;
|
|
266
|
+
return Array.isArray(value.body) ? value.body : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isSupportedFile(uri: string, fileTypes: readonly string[]): boolean {
|
|
270
|
+
const filePath = uri.startsWith("file://") ? decodeFileUri(uri) : uri;
|
|
271
|
+
const extension = path.extname(filePath).replace(/^\./, "");
|
|
272
|
+
return extension.length > 0 && fileTypes.some((type) => type.replace(/^\./, "") === extension);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function decodeFileUri(uri: string): string {
|
|
276
|
+
return uriToFile(uri);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function remainingBudget(
|
|
280
|
+
deadline: number | undefined,
|
|
281
|
+
timeoutMs: number,
|
|
282
|
+
startedAt: number,
|
|
283
|
+
): number {
|
|
284
|
+
const requestRemaining = timeoutMs - (Date.now() - startedAt);
|
|
285
|
+
return deadline === undefined
|
|
286
|
+
? requestRemaining
|
|
287
|
+
: Math.min(requestRemaining, deadline - Date.now());
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function isLinePositionDiagnostic(value: Record<string, unknown>): boolean {
|
|
291
|
+
return value.startLocation !== undefined || value.endLocation !== undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function readLocation(value: unknown): Position | undefined {
|
|
295
|
+
if (!isRecord(value)) return undefined;
|
|
296
|
+
if (
|
|
297
|
+
!isPositiveInteger(value.line) ||
|
|
298
|
+
value.line > MAX_POSITION + 1 ||
|
|
299
|
+
!isPositiveInteger(value.offset) ||
|
|
300
|
+
value.offset > MAX_POSITION + 1
|
|
301
|
+
) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
return { line: value.line - 1, character: value.offset - 1 };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function comparePositions(left: Position, right: Position): number {
|
|
308
|
+
return left.line - right.line || left.character - right.character;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function readCategory(value: unknown): Diagnostic["severity"] | undefined {
|
|
312
|
+
switch (value) {
|
|
313
|
+
case "error":
|
|
314
|
+
return 1;
|
|
315
|
+
case "warning":
|
|
316
|
+
return 2;
|
|
317
|
+
case "message":
|
|
318
|
+
return 3;
|
|
319
|
+
case "suggestion":
|
|
320
|
+
return 4;
|
|
321
|
+
default:
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function hasValidDiagnosticFlag(value: Record<string, unknown>, key: string): boolean {
|
|
327
|
+
return value[key] === undefined || typeof value[key] === "boolean" || isRecord(value[key]);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function buildDiagnosticTags(value: Record<string, unknown>): Array<1 | 2> | undefined {
|
|
331
|
+
const tags: Array<1 | 2> = [];
|
|
332
|
+
if (value.reportsUnnecessary === true || isRecord(value.reportsUnnecessary)) tags.push(1);
|
|
333
|
+
if (value.reportsDeprecated === true || isRecord(value.reportsDeprecated)) tags.push(2);
|
|
334
|
+
return tags.length > 0 ? tags : undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function readRelatedInformation(
|
|
338
|
+
value: unknown,
|
|
339
|
+
cwd: string | undefined,
|
|
340
|
+
): Diagnostic["relatedInformation"] | undefined {
|
|
341
|
+
if (value === undefined) return undefined;
|
|
342
|
+
if (!Array.isArray(value)) return undefined;
|
|
343
|
+
const result: NonNullable<Diagnostic["relatedInformation"]> = [];
|
|
344
|
+
for (const item of value) {
|
|
345
|
+
const related = readRelatedEntry(item, cwd);
|
|
346
|
+
if (related === null) return undefined;
|
|
347
|
+
if (related !== undefined) result.push(related);
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
type RelatedDiagnosticInformation = NonNullable<Diagnostic["relatedInformation"]>[number];
|
|
353
|
+
|
|
354
|
+
function readRelatedEntry(
|
|
355
|
+
value: unknown,
|
|
356
|
+
cwd: string | undefined,
|
|
357
|
+
): RelatedDiagnosticInformation | null | undefined {
|
|
358
|
+
if (!isRecord(value) || typeof value.message !== "string") return null;
|
|
359
|
+
if (readCategory(value.category) === undefined || !isInteger(value.code)) return null;
|
|
360
|
+
if (value.span === undefined) return undefined;
|
|
361
|
+
if (
|
|
362
|
+
!isRecord(value.span) ||
|
|
363
|
+
typeof value.span.file !== "string" ||
|
|
364
|
+
value.span.file.length === 0
|
|
365
|
+
) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const start = readLocation(value.span.start);
|
|
369
|
+
const end = readLocation(value.span.end);
|
|
370
|
+
if (!start || !end || comparePositions(start, end) > 0) return null;
|
|
371
|
+
return {
|
|
372
|
+
location: {
|
|
373
|
+
uri: fileToUri(resolveRelatedFile(value.span.file, cwd)),
|
|
374
|
+
range: { start, end },
|
|
375
|
+
},
|
|
376
|
+
message: value.message,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function resolveRelatedFile(file: string, cwd: string | undefined): string {
|
|
381
|
+
if (file.startsWith("file://")) return uriToFile(file);
|
|
382
|
+
return path.isAbsolute(file) ? file : path.resolve(cwd ?? process.cwd(), file);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function isPositiveInteger(value: unknown): value is number {
|
|
386
|
+
return isInteger(value) && value > 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function isInteger(value: unknown): value is number {
|
|
390
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
394
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
395
|
+
}
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
throwIfCodeRequestInterrupted,
|
|
5
5
|
} from "@mrclrchtr/supi-code-runtime/api";
|
|
6
6
|
import type {
|
|
7
|
-
|
|
7
|
+
DiagnosticPushObservationOutcome,
|
|
8
8
|
DiagnosticSettleResult,
|
|
9
9
|
} from "./client-diagnostic-timing.ts";
|
|
10
10
|
|
|
11
|
-
type DiagnosticWaiter = (outcome: "
|
|
11
|
+
type DiagnosticWaiter = (outcome: "observed" | "released") => void;
|
|
12
12
|
|
|
13
13
|
/** One cancellable wait for diagnostic state change. */
|
|
14
14
|
export interface DiagnosticStateWait {
|
|
@@ -21,7 +21,6 @@ interface DiagnosticSettleOptions {
|
|
|
21
21
|
readonly maxWaitMs: number;
|
|
22
22
|
readonly quietMs: number;
|
|
23
23
|
readonly settleEpoch: number;
|
|
24
|
-
readonly isComplete: () => boolean;
|
|
25
24
|
readonly latestReceived: () => number;
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -41,7 +40,7 @@ export class DiagnosticWaitRegistry {
|
|
|
41
40
|
uri: string,
|
|
42
41
|
timeoutMs: number,
|
|
43
42
|
control?: CodeRequestControl,
|
|
44
|
-
): Promise<
|
|
43
|
+
): Promise<DiagnosticPushObservationOutcome> {
|
|
45
44
|
const deadlineRemaining =
|
|
46
45
|
control?.deadline === undefined ? undefined : control.deadline - Date.now();
|
|
47
46
|
const bindMs =
|
|
@@ -56,7 +55,7 @@ export class DiagnosticWaitRegistry {
|
|
|
56
55
|
return Promise.resolve("timed-out");
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
return new Promise<
|
|
58
|
+
return new Promise<DiagnosticPushObservationOutcome>((resolve, reject) => {
|
|
60
59
|
const cleanup = () => {
|
|
61
60
|
clearTimeout(timer);
|
|
62
61
|
if (abortHandler) control?.signal?.removeEventListener("abort", abortHandler);
|
|
@@ -103,7 +102,7 @@ export class DiagnosticWaitRegistry {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
/** Release pending push waiters for one URI. */
|
|
106
|
-
releaseFile(uri: string, outcome: "
|
|
105
|
+
releaseFile(uri: string, outcome: "observed" | "released" = "released"): void {
|
|
107
106
|
const waiters = this.#pushWaiters.get(uri);
|
|
108
107
|
if (waiters) {
|
|
109
108
|
this.#pushWaiters.delete(uri);
|
|
@@ -134,7 +133,7 @@ export class DiagnosticWaitRegistry {
|
|
|
134
133
|
options: DiagnosticSettleOptions,
|
|
135
134
|
control?: CodeRequestControl,
|
|
136
135
|
): Promise<DiagnosticSettleResult> {
|
|
137
|
-
const { syncStart, maxWaitMs, quietMs, settleEpoch,
|
|
136
|
+
const { syncStart, maxWaitMs, quietMs, settleEpoch, latestReceived } = options;
|
|
138
137
|
const deadlineRemaining =
|
|
139
138
|
control?.deadline === undefined ? undefined : control.deadline - Date.now();
|
|
140
139
|
const deadline =
|
|
@@ -148,14 +147,11 @@ export class DiagnosticWaitRegistry {
|
|
|
148
147
|
}
|
|
149
148
|
const observedAt = latestReceived();
|
|
150
149
|
const elapsed = Date.now() - observedAt;
|
|
151
|
-
|
|
152
|
-
if (observedAt > 0 && complete && elapsed >= quietMs) {
|
|
150
|
+
if (observedAt > 0 && elapsed >= quietMs) {
|
|
153
151
|
return { outcome: "quiet", freshness: "observed" };
|
|
154
152
|
}
|
|
155
153
|
const waitMs =
|
|
156
|
-
observedAt > 0
|
|
157
|
-
? Math.min(quietMs - elapsed, deadline - Date.now())
|
|
158
|
-
: deadline - Date.now();
|
|
154
|
+
observedAt > 0 ? Math.min(quietMs - elapsed, deadline - Date.now()) : deadline - Date.now();
|
|
159
155
|
await this.#waitForStateChange(waitMs, control);
|
|
160
156
|
}
|
|
161
157
|
// A bound absolute deadline makes the settle outcome a deadline
|