@cplieger/deadset-ts 7.2.0-dev.3 → 7.2.0-dev.4

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 (65) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +10 -6
  3. package/dist/src/component-files.js +96 -31
  4. package/dist/src/configuration-files.js +4 -3
  5. package/dist/src/configured-declarations.js +24 -2
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +26 -26
  8. package/dist/src/dependencies.js +2 -2
  9. package/dist/src/destructuring.js +77 -2
  10. package/dist/src/discover.js +68 -86
  11. package/dist/src/edges.js +1 -1
  12. package/dist/src/intra-function-parts.js +17 -6
  13. package/dist/src/inventory.js +11 -0
  14. package/dist/src/json-document.js +5 -5
  15. package/dist/src/namespace-objects.js +28 -18
  16. package/dist/src/overrides.js +7 -2
  17. package/dist/src/paths.js +9 -0
  18. package/dist/src/references.js +25 -12
  19. package/dist/src/reflective-lookup.js +8 -6
  20. package/dist/src/reporters/template.js +22 -13
  21. package/dist/src/serialization-contract.js +43 -12
  22. package/dist/src/session.js +19 -1
  23. package/dist/src/spelled-names.js +58 -0
  24. package/dist/src/suppress-file.js +2 -2
  25. package/dist/src/suppress-inline.js +3 -1
  26. package/dist/src/template-field.js +9 -6
  27. package/dist/src/type-errors.js +82 -28
  28. package/dist/src/value-reach.js +17 -5
  29. package/dist/src/verbs/analyze.js +3 -1
  30. package/dist/src/version-range.js +10 -5
  31. package/dist/src/workspace-resolution.js +19 -3
  32. package/dist/src/workspace.js +1 -1
  33. package/jsr.json +1 -1
  34. package/package.json +1 -1
  35. package/src/analysis.ts +32 -6
  36. package/src/component-files.ts +116 -31
  37. package/src/configuration-files.ts +14 -6
  38. package/src/configured-declarations.ts +33 -4
  39. package/src/conformance-record.ts +1 -1
  40. package/src/contextual-uses.ts +27 -27
  41. package/src/dependencies.ts +2 -2
  42. package/src/destructuring.ts +93 -2
  43. package/src/discover.ts +92 -87
  44. package/src/edges.ts +1 -1
  45. package/src/intra-function-parts.ts +21 -5
  46. package/src/inventory.ts +12 -0
  47. package/src/json-document.ts +7 -7
  48. package/src/namespace-objects.ts +34 -21
  49. package/src/overrides.ts +10 -2
  50. package/src/paths.ts +10 -0
  51. package/src/references.ts +28 -16
  52. package/src/reflective-lookup.ts +9 -7
  53. package/src/reporters/template.ts +21 -13
  54. package/src/serialization-contract.ts +53 -13
  55. package/src/session.ts +22 -1
  56. package/src/spelled-names.ts +102 -0
  57. package/src/suppress-file.ts +2 -2
  58. package/src/suppress-inline.ts +3 -1
  59. package/src/template-field.ts +9 -6
  60. package/src/type-errors.ts +94 -35
  61. package/src/value-reach.ts +19 -6
  62. package/src/verbs/analyze.ts +6 -1
  63. package/src/version-range.ts +10 -5
  64. package/src/workspace-resolution.ts +24 -4
  65. package/src/workspace.ts +1 -1
@@ -319,14 +319,15 @@ export function contextualUses<Brand>(
319
319
  return found;
320
320
  };
321
321
 
322
- const calls = new Map<number, Called | undefined>();
322
+ const calls = new Map<number, readonly Called[]>();
323
323
  /**
324
- * The parameter and return types of a type's one call signature, or of its one construct
325
- * signature where it has no call signature, read once per type.
324
+ * The parameter and return types of each of a type's call signatures, or of each of its
325
+ * construct signatures where it has no call signature, read once per type.
326
326
  */
