@cplieger/deadset-ts 4.0.0 → 5.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 (117) hide show
  1. package/README.md +68 -50
  2. package/dist/bin/component-mapper.js +5 -0
  3. package/dist/bin/mapper-connection.js +101 -0
  4. package/dist/bin/node-host.js +24 -2
  5. package/dist/src/analysis.js +230 -37
  6. package/dist/src/component-files.js +612 -0
  7. package/dist/src/component-layer.js +132 -0
  8. package/dist/src/config.js +9 -2
  9. package/dist/src/configuration-files.js +186 -0
  10. package/dist/src/conformance-record.js +2 -2
  11. package/dist/src/convention-rows.js +383 -0
  12. package/dist/src/conventions.js +456 -0
  13. package/dist/src/dependencies.js +30 -3
  14. package/dist/src/discover.js +11 -4
  15. package/dist/src/emit-map.js +72 -0
  16. package/dist/src/entry-point-gaps.js +81 -35
  17. package/dist/src/entry-points.js +47 -32
  18. package/dist/src/exempt.js +11 -3
  19. package/dist/src/findings/completion.js +19 -29
  20. package/dist/src/findings/emitters.js +22 -1
  21. package/dist/src/findings/unused-declarations.js +7 -2
  22. package/dist/src/graph.js +13 -5
  23. package/dist/src/inventory.js +4 -2
  24. package/dist/src/kinds.js +2 -2
  25. package/dist/src/manifest.js +37 -11
  26. package/dist/src/matrix.js +21 -13
  27. package/dist/src/partial.js +3 -1
  28. package/dist/src/pnpm-workspace.js +120 -0
  29. package/dist/src/position.js +17 -2
  30. package/dist/src/print.js +2 -0
  31. package/dist/src/references.js +209 -30
  32. package/dist/src/report.js +26 -6
  33. package/dist/src/reporters/annotations.js +10 -3
  34. package/dist/src/reporters/template.js +6 -3
  35. package/dist/src/resolve.js +31 -6
  36. package/dist/src/roots.js +37 -8
  37. package/dist/src/run.js +13 -3
  38. package/dist/src/schema.js +4 -2
  39. package/dist/src/session.js +141 -34
  40. package/dist/src/setup-failure.js +23 -0
  41. package/dist/src/source-files.js +4 -61
  42. package/dist/src/sweep.js +4 -2
  43. package/dist/src/template-field.js +38 -6
  44. package/dist/src/test-support.js +59 -0
  45. package/dist/src/type-errors.js +252 -0
  46. package/dist/src/type-query-alias.js +92 -0
  47. package/dist/src/verbs/analyze.js +24 -4
  48. package/dist/src/verbs/explain.js +4 -1
  49. package/dist/src/verbs/print-projects.js +17 -56
  50. package/dist/src/verbs/print-retained.js +7 -2
  51. package/dist/src/verbs/print-roots.js +7 -2
  52. package/dist/src/version-range.js +255 -0
  53. package/dist/src/version.js +1 -1
  54. package/dist/src/workspace-manifests.js +68 -0
  55. package/dist/src/workspace-programs.js +342 -0
  56. package/dist/src/workspace-resolution.js +307 -0
  57. package/dist/src/workspace-run.js +75 -0
  58. package/dist/src/workspace.js +358 -0
  59. package/jsr.json +1 -1
  60. package/package.json +2 -1
  61. package/src/analysis.ts +395 -71
  62. package/src/component-files.ts +718 -0
  63. package/src/component-layer.ts +160 -0
  64. package/src/config.ts +11 -2
  65. package/src/configuration-files.ts +235 -0
  66. package/src/conformance-record.ts +2 -2
  67. package/src/convention-rows.ts +440 -0
  68. package/src/conventions.ts +640 -0
  69. package/src/dependencies.ts +36 -2
  70. package/src/discover.ts +11 -7
  71. package/src/emit-map.ts +93 -0
  72. package/src/entry-point-gaps.ts +92 -38
  73. package/src/entry-points.ts +66 -36
  74. package/src/exempt.ts +21 -3
  75. package/src/findings/completion.ts +24 -33
  76. package/src/findings/emitters.ts +32 -1
  77. package/src/findings/unused-declarations.ts +9 -2
  78. package/src/graph.ts +15 -4
  79. package/src/host.ts +23 -0
  80. package/src/index.ts +1 -1
  81. package/src/inventory.ts +13 -2
  82. package/src/kinds.ts +2 -2
  83. package/src/manifest.ts +47 -14
  84. package/src/matrix.ts +43 -15
  85. package/src/partial.ts +6 -1
  86. package/src/pnpm-workspace.ts +135 -0
  87. package/src/position.ts +23 -2
  88. package/src/print.ts +2 -0
  89. package/src/references.ts +227 -28
  90. package/src/report.ts +56 -6
  91. package/src/reporters/annotations.ts +12 -3
  92. package/src/reporters/template.ts +6 -3
  93. package/src/resolve.ts +59 -6
  94. package/src/roots.ts +60 -9
  95. package/src/run.ts +15 -5
  96. package/src/schema.ts +4 -2
  97. package/src/session.ts +218 -37
  98. package/src/setup-failure.ts +39 -0
  99. package/src/source-files.ts +4 -66
  100. package/src/sweep.ts +9 -2
  101. package/src/template-field.ts +42 -7
  102. package/src/test-support.ts +77 -0
  103. package/src/type-errors.ts +340 -0
  104. package/src/type-query-alias.ts +133 -0
  105. package/src/verbs/analyze.ts +38 -4
  106. package/src/verbs/explain.ts +4 -1
  107. package/src/verbs/print-projects.ts +17 -69
  108. package/src/verbs/print-retained.ts +7 -2
  109. package/src/verbs/print-roots.ts +7 -2
  110. package/src/verbs/verb.ts +6 -3
  111. package/src/version-range.ts +309 -0
  112. package/src/version.ts +1 -1
  113. package/src/workspace-manifests.ts +100 -0
  114. package/src/workspace-programs.ts +486 -0
  115. package/src/workspace-resolution.ts +393 -0
  116. package/src/workspace-run.ts +148 -0
  117. package/src/workspace.ts +463 -0
