@cplieger/deadset-ts 3.13.0 → 4.0.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.
Files changed (92) hide show
  1. package/README.md +67 -49
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +120 -24
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/configured-declarations.js +22 -9
  8. package/dist/src/dependencies.js +34 -17
  9. package/dist/src/deprecation.js +35 -24
  10. package/dist/src/destructuring.js +37 -29
  11. package/dist/src/discover.js +106 -29
  12. package/dist/src/entry-point-gaps.js +2 -2
  13. package/dist/src/entry-points.js +7 -0
  14. package/dist/src/enum-group.js +46 -40
  15. package/dist/src/exempt.js +42 -1
  16. package/dist/src/findings/emitters.js +15 -1
  17. package/dist/src/findings/interfaces.js +7 -1
  18. package/dist/src/framework-lifecycle.js +8 -6
  19. package/dist/src/implementations.js +45 -23
  20. package/dist/src/interface-satisfaction.js +30 -27
  21. package/dist/src/intra-function-parts.js +37 -20
  22. package/dist/src/inventory.js +85 -4
  23. package/dist/src/json-chunks.js +76 -0
  24. package/dist/src/partial.js +23 -0
  25. package/dist/src/query.js +207 -0
  26. package/dist/src/references.js +92 -20
  27. package/dist/src/reflective-lookup.js +18 -9
  28. package/dist/src/report.js +3 -1
  29. package/dist/src/reporters/annotations.js +9 -5
  30. package/dist/src/reporters/json.js +3 -2
  31. package/dist/src/reporters/sarif.js +2 -1
  32. package/dist/src/reporters/template.js +51 -10
  33. package/dist/src/reporters/text.js +8 -6
  34. package/dist/src/roots.js +27 -11
  35. package/dist/src/schema.js +2 -2
  36. package/dist/src/serialization-contract.js +12 -10
  37. package/dist/src/session.js +79 -39
  38. package/dist/src/suppress-file.js +2 -1
  39. package/dist/src/sweep.js +4 -1
  40. package/dist/src/value-reach.js +14 -19
  41. package/dist/src/verbs/analyze.js +20 -3
  42. package/dist/src/verbs/explain.js +22 -9
  43. package/dist/src/verbs/print-projects.js +41 -8
  44. package/dist/src/verbs/print-retained.js +6 -2
  45. package/dist/src/verbs/print-roots.js +5 -1
  46. package/jsr.json +1 -1
  47. package/package.json +3 -3
  48. package/src/alias-chain.ts +75 -11
  49. package/src/analysis.ts +224 -62
  50. package/src/calls.ts +10 -9
  51. package/src/class-members.ts +10 -11
  52. package/src/configured-declarations.ts +29 -13
  53. package/src/dependencies.ts +42 -20
  54. package/src/deprecation.ts +53 -27
  55. package/src/destructuring.ts +50 -39
  56. package/src/discover.ts +178 -34
  57. package/src/entry-point-gaps.ts +2 -2
  58. package/src/entry-points.ts +7 -0
  59. package/src/enum-group.ts +56 -42
  60. package/src/exempt.ts +50 -2
  61. package/src/findings/emitters.ts +18 -1
  62. package/src/findings/interfaces.ts +7 -1
  63. package/src/framework-lifecycle.ts +10 -6
  64. package/src/host.ts +5 -3
  65. package/src/implementations.ts +59 -23
  66. package/src/interface-satisfaction.ts +30 -27
  67. package/src/intra-function-parts.ts +43 -25
  68. package/src/inventory.ts +97 -4
  69. package/src/json-chunks.ts +87 -0
  70. package/src/partial.ts +40 -0
  71. package/src/query.ts +522 -0
  72. package/src/references.ts +119 -38
  73. package/src/reflective-lookup.ts +21 -11
  74. package/src/report.ts +14 -2
  75. package/src/reporters/annotations.ts +23 -21
  76. package/src/reporters/json.ts +3 -2
  77. package/src/reporters/reporter.ts +2 -2
  78. package/src/reporters/sarif.ts +3 -2
  79. package/src/reporters/template.ts +53 -11
  80. package/src/reporters/text.ts +12 -12
  81. package/src/roots.ts +34 -12
  82. package/src/schema.ts +2 -2
  83. package/src/serialization-contract.ts +12 -10
  84. package/src/session.ts +146 -58
  85. package/src/suppress-file.ts +6 -2
  86. package/src/sweep.ts +9 -4
  87. package/src/value-reach.ts +20 -23
  88. package/src/verbs/analyze.ts +25 -3
  89. package/src/verbs/explain.ts +20 -9
  90. package/src/verbs/print-projects.ts +54 -8
  91. package/src/verbs/print-retained.ts +6 -2
  92. package/src/verbs/print-roots.ts +5 -1
