@drkmttr/adhere 0.17.5 → 0.18.1
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 +125 -64
- package/package.json +6 -6
- package/skills/adhere/SKILL.md +50 -33
- package/skills/adhere-setup/SKILL.md +71 -48
- package/src/cli.ts +13 -1
- package/src/config.ts +27 -3
- package/src/init.ts +22 -3
- package/src/install.ts +29 -23
- package/src/services/Jev.http.ts +20 -2
- package/src/services/Jev.provider.test.ts +280 -4
- package/src/services/Jev.reads.test.ts +99 -20
- package/src/services/Jev.ts +76 -12
- package/src/services/SourceReads.test.ts +290 -41
- package/src/services/SourceReads.ts +52 -29
- package/src/workflows/audit.ts +82 -11
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isNote, withoutComments } from "#comments.ts";
|
|
2
|
+
import { DEFAULT_MAX_SYMBOL_DOCUMENTATION_LENGTH, DEFAULT_MAX_SYMBOL_LENGTH } from "#config.ts";
|
|
2
3
|
import { suppressionsOf } from "#suppress.ts";
|
|
3
4
|
import { Effect, Fiber, Layer, Path } from "effect";
|
|
4
5
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
@@ -65,7 +66,13 @@ const sources = {
|
|
|
65
66
|
"node_modules/private/package.json": JSON.stringify({ name: "private", types: "index.d.ts" }),
|
|
66
67
|
"node_modules/private/index.d.ts": "export declare const secret: number;",
|
|
67
68
|
};
|
|
68
|
-
const both: SourceReadRequest = {
|
|
69
|
+
const both: SourceReadRequest = {
|
|
70
|
+
symbols: true,
|
|
71
|
+
references: true,
|
|
72
|
+
includeComments: false,
|
|
73
|
+
maxSymbolDocumentationLength: DEFAULT_MAX_SYMBOL_DOCUMENTATION_LENGTH,
|
|
74
|
+
maxSymbolLength: DEFAULT_MAX_SYMBOL_LENGTH,
|
|
75
|
+
};
|
|
69
76
|
const roots: string[] = [];
|
|
70
77
|
const within = async <A>(promise: Promise<A>, milliseconds: number): Promise<A> => {
|
|
71
78
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -199,11 +206,11 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
199
206
|
"export const first = {} as Box<string>;",
|
|
200
207
|
"export const second = {} as Box<number>;",
|
|
201
208
|
"export const mapped: Readonly<Box<string>> = first;",
|
|
202
|
-
"/** An unrelated reader. */",
|
|
203
|
-
"export const other = { get: () => true };",
|
|
204
209
|
].join("\n"),
|
|
210
|
+
"other.ts": "/** An unrelated reader. */\nexport const other = { get: () => true };",
|
|
205
211
|
"use.ts": [
|
|
206
|
-
'import { first, second, mapped
|
|
212
|
+
'import { first, second, mapped } from "./lib";',
|
|
213
|
+
'import { other } from "./other";',
|
|
207
214
|
"export const results = [first.get(), second.get(), mapped.get(), other.get()];",
|
|
208
215
|
].join("\n"),
|
|
209
216
|
"types.d.ts": "",
|
|
@@ -218,8 +225,79 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
218
225
|
expect(get.match(/\*\*@returns\*\*/g)).toHaveLength(1);
|
|
219
226
|
expect(get).toContain("The stored value.");
|
|
220
227
|
expect(get).toContain("**@example** box.get();");
|
|
221
|
-
expect(result.references
|
|
222
|
-
expect(result.references
|
|
228
|
+
expect(result.references!["get:lib.ts"]).toContain(" get(): T;");
|
|
229
|
+
expect(result.references!["get:other.ts"]).toBe("export const other = { get: () => true };");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("caps documentation independently when the whole-symbol limit allows long signatures", async () => {
|
|
233
|
+
const documentation = `Documentation: ${"x".repeat(600)}`;
|
|
234
|
+
const example = "y".repeat(600);
|
|
235
|
+
const parameters = Array.from({ length: 24 }, (_, index) => `argument${index}: number`).join(
|
|
236
|
+
", ",
|
|
237
|
+
);
|
|
238
|
+
const { root, read } = await fixture({
|
|
239
|
+
"lib.ts": [
|
|
240
|
+
`/** ${documentation} */`,
|
|
241
|
+
`export function original(${parameters}) { return argument0; }`,
|
|
242
|
+
`/** A value.\n * @example ${example}\n */`,
|
|
243
|
+
"export const tagged = 42;",
|
|
244
|
+
].join("\n"),
|
|
245
|
+
"use.ts":
|
|
246
|
+
'import { original, tagged } from "./lib";\nexport const result = [original, tagged];',
|
|
247
|
+
"types.d.ts": "",
|
|
248
|
+
});
|
|
249
|
+
const results = await run(
|
|
250
|
+
root,
|
|
251
|
+
Effect.forEach([256, 32, 0, 1024], (maxSymbolDocumentationLength) =>
|
|
252
|
+
read("use.ts", { ...both, maxSymbolDocumentationLength, maxSymbolLength: 4096 }),
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
for (const [index, limit] of [256, 32, 0, 1024].entries()) {
|
|
256
|
+
const result = results[index];
|
|
257
|
+
const content = result?.symbols?.original ?? "";
|
|
258
|
+
expect(content).toMatch(/^```typescript\n[\s\S]+\n```/);
|
|
259
|
+
const [, signature = "", docs = ""] =
|
|
260
|
+
content.match(/^```typescript\n([\s\S]*?)\n```(?:\n\n([\s\S]*))?$/) ?? [];
|
|
261
|
+
expect(signature.length).toBeGreaterThan(256);
|
|
262
|
+
expect(signature).toContain("argument23: number");
|
|
263
|
+
expect(docs).toBe(documentation.slice(0, limit));
|
|
264
|
+
const tags = `A value.\n\n**@example** ${example}`.slice(0, limit);
|
|
265
|
+
expect(result?.symbols?.tagged).toBe(
|
|
266
|
+
`\`\`\`typescript\n42\n\`\`\`${tags ? `\n\n${tags}` : ""}`,
|
|
267
|
+
);
|
|
268
|
+
expect(result?.references).toEqual(results[0]?.references);
|
|
269
|
+
expect(result?.references?.["original:lib.ts"]).toContain(parameters);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("caps each final symbol across bindings and documentation without shortening references", async () => {
|
|
274
|
+
const { root, read } = await fixture({
|
|
275
|
+
...sources,
|
|
276
|
+
"lib.ts": library.replace("The original documented value.", "x".repeat(600)),
|
|
277
|
+
});
|
|
278
|
+
const { full, capped } = await run(
|
|
279
|
+
root,
|
|
280
|
+
Effect.gen(function* () {
|
|
281
|
+
const full = yield* read("use.ts", { ...both, maxSymbolLength: 4096 });
|
|
282
|
+
const capped = yield* Effect.forEach([256, 32, 0, 1024], (maxSymbolLength) =>
|
|
283
|
+
read("use.ts", { ...both, maxSymbolLength }),
|
|
284
|
+
);
|
|
285
|
+
return { full, capped };
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
expect(full.symbols?.local?.length).toBeGreaterThan(256);
|
|
289
|
+
expect(full.symbols?.local).toContain("```typescript\nstring\n```");
|
|
290
|
+
for (const [index, limit] of [256, 32, 0, 1024].entries()) {
|
|
291
|
+
expect(capped[index]?.symbols).toEqual(
|
|
292
|
+
Object.fromEntries(
|
|
293
|
+
Object.entries(full.symbols ?? {}).map(([name, content]) => [
|
|
294
|
+
name,
|
|
295
|
+
content.slice(0, limit),
|
|
296
|
+
]),
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
expect(capped[index]?.references).toEqual(full.references);
|
|
300
|
+
}
|
|
223
301
|
});
|
|
224
302
|
|
|
225
303
|
it("reads const-asserted empty tuple properties and their normal usages without losing surrounding types or references", async () => {
|
|
@@ -241,8 +319,7 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
241
319
|
'{ readonly action: "update"; readonly stables: readonly []; }',
|
|
242
320
|
);
|
|
243
321
|
expect(result.symbols!.action).toBe('```typescript\n"update"\n```');
|
|
244
|
-
expect(result.references
|
|
245
|
-
expect(result.references).not.toHaveProperty("stables");
|
|
322
|
+
expect(result.references).toEqual({ "original:lib.ts": "export const original = 42;" });
|
|
246
323
|
});
|
|
247
324
|
|
|
248
325
|
it("keeps error types unavailable without hiding explicitly declared any types", async () => {
|
|
@@ -261,6 +338,188 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
261
338
|
expect(result.symbols!.explicit).toBe("```typescript\nany\n```");
|
|
262
339
|
});
|
|
263
340
|
|
|
341
|
+
it("omits standard built-ins without changing audited code or meaningful user signatures", async () => {
|
|
342
|
+
const declaration = [
|
|
343
|
+
"export function collect(values: Array<string>): Promise<Array<string>> {",
|
|
344
|
+
" return Promise.resolve(values);",
|
|
345
|
+
"}",
|
|
346
|
+
].join("\n");
|
|
347
|
+
const contents = [
|
|
348
|
+
'import { collect } from "./lib";',
|
|
349
|
+
'export const values: Array<string> = [" value "];',
|
|
350
|
+
"export const copied = Array.from(values).map(value => value.trim());",
|
|
351
|
+
"export const objectKeys = Object.keys({ copied });",
|
|
352
|
+
"export const pending: Promise<string[]> = collect(copied);",
|
|
353
|
+
'pending.then(value => console.log(value.join(","))).catch((error: unknown) => String(error));',
|
|
354
|
+
"export function inspect(flag: boolean, count: number): unknown {",
|
|
355
|
+
" if (flag && count > 0) {",
|
|
356
|
+
" try { return arguments[0] ?? undefined; } catch { return undefined; }",
|
|
357
|
+
" }",
|
|
358
|
+
" return undefined;",
|
|
359
|
+
"}",
|
|
360
|
+
].join("\n");
|
|
361
|
+
const { root } = await fixture(
|
|
362
|
+
{
|
|
363
|
+
"lib.ts": "/** Collects typed values. */\n" + declaration,
|
|
364
|
+
"use.ts": contents,
|
|
365
|
+
"types.d.ts": "",
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
compilerOptions: {
|
|
369
|
+
strict: true,
|
|
370
|
+
target: "ES2022",
|
|
371
|
+
types: [],
|
|
372
|
+
module: "Preserve",
|
|
373
|
+
moduleResolution: "Bundler",
|
|
374
|
+
},
|
|
375
|
+
files: ["lib.ts", "use.ts", "types.d.ts"],
|
|
376
|
+
},
|
|
377
|
+
);
|
|
378
|
+
const file = { path: join(root, "use.ts"), contents };
|
|
379
|
+
const lines = normalized(contents);
|
|
380
|
+
const result = (await run(
|
|
381
|
+
root,
|
|
382
|
+
Effect.flatMap(SourceReads, (reads) => reads.read(file, lines, both)),
|
|
383
|
+
)) as SourceReadState;
|
|
384
|
+
for (const name of [
|
|
385
|
+
"Array",
|
|
386
|
+
"console",
|
|
387
|
+
"Object",
|
|
388
|
+
"Promise",
|
|
389
|
+
"String",
|
|
390
|
+
"from",
|
|
391
|
+
"map",
|
|
392
|
+
"trim",
|
|
393
|
+
"keys",
|
|
394
|
+
"then",
|
|
395
|
+
"log",
|
|
396
|
+
"join",
|
|
397
|
+
"catch",
|
|
398
|
+
"undefined",
|
|
399
|
+
"arguments",
|
|
400
|
+
"string",
|
|
401
|
+
"number",
|
|
402
|
+
"boolean",
|
|
403
|
+
"unknown",
|
|
404
|
+
"if",
|
|
405
|
+
"try",
|
|
406
|
+
]) {
|
|
407
|
+
expect(result.symbols).not.toHaveProperty(name);
|
|
408
|
+
expect(Object.keys(result.references!).some((key) => key.startsWith(`${name}:`))).toBe(false);
|
|
409
|
+
}
|
|
410
|
+
expect(result.symbols!.collect).toContain("(values: Array<string>) => Promise<Array<string>>");
|
|
411
|
+
expect(result.symbols!.collect).toContain("Collects typed values.");
|
|
412
|
+
expect(result.symbols!.inspect).toContain("(flag: boolean, count: number) => unknown");
|
|
413
|
+
expect(result.references!["collect:lib.ts"]).toBe(declaration);
|
|
414
|
+
expect(file.contents).toBe(contents);
|
|
415
|
+
expect(lines).toEqual(normalized(contents));
|
|
416
|
+
expect(await readFile(file.path, "utf8")).toBe(contents);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("omits aliased runtime built-ins while preserving third-party declarations and augmentations", async () => {
|
|
420
|
+
const files = {
|
|
421
|
+
"lib.ts": "",
|
|
422
|
+
"use.ts": [
|
|
423
|
+
'import { readFile as readText } from "node:fs";',
|
|
424
|
+
'import { hash as hashText } from "bun";',
|
|
425
|
+
'import { Database as SqliteDatabase } from "bun:sqlite";',
|
|
426
|
+
'import { readFile as vendorRead } from "third-party";',
|
|
427
|
+
'export const runtimeText = readText("fixture.txt");',
|
|
428
|
+
"export const runtimeHash = hashText(runtimeText);",
|
|
429
|
+
'export const database = new SqliteDatabase(":memory:");',
|
|
430
|
+
'export const vendorText = vendorRead("fixture.txt");',
|
|
431
|
+
"export const vendorValues = Array.from([vendorText]);",
|
|
432
|
+
"export const extended = vendorValues.vendorFeature();",
|
|
433
|
+
].join("\n"),
|
|
434
|
+
"types.d.ts": [
|
|
435
|
+
'/// <reference path="./node_modules/@types/node/index.d.ts" />',
|
|
436
|
+
'/// <reference path="./node_modules/@types/bun/index.d.ts" />',
|
|
437
|
+
'/// <reference path="./node_modules/bun-types/index.d.ts" />',
|
|
438
|
+
].join("\n"),
|
|
439
|
+
"node_modules/@types/node/index.d.ts":
|
|
440
|
+
'declare module "node:fs" { /** Reads a runtime file. */ export function readFile(path: string): string; }',
|
|
441
|
+
"node_modules/@types/bun/index.d.ts":
|
|
442
|
+
'declare module "bun" { /** Hashes a runtime value. */ export function hash(value: string): number; }',
|
|
443
|
+
"node_modules/bun-types/index.d.ts":
|
|
444
|
+
'declare module "bun:sqlite" { /** Opens a runtime database. */ export class Database { constructor(path: string); } }',
|
|
445
|
+
"node_modules/third-party/package.json": JSON.stringify({
|
|
446
|
+
name: "third-party",
|
|
447
|
+
types: "index.d.ts",
|
|
448
|
+
}),
|
|
449
|
+
"node_modules/third-party/index.d.ts": [
|
|
450
|
+
"/** Third-party file reader. */",
|
|
451
|
+
"export declare function readFile(path: string): string;",
|
|
452
|
+
"declare global {",
|
|
453
|
+
" interface Array<T> {",
|
|
454
|
+
" /** Third-party array extension. */",
|
|
455
|
+
" vendorFeature(): string;",
|
|
456
|
+
" }",
|
|
457
|
+
"}",
|
|
458
|
+
].join("\n"),
|
|
459
|
+
};
|
|
460
|
+
const { root, read } = await fixture(files);
|
|
461
|
+
const result = await run(root, read("use.ts"));
|
|
462
|
+
for (const name of ["readText", "hashText", "SqliteDatabase"]) {
|
|
463
|
+
expect(result.symbols).not.toHaveProperty(name);
|
|
464
|
+
expect(Object.keys(result.references!).some((key) => key.startsWith(`${name}:`))).toBe(false);
|
|
465
|
+
}
|
|
466
|
+
expect(result.symbols!.vendorRead).toContain("(path: string) => string");
|
|
467
|
+
expect(result.symbols!.vendorRead).toContain("Third-party file reader.");
|
|
468
|
+
expect(result.symbols!.vendorFeature).toContain("Third-party array extension.");
|
|
469
|
+
expect(result.symbols!.Array).toEqual(expect.any(String));
|
|
470
|
+
expect(result.symbols!.database).toEqual(expect.any(String));
|
|
471
|
+
expect(result.references).toEqual({});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("preserves project-owned built-in names, intrinsic-name properties and imported aliases", async () => {
|
|
475
|
+
const declarations = {
|
|
476
|
+
Array: "export const Array = (value: string) => value;",
|
|
477
|
+
Promise: "export const Promise = (value: number) => value;",
|
|
478
|
+
console: "export const console = { log(value: string) { return value; } };",
|
|
479
|
+
Object: "export const Object = { catch(value: string) { return value; } };",
|
|
480
|
+
tokens:
|
|
481
|
+
'export const tokens = { undefined: "project marker", arguments: "project arguments" };',
|
|
482
|
+
};
|
|
483
|
+
const files = {
|
|
484
|
+
"lib.ts": [
|
|
485
|
+
"/** Project array builder. */",
|
|
486
|
+
declarations.Array,
|
|
487
|
+
"/** Project promise maker. */",
|
|
488
|
+
declarations.Promise,
|
|
489
|
+
declarations.console,
|
|
490
|
+
declarations.Object,
|
|
491
|
+
declarations.tokens,
|
|
492
|
+
].join("\n"),
|
|
493
|
+
"use.ts": [
|
|
494
|
+
'import { Array, Promise as createPromise, console, Object, tokens } from "./lib";',
|
|
495
|
+
'export const result = createPromise(Array("value").length);',
|
|
496
|
+
'console.log(Object.catch("value"));',
|
|
497
|
+
"export const markers = [tokens.undefined, tokens.arguments];",
|
|
498
|
+
].join("\n"),
|
|
499
|
+
"types.d.ts": "",
|
|
500
|
+
};
|
|
501
|
+
const { root, read } = await fixture(files);
|
|
502
|
+
const result = await run(root, read("use.ts"));
|
|
503
|
+
for (const name of [
|
|
504
|
+
"Array",
|
|
505
|
+
"createPromise",
|
|
506
|
+
"console",
|
|
507
|
+
"Object",
|
|
508
|
+
"log",
|
|
509
|
+
"catch",
|
|
510
|
+
"tokens",
|
|
511
|
+
"undefined",
|
|
512
|
+
"arguments",
|
|
513
|
+
]) {
|
|
514
|
+
expect(result.symbols![name]).toEqual(expect.any(String));
|
|
515
|
+
expect(result.references).toHaveProperty([`${name}:lib.ts`]);
|
|
516
|
+
}
|
|
517
|
+
expect(result.symbols!.Array).toContain("Project array builder.");
|
|
518
|
+
expect(result.symbols!.createPromise).toContain("Project promise maker.");
|
|
519
|
+
expect(result.references!["Array:lib.ts"]).toBe(declarations.Array);
|
|
520
|
+
expect(result.references!["createPromise:lib.ts"]).toBe(declarations.Promise);
|
|
521
|
+
});
|
|
522
|
+
|
|
264
523
|
it("keeps declaration references and filename casing under case-insensitive native paths", async () => {
|
|
265
524
|
const declaration = [
|
|
266
525
|
"export function twice(value: number): number {",
|
|
@@ -303,7 +562,7 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
303
562
|
expect(api.getCanonicalFileName(root)).not.toBe(root);
|
|
304
563
|
expect(result.symbols!.scale).toContain("(value: number) => number");
|
|
305
564
|
expect(result.symbols!.scale).toContain("Double a number.");
|
|
306
|
-
expect(result.references).toEqual({ scale:
|
|
565
|
+
expect(result.references).toEqual({ "scale:Library.ts": declaration });
|
|
307
566
|
} finally {
|
|
308
567
|
await api.close();
|
|
309
568
|
}
|
|
@@ -313,20 +572,18 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
313
572
|
const { root, read } = await fixture();
|
|
314
573
|
const result = await run(root, read("use.ts"));
|
|
315
574
|
const refs = result.references!;
|
|
316
|
-
expect(refs
|
|
317
|
-
expect(refs
|
|
318
|
-
expect(refs
|
|
575
|
+
expect(refs["local:lib.ts"]).toBe("export const original = 42;");
|
|
576
|
+
expect(refs["local:lib.ts"]).not.toContain("neighbor");
|
|
577
|
+
expect(refs["Shape:lib.ts"]).toBe(
|
|
319
578
|
"export interface Shape { first: number }\n…\nexport interface Shape { second: string }",
|
|
320
579
|
);
|
|
321
|
-
expect(refs
|
|
322
|
-
expect(refs
|
|
323
|
-
expect(refs
|
|
324
|
-
expect(refs).
|
|
325
|
-
expect(refs).
|
|
326
|
-
expect(refs).not.toHaveProperty("dependency");
|
|
327
|
-
expect(Object.values(refs).flatMap(Object.keys)).not.toContain("use.ts");
|
|
580
|
+
expect(refs["pair:lib.ts"]).toContain("\n…\n");
|
|
581
|
+
expect(refs["pair:lib.ts"]).toContain("export function pair(value: number): number;");
|
|
582
|
+
expect(refs["pair:lib.ts"]).toContain("export function pair(value: string): string;");
|
|
583
|
+
expect(Object.keys(refs).sort()).toEqual(["Shape:lib.ts", "local:lib.ts", "pair:lib.ts"]);
|
|
584
|
+
expect(Object.values(refs).every((text) => typeof text === "string")).toBe(true);
|
|
328
585
|
expect(JSON.stringify(refs)).not.toMatch(/ordinary comment|never send this directive/);
|
|
329
|
-
expect(refs
|
|
586
|
+
expect(refs["local:lib.ts"]).not.toContain("@adhere");
|
|
330
587
|
expect(JSON.stringify(refs)).not.toContain(library);
|
|
331
588
|
});
|
|
332
589
|
|
|
@@ -425,13 +682,11 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
425
682
|
const { root, read } = await fixture(files);
|
|
426
683
|
const result = await run(root, read("use.ts", { ...both, symbols: false }));
|
|
427
684
|
for (const [name, text] of Object.entries(declarations)) {
|
|
428
|
-
expect(result.references![name]).
|
|
685
|
+
expect(result.references![`${name}:lib.ts`]).toBe(text);
|
|
429
686
|
}
|
|
430
|
-
expect(result.references
|
|
431
|
-
expect(result.references
|
|
432
|
-
expect(result.references
|
|
433
|
-
"lib.ts": ` first: 1,\n…\n${destructured}`,
|
|
434
|
-
});
|
|
687
|
+
expect(result.references!["run:lib.ts"]).toBe(method);
|
|
688
|
+
expect(result.references!["current:lib.ts"]).toBe(accessors);
|
|
689
|
+
expect(result.references!["first:lib.ts"]).toBe(` first: 1,\n…\n${destructured}`);
|
|
435
690
|
expect(JSON.stringify(result.references)).not.toMatch(/outsideBefore|outsideAfter/);
|
|
436
691
|
});
|
|
437
692
|
|
|
@@ -450,13 +705,13 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
450
705
|
expect(Object.keys(result.symbols)).toEqual(["symbols"]);
|
|
451
706
|
expect(result.symbols.symbols!.local).toContain("The original documented value.");
|
|
452
707
|
expect(Object.keys(result.references)).toEqual(["references"]);
|
|
453
|
-
expect(result.references.references
|
|
708
|
+
expect(result.references.references!["local:lib.ts"]).toBe(
|
|
454
709
|
normalized(library, true).slice(1, 6).join("\n"),
|
|
455
710
|
);
|
|
456
|
-
expect(result.references.references
|
|
711
|
+
expect(result.references.references!["local:lib.ts"]).toContain(
|
|
457
712
|
"/** The original documented value. */",
|
|
458
713
|
);
|
|
459
|
-
expect(result.references.references
|
|
714
|
+
expect(result.references.references!["local:lib.ts"]).not.toContain("adhere-ignore");
|
|
460
715
|
});
|
|
461
716
|
|
|
462
717
|
it("serializes concurrent same-file and child-project reads while isolating aliases and inferred ownership", async () => {
|
|
@@ -517,12 +772,8 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
517
772
|
);
|
|
518
773
|
expect(result[0]!.symbols!.local).toContain("First project.");
|
|
519
774
|
expect(result[1]!.symbols!.local).toContain("Second project.");
|
|
520
|
-
expect(result[0]!.references
|
|
521
|
-
|
|
522
|
-
});
|
|
523
|
-
expect(result[1]!.references!.local).toEqual({
|
|
524
|
-
"packages/b/alt/value.ts": "export const value = 2;",
|
|
525
|
-
});
|
|
775
|
+
expect(result[0]!.references!["local:packages/a/src/value.ts"]).toBe("export const value = 1;");
|
|
776
|
+
expect(result[1]!.references!["local:packages/b/alt/value.ts"]).toBe("export const value = 2;");
|
|
526
777
|
expect(result[2]!.symbols!.outside).toContain("```typescript\n3\n```");
|
|
527
778
|
expect(result[2]!.references).toEqual({});
|
|
528
779
|
expect(result[3]).toEqual(result[0]);
|
|
@@ -542,7 +793,7 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
542
793
|
prepare(["lib.ts", "use.ts"]).pipe(Effect.andThen(read("use.ts"))),
|
|
543
794
|
);
|
|
544
795
|
expect(result.symbols!.local).toContain("Inferred docs.");
|
|
545
|
-
expect(result.references
|
|
796
|
+
expect(result.references!["local:lib.ts"]).toBe("export const value = 1;");
|
|
546
797
|
expect(Object.keys(result).sort()).toEqual(["references", "symbols"]);
|
|
547
798
|
});
|
|
548
799
|
|
|
@@ -570,10 +821,8 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
570
821
|
const result = await run(join(root, "workspace"), read("workspace/use.ts"));
|
|
571
822
|
expect(result.symbols!.original).toContain("Unicode documentation 😀.");
|
|
572
823
|
expect(result.symbols!.outside).toContain("Outside documentation.");
|
|
573
|
-
expect(result.references
|
|
574
|
-
|
|
575
|
-
});
|
|
576
|
-
expect(result.references).not.toHaveProperty("outside");
|
|
824
|
+
expect(result.references!["original:lib.ts"]).toBe("export const original = 42;");
|
|
825
|
+
expect(Object.keys(result.references!).some((key) => key.startsWith("outside:"))).toBe(false);
|
|
577
826
|
});
|
|
578
827
|
|
|
579
828
|
it("pins each read to its supplied text despite disk changes and accepts new text in a later read", async () => {
|
|
@@ -799,7 +1048,7 @@ describe("native TypeScript compiler and declaration reads", () => {
|
|
|
799
1048
|
const state = yield* Effect.promise(() => within(Effect.runPromise(reading), 3000));
|
|
800
1049
|
expect(state.symbols!.local).toContain("```typescript\n42\n```");
|
|
801
1050
|
expect(state.symbols!.local).toContain("The original documented value.");
|
|
802
|
-
expect(state.references
|
|
1051
|
+
expect(state.references!["local:lib.ts"]).toBe("export const original = 42;");
|
|
803
1052
|
expect(closeDocument).toHaveBeenCalledTimes(2);
|
|
804
1053
|
expect(dispose).toHaveBeenCalledTimes(2);
|
|
805
1054
|
expect(close).not.toHaveBeenCalled();
|
|
@@ -11,7 +11,7 @@ import { NativeLsp } from "./NativeLsp.ts";
|
|
|
11
11
|
import type { Project, Symbol, Type } from "typescript/async";
|
|
12
12
|
import type { Node, SourceFile } from "typescript/ast";
|
|
13
13
|
|
|
14
|
-
export type SourceReferences = Record<string,
|
|
14
|
+
export type SourceReferences = Record<string, string>;
|
|
15
15
|
export type SourceReadState = ReadState & {
|
|
16
16
|
readonly symbols?: Record<string, string>;
|
|
17
17
|
readonly references?: SourceReferences;
|
|
@@ -20,6 +20,8 @@ export interface SourceReadRequest {
|
|
|
20
20
|
readonly symbols: boolean;
|
|
21
21
|
readonly references: boolean;
|
|
22
22
|
readonly includeComments: boolean;
|
|
23
|
+
readonly maxSymbolLength: number;
|
|
24
|
+
readonly maxSymbolDocumentationLength: number;
|
|
23
25
|
}
|
|
24
26
|
export class SourceReads extends Context.Service<
|
|
25
27
|
SourceReads,
|
|
@@ -121,6 +123,28 @@ async function readOf(
|
|
|
121
123
|
? undefined
|
|
122
124
|
: target;
|
|
123
125
|
});
|
|
126
|
+
const builtins = new Map<number, Promise<boolean>>();
|
|
127
|
+
const builtinOf = (symbol: Symbol) =>
|
|
128
|
+
once(builtins, symbol.id, async () => {
|
|
129
|
+
if (symbol.declarations.length === 0) {
|
|
130
|
+
if (symbol.name === "undefined")
|
|
131
|
+
return native.sdk("isUndefinedSymbol", () => project.checker.isUndefinedSymbol(symbol));
|
|
132
|
+
if (symbol.name === "arguments")
|
|
133
|
+
return native.sdk("isArgumentsSymbol", () => project.checker.isArgumentsSymbol(symbol));
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
for (const { path } of symbol.declarations) {
|
|
137
|
+
if (owned(path)) return false;
|
|
138
|
+
const pathname = canonicalOf(path).split(sep).join("/");
|
|
139
|
+
if (/(?:^|\/)node_modules\/(?:@types\/(?:node|bun)|bun-types)\//.test(pathname)) continue;
|
|
140
|
+
// Metadata classifies standard libraries without loading their full declaration ASTs.
|
|
141
|
+
const metadata = await native.sdk("getSourceFileMetadata", () =>
|
|
142
|
+
project.program.getSourceFileMetadataByPath(path),
|
|
143
|
+
);
|
|
144
|
+
if (!metadata?.isDefaultLibrary) return false;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
});
|
|
124
148
|
const definitionsOf = (symbol: Symbol) =>
|
|
125
149
|
once(definitions, symbol.id, async () => {
|
|
126
150
|
const found: Node[] = [];
|
|
@@ -154,6 +178,8 @@ async function readOf(
|
|
|
154
178
|
for (const [index, node] of nodes.entries()) {
|
|
155
179
|
const local = symbols[index];
|
|
156
180
|
if (!local) continue;
|
|
181
|
+
const binding = local.flags & sdk.SymbolFlags.Alias ? await targetOf(local) : local;
|
|
182
|
+
if (binding && (await builtinOf(binding))) continue;
|
|
157
183
|
const name = node.getText(source);
|
|
158
184
|
const shorthand = ast.isShorthandPropertyAssignment(node.parent);
|
|
159
185
|
const used = usedOf(node);
|
|
@@ -214,7 +240,8 @@ async function readOf(
|
|
|
214
240
|
);
|
|
215
241
|
return [comment, ...tags.map(({ name, text }) => `**@${name}**${text ? ` ${text}` : ""}`)]
|
|
216
242
|
.filter(Boolean)
|
|
217
|
-
.join("\n\n")
|
|
243
|
+
.join("\n\n")
|
|
244
|
+
.slice(0, requested.maxSymbolDocumentationLength);
|
|
218
245
|
});
|
|
219
246
|
const formattedTypes = new Map<string, Promise<string>>();
|
|
220
247
|
const descriptions = new Map<
|
|
@@ -254,32 +281,26 @@ async function readOf(
|
|
|
254
281
|
}
|
|
255
282
|
const cleaned = new Map<SourceFile, readonly string[]>();
|
|
256
283
|
const references = sorted(
|
|
257
|
-
[...excerpts].
|
|
258
|
-
([
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
pathname,
|
|
278
|
-
merged.map(({ first, last }) => lines.slice(first, last).join("\n")).join("\n…\n"),
|
|
279
|
-
] as const;
|
|
280
|
-
}),
|
|
281
|
-
),
|
|
282
|
-
] as const,
|
|
284
|
+
[...excerpts].flatMap(([name, byFile]) =>
|
|
285
|
+
[...byFile].map(([pathname, { source: linked, windows }]) => {
|
|
286
|
+
let lines = cleaned.get(linked);
|
|
287
|
+
if (!lines) {
|
|
288
|
+
const directed = suppressionsOf(linked.text.split("\n")).lines;
|
|
289
|
+
lines = requested.includeComments ? directed : withoutComments(directed, isNote);
|
|
290
|
+
cleaned.set(linked, lines);
|
|
291
|
+
}
|
|
292
|
+
const merged: Array<{ first: number; last: number }> = [];
|
|
293
|
+
for (const window of windows.sort((a, b) => a.first - b.first || a.last - b.last)) {
|
|
294
|
+
const previous = merged.at(-1);
|
|
295
|
+
if (previous && window.first <= previous.last)
|
|
296
|
+
previous.last = Math.max(previous.last, window.last);
|
|
297
|
+
else merged.push({ ...window });
|
|
298
|
+
}
|
|
299
|
+
return [
|
|
300
|
+
`${name}:${pathname}`,
|
|
301
|
+
merged.map(({ first, last }) => lines.slice(first, last).join("\n")).join("\n…\n"),
|
|
302
|
+
] as const;
|
|
303
|
+
}),
|
|
283
304
|
),
|
|
284
305
|
);
|
|
285
306
|
return {
|
|
@@ -301,7 +322,9 @@ async function readOf(
|
|
|
301
322
|
.join("\n\n"),
|
|
302
323
|
),
|
|
303
324
|
),
|
|
304
|
-
]
|
|
325
|
+
]
|
|
326
|
+
.join("\n\n---\n\n")
|
|
327
|
+
.slice(0, requested.maxSymbolLength),
|
|
305
328
|
] as const,
|
|
306
329
|
),
|
|
307
330
|
),
|