@cplieger/deadset-ts 3.13.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +3 -3
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +119 -23
  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 +105 -28
  12. package/dist/src/entry-points.js +7 -0
  13. package/dist/src/enum-group.js +46 -40
  14. package/dist/src/exempt.js +42 -1
  15. package/dist/src/findings/emitters.js +15 -1
  16. package/dist/src/findings/interfaces.js +7 -1
  17. package/dist/src/framework-lifecycle.js +8 -6
  18. package/dist/src/implementations.js +45 -23
  19. package/dist/src/interface-satisfaction.js +30 -27
  20. package/dist/src/intra-function-parts.js +37 -20
  21. package/dist/src/inventory.js +85 -4
  22. package/dist/src/json-chunks.js +76 -0
  23. package/dist/src/partial.js +23 -0
  24. package/dist/src/query.js +207 -0
  25. package/dist/src/references.js +92 -20
  26. package/dist/src/reflective-lookup.js +18 -9
  27. package/dist/src/report.js +3 -1
  28. package/dist/src/reporters/annotations.js +9 -5
  29. package/dist/src/reporters/json.js +3 -2
  30. package/dist/src/reporters/sarif.js +2 -1
  31. package/dist/src/reporters/template.js +48 -7
  32. package/dist/src/reporters/text.js +8 -6
  33. package/dist/src/roots.js +27 -11
  34. package/dist/src/serialization-contract.js +12 -10
  35. package/dist/src/session.js +79 -39
  36. package/dist/src/suppress-file.js +2 -1
  37. package/dist/src/sweep.js +4 -1
  38. package/dist/src/value-reach.js +14 -19
  39. package/dist/src/verbs/analyze.js +20 -3
  40. package/dist/src/verbs/explain.js +22 -9
  41. package/dist/src/verbs/print-projects.js +40 -7
  42. package/dist/src/verbs/print-retained.js +6 -2
  43. package/dist/src/verbs/print-roots.js +5 -1
  44. package/jsr.json +1 -1
  45. package/package.json +3 -3
  46. package/src/alias-chain.ts +75 -11
  47. package/src/analysis.ts +223 -61
  48. package/src/calls.ts +10 -9
  49. package/src/class-members.ts +10 -11
  50. package/src/configured-declarations.ts +29 -13
  51. package/src/dependencies.ts +42 -20
  52. package/src/deprecation.ts +53 -27
  53. package/src/destructuring.ts +50 -39
  54. package/src/discover.ts +177 -33
  55. package/src/entry-points.ts +7 -0
  56. package/src/enum-group.ts +56 -42
  57. package/src/exempt.ts +50 -2
  58. package/src/findings/emitters.ts +18 -1
  59. package/src/findings/interfaces.ts +7 -1
  60. package/src/framework-lifecycle.ts +10 -6
  61. package/src/host.ts +5 -3
  62. package/src/implementations.ts +59 -23
  63. package/src/interface-satisfaction.ts +30 -27
  64. package/src/intra-function-parts.ts +43 -25
  65. package/src/inventory.ts +97 -4
  66. package/src/json-chunks.ts +87 -0
  67. package/src/partial.ts +40 -0
  68. package/src/query.ts +522 -0
  69. package/src/references.ts +119 -38
  70. package/src/reflective-lookup.ts +21 -11
  71. package/src/report.ts +14 -2
  72. package/src/reporters/annotations.ts +23 -21
  73. package/src/reporters/json.ts +3 -2
  74. package/src/reporters/reporter.ts +2 -2
  75. package/src/reporters/sarif.ts +3 -2
  76. package/src/reporters/template.ts +50 -8
  77. package/src/reporters/text.ts +12 -12
  78. package/src/roots.ts +34 -12
  79. package/src/serialization-contract.ts +12 -10
  80. package/src/session.ts +146 -58
  81. package/src/suppress-file.ts +6 -2
  82. package/src/sweep.ts +9 -4
  83. package/src/value-reach.ts +20 -23
  84. package/src/verbs/analyze.ts +25 -3
  85. package/src/verbs/explain.ts +20 -9
  86. package/src/verbs/print-projects.ts +53 -7
  87. package/src/verbs/print-retained.ts +6 -2
  88. package/src/verbs/print-roots.ts +5 -1
@@ -4,8 +4,18 @@
4
4
  * declaration every link of that chain is.
5
5
  */
6
6
 
7
+ import {
8
+ isExportAssignment,
9
+ isExportSpecifier,
10
+ isIdentifier,
11
+ isImportClause,
12
+ isImportSpecifier,
13
+ isNamespaceExport,
14
+ isNamespaceImport,
15
+ } from "@typescript/native/unstable/ast";
7
16
  import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/unstable/sync";
8
- import { nodeKey, type Inventory } from "./inventory.ts";
17
+ import { declarationsByName, nodeKey, type Inventory } from "./inventory.ts";
18
+ import { UNANSWERED, type Answer } from "./query.ts";
9
19
  import type { ProjectView } from "./session.ts";
10
20
 
11
21
  /** One declaration a chain reaches, and whether a step along an alias reached it. */
@@ -13,6 +23,11 @@ export interface ChainTarget {
13
23
  readonly id: string;
14
24
  /** False for a declaration the head symbol itself is, true for one a link stands for. */
15
25
  readonly stepped: boolean;
26
+ /**
27
+ * True where the compiler did not answer a step and the declaration is one the link
28
+ * may stand for by the name it carries.
29
+ */
30
+ readonly guessed: boolean;
16
31
  }
17
32
 