327
- const callOf = (type: Type): Called | undefined => {
328
- if (calls.has(type.id)) {
329
- return calls.get(type.id);
327
+ const callsOf = (type: Type): readonly Called[] => {
328
+ const known = calls.get(type.id);
329
+ if (known !== undefined) {
330
+ return known;
330
331
  }
331
332
  lookups += 1;
332
333
  let kind = SignatureKind.Call;
@@ -336,31 +337,29 @@ export function contextualUses<Brand>(
336
337
  kind = SignatureKind.Construct;
337
338
  signatures = queries.signaturesOf(type, kind);
338
339
  }
339
- let called: Called | undefined;
340
- const [signature, ...more] = isAnswered(signatures) ? signatures : [];
341
- if (signature !== undefined && more.length === 0) {
340
+ const called = (isAnswered(signatures) ? signatures : []).map((signature): Called => {
342
341
  const parameters = signature.parameters.map((_parameter, index) =>
343
342
  queries.parameterType(signature, index),
344
343
  );
345
344
  const result = queries.returnTypeOf(signature);
346
345
  lookups += parameters.length + 1;
347
- called = {
346
+ return {
348
347
  kind,
349
348
  parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
350
349
  result: isAnswered(result) ? result : undefined,
351
350
  };
352
- }
351
+ });
353
352
  calls.set(type.id, called);
354
353
  return called;
355
354
  };
356
355
 
357
356
  /**
358
357
  * Each object type a value of `source` is checked as against the object types of
359
- * `target`: the value's own, and through array elements, a function's parameters,
360
- * checked the other way, and result, and the type arguments two instantiations share
361
- * by place, an array among them, as an array reaches `Iterable<T>`.
362
- * A source that is one of the target's own object types matches it as it stands. Each
363
- * pair is compared once, which ends the walk over a recursive type.
358
+ * `target`: the value's own, and through array elements, every pair of signatures
359
+ * (parameters checked the other way, then results), which reads more than the overload
360
+ * the checker picks, and the type arguments of two instantiations by place, as an array
361
+ * reaches `Iterable<T>`. A source that is one of the target's own object types matches
362
+ * it as it stands. Each pair is compared once, which ends the walk over a recursive type.
364
363
  */
365
364
  const compared = (
366
365
  source: Type | undefined,
@@ -385,7 +384,7 @@ export function contextualUses<Brand>(
385
384
  into.push({ source: one, targets, outside: true });
386
385
  }
387
386
  const element = elementOf(one);
388
- const called = element === undefined ? callOf(one) : undefined;
387
+ const called = element === undefined ? callsOf(one) : [];
389
388
  const sourceArguments = typeArgumentsOf(one);
390
389
  for (const part of targets) {
391
390
  const partElement = elementOf(part);
@@ -399,17 +398,17 @@ export function contextualUses<Brand>(
399
398
  sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
400
399
  compared(argument, targetArguments[index], into, seen);
401
400
  });
402
- if (element !== undefined) {
401
+ if (element !== undefined || called.length === 0) {
403
402
  continue;
404
403
  }
405
- const other = called === undefined ? undefined : callOf(part);
406
- if (called === undefined || other?.kind !== called.kind) {
407
- continue;
404
+ for (const other of callsOf(part)) {
405
+ for (const signature of called.filter((one) => one.kind === other.kind)) {
406
+ other.parameters.forEach((parameter, index) => {
407
+ compared(parameter, signature.parameters[index], into, seen);
408
+ });
409
+ compared(signature.result, other.result, into, seen);
410
+ }
408
411
  }
409
- other.parameters.forEach((parameter, index) => {
410
- compared(parameter, called.parameters[index], into, seen);
411
- });
412
- compared(called.result, other.result, into, seen);
413
412
  }
414
413
  }
415
414
  };
@@ -431,7 +430,7 @@ export function contextualUses<Brand>(
431
430
  if (element !== undefined) {
432
431
  return mayCarry(element, seen);
433
432
  }
434
- return callOf(part) !== undefined || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
433
+ return callsOf(part).length > 0 || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
435
434
  });
436
435
  };
437
436
 
@@ -467,11 +466,12 @@ export function contextualUses<Brand>(
467
466
  continue;
468
467
  }
469
468
  for (const [name, member] of sourceMembers) {
469
+ // A member the position shares with the source is no read of it. A member with no
470
+ // declaration, as a mapped type over literal keys has, is never one it shares.
470
471
  const read = tables.some((table) => {
471
472
  const declared = table.get(name);
472
473
  return (
473
474
  declared !== undefined &&
474
- declared.handles.size > 0 &&
475
475
  ![...declared.handles].some((handle) => member.handles.has(handle))
476
476
  );
477
477
  });
@@ -376,8 +376,8 @@ export function projectNeeds<Brand>(
376
376
  sourceFilesOf(project, targetRoot),
377
377
  parse,
378
378
  );
379
- for (const { file } of configuration.modules) {
380
- const names = declaredAt(file.fileName);
379
+ for (const { file, manifestDir } of configuration.modules) {
380
+ const names = declaredAt(joinPath(manifestDir, MANIFEST));
381
381
  for (const { node, text } of moduleStrings(file)) {
382
382
  use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
383
383
  }
@@ -24,7 +24,12 @@ import {
24
24
  type BinaryExpression,
25
25
  type Node,
26
26
  } from "@typescript/native/ast";
27
- import { TypeFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/sync";
27
+ import {
28
+ SignatureKind,
29
+ TypeFlags,
30
+ type Symbol as TSSymbol,
31
+ type Type,
32
+ } from "@typescript/native/sync";
28
33
  import {
29
34
  isAnswered,
30
35
  propertyTables,
@@ -34,6 +39,10 @@ import {
34
39
  } from "./query.ts";
35
40
  import type { ProjectView } from "./session.ts";
36
41
 
42
+ /** The types of an iteration result's `value` that carry no element of the iterable. */
43
+ const NO_ELEMENT =
44
+ TypeFlags.AnyOrUnknown | TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Null | TypeFlags.Never;
45
+
37
46
  /** One property one object pattern reads. */
38
47
  export interface PropertyRead {
39
48
  /** The pattern, whose destructured value declares the property. */
@@ -298,11 +307,93 @@ export function destructuring<Brand>(
298
307
  return isAnswered(elements) ? elements[0] : UNANSWERED;
299
308
  };
300
309
 
310
+ /** The type of the one property of a type that `matches` accepts, unanswered where two do. */
311
+ const propertyTypeOf = (owner: Type, matches: (property: TSSymbol) => boolean): Found => {
312
+ const table = tableOf(owner);
313
+ if (!isAnswered(table)) {
314
+ return UNANSWERED;
315
+ }
316
+ const [property, ...more] = [...table.values()].filter(matches);
317
+ if (property === undefined) {
318
+ return undefined;
319
+ }
320
+ if (more.length > 0) {
321
+ return UNANSWERED;
322
+ }
323
+ patternLookups += 1;
324
+ return queries.typeOfSymbol(property);
325
+ };
326
+
327
+ const resultOf = (callee: Found): Found => {
328
+ if (callee === undefined || callee === UNANSWERED) {
329
+ return callee;
330
+ }
331
+ patternLookups += 1;
332
+ const signatures = queries.signaturesOf(callee, SignatureKind.Call);
333
+ if (!isAnswered(signatures)) {
334
+ return UNANSWERED;
335
+ }
336
+ const [signature] = signatures;
337
+ if (signature === undefined) {
338
+ return undefined;
339
+ }
340
+ patternLookups += 1;
341
+ return queries.returnTypeOf(signature);
342
+ };
343
+
344
+ const iterations = new Map<number, Found>();
345
+ /**
346
+ * The type a `for…of` takes from one iterable that is no array, read through the
347
+ * iteration protocol: the `value` of what the `next()` of its `[Symbol.iterator]()`
348
+ * answers. A constituent of that answer whose value is {@link NO_ELEMENT} carries no
349
+ * element; where more than one carries one, the element is left unanswered, so a read
350
+ * through it is resolved by name.
351
+ */
352
+ const iteratedOf = (type: Type): Found => {
353
+ if (iterations.has(type.id)) {
354
+ return iterations.get(type.id);
355
+ }
356
+ // The checker keys a member a unique symbol names `__@<name>@<symbol id>`, and escapes
357
+ // a written key that starts with `__` with one more `_`, so no string key matches; a
358
+ // unique symbol of the program's own named `iterator` does, which leaves two matches:
359
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/checker/checker.go#L23096-L23108
360
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/ast/symbol.go#L91-L103
361
+ const iterator = resultOf(
362
+ propertyTypeOf(type, (one) => one.escapedName.startsWith("__@iterator@")),
363
+ );
364
+ const result =
365
+ iterator === undefined || iterator === UNANSWERED
366
+ ? iterator
367
+ : resultOf(propertyTypeOf(iterator, (one) => one.name === "next"));
368
+ let found: Found = result;
369
+ if (result !== undefined && result !== UNANSWERED) {
370
+ let parts: Answer<readonly Type[]> = [result];
371
+ if (result.isUnionType()) {
372
+ patternLookups += 1;
373
+ parts = queries.constituents(result);
374
+ }
375
+ const values: readonly Found[] = isAnswered(parts)
376
+ ? parts.map((part) => propertyTypeOf(part, (one) => one.name === "value"))
377
+ : [UNANSWERED];
378
+ const carried = values.filter(
379
+ (value): value is Answer<Type> =>
380
+ value === UNANSWERED || (value !== undefined && (value.flags & NO_ELEMENT) === 0),
381
+ );
382
+ const [only, ...more] = carried;
383
+ found = more.length > 0 ? UNANSWERED : only;
384
+ }
385
+ iterations.set(type.id, found);
386
+ return found;
387
+ };
388
+
301
389
  const step = (type: Type, next: Step): Found => {
302
390
  const defined = definedOf(type);
303
391
  if (defined === undefined || defined === UNANSWERED) {
304
392
  return defined;
305
393
  }
394
+ if (next.kind === "iterated") {
395
+ return elementOf(defined, undefined) ?? iteratedOf(defined);
396
+ }
306
397
  if (next.kind === "property") {
307
398
  const table = tableOf(defined);
308
399
  if (!isAnswered(table)) {
@@ -315,7 +406,7 @@ export function destructuring<Brand>(
315
406
  patternLookups += 1;
316
407
  return queries.typeOfSymbol(property);
317
408
  }
318
- return elementOf(defined, next.kind === "element" ? next.index : undefined);
409
+ return elementOf(defined, next.index);
319
410
  };
320
411
 
321
412
  const resolve = (reads: readonly PropertyRead[]): readonly Answer<TSSymbol | undefined>[] => {
package/src/discover.ts CHANGED
@@ -2,7 +2,7 @@ import { DiagnosticCategory } from "@typescript/native/sync";
2
2
  import type { Diagnostic } from "@typescript/native/sync";
3
3
  import type { BuildConfiguration, ProjectConfiguration } from "./config.ts";
4
4
  import type { Host } from "./host.ts";
5
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
5
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
6
6
  import type { Scope } from "./scope.ts";
7
7
  import { SetupError } from "./setup-failure.ts";
8
8
  import type { Engine, ProjectDiagnostics } from "./session.ts";
@@ -95,20 +95,46 @@ export function diagnosticErrors(sets: {
95
95
  /** Why one open project cannot be analyzed, split by whether it loaded at all. */
96
96
  interface ProjectErrors {
97
97
  /**
98
- * The errors in the configuration itself: no input matched, an option that does not
99
- * parse. A project carrying one did not load, whatever its files say.
98
+ * The errors in the configuration itself, and the errors the compiler places in no file
99
+ * that leave its checker without a type it needs: no input matched, an option that
100
+ * does not parse, a missing global type. A project carrying one did not load, whatever
101
+ * its files say.
100
102
  */
101
103
  readonly load: readonly LocatedDiagnostic[];
102
104
  /** The errors in the files of a project that loaded: their syntax and their types. */
103
105
  readonly check: readonly LocatedDiagnostic[];
106
+ /**
107
+ * The libraries the configuration names that the compiler cannot find, which leave it
108
+ * loaded: a use of a name only such a library declares is an error in its file.
109
+ */
110
+ readonly unfound: readonly LocatedDiagnostic[];
104
111
  }
105
112
 
113
+ /**
114
+ * The codes of a type library and a lib the configuration names that the compiler
115
+ * cannot find, which it reports while it gathers the program's files:
116
+ * https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go
117
+ */
118
+ const UNFOUND_LIBRARY: ReadonlySet<number> = new Set([2688, 2726, 2727]);
119
+
106
120
  /** The errors one open project carries, each located in the file it names. */
107
121
  export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
108
- const load = sets.configParsing.filter(isError);
122
+ // The compiler numbers its semantic messages from 2000 and its option messages from
123
+ // 5000. A file-less option error, a removed option or an output layout, leaves the
124
+ // program whole, and the compiler checks it all the same:
125
+ // https://github.com/microsoft/TypeScript/wiki/Coding-guidelines#diagnostic-message-codes
126
+ const semantic = sets.unplaced.filter(
127
+ (diagnostic) => isError(diagnostic) && diagnostic.code >= 2000 && diagnostic.code < 3000,
128
+ );
129
+ const unfound = semantic.filter(({ code }) => UNFOUND_LIBRARY.has(code));
130
+ const load = [
131
+ ...sets.configParsing.filter(isError),
132
+ ...semantic.filter(({ code }) => !UNFOUND_LIBRARY.has(code)),
133
+ ];
109
134
  return {
110
135
  load: locatedIn(load, sets.locate),
111
136
  check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
137
+ unfound: load.length > 0 ? [] : locatedIn(unfound, sets.locate),
112
138
  };
113
139
  }
114
140
 
@@ -182,10 +208,6 @@ interface Discovery {
182
208
  readonly fromScope: readonly string[];
183
209
  }
184
210
 
185
- function isRecord(value: unknown): value is Record<string, unknown> {
186
- return typeof value === "object" && value !== null && !Array.isArray(value);
187
- }
188
-
189
211
  /**
190
212
  * The configuration files below `root`, in ascending path order, skipping an
191
213
  * ignored directory. A directory that cannot be read is skipped: a tree the
@@ -221,45 +243,6 @@ function configFilesUnder(host: Host, root: string): string[] {
221
243
  return found.sort();
222
244
  }
223
245
 
224
- /**
225
- * The configuration files one configuration's `references` name, resolved against
226
- * that configuration's own directory. A reference naming a directory names the
227
- * `tsconfig.json` in it, which is the compiler's own rule.
228
- */
229
- function referencesOf(host: Host, configFile: string): string[] {
230
- let text: string;
231
- try {
232
- text = host.readFile(configFile);
233
- } catch {
234
- return [];
235
- }
236
- let value: unknown;
237
- try {
238
- value = JSON.parse(text);
239
- } catch {
240
- // What a configuration's contents say is refused by the compiler's own
241
- // diagnostics, which name the position; nothing is inferred from it here.
242
- return [];
243
- }
244
- if (!isRecord(value) || !Array.isArray(value["references"])) {
245
- return [];
246
- }
247
- const dir = dirnamePath(configFile);
248
- const found: string[] = [];
249
- for (const entry of value["references"] as unknown[]) {
250
- if (!isRecord(entry)) {
251
- continue;
252
- }
253
- const path = entry["path"];
254
- if (typeof path !== "string" || path === "") {
255
- continue;
256
- }
257
- const resolved = isAbsolutePath(path) ? path : joinPath(dir, path);
258
- found.push(resolved.endsWith(".json") ? resolved : joinPath(resolved, "tsconfig.json"));
259
- }
260
- return found;
261
- }
262
-
263
246
  /**
264
247
  * The configuration file the scope names for the target directly, where its path
265
248
  * names one rather than a directory. A target naming a directory is reached by the
@@ -274,15 +257,16 @@ function scopeConfigFiles(host: Host, scope: Scope): string[] {
274
257
 
275
258
  /**
276
259
  * Every configuration file reached from `pending`, in the order discovery reads them:
277
- * each once, each followed by the configurations it references, transitively. A
278
- * configuration the compiler cannot read ends the run naming it, unless `unreadable`
279
- * is given, which is then told the configuration and why, and the walk goes on
280
- * without it.
260
+ * each once, each followed by the configurations it references that `follows` admits,
261
+ * transitively. A configuration the compiler cannot read ends the run naming it, unless
262
+ * `unreadable` is given, which is then told the configuration and why, and the walk
263
+ * goes on without it.
281
264
  */
282
265
  function derivedFrom(
283
266
  engine: Engine,
284
267
  host: Host,
285
268
  pending: string[],
269
+ follows: (reference: string) => boolean,
286
270
  unreadable?: (configFile: string, reason: string) => void,
287
271
  ): string[] {
288
272
  const seen = new Set<string>();
@@ -297,19 +281,19 @@ function derivedFrom(
297
281
  continue;
298
282
  }
299
283
  seen.add(path);
300
- const reason = unreadableReason(engine, path);
301
- if (reason !== undefined) {
284
+ const read = readConfiguration(engine, path);
285
+ if ("reason" in read) {
302
286
  if (unreadable === undefined) {
303
287
  throw new DiscoveryError(
304
- `${path} cannot be read as a compiler configuration: ${reason}`,
288
+ `${path} cannot be read as a compiler configuration: ${read.reason}`,
305
289
  [],
306
290
  );
307
291
  }
308
- unreadable(path, reason);
292
+ unreadable(path, read.reason);
309
293
  continue;
310
294
  }
311
295
  found.push(path);
312
- pending.push(...referencesOf(host, path));
296
+ pending.push(...read.references.filter(follows));
313
297
  }
314
298
  return found;
315
299
  }
@@ -335,7 +319,7 @@ export function consumerProjects(engine: Engine, host: Host, root: string): read
335
319
  if (kind !== "directory") {
336
320
  throw new DiscoveryError(`the consumer ${root} is not a directory`, []);
337
321
  }
338
- const found = derivedFrom(engine, host, configFilesUnder(host, root));
322
+ const found = derivedFrom(engine, host, configFilesUnder(host, root), () => true);
339
323
  if (found.length === 0) {
340
324
  throw new DiscoveryError(
341
325
  `the consumer ${root} holds no compiler configuration, so none of its references can be read`,
@@ -353,39 +337,52 @@ function derivedId(targetRoot: string, configFile: string): string {
353
337
  return relativePath(targetRoot, configFile) ?? configFile;
354
338
  }
355
339
 
356
- /** Why the compiler cannot read one configuration file, or undefined where it can. */
357
- function unreadableReason(engine: Engine, configFile: string): string | undefined {
340
+ /**
341
+ * The configuration files one configuration's `references` name, or why the compiler
342
+ * cannot read the configuration.
343
+ */
344
+ function readConfiguration(
345
+ engine: Engine,
346
+ configFile: string,
347
+ ): { readonly references: readonly string[] } | { readonly reason: string } {
358
348
  try {
359
- engine.parseConfigFile(configFile);
360
- return undefined;
349
+ const parsed = engine.parseConfigFile(configFile);
350
+ // The parse answers a directory reference as the directory; the compiler reads a
351
+ // path not ending `.json` as its `tsconfig.json`:
352
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/core/projectreference.go#L18
353
+ return {
354
+ references: (parsed.projectReferences ?? []).map(({ path }) =>
355
+ path.endsWith(".json") ? path : joinPath(path, "tsconfig.json"),
356
+ ),
357
+ };
361
358
  } catch (error: unknown) {
362
- return error instanceof Error ? error.message : String(error);
359
+ return { reason: error instanceof Error ? error.message : String(error) };
363
360
  }
364
361
  }
365
362
 
366
363
  /**
367
- * Resolves one compiler configuration file, or ends the run naming it: a project
368
- * opened from a configuration the compiler cannot read would be analyzed with no
369
- * type information.
364
+ * The references of one compiler configuration file, or ends the run naming it: a
365
+ * project opened from a configuration the compiler cannot read would be analyzed with
366
+ * no type information.
370
367
  */
371
- function parseOrRefuse(engine: Engine, configFile: string, named: string): void {
372
- const reason = unreadableReason(engine, configFile);
373
- if (reason !== undefined) {
374
- throw new DiscoveryError(`${named} cannot be read as a compiler configuration: ${reason}`, []);
368
+ function parseOrRefuse(engine: Engine, configFile: string, named: string): readonly string[] {
369
+ const read = readConfiguration(engine, configFile);
370
+ if ("reason" in read) {
371
+ throw new DiscoveryError(
372
+ `${named} cannot be read as a compiler configuration: ${read.reason}`,
373
+ [],
374
+ );
375
375
  }
376
+ return read.references;
376
377
  }
377
378
 
378
379
  /**
379
- * The projects a build matrix declares, in the order it lists them. Each entry
380
- * names a file below the target root, and an entry naming a file that is absent,
381
- * that is a directory, or that the compiler cannot read ends the run naming it.
382
- *
383
- * A declared project's `references` are not followed, because the entries are the
384
- * matrix, and a reference to a configuration the matrix does not name ends the run
385
- * naming both: the compiler builds a referenced project's sources into the program
386
- * that references it without reporting their diagnostics, so the project the matrix
387
- * left out would be analyzed with its errors unread. An entry naming a file an
388
- * earlier entry named adds nothing, so the earlier identifier stands.
380
+ * The projects a build matrix declares, in its order, each entry a file below the target
381
+ * root: an entry naming an absent file, a directory or a file the compiler cannot read
382
+ * ends the run naming it, and one naming an earlier entry's file adds nothing. A
383
+ * reference below the root the matrix does not name ends the run naming both, because the
384
+ * compiler builds a referenced project's sources into the referencing program without
385
+ * reporting their diagnostics. A reference outside the root is an outside project.
389
386
  */
390
387
  function declaredProjects(
391
388
  engine: Engine,
@@ -395,6 +392,7 @@ function declaredProjects(
395
392
  ): Discovery {
396
393
  const seen = new Set<string>();
397
394
  const projects: DiscoveredProject[] = [];
395
+ const references = new Map<string, readonly string[]>();
398
396
  for (const entry of entries) {
399
397
  const configFile = joinPath(targetRoot, entry.project);
400
398
  const named = `the project ${JSON.stringify(entry.id)} (${configFile})`;
@@ -411,11 +409,13 @@ function declaredProjects(
411
409
  continue;
412
410
  }
413
411
  seen.add(configFile);
414
- parseOrRefuse(engine, configFile, named);
412
+ references.set(configFile, parseOrRefuse(engine, configFile, named));
415
413
  projects.push({ id: entry.id, configFile });
416
414
  }
417
415
  for (const project of projects) {
418
- const unnamed = referencesOf(host, project.configFile).find((path) => !seen.has(path));
416
+ const unnamed = references
417
+ .get(project.configFile)
418
+ ?.find((path) => !seen.has(path) && relativePath(targetRoot, path) !== undefined);
419
419
  if (unnamed !== undefined) {
420
420
  throw new DiscoveryError(
421
421
  `the project ${JSON.stringify(project.id)} (${project.configFile}) references ` +
@@ -435,21 +435,26 @@ function declaredProjects(
435
435
  }
436
436
 
437
437
  /**
438
- * The projects a run derives where the matrix declares none, in the order discovery
439
- * reads them: the paths the scope names, then every compiler configuration under
440
- * the target root that is not inside an ignored directory, then the configurations
441
- * each of those references, transitively. Each configuration appears once. A
442
- * configuration the scope names directly is the caller's own and ends the run where
443
- * it cannot be read; one the walk found is dropped and named.
438
+ * The projects a run derives where the matrix declares none, in discovery order: the
439
+ * paths the scope names, every compiler configuration under the target root outside an
440
+ * ignored directory, then each configuration below the root those reference,
441
+ * transitively, each once. A reference outside the root is an outside project, whose
442
+ * sources the compiler builds into the referencing program. A configuration the scope
443
+ * names ends the run where it cannot be read; one the walk found is dropped and named.
444
444
  */
445
445
  function derivedProjects(engine: Engine, host: Host, scope: Scope): Discovery {
446
446
  const targetRoot = scope.target.path;
447
447
  const fromScope = scopeConfigFiles(host, scope);
448
448
  const notBuilt: NotBuilt[] = [];
449
+ const rootDir = resolvePath(
450
+ host.workingDirectory(),
451
+ fromScope.length > 0 ? dirnamePath(targetRoot) : targetRoot,
452
+ );
449
453
  const configFiles = derivedFrom(
450
454
  engine,
451
455
  host,
452
456
  [...fromScope, ...configFilesUnder(host, targetRoot)],
457
+ (reference) => relativePath(rootDir, reference) !== undefined,
453
458
  (configFile, reason) => {
454
459
  if (fromScope.includes(configFile)) {
455
460
  throw new DiscoveryError(
package/src/edges.ts CHANGED
@@ -156,7 +156,7 @@ export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide
156
156
 
157
157
  const sides: EdgeSide[] = [];
158
158
  (edges as readonly unknown[]).forEach((value, index) => {
159
- const offset = walk.opened[index];
159
+ const offset = walk.starts[index];
160
160
  const at =
161
161
  offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
162
162
  const edge = readEdge(value, at);
@@ -29,6 +29,7 @@ import {
29
29
  isExpressionStatement,
30
30
  isFunctionDeclaration,
31
31
  isFunctionExpression,
32
+ isFunctionLikeDeclaration,
32
33
  isHeritageClause,
33
34
  isIdentifier,
34
35
  isIfStatement,
@@ -49,6 +50,7 @@ import {
49
50
  isQualifiedName,
50
51
  isReturnStatement,
51
52
  isShorthandPropertyAssignment,
53
+ isSourceFile,
52
54
  isStringLiteral,
53
55
  isSwitchStatement,
54
56
  isThrowStatement,
@@ -77,6 +79,7 @@ import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
77
79
  import type { Reference } from "./references.ts";
78
80
  import type { ProjectView } from "./session.ts";
79
81
  import { signatureOf, type SignatureNode } from "./signatures.ts";
82
+ import { spellsNoBinding } from "./spelled-names.ts";
80
83
 
81
84
  /** The subject kind of each part this module reads. */
82
85
  type PartKind = "parameter" | "result" | "statement" | "store" | "case";
@@ -268,13 +271,12 @@ function caseKey(expression: Node): string | undefined {
268
271
  return undefined;
269
272
  }
270
273
 
271
- /** Every identifier below one node whose text one of the names spells, a member's name aside. */
274
+ /** Every identifier below one node whose text one of the names spells as a reference. */
272
275
  function identifiersNaming(node: Node, names: ReadonlySet<string>): Identifier[] {
273
276
  const found: Identifier[] = [];
274
277
  const visit = (at: Node): void => {
275
278
  if (isIdentifier(at)) {
276
- const parent = at.parent;
277
- if (names.has(at.text) && !(isPropertyAccessExpression(parent) && parent.name === at)) {
279
+ if (names.has(at.text) && !spellsNoBinding(at)) {
278
280
  found.push(at);
279
281
  }
280
282
  return;
@@ -310,6 +312,16 @@ function inNestedFunction(node: Node, outer: Node): boolean {
310
312
  return false;
311
313
  }
312
314
 
315
+ /** The nearest function around one node whose `arguments` object it names: an arrow has none. */
316
+ function ownArgumentsOf(node: Node): Node | undefined {
317
+ for (let at = node.parent; !isSourceFile(at); at = at.parent) {
318
+ if (isFunctionLikeDeclaration(at) && !isArrowFunction(at)) {
319
+ return at;
320
+ }
321
+ }
322
+ return undefined;
323
+ }
324
+
313
325
  /** Whether a body is a stub: it holds no statement, or nothing but `throw` statements. */
314
326
  function isStub(signature: SignatureNode): boolean {
315
327
  const body = signature.body;
@@ -876,9 +888,13 @@ function parameterParts(
876
888
  ids: ReadonlyMap<Node, Answer<number>>,
877
889
  inValue: boolean,
878
890
  ): { readonly parts: PartFact[]; readonly contextual: ContextualParameter[] } {
879
- // A function that reads `arguments` reads every parameter by position.
891
+ // A function that reads its `arguments` reads every parameter by position. A nested
892
+ // function other than an arrow has an `arguments` object of its own.
880
893
  const readsArguments =
881
- !isArrowFunction(signature) && identifiersNaming(signature, new Set(["arguments"])).length > 0;
894
+ !isArrowFunction(signature) &&
895
+ identifiersNaming(signature, new Set(["arguments"])).some(
896
+ (use) => ownArgumentsOf(use) === signature,
897
+ );
882
898
  const read = reader.names.map(
883
899
  ({ name }) =>
884
900
  readsArguments ||
package/src/inventory.ts CHANGED
@@ -459,6 +459,18 @@ function nameNodeOf(node: Node): Node | undefined {
459
459
  return (node as { readonly name?: Node }).name;
460
460
  }
461
461
 
462
+ /**
463
+ * The fragment component one declaration's name spells in a reference, as the inventory
464
+ * spells it, or undefined for a name the grammar does not represent.
465
+ */
466
+ export function declaredComponent(node: Node): string | undefined {
467
+ const component = componentOf(nameNodeOf(node));
468
+ if (component === undefined) {
469
+ return undefined;
470
+ }
471
+ return component.computed ? computedComponent(component.text) : nameComponent(component.text);
472
+ }
473
+
462
474
  /**
463
475
  * One node's identity inside one program, which is how two readings of that program
464
476
  * name one declaration.