@@ -7,15 +7,17 @@
7
7
  * carries a project's identity in its type so a handle cannot cross to another
8
8
  * project's checker, and that holds only for views the session itself hands out.
9
9
  */
10
+ import { lineOf, readComponent } from "./component-files.js";
10
11
  import { consumerLoads, consumerReferences, moduleIdentity, } from "./consumers.js";
11
12
  import { configuredDeclarations, configuredRef, declarationsNamed, } from "./configured-declarations.js";
12
13
  import { decorator } from "./decorator.js";
13
14
  import { dependenciesOf, projectNeeds } from "./dependencies.js";
14
15
  import { deprecatedDeclarations } from "./deprecation.js";
15
16
  import { discoverProjects, DiscoveryError, isGuess, projectErrors, renderDiagnostic, targetRelative, } from "./discover.js";
17
+ import { conventionFailuresFor, readConventions, } from "./conventions.js";
16
18
  import { readEdgeSides } from "./edges.js";
17
19
  import { enumGroup } from "./enum-group.js";
18
- import { computeExemptions, disabledClasses, exemptionsOf, isUnansweredRecord, retainedIn, } from "./exempt.js";
20
+ import { computeExemptions, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
19
21
  import { heldByInclusion, sourceTree } from "./file-facts.js";
20
22
  import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
21
23
  import { NO_SELF_CHECK, unmatchedRootFindings, } from "./findings/self-check.js";
@@ -26,19 +28,23 @@ import { interfaceSatisfaction } from "./interface-satisfaction.js";
26
28
  import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
27
29
  import { inventory } from "./inventory.js";
28
30
  import { readManifest } from "./manifest.js";
29
- import { matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
30
- import { relativePath, resolvePath } from "./paths.js";
31
- import { byPosition } from "./position.js";
31
+ import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
32
+ import { joinPath, relativePath, resolvePath } from "./paths.js";
33
+ import { byPosition, isComponentFile } from "./position.js";
34
+ import { SetupError, setupLine } from "./setup-failure.js";
35
+ import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
32
36
  import { reflectiveLookup } from "./reflective-lookup.js";
33
37
  import { references, testFileRulesOf } from "./references.js";
34
38
  import { roots, unmatchedEverywhere } from "./roots.js";
35
39
  import { serializationContract } from "./serialization-contract.js";
36
- import { diagnosticsOf, runSession } from "./session.js";
40
+ import { supportReferences, testSupportFiles } from "./test-support.js";
41
+ import { diagnosticsOf, ownedAs, runSession } from "./session.js";
37
42
  import { accessorsOf, storesOf } from "./stores.js";
38
43
  import { isPartKind } from "./suppress.js";
39
44
  import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
40
45
  import { inlineDirectives, inlineSuppressions } from "./suppress-inline.js";
41
46
  import { readTemplates, templateField } from "./template-field.js";
47
+ import { ownErrors, workspaceRun } from "./workspace-run.js";
42
48
  /** The setting whose source decides where a finding about a configured root sits. */
43
49
  const ROOTS_SETTING = "roots.patterns";
44
50
  /**
@@ -63,12 +69,29 @@ function compare(a, b) {
63
69
  return a < b ? -1 : 1;
64
70
  }
65
71
  /**
66
- * Reads every project the scope discovers, and every project of each consumer it
67
- * declares, in one snapshot, each checked for errors first. A target project is
68
- * enumerated, rooted and handed to `stage`; a consumer project is read for its references
69
- * to the target. A guess that does not load is dropped into `notBuilt` (see
70
- * {@link isGuess}); any other project that does not load or carries an error fails the
71
- * run, as does a run whose every guess was dropped.
72
+ * The manifests the analysis reads, each once: the target's own, then every workspace
73
+ * member's below the target root.
74
+ */
75
+ function conventionManifests(host, targetRoot, memberDirs) {
76
+ const found = new Map();
77
+ if (host.kindOf(joinPath(targetRoot, "package.json")) === "file") {
78
+ found.set(targetRoot, { dir: targetRoot, path: "package.json" });
79
+ }
80
+ for (const dir of memberDirs) {
81
+ const below = relativePath(targetRoot, dir);
82
+ if (below !== undefined && !found.has(dir)) {
83
+ found.set(dir, { dir, path: `${below}/package.json` });
84
+ }
85
+ }
86
+ return [...found.values()];
87
+ }
88
+ /**
89
+ * Reads every project the scope discovers, and each declared consumer's, in one snapshot,
90
+ * each checked for errors first. `stage` is handed a target project and enumerates and roots
91
+ * it if it reads it; a consumer project is read for its references to the target. A type error
92
+ * in an own file skips the unit holding it. A guess that does not load or meets a setup failure
93
+ * is dropped into `notBuilt` (see {@link isGuess}); any other such project fails the run, as
94
+ * does a run whose every guess was dropped.
72
95
  */
73
96
  function readProjects(engine, host, scope, config, stage) {
74
97
  const targetRoot = scope.target.path;
@@ -76,14 +99,19 @@ function readProjects(engine, host, scope, config, stage) {
76
99
  const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
77
100
  const loads = consumerLoads(engine, host, scope, discovered.configFiles);
78
101
  const consumerOf = new Map(loads.flatMap((load) => load.configFiles.map((configFile) => [configFile, load.consumer])));
102
+ const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
103
+ const workspace = workspaceRun(engine, host, absoluteRoot, discovered.configFiles, readManifest(host, targetRoot));
104
+ const conventions = readConventions(host, (fileName, text) => engine.parseSourceFile(fileName, text), conventionManifests(host, absoluteRoot, workspace?.memberDirs ?? []), config.ts.disabledConventions);
79
105
  const options = {
80
- manifest: readManifest(host, targetRoot),
106
+ host,
107
+ manifest: workspace?.manifest ?? readManifest(host, targetRoot),
108
+ workspaceEntries: workspace?.entries,
81
109
  patterns: config.rootPatterns,
82
110
  entryFiles: config.ts.entryFiles,
111
+ conventions,
83
112
  testFiles: config.ts.testFiles,
84
113
  publishedAPI: config.targetKind === "library",
85
114
  };
86
- const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
87
115
  const notBuilt = [...discovered.notBuilt];
88
116
  const dropped = (configFile, error) => {
89
117
  notBuilt.push({
@@ -93,10 +121,36 @@ function readProjects(engine, host, scope, config, stage) {
93
121
  });
94
122
  };
95
123
  const failures = [];
124
+ const unbuildable = [];
125
+ const setupDropped = [];
96
126
  const consumed = new Map();
97
- const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (project) => {
98
- const errors = projectErrors(diagnosticsOf(project));
99
- const consumer = consumerOf.get(project.configFile);
127
+ const setupMet = (configFile, met) => {
128
+ const [first] = met;
129
+ if (first !== undefined && isGuess(discovered, configFile)) {
130
+ dropped(configFile, setupLine(first));
131
+ setupDropped.push(...met);
132
+ }
133
+ else {
134
+ unbuildable.push(...met);
135
+ }
136
+ };
137
+ const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (opened) => {
138
+ const consumer = consumerOf.get(opened.configFile);
139
+ const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
140
+ if (setup !== undefined) {
141
+ setupMet(opened.configFile, [
142
+ { setupClass: "workspace-member-without-source", detail: setup },
143
+ ]);
144
+ return undefined;
145
+ }
146
+ const referenceOnly = workspace === undefined || consumer !== undefined
147
+ ? () => false
148
+ : workspace.referenceOnly(opened.configFile, opened.rootFiles);
149
+ const project = workspace === undefined || consumer !== undefined
150
+ ? opened
151
+ : ownedAs(opened, (file, fromExternalLibrary) => workspace.ownFile(file, fromExternalLibrary));
152
+ const raw = projectErrors(diagnosticsOf(project));
153
+ const errors = { load: raw.load, check: ownErrors(raw.check, referenceOnly) };
100
154
  const [unloaded] = errors.load;
101
155
  if (unloaded !== undefined &&
102
156
  consumer === undefined &&
@@ -104,8 +158,37 @@ function readProjects(engine, host, scope, config, stage) {
104
158
  dropped(project.configFile, renderDiagnostic(unloaded, (path) => relativePath(absoluteRoot, path) ?? path));
105
159
  return undefined;
106
160
  }
107
- if (errors.load.length > 0 || errors.check.length > 0) {
108
- failures.push(...errors.load, ...errors.check);
161
+ if (errors.load.length > 0) {
162
+ failures.push(...errors.load);
163
+ return undefined;
164
+ }
165
+ const reading = readErrors(project, errors.check, {
166
+ host,
167
+ targetRoot: consumer === undefined
168
+ ? absoluteRoot
169
+ : resolvePath(host.workingDirectory(), consumer.path),
170
+ paths: pathsOf(engine, project.configFile),
171
+ });
172
+ if (reading.failing.length > 0) {
173
+ failures.push(...reading.failing);
174
+ return undefined;
175
+ }
176
+ if (consumer !== undefined && reading.missing.length > 0) {
177
+ unbuildable.push(...reading.missing.map((one) => ({
178
+ setupClass: "missing-consumer",
179
+ detail: `the consumer ${consumer.path} does not build, its dependencies not installed: ${one.detail}`,
180
+ })));
181
+ return undefined;
182
+ }
183
+ if (reading.missing.length > 0) {
184
+ setupMet(project.configFile, reading.missing);
185
+ return undefined;
186
+ }
187
+ const unconventional = consumer === undefined
188
+ ? conventionFailuresFor(conventions, project.configFile, project.ownSourceFiles().map((file) => file.fileName))
189
+ : [];
190
+ if (unconventional.length > 0) {
191
+ setupMet(project.configFile, unconventional);
109
192
  return undefined;
110
193
  }
111
194
  if (consumer !== undefined) {
@@ -117,22 +200,52 @@ function readProjects(engine, host, scope, config, stage) {
117
200
  }
118
201
  return undefined;
119
202
  }
120
- const held = inventory(project, host, targetRoot);
121
- const rooted = roots(project, held, targetRoot, options);
122
203
  const configuration = ids.get(project.configFile) ?? project.configFile;
123
- return stage(project, { configuration, configFile: project.configFile, held, rooted });
124
- }, discovered.derived ? "record" : "refuse");
204
+ const read = () => {
205
+ const held = inventory(project, host, targetRoot);
206
+ const found = roots(project, held, targetRoot, options);
207
+ const kept = skippedRoots(project, held, reading.skips);
208
+ const rooted = kept.length === 0
209
+ ? found
210
+ : {
211
+ ...found,
212
+ liveUnderReachability: [
213
+ ...found.liveUnderReachability,
214
+ ...kept.map((id) => ({ id, kind: "type-error", source: "" })),
215
+ ],
216
+ };
217
+ return {
218
+ configuration,
219
+ configFile: project.configFile,
220
+ held,
221
+ rooted,
222
+ typeErrorSkips: reading.skips.map((one) => one.record),
223
+ skipped: skippedUnits(reading.skips, absoluteRoot),
224
+ referenceOnly: project
225
+ .ownSourceFiles()
226
+ .filter((file) => referenceOnly(file.fileName))
227
+ .flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
228
+ };
229
+ };
230
+ return stage(project, { configuration, read });
231
+ }, discovered.derived ? "record" : "refuse", workspace?.programs);
125
232
  for (const configFile of session.unopened) {
126
233
  if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
127
234
  throw new DiscoveryError(`${configFile} opened no project`, []);
128
235
  }
129
236
  dropped(configFile, "the compiler opened no project for the configuration");
130
237
  }
238
+ if (unbuildable.length > 0) {
239
+ throw new SetupError(unbuildable);
240
+ }
131
241
  if (failures.length > 0) {
132
- throw new DiscoveryError(`${String(failures.length)} error(s); no answer was produced`, failures);
242
+ throw new DiscoveryError(`${String(failures.length)} error(s), so no answer was produced`, failures);
133
243
  }
134
244
  const answers = session.projects.filter((answer) => answer !== undefined);
135
245
  const [first] = notBuilt;
246
+ if (answers.length === 0 && setupDropped.length > 0) {
247
+ throw new SetupError(setupDropped);
248
+ }
136
249
  if (answers.length === 0 && first !== undefined) {
137
250
  throw new DiscoveryError(`no configuration discovery derived could be built; ${first.id}: ${first.error}`, []);
138
251
  }
@@ -144,8 +257,19 @@ function readProjects(engine, host, scope, config, stage) {
144
257
  },
145
258
  notBuilt,
146
259
  unanswered: session.unanswered,
260
+ componentsUnread: workspace?.componentsUnread ?? [],
261
+ conventionsApplied: conventions.applied,
147
262
  };
148
263
  }
264
+ /**
265
+ * The projects the run the scope and the configuration describe analyzes, each read
266
+ * for the errors and setup failures that decide whether it is analyzed, dropped or
267
+ * fails the run, exactly as the analysis reads them.
268
+ */
269
+ export function runProjects(engine, host, scope, config) {
270
+ const read = readProjects(engine, host, scope, config, (_project, opened) => opened.configuration);
271
+ return { configurations: read.answers, notBuilt: read.notBuilt, unanswered: read.unanswered };
272
+ }
149
273
  /**
150
274
  * The root set of the run the scope and the configuration describe.
151
275
  *
@@ -154,7 +278,7 @@ function readProjects(engine, host, scope, config, stage) {
154
278
  */
155
279
  export function runRoots(engine, host, scope, config, provenance) {
156
280
  const targetRoot = scope.target.path;
157
- const read = readProjects(engine, host, scope, config, (_project, projectRead) => projectRead);
281
+ const read = readProjects(engine, host, scope, config, (_project, opened) => opened.read());
158
282
  const projects = read.answers;
159
283
  const configurations = [];
160
284
  const merged = new Map();
@@ -217,36 +341,53 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
217
341
  ? { delimiters: config.analysis.templateDelimiters, files: [] }
218
342
  : readTemplates(host, targetRoot, config.analysis);
219
343
  const consumers = scope.consumers.map((consumer) => consumer.path);
220
- const read = readProjects(engine, host, scope, config, (project, projectRead) => {
344
+ const read = readProjects(engine, host, scope, config, (project, opened) => {
345
+ const projectRead = opened.read();
221
346
  const resolved = references(project, projectRead.held, targetRoot, {
222
347
  testFiles: config.ts.testFiles,
223
348
  });
349
+ const support = testSupportFiles(projectRead.held.symbols, resolved.references, projectRead.rooted.liveUnderReachability, resolved.testFilePaths);
350
+ const made = supportReferences(resolved.references, support);
224
351
  const configured = {
225
352
  configuration: projectRead.configuration,
226
353
  symbols: projectRead.held.symbols,
227
- references: resolved.references,
354
+ references: made,
228
355
  roots: projectRead.rooted.liveUnderReachability,
229
356
  testFiles: resolved.testFilePaths,
357
+ ...(support.size === 0 ? {} : { supportFiles: [...support].sort() }),
358
+ ...(projectRead.referenceOnly.length === 0
359
+ ? {}
360
+ : { referenceOnly: projectRead.referenceOnly }),
230
361
  };
231
362
  const exempt = computeExemptions({ project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers }, detectors, {
232
363
  disabled,
233
364
  mode,
234
- testFiles: new Set(resolved.testFilePaths),
365
+ testFiles: new Set([...resolved.testFilePaths, ...support]),
235
366
  });
236
367
  const unanswered = [
237
368
  ...projectRead.held.unanswered,
238
369
  ...projectRead.rooted.unanswered,
239
- ...resolved.references
370
+ ...made
240
371
  .filter((reference) => reference.resolution === "by-name")
241
372
  .map((reference) => reference.to),
242
373
  ...exempt.filter(isUnansweredRecord).map((record) => record.id),
243
374
  ];
375
+ const components = project.ownSourceFiles().filter(isComponentFile);
376
+ const pathOf = (file) => relativePath(targetRoot, file.fileName) ?? file.fileName;
244
377
  return {
245
378
  configFile: projectRead.configFile,
246
379
  configured,
247
380
  exempt,
248
381
  unanswered,
249
- extra: extra(project, projectRead, resolved.references),
382
+ typeErrorSkips: projectRead.typeErrorSkips,
383
+ skipped: projectRead.skipped,
384
+ componentFiles: components.map(pathOf),
385
+ componentWarnings: components.flatMap((file) => readComponent(file.originalText).warnings.map((warning) => ({
386
+ path: pathOf(file),
387
+ line: lineOf(file.originalText, warning.offset),
388
+ reason: warning.reason,
389
+ }))),
390
+ extra: extra(project, projectRead, made),
250
391
  };
251
392
  });
252
393
  const consumed = read.consumers.references;
@@ -272,8 +413,33 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
272
413
  consumers: read.consumers.loaded,
273
414
  notBuilt: read.notBuilt,
274
415
  unanswered: read.unanswered,
416
+ componentsUnread: read.componentsUnread,
417
+ conventionsApplied: read.conventionsApplied,
275
418
  };
276
419
  }
420
+ /**
421
+ * The run swept under one set of marks with the records that hold. A record a component
422
+ * file's markup holds counts only while the file is live, and what it holds may be what
423
+ * keeps the file live, so each file's markup joins only once a sweep without it finds the
424
+ * file live, until a sweep finds no further file live.
425
+ */
426
+ function holdingSweep(read, input) {
427
+ const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
428
+ let live = new Set();
429
+ for (;;) {
430
+ const swept = {
431
+ ...input,
432
+ exempt: holdingWhile(read.exempt, live),
433
+ unanswered: read.unansweredHeld,
434
+ };
435
+ const result = sweepMatrix(read.matrix, swept);
436
+ const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
437
+ if (next.size === live.size) {
438
+ return { swept, result };
439
+ }
440
+ live = next;
441
+ }
442
+ }
277
443
  /**
278
444
  * The sweep of a run read once, under one set of marks. Where a question went unanswered,
279
445
  * the run is swept once more without what that question could have kept live and without
@@ -282,8 +448,8 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
282
448
  * every declaration that makes such a reference.
283
449
  */
284
450
  function sweptOf(read, input) {
285
- const swept = { ...input, exempt: read.exempt, unanswered: read.unansweredHeld };
286
- const result = sweepMatrix(read.matrix, swept);
451
+ const { swept, result } = holdingSweep(read, input);
452
+ const exempt = swept.exempt ?? [];
287
453
  const held = new Set(read.unansweredHeld);
288
454
  if (held.size > 0) {
289
455
  // What a declaration reads where the checker did not answer is unknown, so a rule
@@ -302,7 +468,7 @@ function sweptOf(read, input) {
302
468
  })));
303
469
  const bare = {
304
470
  ...swept,
305
- exempt: read.exempt.filter((record) => !isUnansweredRecord(record)),
471
+ exempt: exempt.filter((record) => !isUnansweredRecord(record)),
306
472
  unanswered: [],
307
473
  };
308
474
  for (const candidate of sweepMatrix(answered, bare).candidates) {
@@ -316,10 +482,13 @@ function sweptOf(read, input) {
316
482
  return {
317
483
  matrix: read.matrix,
318
484
  sweep: result,
485
+ exempt,
319
486
  retained: retainedIn(read.matrix, swept),
320
487
  notBuilt: read.notBuilt,
321
488
  unanswered: read.unanswered,
322
489
  heldByUnanswered: [...held],
490
+ skipped: read.projects.flatMap((one) => one.skipped),
491
+ componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(compare),
323
492
  };
324
493
  }
325
494
  /**
@@ -341,11 +510,11 @@ function readEmitterRun(engine, host, scope, config, mode, detectors) {
341
510
  const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
342
511
  deprecation: deprecatedDeclarations(project, read.held),
343
512
  accessors: accessorsOf(project, read.held),
344
- needs: projectNeeds(project, read.held, host),
513
+ needs: projectNeeds(project, read.held, host, targetRoot),
345
514
  implementations: implementations(project, read.held),
346
515
  heldByInclusion: heldByInclusion(project, targetRoot),
347
516
  rooted: read.rooted,
348
- directives: inlineDirectives(project, read.held, targetRoot),
517
+ directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
349
518
  declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
350
519
  parts: projectParts(project, read.held, references, targetRoot),
351
520
  }));
@@ -371,7 +540,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
371
540
  stores: storesOf(projects.map((one) => ({
372
541
  references: one.configured.references,
373
542
  accessors: one.extra.accessors,
374
- })), read.exempt, input.mode),
543
+ })), swept.exempt, input.mode),
375
544
  dependencies: dependenciesOf(host, targetRoot, projects.map((one) => one.extra.needs), swept.sweep.candidates.map((candidate) => candidate.id)),
376
545
  implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
377
546
  files: {
@@ -387,7 +556,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
387
556
  edges: readEdgeSides(host, targetRoot),
388
557
  },
389
558
  selfCheck,
390
- intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...read.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
559
+ intraFunction: intraFunctionFacts(projects.map((one) => one.extra.parts), projects.flatMap((one) => one.configured.references), new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered])),
391
560
  };
392
561
  }
393
562
  /**
@@ -424,10 +593,18 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
424
593
  id: one.configured.configuration,
425
594
  configFile: one.configFile,
426
595
  })),
427
- testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles)),
596
+ testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles), read.projects.flatMap((one) => one.configured.supportFiles ?? [])),
597
+ typeErrorSkips: read.projects.flatMap((one) => one.typeErrorSkips),
598
+ componentWarnings: [
599
+ ...new Map(read.projects
600
+ .flatMap((one) => one.componentWarnings)
601
+ .map((one) => [`${one.path}:${String(one.line)}:${one.reason}`, one])).values(),
602
+ ],
428
603
  consumers: read.consumers,
429
604
  notBuilt: read.notBuilt,
430
605
  unanswered: read.unanswered,
606
+ componentsUnread: read.componentsUnread,
607
+ conventionsApplied: read.conventionsApplied,
431
608
  },
432
609
  configured: read.projects.map((one) => one.configured),
433
610
  symbols,
@@ -475,6 +652,22 @@ export function runAnalysis(engine, host, scope, config, provenance, mode, detec
475
652
  const inputs = reader.inputsUnder(readBaseline(host, scope.target.path, reader.symbols));
476
653
  return analysisOf(reader, inputs);
477
654
  }
655
+ /**
656
+ * One count per configuration of the run in which the checker left a question unanswered:
657
+ * the questions asked in its project, and the declarations of its own the run holds for them.
658
+ */
659
+ export function unansweredCounts(analysis) {
660
+ const held = new Set(analysis.swept.heldByUnanswered);
661
+ return analysis.run.projects.flatMap((project) => {
662
+ const questions = analysis.run.unanswered.filter((one) => one.configFile === project.configFile).length;
663
+ if (questions === 0) {
664
+ return [];
665
+ }
666
+ const symbols = analysis.configured.find((one) => one.configuration === project.id)?.symbols;
667
+ const declarations = new Set((symbols ?? []).map((one) => one.id).filter((id) => held.has(id)));
668
+ return [{ configuration: project.id, questions, declarations: declarations.size }];
669
+ });
670
+ }
478
671
  function analysisOf(reader, inputs) {
479
672
  return {
480
673
  result: findingsPass(inputs),