18
33
  /** The chain walks over one project's aliases, each step asked of the compiler once. */
@@ -33,7 +48,7 @@ export interface AliasChains {
33
48
  * inventory declares, stepping over the links it does not, or none where no later
34
49
  * link is declared.
35
50
  */
36
- nextDeclared(symbol: TSSymbol): readonly string[];
51
+ nextDeclared(symbol: TSSymbol): readonly ChainTarget[];
37
52
  /** How many steps the compiler was asked for: one per alias, whatever the chains it heads. */
38
53
  readonly steps: number;
39
54
  }
@@ -47,8 +62,8 @@ export interface AliasChains {
47
62
  * library file is never fetched to find out that the inventory does not hold it.
48
63
  */
49
64
  export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory): AliasChains {
50
- const ownFiles = new Set(project.ownSourceFiles().map((file) => file.fileName));
51
- const steps = new Map<number, TSSymbol | undefined>();
65
+ const ownFiles = project.ownPaths();
66
+ const steps = new Map<number, Answer<TSSymbol | undefined>>();
52
67
  const chains = new Map<number, readonly ChainTarget[]>();
53
68
  let asked = 0;
54
69
 
@@ -74,7 +89,7 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
74
89
  * expression is written in an alias form and names nothing declared elsewhere, so its
75
90
  * chain ends at itself.
76
91
  */
77
- const stepOf = (symbol: TSSymbol): TSSymbol | undefined => {
92
+ const stepOf = (symbol: TSSymbol): Answer<TSSymbol | undefined> => {
78
93
  if ((symbol.flags & SymbolFlags.Alias) === 0) {
79
94
  return undefined;
80
95
  }
@@ -87,38 +102,87 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
87
102
  return next;
88
103
  };
89
104
 
105
+ const files = held.symbols.filter((one) => one.kind === "file").map((one) => one.id);
106
+
107
+ /**
108
+ * The declarations an alias whose step went unanswered may stand for: every one the
109
+ * inventory holds under its own name or the name it carries forward, and every file
110
+ * where it carries a module, so a use through it still counts for what it reaches.
111
+ */
112
+ const sameNamed = (alias: TSSymbol): readonly ChainTarget[] => {
113
+ const names = new Set([alias.name]);
114
+ let module = false;
115
+ for (const handle of alias.declarations) {
116
+ const node = ownFiles.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
117
+ if (node === undefined) {
118
+ continue;
119
+ }
120
+ if ((isImportSpecifier(node) || isExportSpecifier(node)) && node.propertyName !== undefined) {
121
+ names.add(node.propertyName.text);
122
+ } else if (isImportClause(node)) {
123
+ names.add("default");
124
+ } else if (isNamespaceImport(node) || isNamespaceExport(node)) {
125
+ module = true;
126
+ } else if (isExportAssignment(node) && isIdentifier(node.expression)) {
127
+ names.add(node.expression.text);
128
+ }
129
+ }
130
+ const byName = declarationsByName(held);
131
+ const ids = [...names].flatMap((name) => byName.get(name) ?? []);
132
+ return [...new Set([...ids, ...(module ? files : [])])].map((id) => ({
133
+ id,
134
+ stepped: true,
135
+ guessed: true,
136
+ }));
137
+ };
138
+
90
139
  const chainOf = (symbol: TSSymbol): readonly ChainTarget[] => {
91
140
  const cached = chains.get(symbol.id);
92
141
  if (cached !== undefined) {
93
142
  return cached;
94
143
  }
95
144
  const targets: ChainTarget[] = [];
145
+ const add = (id: string, stepped: boolean, guessed = false): void => {
146
+ if (!targets.some((target) => target.id === id)) {
147
+ targets.push({ id, stepped, guessed });
148
+ }
149
+ };
96
150
  const walked = new Set<number>();
97
151
  let at: TSSymbol | undefined = symbol;
98
152
  let stepped = false;
99
153
  while (at !== undefined && !walked.has(at.id)) {
100
154
  walked.add(at.id);
101
155
  for (const id of declarationsOf(at)) {
102
- if (!targets.some((target) => target.id === id)) {
103
- targets.push({ id, stepped });
156
+ add(id, stepped);
157
+ }
158
+ const next = stepOf(at);
159
+ if (next === UNANSWERED) {
160
+ for (const target of sameNamed(at)) {
161
+ add(target.id, true, true);
104
162
  }
163
+ break;
105
164
  }
106
- at = stepOf(at);
165
+ at = next;
107
166
  stepped = true;
108
167
  }
109
168
  chains.set(symbol.id, targets);
110
169
  return targets;
111
170
  };
112
171
 
113
- const nextDeclared = (symbol: TSSymbol): readonly string[] => {
172
+ const nextDeclared = (symbol: TSSymbol): readonly ChainTarget[] => {
114
173
  const walked = new Set<number>([symbol.id]);
174
+ let from = symbol;
115
175
  let at = stepOf(symbol);
116
- while (at !== undefined && !walked.has(at.id)) {
176
+ while (at !== undefined && (at === UNANSWERED || !walked.has(at.id))) {
177
+ if (at === UNANSWERED) {
178
+ return sameNamed(from);
179
+ }
117
180
  walked.add(at.id);
118
181
  const ids = declarationsOf(at);
119
182
  if (ids.length > 0) {
120
- return ids;
183
+ return ids.map((id) => ({ id, stepped: true, guessed: false }));
121
184
  }
185
+ from = at;
122
186
  at = stepOf(at);
123
187
  }
124
188
  return [];
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
273
  `${String(failures.length)} error(s); 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
  ? []