@levr-one/cli 0.12.4 → 0.12.6

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.
Files changed (23) hide show
  1. package/dist/{addHandler-LO6SChns.js → addHandler-ybX54_3f.js} +6 -6
  2. package/dist/{appendHandler-DtxLpPu6.js → appendHandler-CKkl9ymw.js} +7 -7
  3. package/dist/{artifact-upload-BvBSevIx.js → artifact-upload-Ba801tb6.js} +1 -1
  4. package/dist/{attachHandler-DXZi2ILT.js → attachHandler-CfNvVAV2.js} +7 -7
  5. package/dist/cli.js +77 -18
  6. package/dist/{connect-DVjgyfls.js → connect-yq3fGXkW.js} +9 -8
  7. package/dist/{find-workspace-Bl4rfr73.js → find-workspace-4Us2vEb7.js} +1 -1
  8. package/dist/{importHandler-uFdalKtw.js → importHandler-bYBWYC_0.js} +8 -8
  9. package/dist/{listHandler-BW92JLCl.js → listHandler-Du_YGzre.js} +4 -4
  10. package/dist/{loginHandler-C9Oi92IY.js → loginHandler-PbKmDAGo.js} +4 -4
  11. package/dist/{logoutHandler-Cf5aI1wq.js → logoutHandler-DxnzGPYx.js} +1 -1
  12. package/dist/{pushHandler-CXFZt3SO.js → pushHandler-BulraEwP.js} +15 -13
  13. package/dist/refsHandler-DtcceTRP.js +1757 -0
  14. package/dist/{resolve-token-ByZq05Us.js → resolve-token-DKEXT7SA.js} +8 -5
  15. package/dist/{resolve-workspace-BbK3uYQc.js → resolve-workspace-D8y7twod.js} +37 -5
  16. package/dist/{runHandler-IyPhhiGs.js → runHandler-B-u-0eRk.js} +25 -13
  17. package/dist/{sdk-client-B4FMd_Fa.js → sdk-client-BgzKzVIR.js} +1 -1
  18. package/dist/{selectHandler-D6cvq60S.js → selectHandler-C0gs-Q70.js} +9 -4
  19. package/dist/{statusHandler-BQXn93-r.js → statusHandler-Dnnh80ZN.js} +7 -5
  20. package/dist/{token-refresh-CJB7Qlxy.js → token-refresh-BpFuU4Ij.js} +26 -2
  21. package/package.json +1 -1
  22. /package/dist/{currentHandler-QhCywr3S.js → currentHandler-PaLZKLGF.js} +0 -0
  23. /package/dist/{env-CvjMSRd7.js → env-DjeRwmZ7.js} +0 -0