package/src/analysis.ts CHANGED
@@ -8,7 +8,6 @@
8
8
  * project's checker, and that holds only for views the session itself hands out.
9
9
  */
10
10
 
11
- import type { Diagnostic } from "@typescript/native/unstable/sync";
12
11
  import type { Config, Provenance } from "./config.ts";
13
12
  import {
14
13
  consumerLoads,
@@ -23,14 +22,24 @@ import {
23
22
  } from "./configured-declarations.ts";
24
23
  import { decorator } from "./decorator.ts";
25
24
  import { dependenciesOf, projectNeeds } from "./dependencies.ts";
26
- import { deprecatedDeclarations } from "./deprecation.ts";
27
- import { diagnosticErrors, discoverProjects, DiscoveryError } from "./discover.ts";
25
+ import { deprecatedDeclarations, type Deprecation } from "./deprecation.ts";
26
+ import {
27
+ discoverProjects,
28
+ DiscoveryError,
29
+ isGuess,
30
+ projectErrors,
31
+ renderDiagnostic,
32
+ targetRelative,
33
+ type LocatedDiagnostic,
34
+ type NotBuilt,
35
+ } from "./discover.ts";
28
36
  import { readEdgeSides } from "./edges.ts";
29
37
  import { enumGroup } from "./enum-group.ts";
30
38
  import {
31
39
  computeExemptions,
32
40
  disabledClasses,
33
41
  exemptionsOf,
42
+ isUnansweredRecord,
34
43
  retainedIn,
35
44
  type Detector,
36
45
  type Detectors,
@@ -62,6 +71,7 @@ import {
62
71
  type SweepResult,
63
72
  } from "./matrix.ts";
64
73
  import { relativePath, resolvePath } from "./paths.ts";
74
+ import type { UnansweredQuestion } from "./query.ts";
65
75
  import { byPosition, type Position } from "./position.ts";
66
76
  import { reflectiveLookup } from "./reflective-lookup.ts";
67
77
  import { references, testFileRulesOf, type Reference, type TestFileRule } from "./references.ts";
@@ -105,6 +115,10 @@ export interface RunRoots {
105
115
  readonly roots: readonly RunRoot[];
106
116
  /** One finding per configured root or pattern that named nothing in any project. */
107
117
  readonly findings: readonly Finding[];
118
+ /** The derived configurations the run dropped. */
119
+ readonly notBuilt: readonly NotBuilt[];
120
+ /** Every question the checker could not answer, each once. */
121
+ readonly unanswered: readonly UnansweredQuestion[];
108
122
  }
109
123
 
110
124
  /**
@@ -156,15 +170,19 @@ interface ConsumerReads {
156
170
  interface ReadProjects<Answer> {
157
171
  readonly answers: readonly Answer[];
158
172
  readonly consumers: ConsumerReads;
173
+ /** The derived configurations the run dropped, in the order it met them. */
174
+ readonly notBuilt: readonly NotBuilt[];
175
+ /** Every question the checker could not answer, each once. */
176
+ readonly unanswered: readonly UnansweredQuestion[];
159
177
  }
160
178
 
161
179
  /**
162
- * Reads every project the scope discovers, and every project of every consumer it
163
- * declares, in one snapshot. Each is checked for errors first. A target project is then
164
- * enumerated and rooted, and `stage` reads the rest of what it needs from the project
165
- * while its view is open; a consumer project is read for its references to the target's
166
- * declarations. A project carrying an error fails the run with no answer, as every verb
167
- * that reads declarations does, and so does a declared consumer that cannot be loaded.
180
+ * Reads every project the scope discovers, and every project of each consumer it
181
+ * declares, in one snapshot, each checked for errors first. A target project is
182
+ * enumerated, rooted and handed to `stage`; a consumer project is read for its references
183
+ * to the target. A guess that does not load is dropped into `notBuilt` (see
184
+ * {@link isGuess}); any other project that does not load or carries an error fails the
185
+ * run, as does a run whose every guess was dropped.
168
186
  */
169
187
  function readProjects<Answer>(
170
188
  engine: Engine,
@@ -189,18 +207,39 @@ function readProjects<Answer>(
189
207
  testFiles: config.ts.testFiles,
190
208
  publishedAPI: config.targetKind === "library",
191
209
  };
192
- const failures: Diagnostic[] = [];
210
+ const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
211
+ const notBuilt: NotBuilt[] = [...discovered.notBuilt];
212
+ const dropped = (configFile: string, error: string): void => {
213
+ notBuilt.push({
214
+ id: ids.get(configFile) ?? configFile,
215
+ configFile,
216
+ error: targetRelative(error, absoluteRoot),
217
+ });
218
+ };
219
+ const failures: LocatedDiagnostic[] = [];
193
220
  const consumed = new Map<string, Reference>();
194
- const { projects } = runSession(
221
+ const session = runSession(
195
222
  engine,
196
223
  [...discovered.configFiles, ...consumerOf.keys()],
197
224
  (project) => {
198
- const errors = diagnosticErrors(diagnosticsOf(project));
199
- if (errors.length > 0) {
200
- failures.push(...errors);
225
+ const errors = projectErrors(diagnosticsOf(project));
226
+ const consumer = consumerOf.get(project.configFile);
227
+ const [unloaded] = errors.load;
228
+ if (
229
+ unloaded !== undefined &&
230
+ consumer === undefined &&
231
+ isGuess(discovered, project.configFile)
232
+ ) {
233
+ dropped(
234
+ project.configFile,
235
+ renderDiagnostic(unloaded, (path) => relativePath(absoluteRoot, path) ?? path),
236
+ );
237
+ return undefined;
238
+ }
239
+ if (errors.load.length > 0 || errors.check.length > 0) {
240
+ failures.push(...errors.load, ...errors.check);
201
241
  return undefined;
202
242
  }
203
- const consumer = consumerOf.get(project.configFile);
204
243
  if (consumer !== undefined) {
205
244
  for (const reference of consumerReferences(
206
245
  project,
@@ -221,19 +260,36 @@ function readProjects<Answer>(
221
260
  const configuration = ids.get(project.configFile) ?? project.configFile;
222
261
  return stage(project, { configuration, configFile: project.configFile, held, rooted });
223
262
  },
263
+ discovered.derived ? "record" : "refuse",
224
264
  );
265
+ for (const configFile of session.unopened) {
266
+ if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
267
+ throw new DiscoveryError(`${configFile} opened no project`, []);
268
+ }
269
+ dropped(configFile, "the compiler opened no project for the configuration");
270
+ }
225
271
  if (failures.length > 0) {
226
272
  throw new DiscoveryError(
227
- `${String(failures.length)} error(s); no answer was produced`,
273
+ `${String(failures.length)} error(s), so no answer was produced`,
228
274
  failures,
229
275
  );
230
276
  }
277
+ const answers = session.projects.filter((answer): answer is Answer => answer !== undefined);
278
+ const [first] = notBuilt;
279
+ if (answers.length === 0 && first !== undefined) {
280
+ throw new DiscoveryError(
281
+ `no configuration discovery derived could be built; ${first.id}: ${first.error}`,
282
+ [],
283
+ );
284
+ }
231
285
  return {
232
- answers: projects.filter((answer): answer is Answer => answer !== undefined),
286
+ answers,
233
287
  consumers: {
234
288
  loaded: loads.map((load) => load.consumer),
235
289
  references: [...consumed.values()],
236
290
  },
291
+ notBuilt,
292
+ unanswered: session.unanswered,
237
293
  };
238
294
  }
239
295
 
@@ -251,7 +307,8 @@ export function runRoots(
251
307
  provenance: Provenance,
252
308
  ): RunRoots {
253
309
  const targetRoot = scope.target.path;
254
- const projects = readProjects(engine, host, scope, config, (_project, read) => read).answers;
310
+ const read = readProjects(engine, host, scope, config, (_project, projectRead) => projectRead);
311
+ const projects = read.answers;
255
312
 
256
313
  const configurations: string[] = [];
257
314
  const merged = new Map<string, Omit<RunRoot, "configurations"> & { configurations: string[] }>();
@@ -295,6 +352,8 @@ export function runRoots(
295
352
  unmatched,
296
353
  documentOf(host, provenance, ROOTS_SETTING, targetRoot),
297
354
  ),
355
+ notBuilt: read.notBuilt,
356
+ unanswered: read.unanswered,
298
357
  };
299
358
  }
300
359
 
@@ -307,6 +366,15 @@ export interface RunSweep {
307
366
  * would otherwise judge dead, in the declarations' site order.
308
367
  */
309
368
  readonly retained: readonly Exemption[];
369
+ /** The derived configurations the run dropped. */
370
+ readonly notBuilt: readonly NotBuilt[];
371
+ /** Every question the checker could not answer, each once. */
372
+ readonly unanswered: readonly UnansweredQuestion[];
373
+ /**
374
+ * Every declaration an unanswered question could have kept live, and every one only
375
+ * such a declaration keeps live. The run reports none of them.
376
+ */
377
+ readonly heldByUnanswered: readonly string[];
310
378
  }
311
379
 
312
380
  /** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
@@ -326,6 +394,8 @@ interface SweptProject<Extra> {
326
394
  readonly configFile: string;
327
395
  readonly configured: Configured;
328
396
  readonly exempt: readonly Exemption[];
397
+ /** The declarations of the project an unanswered question could have kept live. */
398
+ readonly unanswered: readonly string[];
329
399
  readonly extra: Extra;
330
400
  }
331
401
 
@@ -333,9 +403,15 @@ interface SweptProject<Extra> {
333
403
  interface ReadRun<Extra> {
334
404
  readonly matrix: Matrix;
335
405
  readonly exempt: readonly Exemption[];
406
+ /** The declarations of the run an unanswered question could have kept live, each once. */
407
+ readonly unansweredHeld: readonly string[];
336
408
  readonly projects: readonly SweptProject<Extra>[];
337
409
  /** Every declared consumer, each loaded, in the scope's order. */
338
410
  readonly consumers: readonly LoadedConsumer[];
411
+ /** The derived configurations the run dropped. */
412
+ readonly notBuilt: readonly NotBuilt[];
413
+ /** Every question the checker could not answer, each once. */
414
+ readonly unanswered: readonly UnansweredQuestion[];
339
415
  }
340
416
 
341
417
  /**
@@ -363,33 +439,48 @@ function readRun<Extra>(
363
439
  ? { delimiters: config.analysis.templateDelimiters, files: [] }
364
440
  : readTemplates(host, targetRoot, config.analysis);
365
441
  const consumers = scope.consumers.map((consumer) => consumer.path);
366
- const read = readProjects(engine, host, scope, config, (project, projectRead) => {
367
- const resolved = references(project, projectRead.held, targetRoot, {
368
- testFiles: config.ts.testFiles,
369
- });
370
- const configured: Configured = {
371
- configuration: projectRead.configuration,
372
- symbols: projectRead.held.symbols,
373
- references: resolved.references,
374
- roots: projectRead.rooted.liveUnderReachability,
375
- testFiles: resolved.testFilePaths,
376
- };
377
- const exempt = computeExemptions(
378
- { project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers },
379
- detectors,
380
- {
381
- disabled,
382
- mode,
383
- testFiles: new Set(resolved.testFilePaths),
384
- },
385
- );
386
- return {
387
- configFile: projectRead.configFile,
388
- configured,
389
- exempt,
390
- extra: extra(project, projectRead, resolved.references),
391
- };
392
- });
442
+ const read = readProjects<SweptProject<Extra>>(
443
+ engine,
444
+ host,
445
+ scope,
446
+ config,
447
+ (project, projectRead) => {
448
+ const resolved = references(project, projectRead.held, targetRoot, {
449
+ testFiles: config.ts.testFiles,
450
+ });
451
+ const configured: Configured = {
452
+ configuration: projectRead.configuration,
453
+ symbols: projectRead.held.symbols,
454
+ references: resolved.references,
455
+ roots: projectRead.rooted.liveUnderReachability,
456
+ testFiles: resolved.testFilePaths,
457
+ };
458
+ const exempt = computeExemptions(
459
+ { project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers },
460
+ detectors,
461
+ {
462
+ disabled,
463
+ mode,
464
+ testFiles: new Set(resolved.testFilePaths),
465
+ },
466
+ );
467
+ const unanswered = [
468
+ ...projectRead.held.unanswered,
469
+ ...projectRead.rooted.unanswered,
470
+ ...resolved.references
471
+ .filter((reference) => reference.resolution === "by-name")
472
+ .map((reference) => reference.to),
473
+ ...exempt.filter(isUnansweredRecord).map((record) => record.id),
474
+ ];
475
+ return {
476
+ configFile: projectRead.configFile,
477
+ configured,
478
+ exempt,
479
+ unanswered,
480
+ extra: extra(project, projectRead, resolved.references),
481
+ };
482
+ },
483
+ );
393
484
  const consumed = read.consumers.references;
394
485
  const projects = read.answers.map((one) =>
395
486
  consumed.length === 0
@@ -405,21 +496,72 @@ function readRun<Extra>(
405
496
  return {
406
497
  matrix: matrixOf(projects.map((one) => one.configured)),
407
498
  exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
499
+ unansweredHeld: [
500
+ ...new Set([
501
+ ...read.answers.flatMap((one) => one.unanswered),
502
+ ...consumed.filter((one) => one.resolution === "by-name").map((one) => one.to),
503
+ ]),
504
+ ],
408
505
  projects,
409
506
  consumers: read.consumers.loaded,
507
+ notBuilt: read.notBuilt,
508
+ unanswered: read.unanswered,
410
509
  };
411
510
  }
412
511
 
413
- /** The sweep of a run read once, under one set of marks. */
512
+ /**
513
+ * The sweep of a run read once, under one set of marks. Where a question went unanswered,
514
+ * the run is swept once more without what that question could have kept live and without
515
+ * the references that stood in for its answer, and every declaration that sweep judges
516
+ * dead and this one judges live, or dead by another rule, is held by the gap too, as is
517
+ * every declaration that makes such a reference.
518
+ */
414
519
  function sweptOf<Extra>(
415
520
  read: ReadRun<Extra>,
416
521
  input: Pick<SweepInput, "marked" | "mode">,
417
522
  ): RunSweep {
418
- const swept: SweepInput = { ...input, exempt: read.exempt };
523
+ const swept: SweepInput = { ...input, exempt: read.exempt, unanswered: read.unansweredHeld };
524
+ const result = sweepMatrix(read.matrix, swept);
525
+ const held = new Set(read.unansweredHeld);
526
+ if (held.size > 0) {
527
+ // What a declaration reads where the checker did not answer is unknown, so a rule
528
+ // over what it reads, the test of dead code among them, cannot judge it.
529
+ for (const { configured } of read.projects) {
530
+ for (const reference of configured.references) {
531
+ if (reference.resolution === "by-name") {
532
+ held.add(reference.from);
533
+ }
534
+ }
535
+ }
536
+ const dead = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
537
+ const answered = matrixOf(
538
+ read.projects.map(({ configured }) => ({
539
+ ...configured,
540
+ references: configured.references.filter((one) => one.resolution !== "by-name"),
541
+ })),
542
+ );
543
+ const bare: SweepInput = {
544
+ ...swept,
545
+ exempt: read.exempt.filter((record) => !isUnansweredRecord(record)),
546
+ unanswered: [],
547
+ };
548
+ for (const candidate of sweepMatrix(answered, bare).candidates) {
549
+ const kept = dead.get(candidate.id);
550
+ if (
551
+ kept?.testOfDeadCode !== candidate.testOfDeadCode ||
552
+ kept.relation !== candidate.relation
553
+ ) {
554
+ held.add(candidate.id);
555
+ }
556
+ }
557
+ }
419
558
  return {
420
559
  matrix: read.matrix,
421
- sweep: sweepMatrix(read.matrix, swept),
560
+ sweep: result,
422
561
  retained: retainedIn(read.matrix, swept),
562
+ notBuilt: read.notBuilt,
563
+ unanswered: read.unanswered,
564
+ heldByUnanswered: [...held],
423
565
  };
424
566
  }
425
567
 
@@ -449,7 +591,7 @@ export function runSweep(
449
591
 
450
592
  /** What each project contributes to the facts every emitter reads beside the sweep. */
451
593
  interface EmitterExtra {
452
- readonly deprecated: readonly string[];
594
+ readonly deprecation: Deprecation;
453
595
  readonly accessors: ReturnType<typeof accessorsOf>;
454
596
  readonly needs: ReturnType<typeof projectNeeds>;
455
597
  readonly implementations: ReturnType<typeof implementations>;
@@ -470,17 +612,31 @@ function readEmitterRun(
470
612
  detectors: Detectors,
471
613
  ): ReadRun<EmitterExtra> {
472
614
  const targetRoot = scope.target.path;
473
- return readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
474
- deprecated: deprecatedDeclarations(project, read.held),
475
- accessors: accessorsOf(project, read.held),
476
- needs: projectNeeds(project, read.held, host),
477
- implementations: implementations(project, read.held),
478
- heldByInclusion: heldByInclusion(project, targetRoot),
479
- rooted: read.rooted,
480
- directives: inlineDirectives(project, read.held, targetRoot),
481
- declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
482
- parts: projectParts(project, read.held, references, targetRoot),
483
- }));
615
+ const run = readRun(
616
+ engine,
617
+ host,
618
+ scope,
619
+ config,
620
+ mode,
621
+ detectors,
622
+ (project, read, references) => ({
623
+ deprecation: deprecatedDeclarations(project, read.held),
624
+ accessors: accessorsOf(project, read.held),
625
+ needs: projectNeeds(project, read.held, host),
626
+ implementations: implementations(project, read.held),
627
+ heldByInclusion: heldByInclusion(project, targetRoot),
628
+ rooted: read.rooted,
629
+ directives: inlineDirectives(project, read.held, targetRoot),
630
+ declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
631
+ parts: projectParts(project, read.held, references, targetRoot),
632
+ }),
633
+ );
634
+ // Whether a declaration carries the marker decides its code, so one whose marker went
635
+ // unread is reported by nothing.
636
+ const unread = run.projects.flatMap((one) => one.extra.deprecation.unanswered);
637
+ return unread.length === 0
638
+ ? run
639
+ : { ...run, unansweredHeld: [...new Set([...run.unansweredHeld, ...unread])] };
484
640
  }
485
641
 
486
642
  /**
@@ -501,7 +657,7 @@ function emitterInputOver(
501
657
  return {
502
658
  config,
503
659
  swept,
504
- deprecated: new Set(projects.flatMap((one) => one.extra.deprecated)),
660
+ deprecated: new Set(projects.flatMap((one) => one.extra.deprecation.deprecated)),
505
661
  stores: storesOf(
506
662
  projects.map((one) => ({
507
663
  references: one.configured.references,
@@ -533,7 +689,7 @@ function emitterInputOver(
533
689
  intraFunction: intraFunctionFacts(
534
690
  projects.map((one) => one.extra.parts),
535
691
  projects.flatMap((one) => one.configured.references),
536
- new Set(read.exempt.map((one) => one.id)),
692
+ new Set([...read.exempt.map((one) => one.id), ...swept.heldByUnanswered]),
537
693
  ),
538
694
  };
539
695
  }
@@ -614,6 +770,8 @@ function readAnalysis(
614
770
  read.projects.flatMap((one) => one.configured.testFiles),
615
771
  ),
616
772
  consumers: read.consumers,
773
+ notBuilt: read.notBuilt,
774
+ unanswered: read.unanswered,
617
775
  },
618
776
  configured: read.projects.map((one) => one.configured),
619
777
  symbols,
@@ -680,6 +838,10 @@ export interface RunFacts {
680
838
  readonly testFileRules: readonly TestFileRule[];
681
839
  /** Every consumer the run loaded beside the target, in the scope's order. */
682
840
  readonly consumers: readonly LoadedConsumer[];
841
+ /** The derived configurations the run dropped. */
842
+ readonly notBuilt: readonly NotBuilt[];
843
+ /** Every question the checker could not answer, each once. */
844
+ readonly unanswered: readonly UnansweredQuestion[];
683
845
  }
684
846
 
685
847
  /** The findings of one run, beside what a report states about the run itself. */
package/src/calls.ts CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/unstable/sync";
27
27
  import type { AliasChains } from "./alias-chain.ts";
28
28
  import { nodeKey, type Inventory } from "./inventory.ts";
29
- import { DEFAULT_BATCH_CAP } from "./references.ts";
29
+ import { must } from "./query.ts";
30
30
  import type { ProjectView } from "./session.ts";
31
31
 
32
32
  /** One value written where a call passes it, and the node its declaration is resolved from. */
@@ -77,7 +77,8 @@ export function decoratorsOf(node: Node): readonly Decorator[] {
77
77
  /**
78
78
  * What each of a batch of nodes resolves to, every alias followed to the declaration
79
79
  * it stands for. A node the checker resolves to nothing, and an alias that resolves
80
- * to nothing, are absent from the answer. Each alias is followed once.
80
+ * to nothing, are absent from the answer. Each alias is followed once. A question the
81
+ * checker does not answer stops the pass, as {@link must} stops.
81
82
  */
82
83
  export function resolvedTargets<Brand>(
83
84
  project: ProjectView<Brand>,
@@ -85,17 +86,18 @@ export function resolvedTargets<Brand>(
85
86
  ): ReadonlyMap<Node, TSSymbol> {
86
87
  const followed = new Map<number, TSSymbol | undefined>();
87
88
  const targets = new Map<Node, TSSymbol>();
88
- for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
89
- const run = nodes.slice(from, from + DEFAULT_BATCH_CAP);
90
- project.symbolsAt(run.map((node) => project.handle(node))).forEach((symbol, index) => {
91
- const node = run[index];
89
+ project
90
+ .symbolsAt(nodes.map((node) => project.handle(node)))
91
+ .map(must)
92
+ .forEach((symbol, index) => {
93
+ const node = nodes[index];
92
94
  if (symbol === undefined || node === undefined) {
93
95
  return;
94
96
  }
95
97
  let target: TSSymbol | undefined = symbol;
96
98
  if ((symbol.flags & SymbolFlags.Alias) !== 0) {
97
99
  if (!followed.has(symbol.id)) {
98
- const aliased = project.checker.getAliasedSymbol(symbol);
100
+ const aliased = must(project.queries.aliased(symbol));
99
101
  followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
100
102
  }
101
103
  target = followed.get(symbol.id);
@@ -104,7 +106,6 @@ export function resolvedTargets<Brand>(
104
106
  targets.set(node, target);
105
107
  }
106
108
  });
107
- }
108
109
  return targets;
109
110
  }
110
111
 
@@ -166,7 +167,7 @@ export function classesPassed<Brand>(
166
167
  );
167
168
  const classesOf = (value: PassedValue): readonly string[] => {
168
169
  const symbol = value.shorthand
169
- ? project.shorthandValueAt(project.handle(value.name))
170
+ ? must(project.shorthandValueAt(project.handle(value.name)))
170
171
  : targets.get(value.name);
171
172
  return symbol === undefined
172
173
  ? []
@@ -7,7 +7,7 @@
7
7
  import { isClassDeclaration, isClassExpression, type Node } from "@typescript/native/unstable/ast";
8
8
  import type { Symbol as TSSymbol, SymbolFlags, Type } from "@typescript/native/unstable/sync";
9
9
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
10
- import { DEFAULT_BATCH_CAP } from "./references.ts";
10
+ import { must } from "./query.ts";
11
11
  import type { ProjectView } from "./session.ts";
12
12
 
13
13
  /** One member of a class's value that the target declares. */
@@ -44,16 +44,15 @@ export function classNames<Brand>(
44
44
  return names;
45
45
  }
46
46
 
47
- /** The types of a batch of nodes, asked in capped batches. */
47
+ /**
48
+ * The types of a batch of nodes, for a pass that cannot go on past a type the checker
49
+ * did not answer: it stops there, as {@link must} stops.
50
+ */
48
51
  export function typesAt<Brand>(
49
52
  project: ProjectView<Brand>,
50
53
  nodes: readonly Node[],
51
54
  ): (Type | undefined)[] {
52
- const types: (Type | undefined)[] = [];
53
- for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
54
- types.push(...project.checker.getTypeAtLocation(nodes.slice(from, from + DEFAULT_BATCH_CAP)));
55
- }
56
- return types;
55
+ return project.queries.typesAt(nodes).map(must);
57
56
  }
58
57
 
59
58
  /**
@@ -68,9 +67,9 @@ function sideTypes<Brand>(
68
67
  if (side === "instance") {
69
68
  return typesAt(project, nodes);
70
69
  }
71
- const symbols = project.symbolsAt(nodes.map((node) => project.handle(node)));
70
+ const symbols = project.symbolsAt(nodes.map((node) => project.handle(node))).map(must);
72
71
  const present = symbols.filter((symbol): symbol is TSSymbol => symbol !== undefined);
73
- const types = present.length === 0 ? [] : project.checker.getTypeOfSymbol(present);
72
+ const types = project.queries.typesOfSymbols(present).map(must);
74
73
  let next = 0;
75
74
  return symbols.map((symbol) => (symbol === undefined ? undefined : types[next++]));
76
75
  }
@@ -87,7 +86,7 @@ export function classMembers<Brand>(
87
86
  side: ClassSide,
88
87
  ): ReadonlyMap<string, readonly ClassMember[]> {
89
88
  const named = [...classNames(project, held)].filter(([id]) => classIds.has(id));
90
- const own = new Set(project.ownSourceFiles().map((file) => file.fileName));
89
+ const own = project.ownPaths();
91
90
  const types = sideTypes(
92
91
  project,
93
92
  named.map(([, node]) => node),
@@ -100,7 +99,7 @@ export function classMembers<Brand>(
100
99
  return;
101
100
  }
102
101
  const found: ClassMember[] = [];
103
- for (const property of project.checker.getPropertiesOfType(type)) {
102
+ for (const property of must(project.queries.propertiesOf(type))) {
104
103
  for (const handle of property.declarations) {
105
104
  if (!own.has(handle.path)) {
106
105
  continue;
@@ -25,6 +25,7 @@ import type { AliasChains } from "./alias-chain.ts";
25
25
  import { resolvedTargets } from "./calls.ts";
26
26
  import type { Config, DeclarationEntry } from "./config.ts";
27
27
  import type { Inventory } from "./inventory.ts";
28
+ import { must, Unanswerable } from "./query.ts";
28
29
  import { nameComponent } from "./ref.ts";
29
30
  import type { ProjectView } from "./session.ts";
30
31
 
@@ -90,7 +91,7 @@ function unaliased<Brand>(project: ProjectView<Brand>, symbol: TSSymbol): TSSymb
90
91
  if ((symbol.flags & SymbolFlags.Alias) === 0) {
91
92
  return symbol;
92
93
  }
93
- const target = project.checker.getAliasedSymbol(symbol);
94
+ const target = must(project.queries.aliased(symbol));
94
95
  return target.declarations.length === 0 ? undefined : target;
95
96
  }
96
97
 
@@ -105,14 +106,18 @@ function isClassMember(symbol: TSSymbol): boolean {
105
106
  * with the static suffix names one of the first and a component without it names an
106
107
  * instance member or one of the second.
107
108
  */
108
- function candidatesOf(container: TSSymbol, isStatic: boolean): readonly TSSymbol[] {
109
- const exported = [...container.getExports().values()];
109
+ function candidatesOf<Brand>(
110
+ project: ProjectView<Brand>,
111
+ container: TSSymbol,
112
+ isStatic: boolean,
113
+ ): readonly TSSymbol[] {
114
+ const exported = [...must(project.queries.exportsOf(container)).values()];
110
115
  if (isStatic) {
111
116
  return exported.filter(isClassMember);
112
117
  }
113
118
  const isClass = (container.flags & SymbolFlags.Class) !== 0;
114
119
  return [
115
- ...container.getMembers().values(),
120
+ ...must(project.queries.membersOf(container)).values(),
116
121
  ...exported.filter((member) => !isClass || !isClassMember(member)),
117
122
  ];
118
123
  }
@@ -124,7 +129,7 @@ function memberOf<Brand>(
124
129
  component: string,
125
130
  isStatic: boolean,
126
131
  ): TSSymbol | undefined {
127
- const member = candidatesOf(container, isStatic).find(
132
+ const member = candidatesOf(project, container, isStatic).find(
128
133
  (candidate) => nameComponent(candidate.name) === component,
129
134
  );
130
135
  return member === undefined ? undefined : unaliased(project, member);
@@ -190,7 +195,7 @@ function modulesNamed<Brand>(
190
195
  file.forEachChild(visit);
191
196
  }
192
197
  const specified = [...written];
193
- const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node)));
198
+ const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node))).map(must);
194
199
  const modules = new Map<string, TSSymbol>();
195
200
  specified.forEach(([name], index) => {
196
201
  const symbol = symbols[index];
@@ -238,7 +243,7 @@ export function resolveEntries<Brand>(
238
243
  const module = modules.get(entry.module);
239
244
  start = module === undefined ? undefined : memberOf(project, module, first, false);
240
245
  } else if (nameComponent(first) === first) {
241
- const global = project.checker.resolveName(first, ANY_MEANING, undefined, false);
246
+ const global = must(project.queries.resolveName(first, ANY_MEANING, undefined, false));
242
247
  start = global === undefined ? undefined : unaliased(project, global);
243
248
  }
244
249
  const declared = walkPath(project, start, rest, isStatic);
@@ -334,17 +339,28 @@ export function configuredDeclarations(config: Config): readonly ConfiguredDecla
334
339
  ];
335
340
  }
336
341
 
337
- /** Whether each configured entry names a declaration in one project, in the entries' order. */
342
+ /**
343
+ * Whether each configured entry names a declaration in one project, in the entries'
344
+ * order. Where the checker leaves a question of the resolution unanswered, every entry
345
+ * counts as naming one, so no entry is reported as naming nothing on a gap.
346
+ */
338
347
  export function declarationsNamed<Brand>(
339
348
  project: ProjectView<Brand>,
340
349
  held: Inventory,
341
350
  configured: readonly ConfiguredDeclaration[],
342
351
  ): readonly boolean[] {
343
- return resolveEntries(
344
- project,
345
- held,
346
- configured.map((one) => one.entry),
347
- ).map(namesADeclaration);
352
+ try {
353
+ return resolveEntries(
354
+ project,
355
+ held,
356
+ configured.map((one) => one.entry),
357
+ ).map(namesADeclaration);
358
+ } catch (error: unknown) {
359
+ if (!(error instanceof Unanswerable)) {
360
+ throw error;
361
+ }
362
+ return configured.map(() => true);
363
+ }
348
364
  }
349
365
 
350
366
  /**