@@ -0,0 +1,1757 @@
1
+ import { createRequire } from "node:module";
2
+ import * as fs from "node:fs";
3
+ import * as path$1 from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { spawn, spawnSync } from "node:child_process";
6
+
7
+ //#region ../code-intel/dist/shared.js
8
+ /** Directories never worth walking — they dwarf the real source tree. */
9
+ const IGNORED_DIRS = [
10
+ "node_modules",
11
+ "dist",
12
+ "build",
13
+ "coverage",
14
+ ".git",
15
+ ".turbo",
16
+ ".yarn",
17
+ ".next",
18
+ "out"
19
+ ];
20
+ /** The files the TypeScript engines load. */
21
+ const TS_SOURCE = /\.tsx?$/;
22
+ const TEST_FILE_RE = /\.(test|spec)\.[cm]?tsx?$/;
23
+ function isTestFile(filePath) {
24
+ return TEST_FILE_RE.test(filePath);
25
+ }
26
+ /** Walk up from `start` until a directory containing `.git` is found. */
27
+ function findProjectRoot(start = process.cwd()) {
28
+ let dir = path$1.resolve(start);
29
+ for (;;) {
30
+ if (fs.existsSync(path$1.join(dir, ".git"))) return dir;
31
+ const parent = path$1.dirname(dir);
32
+ if (parent === dir) return path$1.resolve(start);
33
+ dir = parent;
34
+ }
35
+ }
36
+ /**
37
+ * Nearest `tsconfig.json` at or above `scopeRoot`, bounded by `projectRoot`.
38
+ *
39
+ * Load-bearing, not a nicety. `apps/backender` mandates non-relative imports
40
+ * (`import { X } from 'src/service/x'`), resolved through the `paths` mapping in
41
+ * its tsconfig. Without those options the specifier does not resolve, the import
42
+ * never binds to the declaration, and a reference search cannot see the
43
+ * consumer — so the gate reports a well-wired service as orphaned and FAILS
44
+ * CLOSED ON CORRECT CODE.
45
+ *
46
+ * That is a worse failure than the one this gate replaced: Axon was confidently
47
+ * wrong, and a gate that fails correct work is confidently wrong with authority.
48
+ */
49
+ function findTsConfig(scopeRoot, projectRoot) {
50
+ let dir = path$1.resolve(scopeRoot);
51
+ const stop = path$1.resolve(projectRoot);
52
+ for (;;) {
53
+ const candidate = path$1.join(dir, "tsconfig.json");
54
+ if (fs.existsSync(candidate)) return candidate;
55
+ if (dir === stop) return null;
56
+ const parent = path$1.dirname(dir);
57
+ if (parent === dir) return null;
58
+ dir = parent;
59
+ }
60
+ }
61
+ function listSourceFiles(scopeRoot, extension = TS_SOURCE, skipDir) {
62
+ if (extension.test(scopeRoot) && fs.statSync(scopeRoot, { throwIfNoEntry: false })?.isFile()) return [scopeRoot];
63
+ const files = [];
64
+ const walk = (dir) => {
65
+ let entries;
66
+ try {
67
+ entries = fs.readdirSync(dir, { withFileTypes: true });
68
+ } catch {
69
+ return;
70
+ }
71
+ for (const entry of entries) if (entry.isDirectory()) {
72
+ if (IGNORED_DIRS.includes(entry.name) || skipDir?.(dir, entry.name)) continue;
73
+ walk(path$1.join(dir, entry.name));
74
+ } else if (extension.test(entry.name)) files.push(path$1.join(dir, entry.name));
75
+ };
76
+ walk(scopeRoot);
77
+ return files.sort();
78
+ }
79
+ /**
80
+ * A name so common that answering for it would load the machine's memory
81
+ * rather than a project — `id`, `name`, `run` — unscoped or in a scope
82
+ * where it appears everywhere. A misconfigured GATE (exit 2 in the CLIs),
83
+ * never a verdict: the remedy is `--scope`, and running unbounded ended in
84
+ * a SIGKILLed 19 GB `tsgo --lsp` with no answer at all (ENG-5121 review
85
+ * F-025).
86
+ */
87
+ var SymbolTooCommonError = class extends Error {
88
+ symbol;
89
+ count;
90
+ limit;
91
+ what;
92
+ constructor(symbol, count, limit, what) {
93
+ super(`"${symbol}" is too common to resolve here: ${count} ${what}, above the ${limit} limit. Narrow it with --scope (e.g. --scope apps/backender/src/<module>).`);
94
+ this.symbol = symbol;
95
+ this.count = count;
96
+ this.limit = limit;
97
+ this.what = what;
98
+ this.name = "SymbolTooCommonError";
99
+ }
100
+ };
101
+ function escapeRegExp$1(s) {
102
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
103
+ }
104
+ /**
105
+ * Does this source text LOOK like it declares `symbol` at top level?
106
+ *
107
+ * A candidate filter only — cheap enough to run over every file in scope so
108
+ * an engine knows which files to ask the compiler about. The compiler (or the
109
+ * LSP server's symbol tree) is what decides; this must merely never say "no"
110
+ * for a file that does declare the symbol.
111
+ */
112
+ function looksLikeTopLevelDeclaration(symbol, text) {
113
+ const name = escapeRegExp$1(symbol);
114
+ if (new RegExp(String.raw`^[ \t]*(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:class|function\s*\*?|interface|type|enum|const\s+enum|const|let|var)\s+${name}\b`, "m").test(text)) return true;
115
+ return new RegExp(String.raw`(?:^|,)[ \t]*${name}\s*[=:,;]`, "m").test(text);
116
+ }
117
+ /**
118
+ * Does this source text LOOK like it declares a class/interface MEMBER named
119
+ * `symbol`? Covers a decorator on the same line, a definite-assignment `!`,
120
+ * and a member on the class's own line (`class A { X = 1 }`) — all shapes
121
+ * ts-morph's `getMembers()` returns and the earlier filter missed
122
+ * (ENG-5121 review F-023).
123
+ */
124
+ function looksLikeMemberDeclaration(symbol, text) {
125
+ return new RegExp(String.raw`(?:^[ \t]*|[{;][ \t]*)(?:@\w+(?:\([^)]*\))?\s*)*(?:(?:public|private|protected|static|readonly|abstract|override|async|declare|accessor|get|set)\s+)*\*?${escapeRegExp$1(symbol)}\s*[!?]?\s*[(<:=;,}]`, "m").test(text);
126
+ }
127
+ /** Convert an absolute path under `projectRoot` to the repo-relative form results use. */
128
+ function relativeTo(projectRoot, absolute) {
129
+ return path$1.relative(projectRoot, absolute) || absolute;
130
+ }
131
+ /** An empty "not found" result, so both engines produce a byte-identical one. */
132
+ function notFound(filesScanned, tsConfigPath) {
133
+ return {
134
+ found: false,
135
+ resolvedAsMember: false,
136
+ declarationFiles: [],
137
+ referenceCount: 0,
138
+ referencingFiles: [],
139
+ excludedTestReferences: 0,
140
+ filesScanned,
141
+ tsConfigPath
142
+ };
143
+ }
144
+ /**
145
+ * Upper bound on source files one request may load.
146
+ *
147
+ * A TS program holds every loaded file's AST and type information in memory, so
148
+ * cost scales with the file count. Node's default old-space is ~4GB, and an
149
+ * UNSCOPED run over this monorepo (11,460 `.ts`/`.tsx` files) exceeds it:
150
+ *
151
+ * FATAL ERROR: Ineffective mark-compacts near heap limit
152
+ * Allocation failed - JavaScript heap out of memory
153
+ *
154
+ * That is the worst possible failure for a gate. It exits 134 with a V8 stack
155
+ * trace and no verdict — not a pass, not a fail, just noise a caller has to
156
+ * interpret. The gate framework exists to make "verified" and "not verified"
157
+ * distinguishable, and an OOM is neither.
158
+ *
159
+ * 8000 sits above the largest legitimate single scope in this repo
160
+ * (`apps/backender` at 6,314, which resolves comfortably) and below the
161
+ * whole-repo set that does not. Override deliberately with `--max-files` if a
162
+ * genuinely larger scope is ever needed — and expect to raise Node's heap with
163
+ * `NODE_OPTIONS=--max-old-space-size=...` at the same time.
164
+ *
165
+ * The cap applies to every engine. An LSP engine would survive a larger
166
+ * scope, but an unscoped gate must behave identically whichever engine the
167
+ * workspace has bound — "exit 2, narrow the scope" — or flipping the binding
168
+ * would change verdicts for reasons that have nothing to do with the code.
169
+ * (Moved from `scripts/gates/symbol-referenced.lib.ts`, ENG-7284 D5.)
170
+ */
171
+ const DEFAULT_MAX_FILES = 8e3;
172
+ /** Thrown when the requested scope holds more files than the resolver will load. */
173
+ var ScopeTooBroadError = class extends Error {
174
+ fileCount;
175
+ maxFiles;
176
+ scopeLabel;
177
+ constructor(fileCount, maxFiles, scopeLabel) {
178
+ super(`scope "${scopeLabel}" contains ${fileCount} source files, above the ${maxFiles}-file limit. Loading them all would exhaust Node's heap and abort with no verdict. Narrow it with --scope (e.g. --scope apps/backender/src), or raise the cap deliberately with --max-files.`);
179
+ this.fileCount = fileCount;
180
+ this.maxFiles = maxFiles;
181
+ this.scopeLabel = scopeLabel;
182
+ this.name = "ScopeTooBroadError";
183
+ }
184
+ };
185
+ /**
186
+ * A toolchain the engine needs is missing or unusable in the target checkout
187
+ * (no `typescript`, no tsgo binary, no `rust-analyzer`, an index that never
188
+ * settles). Exit 2 in every CLI: a misconfigured environment, never a
189
+ * verdict. `remedy` says what to install.
190
+ */
191
+ var ToolchainError = class extends Error {
192
+ remedy;
193
+ constructor(message, remedy) {
194
+ super(`${message} ${remedy}`);
195
+ this.remedy = remedy;
196
+ this.name = "ToolchainError";
197
+ }
198
+ };
199
+
200
+ //#endregion
201
+ //#region ../code-intel/dist/lsp-client.js
202
+ /** How long to wait for any single server response before failing CLOSED. */
203
+ const RESPONSE_TIMEOUT_MS = 12e4;
204
+ /** How much of the server's stderr to keep for the exit diagnostic. */
205
+ const STDERR_TAIL_BYTES = 4096;
206
+ var LspClient = class LspClient {
207
+ options;
208
+ proc;
209
+ buffer = Buffer.alloc(0);
210
+ nextId = 1;
211
+ pending = /* @__PURE__ */ new Map();
212
+ opened = /* @__PURE__ */ new Set();
213
+ exited = null;
214
+ stderrTail = "";
215
+ listeners = /* @__PURE__ */ new Set();
216
+ name;
217
+ responseTimeoutMs;
218
+ rootDir;
219
+ constructor(options) {
220
+ this.options = options;
221
+ this.rootDir = path$1.resolve(options.rootDir);
222
+ this.name = options.name;
223
+ this.responseTimeoutMs = options.responseTimeoutMs ?? RESPONSE_TIMEOUT_MS;
224
+ const bin = options.bin;
225
+ const [cmd, args] = bin.endsWith(".js") ? [process.execPath, [bin, ...options.args]] : [bin, [...options.args]];
226
+ this.proc = spawn(cmd, args, {
227
+ cwd: this.rootDir,
228
+ stdio: [
229
+ "pipe",
230
+ "pipe",
231
+ "pipe"
232
+ ]
233
+ });
234
+ this.proc.stdout.on("data", (chunk) => this.onData(chunk));
235
+ this.proc.stderr.on("data", (chunk) => {
236
+ this.stderrTail = (this.stderrTail + chunk.toString("utf8")).slice(-STDERR_TAIL_BYTES);
237
+ });
238
+ this.proc.stdin.on("error", (err) => this.fail(err));
239
+ this.proc.on("exit", (code, signal) => {
240
+ const tail = this.stderrTail.trim();
241
+ this.fail(/* @__PURE__ */ new Error(`${this.name} exited (code=${code ?? "null"}, signal=${signal ?? "null"}) before answering` + (tail ? `\n--- ${this.name} stderr (tail) ---\n${tail}` : "")));
242
+ });
243
+ this.proc.on("error", (err) => this.fail(err));
244
+ }
245
+ /** Record the fatal error once and reject everything still waiting. */
246
+ fail(err) {
247
+ if (!this.exited) this.exited = err;
248
+ for (const p of this.pending.values()) p.reject(this.exited);
249
+ this.pending.clear();
250
+ }
251
+ /** Spawn the server and complete the `initialize` handshake. */
252
+ static async start(options) {
253
+ const client = new LspClient(options);
254
+ const rootUri = pathToFileURL(client.rootDir).href;
255
+ try {
256
+ await client.handshake(rootUri);
257
+ } catch (e) {
258
+ client.proc.kill();
259
+ throw e;
260
+ }
261
+ return client;
262
+ }
263
+ async handshake(rootUri) {
264
+ await this.request("initialize", {
265
+ processId: process.pid,
266
+ rootUri,
267
+ capabilities: mergeCapabilities({
268
+ workspace: { configuration: true },
269
+ textDocument: { documentSymbol: { hierarchicalDocumentSymbolSupport: true } }
270
+ }, this.options.capabilities ?? {}),
271
+ ...this.options.initializationOptions !== void 0 ? { initializationOptions: this.options.initializationOptions } : {},
272
+ workspaceFolders: [{
273
+ uri: rootUri,
274
+ name: path$1.basename(this.rootDir)
275
+ }]
276
+ });
277
+ this.notify("initialized", {});
278
+ }
279
+ write(message) {
280
+ if (this.exited) throw this.exited;
281
+ const body = JSON.stringify(message);
282
+ this.proc.stdin.write(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
283
+ }
284
+ onData(chunk) {
285
+ this.buffer = Buffer.concat([this.buffer, chunk]);
286
+ for (;;) {
287
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
288
+ if (headerEnd < 0) return;
289
+ const header = this.buffer.subarray(0, headerEnd).toString("ascii");
290
+ const lengthMatch = /Content-Length: (\d+)/i.exec(header);
291
+ if (!lengthMatch) {
292
+ this.buffer = this.buffer.subarray(headerEnd + 4);
293
+ continue;
294
+ }
295
+ const length = Number(lengthMatch[1]);
296
+ const bodyStart = headerEnd + 4;
297
+ if (this.buffer.length < bodyStart + length) return;
298
+ const body = this.buffer.subarray(bodyStart, bodyStart + length);
299
+ this.buffer = this.buffer.subarray(bodyStart + length);
300
+ let message;
301
+ try {
302
+ message = JSON.parse(body.toString("utf8"));
303
+ } catch (e) {
304
+ this.fail(/* @__PURE__ */ new Error(`${this.name} sent malformed JSON: ${e.message}`));
305
+ return;
306
+ }
307
+ this.dispatch(message);
308
+ }
309
+ }
310
+ dispatch(message) {
311
+ if (message.method !== void 0 && message.id !== void 0) {
312
+ let result = null;
313
+ if (message.method === "workspace/configuration") result = (message.params?.items ?? []).map(() => null);
314
+ this.write({
315
+ jsonrpc: "2.0",
316
+ id: message.id,
317
+ result
318
+ });
319
+ return;
320
+ }
321
+ if (message.method !== void 0) {
322
+ for (const listener of this.listeners) listener(message);
323
+ return;
324
+ }
325
+ if (message.id !== void 0) {
326
+ const waiter = this.pending.get(message.id);
327
+ if (waiter) {
328
+ this.pending.delete(message.id);
329
+ waiter.resolve(message);
330
+ }
331
+ }
332
+ }
333
+ request(method, params) {
334
+ const id = this.nextId++;
335
+ return new Promise((resolve$1, reject) => {
336
+ const timer = setTimeout(() => {
337
+ this.pending.delete(id);
338
+ reject(/* @__PURE__ */ new Error(`${this.name} did not answer ${method} within ${this.responseTimeoutMs} ms`));
339
+ }, this.responseTimeoutMs);
340
+ this.pending.set(id, {
341
+ resolve: (m) => {
342
+ clearTimeout(timer);
343
+ if (m.error) reject(/* @__PURE__ */ new Error(`${method} failed: ${m.error.message}`));
344
+ else resolve$1(m);
345
+ },
346
+ reject: (e) => {
347
+ clearTimeout(timer);
348
+ reject(e);
349
+ }
350
+ });
351
+ try {
352
+ this.write({
353
+ jsonrpc: "2.0",
354
+ id,
355
+ method,
356
+ params
357
+ });
358
+ } catch (e) {
359
+ clearTimeout(timer);
360
+ this.pending.delete(id);
361
+ reject(e instanceof Error ? e : new Error(String(e)));
362
+ }
363
+ });
364
+ }
365
+ notify(method, params) {
366
+ this.write({
367
+ jsonrpc: "2.0",
368
+ method,
369
+ params
370
+ });
371
+ }
372
+ /**
373
+ * Open a file so the server loads the project that contains it, and WAIT
374
+ * for that load to settle (obligation 2 + R1/F-11). Idempotent.
375
+ */
376
+ async open(absolutePath) {
377
+ const resolved = path$1.resolve(absolutePath);
378
+ if (this.opened.has(resolved)) return;
379
+ this.opened.add(resolved);
380
+ this.notify("textDocument/didOpen", { textDocument: {
381
+ uri: pathToFileURL(resolved).href,
382
+ languageId: languageIdOf(resolved),
383
+ version: 1,
384
+ text: fs.readFileSync(resolved, "utf8")
385
+ } });
386
+ await this.request("textDocument/documentSymbol", { textDocument: { uri: pathToFileURL(resolved).href } });
387
+ }
388
+ /** Hierarchical symbols of an (already opened) file. */
389
+ async documentSymbols(absolutePath) {
390
+ await this.open(absolutePath);
391
+ return (await this.request("textDocument/documentSymbol", { textDocument: { uri: pathToFileURL(path$1.resolve(absolutePath)).href } })).result ?? [];
392
+ }
393
+ /**
394
+ * Every reference to the symbol whose identifier is at `position` in
395
+ * `absolutePath`, INCLUDING the declaration itself. Answers only from loaded
396
+ * projects — see the header.
397
+ *
398
+ * `includeDeclaration` is deliberately `true`. In the compiler's model an
399
+ * import specifier (`import { X } from …`) is itself a declaration — of the
400
+ * local alias — so `false` drops every import line along with the real
401
+ * declaration, and the count no longer matches ts-morph's
402
+ * `findReferencesAsNodes()`, which lists them. The caller knows the real
403
+ * declaration's position (it asked for it) and removes exactly that one.
404
+ */
405
+ async references(absolutePath, position) {
406
+ await this.open(absolutePath);
407
+ return (await this.request("textDocument/references", {
408
+ textDocument: { uri: pathToFileURL(path$1.resolve(absolutePath)).href },
409
+ position,
410
+ context: { includeDeclaration: true }
411
+ })).result ?? [];
412
+ }
413
+ /**
414
+ * Where the symbol whose identifier is at `position` is DEFINED. Used to
415
+ * confirm a documentSymbol candidate is the declaration itself and not an
416
+ * alias of one — an import specifier in a wrapped `import { type X, … }`
417
+ * reports as a top-level symbol, and taking it as the declaration made the
418
+ * REAL declaration's identifier count as an external reference
419
+ * (ENG-5121 review F-021).
420
+ */
421
+ async definition(absolutePath, position) {
422
+ await this.open(absolutePath);
423
+ const result = (await this.request("textDocument/definition", {
424
+ textDocument: { uri: pathToFileURL(path$1.resolve(absolutePath)).href },
425
+ position
426
+ })).result;
427
+ if (!result) return [];
428
+ return (Array.isArray(result) ? result : [result]).map((l) => "targetUri" in l ? {
429
+ uri: l.targetUri,
430
+ range: l.targetSelectionRange
431
+ } : l);
432
+ }
433
+ /**
434
+ * `workspace/symbol` for `query`. Never the first call on tsgo (see the
435
+ * header); rust-analyzer answers it once indexing is quiescent.
436
+ */
437
+ async workspaceSymbol(query) {
438
+ return (await this.request("workspace/symbol", { query })).result ?? [];
439
+ }
440
+ /**
441
+ * Resolve with the first server notification named `method` that
442
+ * `accept` takes, or REJECT after `timeoutMs`, or as soon as the server
443
+ * dies. A timeout is a failure, never "it probably settled".
444
+ */
445
+ waitForNotification(method, accept, timeoutMs) {
446
+ if (this.exited) return Promise.reject(this.exited);
447
+ return new Promise((resolve$1, reject) => {
448
+ const done = (fn) => {
449
+ clearTimeout(timer);
450
+ clearInterval(poll);
451
+ this.listeners.delete(listener);
452
+ fn();
453
+ };
454
+ const listener = (m) => {
455
+ if (m.method === method && accept(m.params)) done(() => resolve$1(m.params));
456
+ };
457
+ const timer = setTimeout(() => done(() => reject(/* @__PURE__ */ new Error(`${this.name} sent no matching ${method} within ${timeoutMs} ms`))), timeoutMs);
458
+ const poll = setInterval(() => {
459
+ if (this.exited) done(() => reject(this.exited));
460
+ }, 250);
461
+ this.listeners.add(listener);
462
+ });
463
+ }
464
+ /** Shut the server down. Safe to call twice. */
465
+ async dispose() {
466
+ if (this.exited) return;
467
+ try {
468
+ await Promise.race([this.request("shutdown"), new Promise((resolve$1) => setTimeout(resolve$1, 2e3))]);
469
+ this.notify("exit");
470
+ } catch {}
471
+ this.proc.stdin?.end();
472
+ this.proc.kill();
473
+ }
474
+ };
475
+ /** The LSP `languageId` for a file, by extension. */
476
+ function languageIdOf(file) {
477
+ if (file.endsWith(".rs")) return "rust";
478
+ if (file.endsWith("x")) return "typescriptreact";
479
+ return "typescript";
480
+ }
481
+ /** `base` with `extra` merged in, one level deep (`workspace`, `textDocument`, …). */
482
+ function mergeCapabilities(base, extra) {
483
+ const out = { ...base };
484
+ for (const [key, value] of Object.entries(extra)) {
485
+ const prev = out[key];
486
+ out[key] = prev && typeof prev === "object" && value && typeof value === "object" ? {
487
+ ...prev,
488
+ ...value
489
+ } : value;
490
+ }
491
+ return out;
492
+ }
493
+ /** Convert a `file://` URI the server returned to an absolute path. */
494
+ function uriToPath(uri) {
495
+ return fileURLToPath(uri);
496
+ }
497
+
498
+ //#endregion
499
+ //#region ../code-intel/dist/workspaces.js
500
+ /**
501
+ * Open one source file in every workspace whose `package.json` lists
502
+ * `packageName` as a dependency, so the server loads those projects and
503
+ * `references` can see cross-package consumers. `within` restricts the
504
+ * candidates to workspaces that intersect the search scope — a scope of
505
+ * `apps/backender` has no use for `apps/client`'s project.
506
+ *
507
+ * For `isAutomated` in `packages/shared` that is the difference between 3
508
+ * refs / 1 file and 11 refs / 6 files (ENG-5121 §3.4 E5 → E6). Opening ANY
509
+ * file of the project is enough (E7) — `firstSourceFile` picks one.
510
+ *
511
+ * Returns the files it opened, so callers (and tests) can see what happened.
512
+ */
513
+ async function openDependentWorkspaces(client, packageName, within) {
514
+ const opened = [];
515
+ for (const ws of listWorkspaces(client.rootDir)) {
516
+ if (!ws.dependencies.has(packageName)) continue;
517
+ if (within && !within.some((scope) => intersects(ws.dir, scope))) continue;
518
+ const file = firstSourceFile(ws.dir);
519
+ if (!file) continue;
520
+ await client.open(file);
521
+ opened.push(file);
522
+ }
523
+ return opened;
524
+ }
525
+ let workspaceCache = null;
526
+ /**
527
+ * Every workspace package under `rootDir`, read from the root `package.json`'s
528
+ * `workspaces` globs. Only the one-level `dir/*` and literal `dir/name` forms
529
+ * are supported, which is every form this repo uses.
530
+ */
531
+ function listWorkspaces(rootDir) {
532
+ const root = path$1.resolve(rootDir);
533
+ if (workspaceCache?.root === root) return workspaceCache.list;
534
+ const list = [];
535
+ const patterns = readPackageJson(path$1.join(root, "package.json"))?.workspaces ?? [];
536
+ const seen = /* @__PURE__ */ new Set();
537
+ for (const pattern of patterns) {
538
+ const dirs = pattern.endsWith("/*") ? listDirs(path$1.join(root, pattern.slice(0, -2))) : [path$1.join(root, pattern)];
539
+ for (const dir of dirs) {
540
+ if (seen.has(dir)) continue;
541
+ seen.add(dir);
542
+ const pkg = readPackageJson(path$1.join(dir, "package.json"));
543
+ if (!pkg?.name) continue;
544
+ list.push({
545
+ dir,
546
+ name: pkg.name,
547
+ dependencies: new Set([
548
+ ...Object.keys(pkg.dependencies ?? {}),
549
+ ...Object.keys(pkg.devDependencies ?? {}),
550
+ ...Object.keys(pkg.peerDependencies ?? {})
551
+ ])
552
+ });
553
+ }
554
+ }
555
+ workspaceCache = {
556
+ root,
557
+ list
558
+ };
559
+ return list;
560
+ }
561
+ /** The workspace a file belongs to — nearest ancestor `package.json` with a name — or null. */
562
+ function workspaceOf(rootDir, absoluteFile) {
563
+ const root = path$1.resolve(rootDir);
564
+ const file = path$1.resolve(absoluteFile);
565
+ let best = null;
566
+ for (const ws of listWorkspaces(root)) if (file === ws.dir || file.startsWith(ws.dir + path$1.sep)) {
567
+ if (!best || ws.dir.length > best.dir.length) best = ws;
568
+ }
569
+ return best;
570
+ }
571
+ function readPackageJson(file) {
572
+ try {
573
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
574
+ if (parsed.workspaces && !Array.isArray(parsed.workspaces)) parsed.workspaces = parsed.workspaces.packages ?? [];
575
+ return parsed;
576
+ } catch {
577
+ return null;
578
+ }
579
+ }
580
+ function listDirs(parent) {
581
+ try {
582
+ return fs.readdirSync(parent, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path$1.join(parent, e.name));
583
+ } catch {
584
+ return [];
585
+ }
586
+ }
587
+ function intersects(a, b) {
588
+ const x = path$1.resolve(a);
589
+ const y = path$1.resolve(b);
590
+ return x === y || x.startsWith(y + path$1.sep) || y.startsWith(x + path$1.sep);
591
+ }
592
+ /** Root-level files that are in no workspace's source project. */
593
+ const CONFIG_FILE = /^(?:.*\.config|eslint|vitest|vite|playwright|tsup|tsdown|knip|commitlint|prettier)(?:\..*)?\.tsx?$/;
594
+ /** Where a workspace's source project lives, in the order to try. */
595
+ const SOURCE_DIRS = [
596
+ "src",
597
+ "app",
598
+ "lib",
599
+ "test",
600
+ "scripts"
601
+ ];
602
+ /**
603
+ * The first `.ts`/`.tsx` source file under `dir` (not a `.d.ts`, not a test),
604
+ * used purely to make the server load `dir`'s project — any file will do (E7)
605
+ * AS LONG AS IT IS IN THE PROJECT. A breadth-first walk from the workspace
606
+ * root found `vite.config.ts` / `eslint.config.ts` first for ~35 of 41
607
+ * workspaces, and those are in no `src/**`-scoped tsconfig, so the open
608
+ * loaded nothing and cross-workspace consumers stayed invisible (ENG-5121
609
+ * review F-029). Source directories are tried first; root-level config
610
+ * files are never the answer.
611
+ */
612
+ function firstSourceFile(dir) {
613
+ for (const sub of SOURCE_DIRS) {
614
+ const candidate = path$1.join(dir, sub);
615
+ if (fs.existsSync(candidate)) {
616
+ const found = firstSourceFileUnder(candidate);
617
+ if (found) return found;
618
+ }
619
+ }
620
+ return firstSourceFileUnder(dir, true);
621
+ }
622
+ function firstSourceFileUnder(dir, skipConfigs = false) {
623
+ const queue = [dir];
624
+ while (queue.length) {
625
+ const current = queue.shift();
626
+ let entries;
627
+ try {
628
+ entries = fs.readdirSync(current, { withFileTypes: true });
629
+ } catch {
630
+ continue;
631
+ }
632
+ entries.sort((a, b) => a.name.localeCompare(b.name));
633
+ for (const entry of entries) if (entry.isDirectory()) {
634
+ if (!IGNORED_DIRS.includes(entry.name)) queue.push(path$1.join(current, entry.name));
635
+ } else if (/\.tsx?$/.test(entry.name) && !entry.name.endsWith(".d.ts") && !/\.(test|spec)\.tsx?$/.test(entry.name) && !(skipConfigs && CONFIG_FILE.test(entry.name))) return path$1.join(current, entry.name);
636
+ }
637
+ return null;
638
+ }
639
+
640
+ //#endregion
641
+ //#region ../code-intel/dist/engines/tsgo.js
642
+ /**
643
+ * `typescript` and the tsgo binary, resolved from `projectRoot`'s own
644
+ * dependencies. See the header: never from this package's location.
645
+ */
646
+ function resolveTsToolchain(projectRoot) {
647
+ const manifest = path$1.join(path$1.resolve(projectRoot), "package.json");
648
+ if (!fs.existsSync(manifest)) throw new ToolchainError(`No package.json at ${projectRoot}, so typescript cannot be resolved from the checkout.`, "Run this from the root of a TypeScript repository.");
649
+ const requireFromRepo = createRequire(manifest);
650
+ let typescript;
651
+ try {
652
+ typescript = requireFromRepo("typescript");
653
+ } catch {
654
+ throw new ToolchainError(`typescript is not installed in ${projectRoot}.`, "Install it in this checkout (e.g. `yarn add -D typescript`).");
655
+ }
656
+ const platformPackage = `@typescript/native-preview-${process.platform}-${process.arch}`;
657
+ const exe = process.platform === "win32" ? "tsgo.exe" : "tsgo";
658
+ try {
659
+ const dir = path$1.dirname(requireFromRepo.resolve(`${platformPackage}/package.json`));
660
+ const native = path$1.join(dir, "lib", exe);
661
+ if (fs.existsSync(native)) return {
662
+ ts: typescript,
663
+ tsgoBin: native
664
+ };
665
+ } catch {}
666
+ try {
667
+ const dir = path$1.dirname(requireFromRepo.resolve("@typescript/native-preview/package.json"));
668
+ const launcher = path$1.join(dir, "bin", "tsgo.js");
669
+ if (fs.existsSync(launcher)) return {
670
+ ts: typescript,
671
+ tsgoBin: launcher
672
+ };
673
+ } catch {}
674
+ throw new ToolchainError(`The tsgo binary (@typescript/native-preview) is not installed in ${projectRoot}.`, "Install it in this checkout (e.g. `yarn add -D @typescript/native-preview`).");
675
+ }
676
+ /** LSP `SymbolKind` values a top-level declaration can carry. Type aliases report as Class (5). */
677
+ const TOP_LEVEL_KINDS = new Set([
678
+ 5,
679
+ 10,
680
+ 11,
681
+ 12,
682
+ 13,
683
+ 14
684
+ ]);
685
+ /** Class/interface members: Method 6, Property 7 (accessors also report as 7), Field 8. */
686
+ const MEMBER_KINDS = new Set([
687
+ 6,
688
+ 7,
689
+ 8
690
+ ]);
691
+ /** Containers whose children count as members. */
692
+ const CONTAINER_KINDS = new Set([5, 11]);
693
+ /**
694
+ * Files step 3 may load beyond the configured projects — extra opens plus
695
+ * the excluded files the compiler API answers. Each is a full module load
696
+ * with its import closure; past this many the name is not being resolved,
697
+ * it is being grepped, and the process is on its way to being OOM-killed.
698
+ */
699
+ const MAX_EXTRA_FILES = 200;
700
+ /**
701
+ * Files whose TEXT looks like it declares the symbol. Each costs a
702
+ * `documentSymbol` round trip (~150 ms) plus a `definition` per hit; for
703
+ * `id` inside apps/backender/src that is thousands of object-literal keys
704
+ * and minutes of wall clock before a single reference is asked for.
705
+ */
706
+ const MAX_DECLARATION_CANDIDATES = 200;
707
+ function escapeRegExp(text) {
708
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
709
+ }
710
+ /** Inside the repository and not under a `node_modules` (a dependency's .d.ts). */
711
+ function isRepoSource(file, projectRoot) {
712
+ const root = path$1.resolve(projectRoot) + path$1.sep;
713
+ return file.startsWith(root) && !file.includes(`${path$1.sep}node_modules${path$1.sep}`);
714
+ }
715
+ /** Files whose text looks like it declares `symbol` at top level. */
716
+ function topLevelCandidates(symbol, contents) {
717
+ return [...contents].filter(([, text]) => looksLikeTopLevelDeclaration(symbol, text)).map(([f]) => f);
718
+ }
719
+ /** Files whose text looks like it declares a MEMBER named `symbol`. */
720
+ function memberCandidates(symbol, contents) {
721
+ return [...contents].filter(([, text]) => looksLikeMemberDeclaration(symbol, text)).map(([f]) => f);
722
+ }
723
+ /** Files each tsconfig includes, computed once per config per engine call. */
724
+ var ProjectMembership = class {
725
+ ts;
726
+ projectRoot;
727
+ byConfig = /* @__PURE__ */ new Map();
728
+ constructor(ts, projectRoot) {
729
+ this.ts = ts;
730
+ this.projectRoot = projectRoot;
731
+ }
732
+ /**
733
+ * The tsconfig whose file set contains `file`, or null when no governing
734
+ * config includes it (no tsconfig up the tree, or excluded by the one
735
+ * there is).
736
+ */
737
+ coveringConfig(file) {
738
+ const config = findTsConfig(path$1.dirname(file), this.projectRoot);
739
+ if (!config) return null;
740
+ let files = this.byConfig.get(config);
741
+ if (!files) {
742
+ files = this.load(config);
743
+ this.byConfig.set(config, files);
744
+ }
745
+ return files.has(path$1.resolve(file)) ? config : null;
746
+ }
747
+ /** The governing tsconfig's compiler options (for a file it EXCLUDES). */
748
+ optionsFor(file) {
749
+ const config = findTsConfig(path$1.dirname(file), this.projectRoot);
750
+ if (!config) return {};
751
+ this.coveringConfig(file);
752
+ return this.optionsByConfig.get(config) ?? {};
753
+ }
754
+ optionsByConfig = /* @__PURE__ */ new Map();
755
+ load(config) {
756
+ const ts = this.ts;
757
+ const read = ts.readConfigFile(config, (p) => ts.sys.readFile(p));
758
+ if (read.error || !read.config) return /* @__PURE__ */ new Set();
759
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path$1.dirname(config));
760
+ this.optionsByConfig.set(config, parsed.options);
761
+ return new Set(parsed.fileNames.map((f) => path$1.resolve(f)));
762
+ }
763
+ };
764
+ /**
765
+ * References from files NO tsconfig covers, resolved by the compiler API
766
+ * with the governing tsconfig's OPTIONS. An excluded file opened into the
767
+ * server's inferred project has no `paths`, so an import through an alias
768
+ * (`~/x`) never resolves and its references vanish — correct code FAILED
769
+ * under the tsgo binding (ENG-5121 review F-022). tsgo exposes no way to
770
+ * give an inferred project compiler options (its LSP preferences are
771
+ * editor settings only), so those files are answered here instead: one
772
+ * language service over exactly those files, which pulls the declaring
773
+ * module in through the resolved import.
774
+ */
775
+ function referencesFromUncoveredFiles(ts, uncovered, declarations, membership) {
776
+ const out = /* @__PURE__ */ new Map();
777
+ if (uncovered.length === 0) return out;
778
+ const options = {
779
+ ...membership.optionsFor(uncovered[0]),
780
+ noEmit: true,
781
+ skipLibCheck: true
782
+ };
783
+ const roots = [...new Set([...uncovered, ...declarations.map((d) => d.file)])];
784
+ const service = ts.createLanguageService({
785
+ getCompilationSettings: () => options,
786
+ getScriptFileNames: () => roots,
787
+ getScriptVersion: () => "1",
788
+ getScriptSnapshot: (f) => fs.existsSync(f) ? ts.ScriptSnapshot.fromString(fs.readFileSync(f, "utf8")) : void 0,
789
+ getCurrentDirectory: () => process.cwd(),
790
+ getDefaultLibFileName: (o) => ts.getDefaultLibFilePath(o),
791
+ fileExists: (p) => ts.sys.fileExists(p),
792
+ readFile: (p, encoding) => ts.sys.readFile(p, encoding),
793
+ readDirectory: (...args) => ts.sys.readDirectory(...args),
794
+ directoryExists: (p) => ts.sys.directoryExists(p),
795
+ getDirectories: (p) => ts.sys.getDirectories(p)
796
+ });
797
+ const wanted = new Set(uncovered.map((f) => path$1.resolve(f)));
798
+ try {
799
+ for (const decl of declarations) {
800
+ const source = service.getProgram()?.getSourceFile(decl.file);
801
+ if (!source) continue;
802
+ const offset = source.getPositionOfLineAndCharacter(decl.position.line, decl.position.character);
803
+ for (const entry of service.getReferencesAtPosition(decl.file, offset) ?? []) {
804
+ const file = path$1.resolve(entry.fileName);
805
+ if (!wanted.has(file)) continue;
806
+ const refSource = service.getProgram()?.getSourceFile(entry.fileName);
807
+ if (!refSource) continue;
808
+ const pos = refSource.getLineAndCharacterOfPosition(entry.textSpan.start);
809
+ (out.get(file) ?? out.set(file, []).get(file)).push(pos);
810
+ }
811
+ }
812
+ } finally {
813
+ service.dispose();
814
+ }
815
+ return out;
816
+ }
817
+ /** Keep only candidates whose definition is the candidate itself. */
818
+ async function confirmDeclarations(client, candidates) {
819
+ const confirmed = [];
820
+ for (const c of candidates) {
821
+ const defs = await client.definition(c.file, c.position);
822
+ if (defs.some((d) => uriToPath(d.uri) === c.file && d.range.start.line === c.position.line) || defs.length === 0) confirmed.push(c);
823
+ }
824
+ return confirmed;
825
+ }
826
+ function topLevelDeclarations(symbols, name, file) {
827
+ return symbols.filter((s) => s.name === name && TOP_LEVEL_KINDS.has(s.kind)).map((s) => ({
828
+ file,
829
+ position: s.selectionRange.start
830
+ }));
831
+ }
832
+ function memberDeclarations(symbols, name, file) {
833
+ const out = [];
834
+ for (const container of symbols) {
835
+ if (!CONTAINER_KINDS.has(container.kind)) continue;
836
+ for (const child of container.children ?? []) if (child.name === name && MEMBER_KINDS.has(child.kind)) out.push({
837
+ file,
838
+ position: child.selectionRange.start
839
+ });
840
+ }
841
+ return out;
842
+ }
843
+ async function resolveOne$1(ts, client, symbol, request, contents, scopeFiles, membership) {
844
+ const { projectRoot, includeTests, tsConfigPath } = request;
845
+ const rel = (p) => relativeTo(projectRoot, p);
846
+ let declarations = [];
847
+ const topLevel = topLevelCandidates(symbol, contents);
848
+ if (topLevel.length > MAX_DECLARATION_CANDIDATES) throw new SymbolTooCommonError(symbol, topLevel.length, MAX_DECLARATION_CANDIDATES, "files in scope look like they declare it");
849
+ for (const file of topLevel) declarations.push(...topLevelDeclarations(await client.documentSymbols(file), symbol, file));
850
+ declarations = await confirmDeclarations(client, declarations);
851
+ let resolvedAsMember = false;
852
+ if (declarations.length === 0) {
853
+ const members = memberCandidates(symbol, contents);
854
+ if (members.length > MAX_DECLARATION_CANDIDATES) throw new SymbolTooCommonError(symbol, members.length, MAX_DECLARATION_CANDIDATES, "files in scope look like they declare a member of that name");
855
+ for (const file of members) declarations.push(...memberDeclarations(await client.documentSymbols(file), symbol, file));
856
+ declarations = await confirmDeclarations(client, declarations);
857
+ resolvedAsMember = declarations.length > 0;
858
+ }
859
+ if (declarations.length === 0) return {
860
+ symbol,
861
+ result: notFound(scopeFiles.size, tsConfigPath),
862
+ locations: []
863
+ };
864
+ for (const decl of new Set(declarations.map((d) => d.file))) {
865
+ const ws = workspaceOf(projectRoot, decl);
866
+ if (ws) await openDependentWorkspaces(client, ws.name, request.scopeRoots);
867
+ }
868
+ const mention = /* @__PURE__ */ new RegExp(`\\b${escapeRegExp(symbol)}\\b`);
869
+ const loadedConfigs = /* @__PURE__ */ new Set();
870
+ const uncovered = [];
871
+ let extraFiles = 0;
872
+ for (const [file, text] of contents) {
873
+ if (!mention.test(text)) continue;
874
+ if (!includeTests && isTestFile(file)) continue;
875
+ const config = membership.coveringConfig(file);
876
+ if (config) {
877
+ if (loadedConfigs.has(config)) continue;
878
+ loadedConfigs.add(config);
879
+ await client.open(file);
880
+ continue;
881
+ }
882
+ if (++extraFiles > MAX_EXTRA_FILES) throw new SymbolTooCommonError(symbol, extraFiles, MAX_EXTRA_FILES, "files outside every tsconfig project mention it");
883
+ if (findTsConfig(path$1.dirname(file), projectRoot)) uncovered.push(file);
884
+ else await client.open(file);
885
+ }
886
+ const fromUncovered = referencesFromUncoveredFiles(ts, uncovered, declarations, membership);
887
+ const declarationFiles = /* @__PURE__ */ new Set();
888
+ const referencingFiles = /* @__PURE__ */ new Set();
889
+ const locations = [];
890
+ const seen = /* @__PURE__ */ new Set();
891
+ let referenceCount = 0;
892
+ let excludedTestReferences = 0;
893
+ for (const decl of declarations) {
894
+ declarationFiles.add(decl.file);
895
+ locations.push({
896
+ file: rel(decl.file),
897
+ line: decl.position.line + 1,
898
+ col: decl.position.character + 1,
899
+ kind: "declaration"
900
+ });
901
+ const merged = [...(await client.references(decl.file, decl.position)).map((r) => ({
902
+ file: uriToPath(r.uri),
903
+ start: r.range.start
904
+ })), ...[...fromUncovered].flatMap(([file, positions]) => positions.map((start) => ({
905
+ file,
906
+ start
907
+ })))];
908
+ for (const ref of merged) {
909
+ const refFile = ref.file;
910
+ if (refFile === decl.file) continue;
911
+ if (!isRepoSource(refFile, projectRoot)) continue;
912
+ const key = `${refFile}:${ref.start.line}:${ref.start.character}`;
913
+ if (seen.has(key)) continue;
914
+ seen.add(key);
915
+ const location = {
916
+ file: rel(refFile),
917
+ line: ref.start.line + 1,
918
+ col: ref.start.character + 1
919
+ };
920
+ if (!includeTests && isTestFile(refFile)) {
921
+ excludedTestReferences++;
922
+ locations.push({
923
+ ...location,
924
+ kind: "test"
925
+ });
926
+ continue;
927
+ }
928
+ referenceCount++;
929
+ referencingFiles.add(refFile);
930
+ locations.push({
931
+ ...location,
932
+ kind: "reference"
933
+ });
934
+ }
935
+ }
936
+ return {
937
+ symbol,
938
+ result: {
939
+ found: true,
940
+ resolvedAsMember,
941
+ declarationFiles: [...declarationFiles].map(rel).sort(),
942
+ referenceCount,
943
+ referencingFiles: [...referencingFiles].map(rel).sort(),
944
+ excludedTestReferences,
945
+ filesScanned: scopeFiles.size,
946
+ tsConfigPath
947
+ },
948
+ locations
949
+ };
950
+ }
951
+ /**
952
+ * ONE server per call — and therefore one per process for the CLIs — for N
953
+ * symbols. The server's project loads are the expensive part (2–2.5 s for
954
+ * `apps/client`); each `references` request after that is tens of
955
+ * milliseconds, which is what keeps G47's eight symbols far inside the
956
+ * executor's 30 s cap.
957
+ */
958
+ const tsgoEngine = async (request) => {
959
+ const { ts, tsgoBin } = resolveTsToolchain(request.toolchainRoot ?? request.projectRoot);
960
+ const contents = /* @__PURE__ */ new Map();
961
+ for (const file of request.files) try {
962
+ contents.set(file, fs.readFileSync(file, "utf8"));
963
+ } catch {}
964
+ const scopeFiles = new Set(request.files);
965
+ const client = await LspClient.start({
966
+ rootDir: request.projectRoot,
967
+ bin: tsgoBin,
968
+ args: ["--lsp", "--stdio"],
969
+ name: "tsgo --lsp"
970
+ });
971
+ try {
972
+ for (const scopeRoot of request.scopeRoots) {
973
+ if (findTsConfig(scopeRoot, request.projectRoot)) continue;
974
+ const root = path$1.resolve(scopeRoot);
975
+ for (const file of request.files) if (file === root || file.startsWith(root + path$1.sep)) await client.open(file);
976
+ }
977
+ const membership = new ProjectMembership(ts, request.projectRoot);
978
+ const results = [];
979
+ for (const symbol of request.symbols) results.push(await resolveOne$1(ts, client, symbol, request, contents, scopeFiles, membership));
980
+ return results;
981
+ } finally {
982
+ await client.dispose();
983
+ }
984
+ };
985
+
986
+ //#endregion
987
+ //#region ../code-intel/dist/engines/rust-test-code.js
988
+ /**
989
+ * [issue:ENG-7284] D5 — which lines of a Rust file are TEST code, so the
990
+ * rust-analyzer engine can discount references from them.
991
+ *
992
+ * Pure and linear: each file is lexed ONCE into its comment/string spans and
993
+ * its matching brace pairs, and every question after that (is this position
994
+ * code, where does this item end, which block encloses it) is a lookup.
995
+ * Review round 2 measured the earlier per-attribute rescan at 703 ms for one
996
+ * 100 KB file.
997
+ *
998
+ * ## What is test code
999
+ *
1000
+ * - an ITEM whose outer attribute is `#[test]`, `#[<path>::test]`, or a
1001
+ * `#[cfg(...)]` predicate that cannot hold when `test` is false
1002
+ * (`cfg(test)`, `cfg(all(test, unix))`; not `cfg(any(test, feature))`,
1003
+ * which a feature build turns on, and not `cfg(not(any(test, mock)))`).
1004
+ * Items with a body run to their matching `}`; body-less items (`use`,
1005
+ * `const`, `static`, `type`, `extern crate`, a tuple struct) run to their
1006
+ * `;`. An attribute on anything that is not an item — a struct field, an
1007
+ * enum variant, a parameter, a statement — opens no region;
1008
+ * - the whole file, for an inner `#![cfg(test)]` at file scope, or the
1009
+ * enclosing block for one inside `mod x { … }`;
1010
+ * - an out-of-line `#[cfg(test)] mod x;` is reported by name, and the caller
1011
+ * marks that module's file(s) as test code.
1012
+ */
1013
+ /**
1014
+ * Cargo's build output and vendored crates: `target/` or `vendor/` beside a
1015
+ * `Cargo.toml`. A module that happens to be named `target` or `vendor`
1016
+ * (`src/target/mod.rs`) is source and is walked (review round 3 L7).
1017
+ */
1018
+ const rustSkipDir = (parent, name) => (name === "target" || name === "vendor") && fs.existsSync(path$1.join(parent, "Cargo.toml"));
1019
+ const RAW_STRING = /(?:b|c)?r(#*)"/y;
1020
+ const CHAR_LITERAL = /'(?:\\(?:u\{[0-9a-fA-F]+\}|x[0-9a-fA-F]{2}|.)|[^\\'\n])'/uy;
1021
+ var RustSource = class {
1022
+ text;
1023
+ /** Sorted, non-overlapping `[start, end)` spans of comments and strings. */
1024
+ nonCode = [];
1025
+ /** Code `{` offset → its matching `}` offset. */
1026
+ braceClose = /* @__PURE__ */ new Map();
1027
+ /** Code brace pairs, in order of their `{`. */
1028
+ bracePairs = [];
1029
+ lineStarts = [0];
1030
+ constructor(text) {
1031
+ this.text = text;
1032
+ const stack = [];
1033
+ for (let i = 0; i < text.length;) {
1034
+ const ch = text[i];
1035
+ const next = text[i + 1];
1036
+ if (ch === "\n") {
1037
+ this.lineStarts.push(i + 1);
1038
+ i++;
1039
+ continue;
1040
+ }
1041
+ const end = this.nonCodeEnd(i, ch, next);
1042
+ if (end > i) {
1043
+ this.nonCode.push([i, end]);
1044
+ for (let j = i; j < end; j++) if (text[j] === "\n") this.lineStarts.push(j + 1);
1045
+ i = end;
1046
+ continue;
1047
+ }
1048
+ if (ch === "{") stack.push(i);
1049
+ else if (ch === "}") {
1050
+ const open = stack.pop();
1051
+ if (open !== void 0) {
1052
+ this.braceClose.set(open, i);
1053
+ this.bracePairs.push([open, i]);
1054
+ }
1055
+ }
1056
+ i++;
1057
+ }
1058
+ this.bracePairs.sort((a, b) => a[0] - b[0]);
1059
+ }
1060
+ /** End of the comment/string starting at `i`, or `i` when there is none. */
1061
+ nonCodeEnd(i, ch, next) {
1062
+ const text = this.text;
1063
+ if (ch === "/" && next === "/") {
1064
+ const nl = text.indexOf("\n", i);
1065
+ return nl < 0 ? text.length : nl;
1066
+ }
1067
+ if (ch === "/" && next === "*") {
1068
+ let depth = 1;
1069
+ let j = i + 2;
1070
+ while (j < text.length && depth > 0) if (text[j] === "/" && text[j + 1] === "*") {
1071
+ depth++;
1072
+ j += 2;
1073
+ } else if (text[j] === "*" && text[j + 1] === "/") {
1074
+ depth--;
1075
+ j += 2;
1076
+ } else j++;
1077
+ return j;
1078
+ }
1079
+ if ((ch === "r" || ch === "b" || ch === "c") && !/\w/.test(text[i - 1] ?? "")) {
1080
+ RAW_STRING.lastIndex = i;
1081
+ const raw = RAW_STRING.exec(text);
1082
+ if (raw) {
1083
+ const close = `"${raw[1]}`;
1084
+ const end = text.indexOf(close, i + raw[0].length);
1085
+ return end < 0 ? text.length : end + close.length;
1086
+ }
1087
+ }
1088
+ if (ch === "\"") {
1089
+ let j = i + 1;
1090
+ while (j < text.length && text[j] !== "\"") j += text[j] === "\\" ? 2 : 1;
1091
+ return Math.min(j + 1, text.length);
1092
+ }
1093
+ if (ch === "'") {
1094
+ CHAR_LITERAL.lastIndex = i;
1095
+ const lit = CHAR_LITERAL.exec(text);
1096
+ if (lit) return i + lit[0].length;
1097
+ }
1098
+ return i;
1099
+ }
1100
+ /** The non-code span containing `pos`, or undefined when `pos` is code. */
1101
+ spanAt(pos) {
1102
+ let lo = 0;
1103
+ let hi = this.nonCode.length - 1;
1104
+ while (lo <= hi) {
1105
+ const mid = lo + hi >> 1;
1106
+ const [s, e] = this.nonCode[mid];
1107
+ if (pos < s) hi = mid - 1;
1108
+ else if (pos >= e) lo = mid + 1;
1109
+ else return this.nonCode[mid];
1110
+ }
1111
+ }
1112
+ isCode(pos) {
1113
+ return this.spanAt(pos) === void 0;
1114
+ }
1115
+ /** `pos`, or the end of the non-code span it falls in. */
1116
+ skip(pos) {
1117
+ return this.spanAt(pos)?.[1] ?? pos;
1118
+ }
1119
+ lineAt(offset) {
1120
+ let lo = 0;
1121
+ let hi = this.lineStarts.length - 1;
1122
+ while (lo < hi) {
1123
+ const mid = lo + hi + 1 >> 1;
1124
+ if (this.lineStarts[mid] <= offset) lo = mid;
1125
+ else hi = mid - 1;
1126
+ }
1127
+ return lo;
1128
+ }
1129
+ /** The innermost brace pair enclosing `pos`, or undefined at file scope. */
1130
+ enclosing(pos) {
1131
+ let best;
1132
+ for (const pair of this.bracePairs) {
1133
+ if (pair[0] > pos) break;
1134
+ if (pair[1] > pos) best = pair;
1135
+ }
1136
+ return best;
1137
+ }
1138
+ /** Index of the `]` matching the code `[` at `open`, or -1. */
1139
+ bracketClose(open) {
1140
+ let depth = 0;
1141
+ for (let i = open; i < this.text.length;) {
1142
+ const skipped = this.skip(i);
1143
+ if (skipped !== i) {
1144
+ i = skipped;
1145
+ continue;
1146
+ }
1147
+ const ch = this.text[i];
1148
+ if (ch === "[") depth++;
1149
+ else if (ch === "]" && --depth === 0) return i;
1150
+ i++;
1151
+ }
1152
+ return -1;
1153
+ }
1154
+ /**
1155
+ * The first code character in `stops` at bracket depth 0 at or after
1156
+ * `from`, or -1. `()`, `[]` and nested `{}` always count; with `angles`,
1157
+ * `<…>` does too (an item signature: `impl Foo<{ N + 1 }> {` must not stop
1158
+ * at the const-generic brace, review round 3 L3), where `->` and `=>` are
1159
+ * not closers.
1160
+ */
1161
+ findAtDepth0(from, stops, angles = false) {
1162
+ let depth = 0;
1163
+ for (let i = from; i < this.text.length;) {
1164
+ const skipped = this.skip(i);
1165
+ if (skipped !== i) {
1166
+ i = skipped;
1167
+ continue;
1168
+ }
1169
+ const ch = this.text[i];
1170
+ const prev = this.text[i - 1];
1171
+ if (depth === 0 && stops.includes(ch)) return i;
1172
+ if (ch === "(" || ch === "[" || ch === "{" || angles && ch === "<") depth++;
1173
+ else if (ch === ")" || ch === "]" || ch === "}" || angles && ch === ">" && prev !== "-" && prev !== "=") {
1174
+ if (depth === 0) return -1;
1175
+ depth--;
1176
+ }
1177
+ i++;
1178
+ }
1179
+ return -1;
1180
+ }
1181
+ };
1182
+ /**
1183
+ * Whether a `cfg(...)` predicate can be true when `test` is false. Kleene
1184
+ * logic: `test` is false, every other option is unknown. The item is test
1185
+ * code exactly when the result is definitely false. A predicate that does
1186
+ * not parse is treated as production (never hides a reference).
1187
+ */
1188
+ function cfgIsTestOnly(predicate) {
1189
+ let pos = 0;
1190
+ const src = predicate;
1191
+ const ws = () => {
1192
+ while (pos < src.length && /\s/.test(src[pos])) pos++;
1193
+ };
1194
+ const parse = () => {
1195
+ ws();
1196
+ const ident = /^[A-Za-z_][\w]*/.exec(src.slice(pos));
1197
+ if (!ident) throw new Error("cfg: expected an identifier");
1198
+ const name = ident[0];
1199
+ pos += name.length;
1200
+ ws();
1201
+ if (src[pos] === "=") {
1202
+ pos++;
1203
+ ws();
1204
+ const str = /^"(?:[^"\\]|\\.)*"/.exec(src.slice(pos));
1205
+ if (!str) throw new Error("cfg: expected a string");
1206
+ pos += str[0].length;
1207
+ return null;
1208
+ }
1209
+ if (src[pos] !== "(") return name === "test" ? false : null;
1210
+ pos++;
1211
+ const args = [];
1212
+ ws();
1213
+ while (src[pos] !== ")") {
1214
+ args.push(parse());
1215
+ ws();
1216
+ if (src[pos] === ",") {
1217
+ pos++;
1218
+ ws();
1219
+ } else if (src[pos] !== ")") throw new Error("cfg: expected , or )");
1220
+ }
1221
+ pos++;
1222
+ if (name === "not") {
1223
+ if (args.length !== 1) throw new Error("cfg: not takes one argument");
1224
+ return args[0] === null ? null : !args[0];
1225
+ }
1226
+ if (name === "all") {
1227
+ if (args.includes(false)) return false;
1228
+ return args.every((a) => a === true) ? true : null;
1229
+ }
1230
+ if (name === "any") {
1231
+ if (args.includes(true)) return true;
1232
+ return args.every((a) => a === false) ? false : null;
1233
+ }
1234
+ return null;
1235
+ };
1236
+ try {
1237
+ const result = parse();
1238
+ ws();
1239
+ return pos === src.length && result === false;
1240
+ } catch {
1241
+ return false;
1242
+ }
1243
+ }
1244
+ /** Attribute names (last path segment) that make an item a test. */
1245
+ const TEST_ITEM_ATTRIBUTES = new Set([
1246
+ "test",
1247
+ "rstest",
1248
+ "test_case",
1249
+ "bench"
1250
+ ]);
1251
+ /** Whether an attribute (name, arguments) marks its item as test code. */
1252
+ function isTestAttribute(name, args) {
1253
+ const last = name.split("::").pop() ?? name;
1254
+ if (TEST_ITEM_ATTRIBUTES.has(last)) return true;
1255
+ return name === "cfg" && args !== void 0 && cfgIsTestOnly(args);
1256
+ }
1257
+ const ATTRIBUTE_START = /#(!?)\[/g;
1258
+ const ATTRIBUTE_BODY = /^\s*([\w:]+)\s*(?:\(([\s\S]*)\))?\s*$/;
1259
+ const ITEM = /^(?:pub(?:\s*\([^)]*\))?\s+)?(?:(?:default|async|unsafe|const|extern(?:\s+"[^"]*")?)\s+)*(mod|fn|impl|struct|enum|union|trait|const|static|use|type|extern\s+crate|macro_rules!)(?![\w])/;
1260
+ /** `extern "C" { … }` (optionally `unsafe`), review round 3 L1. */
1261
+ const EXTERN_BLOCK = /^(?:pub(?:\s*\([^)]*\))?\s+)?(?:unsafe\s+)?extern(?:\s+"[^"]*")?\s*\{/;
1262
+ /** A macro invocation used as an item: `mock! {…}`, `thread_local!(…);` (L2). */
1263
+ const MACRO_ITEM = /^(?:[A-Za-z_]\w*::)*[A-Za-z_]\w*!\s*([({[])/;
1264
+ /** `#[path = "…"]` immediately before (possibly among other attributes). */
1265
+ const PATH_ATTRIBUTE = /#\[\s*path\s*=\s*"([^"]+)"\s*\]/;
1266
+ /** A `#[path]` attached to the attribute run that ends at the match. */
1267
+ const PATH_BEFORE = /#\[\s*path\s*=\s*"([^"]+)"\s*\](?:\s*#\[[^\]]*\])*\s*$/;
1268
+ /** An out-of-line module declaration. */
1269
+ const MOD_DECL = /\bmod\s+([A-Za-z_]\w*)\s*;/g;
1270
+ /** Items whose extent is a `{ … }` body (or a `;` when they have none). */
1271
+ const BLOCK_ITEMS = new Set([
1272
+ "mod",
1273
+ "fn",
1274
+ "impl",
1275
+ "struct",
1276
+ "enum",
1277
+ "union",
1278
+ "trait",
1279
+ "macro_rules!"
1280
+ ]);
1281
+ function cfgTestRegions(text) {
1282
+ const src = new RustSource(text);
1283
+ const ranges = [];
1284
+ const outOfLine = [];
1285
+ const outOfLinePaths = [];
1286
+ let wholeFile = false;
1287
+ for (const match of text.matchAll(ATTRIBUTE_START)) {
1288
+ const at = match.index;
1289
+ if (!src.isCode(at)) continue;
1290
+ const open = at + match[0].length - 1;
1291
+ const close = src.bracketClose(open);
1292
+ if (close < 0) continue;
1293
+ const body = ATTRIBUTE_BODY.exec(text.slice(open + 1, close));
1294
+ if (!body || !isTestAttribute(body[1] ?? "", body[2])) continue;
1295
+ if (match[1] === "!") {
1296
+ const block = src.enclosing(at);
1297
+ if (!block) wholeFile = true;
1298
+ else ranges.push([src.lineAt(block[0]), src.lineAt(block[1])]);
1299
+ continue;
1300
+ }
1301
+ let p = close + 1;
1302
+ let pathTarget = PATH_BEFORE.exec(text.slice(Math.max(0, at - 300), at))?.[1];
1303
+ for (;;) {
1304
+ while (p < text.length && (/\s/.test(text[p]) || !src.isCode(p))) p = src.isCode(p) ? p + 1 : src.skip(p);
1305
+ if (text[p] === "#" && text[p + 1] === "[") {
1306
+ const next = src.bracketClose(p + 1);
1307
+ if (next < 0) break;
1308
+ pathTarget = PATH_ATTRIBUTE.exec(text.slice(p, next + 1))?.[1] ?? pathTarget;
1309
+ p = next + 1;
1310
+ continue;
1311
+ }
1312
+ break;
1313
+ }
1314
+ const head = text.slice(p, p + 200);
1315
+ const externBlock = EXTERN_BLOCK.exec(head);
1316
+ if (externBlock) {
1317
+ const end$1 = src.braceClose.get(p + externBlock[0].length - 1);
1318
+ if (end$1 !== void 0) ranges.push([src.lineAt(at), src.lineAt(end$1)]);
1319
+ continue;
1320
+ }
1321
+ const item = ITEM.exec(head);
1322
+ if (!item) {
1323
+ const macro = MACRO_ITEM.exec(head);
1324
+ if (macro) {
1325
+ const open$1 = p + macro[0].length - 1;
1326
+ const end$1 = text[open$1] === "{" ? src.braceClose.get(open$1) : src.findAtDepth0(open$1, ";");
1327
+ if (end$1 !== void 0 && end$1 >= 0) ranges.push([src.lineAt(at), src.lineAt(end$1)]);
1328
+ }
1329
+ continue;
1330
+ }
1331
+ const kind = item[1].replace(/\s+/g, " ");
1332
+ const afterKeyword = p + item[0].length;
1333
+ if (kind === "mod") {
1334
+ const decl = /^\s*([A-Za-z_]\w*)\s*;/.exec(text.slice(afterKeyword));
1335
+ if (decl) {
1336
+ if (pathTarget) outOfLinePaths.push(pathTarget);
1337
+ else outOfLine.push(decl[1]);
1338
+ continue;
1339
+ }
1340
+ }
1341
+ let end;
1342
+ if (BLOCK_ITEMS.has(kind)) {
1343
+ const stop = src.findAtDepth0(afterKeyword, "{;", true);
1344
+ if (stop < 0) continue;
1345
+ end = text[stop] === "{" ? src.braceClose.get(stop) ?? -1 : stop;
1346
+ } else end = src.findAtDepth0(afterKeyword, ";");
1347
+ if (end < 0) continue;
1348
+ ranges.push([src.lineAt(at), src.lineAt(end)]);
1349
+ }
1350
+ const declaredMods = [];
1351
+ for (const m of text.matchAll(MOD_DECL)) if (src.isCode(m.index)) declaredMods.push(m[1]);
1352
+ return {
1353
+ ranges,
1354
+ outOfLine,
1355
+ wholeFile,
1356
+ outOfLinePaths,
1357
+ declaredMods
1358
+ };
1359
+ }
1360
+
1361
+ //#endregion
1362
+ //#region ../code-intel/dist/engines/rust-analyzer.js
1363
+ /** The files the Rust engine treats as source. */
1364
+ const RUST_SOURCE = /\.rs$/;
1365
+ const RA_BIN = "rust-analyzer";
1366
+ const INSTALL_REMEDY = "Install it with `rustup component add rust-analyzer` and make sure `rust-analyzer` is on PATH.";
1367
+ /** Default bound on the indexing wait; `CODE_INTEL_RA_TIMEOUT_MS` overrides. */
1368
+ const DEFAULT_INDEX_TIMEOUT_MS = 3e5;
1369
+ function indexTimeoutMs(env = process.env) {
1370
+ const raw = Number(env.CODE_INTEL_RA_TIMEOUT_MS);
1371
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INDEX_TIMEOUT_MS;
1372
+ }
1373
+ /**
1374
+ * Refuse up front when `rust-analyzer` cannot run. rustup installs a proxy
1375
+ * named `rust-analyzer` for every toolchain, which exits non-zero with
1376
+ * "Unknown binary" until the component is added, so existence on PATH is not
1377
+ * enough: `--version` must succeed.
1378
+ */
1379
+ function assertRustAnalyzer(bin = RA_BIN) {
1380
+ const probe = spawnSync(bin, ["--version"], { encoding: "utf8" });
1381
+ if (probe.error || probe.status !== 0) {
1382
+ const detail = (probe.stderr || probe.error?.message || "").trim();
1383
+ throw new ToolchainError(`rust-analyzer is not available${detail ? ` (${detail.split("\n")[0]})` : ""}.`, INSTALL_REMEDY);
1384
+ }
1385
+ }
1386
+ /**
1387
+ * The directory rust-analyzer should treat as the workspace: the HIGHEST
1388
+ * directory between the scope and the repository root that holds a
1389
+ * `Cargo.toml`, so a scope inside one member of a Cargo workspace still
1390
+ * loads the whole workspace (and its cross-crate references).
1391
+ */
1392
+ function cargoRoot(scopeRoot, projectRoot) {
1393
+ const stop = path$1.resolve(projectRoot);
1394
+ let dir = path$1.resolve(scopeRoot);
1395
+ if (fs.statSync(dir, { throwIfNoEntry: false })?.isFile()) dir = path$1.dirname(dir);
1396
+ let found = null;
1397
+ for (;;) {
1398
+ if (fs.existsSync(path$1.join(dir, "Cargo.toml"))) found = dir;
1399
+ if (dir === stop) break;
1400
+ const parent = path$1.dirname(dir);
1401
+ if (parent === dir) break;
1402
+ dir = parent;
1403
+ }
1404
+ if (!found) throw new ToolchainError(`No Cargo.toml at or above ${relativeTo(projectRoot, scopeRoot)}.`, "Point --scope inside a Cargo crate or workspace.");
1405
+ return found;
1406
+ }
1407
+ /**
1408
+ * A file that is test code in full, by its path alone: under a `tests/`
1409
+ * directory of the crate, or named `tests.rs` / `*_test.rs` / `*_tests.rs`.
1410
+ * Judged relative to `crateRoot`, so a checkout that itself sits under some
1411
+ * `tests/` directory is not all test code.
1412
+ */
1413
+ function isRustTestPath(file, crateRoot) {
1414
+ const resolved = path$1.resolve(file);
1415
+ const parts = (crateRoot ? path$1.relative(crateRoot, resolved) : resolved).split(path$1.sep);
1416
+ if (parts.slice(0, -1).includes("tests")) return true;
1417
+ const base = parts[parts.length - 1] ?? "";
1418
+ return base === "tests.rs" || base.endsWith("_test.rs") || base.endsWith("_tests.rs");
1419
+ }
1420
+ /**
1421
+ * Answers "is this location test code?" for one crate. Every `.rs` file of
1422
+ * the crate is scanned up front for out-of-line `#[cfg(test)] mod x;`
1423
+ * declarations, so a reference in `x.rs` is test code whichever file the
1424
+ * server happens to return first.
1425
+ */
1426
+ var TestCode = class {
1427
+ crateRoot;
1428
+ regions = /* @__PURE__ */ new Map();
1429
+ testFiles = /* @__PURE__ */ new Set();
1430
+ constructor(crateRoot) {
1431
+ this.crateRoot = crateRoot;
1432
+ for (const file of listSourceFiles(crateRoot, RUST_SOURCE, rustSkipDir)) this.regionsOf(path$1.resolve(file));
1433
+ }
1434
+ isTest(file, line) {
1435
+ const resolved = path$1.resolve(file);
1436
+ if (isRustTestPath(resolved, this.crateRoot) || this.testFiles.has(resolved)) return true;
1437
+ const { ranges, wholeFile } = this.regionsOf(resolved);
1438
+ return wholeFile || ranges.some(([a, b]) => line >= a && line <= b);
1439
+ }
1440
+ regionsOf(file) {
1441
+ const cached = this.regions.get(file);
1442
+ if (cached) return cached;
1443
+ let text = "";
1444
+ try {
1445
+ text = fs.readFileSync(file, "utf8");
1446
+ } catch {}
1447
+ const regions = cfgTestRegions(text);
1448
+ const dir = path$1.dirname(file);
1449
+ const stem = path$1.basename(file, ".rs");
1450
+ const moduleDir = [
1451
+ "mod",
1452
+ "lib",
1453
+ "main"
1454
+ ].includes(stem) ? dir : path$1.join(dir, stem);
1455
+ const markModule = (name) => {
1456
+ this.testFiles.add(path$1.join(moduleDir, `${name}.rs`));
1457
+ for (const f of listSourceFiles(path$1.join(moduleDir, name), RUST_SOURCE, rustSkipDir)) this.testFiles.add(path$1.resolve(f));
1458
+ };
1459
+ for (const name of regions.outOfLine) markModule(name);
1460
+ for (const target of regions.outOfLinePaths) this.testFiles.add(path$1.resolve(dir, target));
1461
+ if (regions.wholeFile || this.testFiles.has(file)) for (const name of regions.declaredMods) markModule(name);
1462
+ this.regions.set(file, regions);
1463
+ return regions;
1464
+ }
1465
+ };
1466
+ function inside(file, roots) {
1467
+ const f = path$1.resolve(file);
1468
+ return roots.some((r) => {
1469
+ const root = path$1.resolve(r);
1470
+ return f === root || f.startsWith(root + path$1.sep);
1471
+ });
1472
+ }
1473
+ /** The identifier position of `name` declared in `file` within `range`. */
1474
+ function identifierIn(symbols, name, range) {
1475
+ const within = (p) => (p.line > range.start.line || p.line === range.start.line && p.character >= range.start.character) && (p.line < range.end.line || p.line === range.end.line && p.character <= range.end.character);
1476
+ const stack = [...symbols];
1477
+ while (stack.length) {
1478
+ const s = stack.pop();
1479
+ if (s.name === name && within(s.selectionRange.start)) return s.selectionRange.start;
1480
+ stack.push(...s.children ?? []);
1481
+ }
1482
+ return null;
1483
+ }
1484
+ async function resolveOne(client, symbol, request, testCode) {
1485
+ const { projectRoot, includeTests } = request;
1486
+ const rel = (p) => relativeTo(projectRoot, p);
1487
+ const hits = (await client.workspaceSymbol(symbol)).filter((s) => s.name === symbol && s.location.uri.startsWith("file:") && inside(uriToPath(s.location.uri), request.scopeRoots));
1488
+ const declarations = [];
1489
+ const seenDecl = /* @__PURE__ */ new Set();
1490
+ for (const hit of hits) {
1491
+ const file = uriToPath(hit.location.uri);
1492
+ const position = identifierIn(await client.documentSymbols(file), symbol, hit.location.range) ?? hit.location.range.start;
1493
+ const key = `${file}:${position.line}:${position.character}`;
1494
+ if (seenDecl.has(key)) continue;
1495
+ seenDecl.add(key);
1496
+ declarations.push({
1497
+ file,
1498
+ position
1499
+ });
1500
+ }
1501
+ if (declarations.length === 0) return {
1502
+ symbol,
1503
+ result: notFound(request.files.length, null),
1504
+ locations: []
1505
+ };
1506
+ const declarationFiles = /* @__PURE__ */ new Set();
1507
+ const referencingFiles = /* @__PURE__ */ new Set();
1508
+ const locations = [];
1509
+ const seen = /* @__PURE__ */ new Set();
1510
+ let referenceCount = 0;
1511
+ let excludedTestReferences = 0;
1512
+ const root = path$1.resolve(projectRoot) + path$1.sep;
1513
+ for (const decl of declarations) {
1514
+ declarationFiles.add(decl.file);
1515
+ locations.push({
1516
+ file: rel(decl.file),
1517
+ line: decl.position.line + 1,
1518
+ col: decl.position.character + 1,
1519
+ kind: "declaration"
1520
+ });
1521
+ const refs = await client.references(decl.file, decl.position);
1522
+ for (const ref of refs) {
1523
+ if (!ref.uri.startsWith("file:")) continue;
1524
+ const refFile = uriToPath(ref.uri);
1525
+ if (refFile === decl.file) continue;
1526
+ if (!refFile.startsWith(root)) continue;
1527
+ const start = ref.range.start;
1528
+ const key = `${refFile}:${start.line}:${start.character}`;
1529
+ if (seen.has(key)) continue;
1530
+ seen.add(key);
1531
+ const location = {
1532
+ file: rel(refFile),
1533
+ line: start.line + 1,
1534
+ col: start.character + 1
1535
+ };
1536
+ if (!includeTests && testCode.isTest(refFile, start.line)) {
1537
+ excludedTestReferences++;
1538
+ locations.push({
1539
+ ...location,
1540
+ kind: "test"
1541
+ });
1542
+ continue;
1543
+ }
1544
+ referenceCount++;
1545
+ referencingFiles.add(refFile);
1546
+ locations.push({
1547
+ ...location,
1548
+ kind: "reference"
1549
+ });
1550
+ }
1551
+ }
1552
+ return {
1553
+ symbol,
1554
+ result: {
1555
+ found: true,
1556
+ resolvedAsMember: false,
1557
+ declarationFiles: [...declarationFiles].map(rel).sort(),
1558
+ referenceCount,
1559
+ referencingFiles: [...referencingFiles].map(rel).sort(),
1560
+ excludedTestReferences,
1561
+ filesScanned: request.files.length,
1562
+ tsConfigPath: null
1563
+ },
1564
+ locations
1565
+ };
1566
+ }
1567
+ /**
1568
+ * ONE server per call, for N symbols: indexing is the expensive part, each
1569
+ * `references` request after it is cheap.
1570
+ */
1571
+ const rustAnalyzerEngine = async (request) => {
1572
+ assertRustAnalyzer();
1573
+ const workspaceRoot = cargoRoot(request.scopeRoots[0], request.projectRoot);
1574
+ const timeoutMs = indexTimeoutMs();
1575
+ let client;
1576
+ try {
1577
+ client = await LspClient.start({
1578
+ rootDir: workspaceRoot,
1579
+ bin: RA_BIN,
1580
+ args: [],
1581
+ name: "rust-analyzer",
1582
+ capabilities: {
1583
+ workspace: {
1584
+ configuration: false,
1585
+ symbol: {}
1586
+ },
1587
+ experimental: { serverStatusNotification: true },
1588
+ window: { workDoneProgress: true }
1589
+ },
1590
+ initializationOptions: {
1591
+ checkOnSave: false,
1592
+ workspace: { symbol: { search: {
1593
+ kind: "all_symbols",
1594
+ scope: "workspace"
1595
+ } } }
1596
+ },
1597
+ responseTimeoutMs: timeoutMs
1598
+ });
1599
+ } catch (e) {
1600
+ throw new ToolchainError(`rust-analyzer did not start: ${e.message}`, INSTALL_REMEDY);
1601
+ }
1602
+ try {
1603
+ let status;
1604
+ try {
1605
+ status = await client.waitForNotification("experimental/serverStatus", (p) => p?.quiescent === true, timeoutMs);
1606
+ } catch (e) {
1607
+ throw new ToolchainError(`rust-analyzer did not finish indexing ${relativeTo(request.projectRoot, workspaceRoot) || "."}: ${e.message}.`, "Raise CODE_INTEL_RA_TIMEOUT_MS for a large workspace, or check that `cargo metadata` succeeds there.");
1608
+ }
1609
+ if (status.health === "error") throw new ToolchainError(`rust-analyzer reports an error loading the workspace${status.message ? `: ${status.message}` : ""}.`, "Check that `cargo metadata` succeeds in the crate.");
1610
+ const testCode = new TestCode(workspaceRoot);
1611
+ const results = [];
1612
+ for (const symbol of request.symbols) results.push(await resolveOne(client, symbol, request, testCode));
1613
+ return results;
1614
+ } finally {
1615
+ await client.dispose();
1616
+ }
1617
+ };
1618
+
1619
+ //#endregion
1620
+ //#region ../code-intel/dist/registry.js
1621
+ const engines = {
1622
+ ts: {
1623
+ resolve: tsgoEngine,
1624
+ source: TS_SOURCE,
1625
+ usesTsConfig: true
1626
+ },
1627
+ rust: {
1628
+ resolve: rustAnalyzerEngine,
1629
+ source: RUST_SOURCE,
1630
+ usesTsConfig: false,
1631
+ skipDir: rustSkipDir
1632
+ }
1633
+ };
1634
+ const REFS_LANGUAGES = Object.keys(engines);
1635
+ function isRefsLanguage(value) {
1636
+ return Object.prototype.hasOwnProperty.call(engines, value);
1637
+ }
1638
+ /**
1639
+ * The request an engine receives: absolute scope roots, every source file
1640
+ * in them, and the scope cap applied BEFORE anything loads.
1641
+ */
1642
+ function buildEngineRequest(options) {
1643
+ const engine = engines[options.language];
1644
+ const projectRoot = path$1.resolve(options.projectRoot);
1645
+ const scopeRoots = options.scopes.map((s) => path$1.resolve(projectRoot, s));
1646
+ const files = [...new Set(scopeRoots.flatMap((r) => listSourceFiles(r, engine.source, "skipDir" in engine ? engine.skipDir : void 0)))].sort();
1647
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
1648
+ if (files.length > maxFiles) throw new ScopeTooBroadError(files.length, maxFiles, options.scopes.join(", "));
1649
+ const tsConfig = engine.usesTsConfig && scopeRoots[0] ? findTsConfig(scopeRoots[0], projectRoot) : null;
1650
+ return {
1651
+ symbols: options.symbols,
1652
+ projectRoot,
1653
+ scopeRoots,
1654
+ files,
1655
+ includeTests: options.includeTests,
1656
+ tsConfigPath: tsConfig ? relativeTo(projectRoot, tsConfig) : null
1657
+ };
1658
+ }
1659
+
1660
+ //#endregion
1661
+ //#region src/commands/gates/refsHandler.ts
1662
+ /** Locations printed after the verdict. */
1663
+ const MAX_LOCATIONS = 20;
1664
+ function usage(message) {
1665
+ return {
1666
+ exitCode: 2,
1667
+ stdout: "",
1668
+ stderr: `gates refs: ${message}\n`
1669
+ };
1670
+ }
1671
+ /** A relative path that leaves its base (`..` or `../x`, not `..cache`). */
1672
+ function escapes(rel) {
1673
+ return rel === ".." || rel.startsWith(`..${path$1.sep}`) || path$1.isAbsolute(rel);
1674
+ }
1675
+ /**
1676
+ * The scope, repository-relative, or a reason it is refused. It must exist
1677
+ * and resolve INSIDE the repository, never to the root itself: the root is
1678
+ * the whole repository, which no engine loads (the resolver's ENG-5106
1679
+ * rule). Symlinks are resolved first, so a link cannot point the search
1680
+ * outside.
1681
+ */
1682
+ function resolveScope(projectRoot, scope) {
1683
+ const trimmed = scope.trim();
1684
+ if (!trimmed) return { error: "--scope is required" };
1685
+ const root = fs.realpathSync(projectRoot);
1686
+ const target = path$1.resolve(root, trimmed);
1687
+ let real;
1688
+ try {
1689
+ real = fs.realpathSync(target);
1690
+ } catch {
1691
+ if (escapes(path$1.relative(root, target))) return { error: `--scope "${scope}" resolves outside the repository (${root})` };
1692
+ return { error: `--scope "${scope}" does not exist` };
1693
+ }
1694
+ const rel = path$1.relative(root, real);
1695
+ if (escapes(rel)) return { error: `--scope "${scope}" resolves outside the repository (${root})` };
1696
+ if (rel === "") return { error: `--scope "${scope}" is the repository root; name a directory narrower than the root (e.g. apps/backender/src)` };
1697
+ return { scope: rel };
1698
+ }
1699
+ function formatLocation(loc) {
1700
+ return ` ${loc.kind.padEnd(11)} ${loc.file}:${loc.line}:${loc.col}`;
1701
+ }
1702
+ /** One-line verdict plus up to MAX_LOCATIONS locations. */
1703
+ function formatRefs(resolution, language, scope) {
1704
+ const { symbol, result } = resolution;
1705
+ const where = `[${language}, scope ${scope}]`;
1706
+ if (!result.found) return `FAIL ${symbol}: not found in ${result.filesScanned} file(s) ${where}\n`;
1707
+ const tests = result.excludedTestReferences ? `; ${result.excludedTestReferences} in test code not counted (--include-tests counts them)` : "";
1708
+ const lines = [result.referenceCount > 0 ? `PASS ${symbol}: declared in ${result.declarationFiles.join(", ")}; ${result.referenceCount} reference(s) from ${result.referencingFiles.length} other module(s)${tests} ${where}` : `FAIL ${symbol}: declared in ${result.declarationFiles.join(", ")} but not referenced from any other module${tests} ${where}`];
1709
+ const rank = {
1710
+ declaration: 0,
1711
+ reference: 1,
1712
+ test: 2
1713
+ };
1714
+ const ordered = [...resolution.locations].sort((a, b) => rank[a.kind] - rank[b.kind]);
1715
+ const shown = ordered.slice(0, MAX_LOCATIONS);
1716
+ lines.push(...shown.map(formatLocation));
1717
+ const more = ordered.length - shown.length;
1718
+ if (more > 0) lines.push(` … ${more} more`);
1719
+ return lines.join("\n") + "\n";
1720
+ }
1721
+ /** The whole command minus process I/O, so tests can drive it. */
1722
+ async function executeRefs(flags, cwd = process.cwd()) {
1723
+ if (!isRefsLanguage(flags.lang)) return usage(`--lang "${flags.lang}" is not a language this CLI resolves; expected one of: ${REFS_LANGUAGES.join(", ")}`);
1724
+ const symbol = flags.symbol.trim();
1725
+ if (!symbol) return usage("--symbol is required");
1726
+ const projectRoot = findProjectRoot(cwd);
1727
+ const scoped = resolveScope(projectRoot, flags.scope);
1728
+ if ("error" in scoped) return usage(scoped.error);
1729
+ try {
1730
+ const request = buildEngineRequest({
1731
+ language: flags.lang,
1732
+ symbols: [symbol],
1733
+ projectRoot,
1734
+ scopes: [scoped.scope],
1735
+ includeTests: flags["include-tests"]
1736
+ });
1737
+ const [resolution] = await engines[flags.lang].resolve(request);
1738
+ if (!resolution) return usage(`no answer for "${symbol}"`);
1739
+ return {
1740
+ exitCode: resolution.result.found && resolution.result.referenceCount > 0 ? 0 : 1,
1741
+ stdout: formatRefs(resolution, flags.lang, scoped.scope),
1742
+ stderr: ""
1743
+ };
1744
+ } catch (error) {
1745
+ if (error instanceof ToolchainError || error instanceof ScopeTooBroadError || error instanceof SymbolTooCommonError) return usage(error.message);
1746
+ return usage(`resolution failed for "${symbol}": ${error instanceof Error ? error.message : String(error)}`);
1747
+ }
1748
+ }
1749
+ async function gatesRefsHandler(flags) {
1750
+ const result = await executeRefs(flags);
1751
+ if (result.stdout.length > 0) this.process.stdout.write(result.stdout);
1752
+ if (result.stderr.length > 0) this.process.stderr.write(result.stderr);
1753
+ this.process.exitCode = result.exitCode;
1754
+ }
1755
+
1756
+ //#endregion
1757
+ export { gatesRefsHandler };