@cplieger/deadset-ts 4.0.1 → 5.1.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 (138) hide show
  1. package/README.md +4 -4
  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 +301 -41
  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 +290 -0
  10. package/dist/src/conformance-record.js +3 -9
  11. package/dist/src/contextual-uses.js +240 -0
  12. package/dist/src/convention-rows.js +389 -0
  13. package/dist/src/conventions.js +565 -0
  14. package/dist/src/dependencies.js +36 -3
  15. package/dist/src/destructuring.js +6 -6
  16. package/dist/src/discover.js +10 -3
  17. package/dist/src/emit-map.js +72 -0
  18. package/dist/src/entry-point-gaps.js +81 -35
  19. package/dist/src/entry-points.js +50 -32
  20. package/dist/src/exempt-classes.js +6 -1
  21. package/dist/src/exempt.js +32 -20
  22. package/dist/src/findings/completion.js +46 -32
  23. package/dist/src/findings/emitters.js +22 -1
  24. package/dist/src/findings/interfaces.js +6 -2
  25. package/dist/src/findings/reads-and-writes.js +53 -9
  26. package/dist/src/findings/unused-declarations.js +47 -3
  27. package/dist/src/generated-file.js +24 -0
  28. package/dist/src/graph.js +13 -5
  29. package/dist/src/interface-satisfaction.js +2 -68
  30. package/dist/src/inventory.js +8 -3
  31. package/dist/src/kinds.js +6 -5
  32. package/dist/src/manifest.js +37 -11
  33. package/dist/src/matrix.js +21 -13
  34. package/dist/src/partial.js +3 -1
  35. package/dist/src/pnpm-workspace.js +120 -0
  36. package/dist/src/position.js +17 -2
  37. package/dist/src/print.js +2 -0
  38. package/dist/src/query.js +15 -0
  39. package/dist/src/references.js +404 -36
  40. package/dist/src/report.js +26 -6
  41. package/dist/src/reporters/annotations.js +10 -3
  42. package/dist/src/reporters/template.js +3 -0
  43. package/dist/src/resolve.js +37 -11
  44. package/dist/src/roots.js +37 -8
  45. package/dist/src/run.js +13 -3
  46. package/dist/src/schema.js +2 -0
  47. package/dist/src/session.js +141 -34
  48. package/dist/src/setup-failure.js +23 -0
  49. package/dist/src/source-files.js +4 -61
  50. package/dist/src/stores.js +3 -0
  51. package/dist/src/sweep.js +4 -2
  52. package/dist/src/template-field.js +38 -6
  53. package/dist/src/test-support.js +59 -0
  54. package/dist/src/type-errors.js +252 -0
  55. package/dist/src/type-query-alias.js +92 -0
  56. package/dist/src/value-flow.js +74 -0
  57. package/dist/src/verbs/analyze.js +24 -4
  58. package/dist/src/verbs/explain.js +4 -1
  59. package/dist/src/verbs/print-projects.js +17 -56
  60. package/dist/src/verbs/print-retained.js +7 -2
  61. package/dist/src/verbs/print-roots.js +7 -2
  62. package/dist/src/version-range.js +255 -0
  63. package/dist/src/version.js +1 -1
  64. package/dist/src/workspace-manifests.js +68 -0
  65. package/dist/src/workspace-programs.js +342 -0
  66. package/dist/src/workspace-resolution.js +307 -0
  67. package/dist/src/workspace-run.js +75 -0
  68. package/dist/src/workspace.js +358 -0
  69. package/jsr.json +1 -1
  70. package/package.json +3 -2
  71. package/src/analysis.ts +475 -72
  72. package/src/component-files.ts +718 -0
  73. package/src/component-layer.ts +160 -0
  74. package/src/config.ts +11 -2
  75. package/src/configuration-files.ts +370 -0
  76. package/src/conformance-record.ts +3 -10
  77. package/src/contextual-uses.ts +354 -0
  78. package/src/convention-rows.ts +448 -0
  79. package/src/conventions.ts +788 -0
  80. package/src/dependencies.ts +49 -2
  81. package/src/destructuring.ts +16 -6
  82. package/src/discover.ts +10 -6
  83. package/src/emit-map.ts +93 -0
  84. package/src/entry-point-gaps.ts +92 -38
  85. package/src/entry-points.ts +79 -36
  86. package/src/exempt-classes.ts +6 -1
  87. package/src/exempt.ts +69 -20
  88. package/src/file-facts.ts +2 -0
  89. package/src/findings/completion.ts +55 -36
  90. package/src/findings/emitters.ts +32 -1
  91. package/src/findings/interfaces.ts +9 -2
  92. package/src/findings/reads-and-writes.ts +70 -9
  93. package/src/findings/unused-declarations.ts +60 -3
  94. package/src/generated-file.ts +27 -0
  95. package/src/graph.ts +15 -4
  96. package/src/host.ts +23 -0
  97. package/src/index.ts +1 -1
  98. package/src/interface-satisfaction.ts +1 -92
  99. package/src/inventory.ts +17 -3
  100. package/src/kinds.ts +7 -5
  101. package/src/manifest.ts +47 -14
  102. package/src/matrix.ts +43 -15
  103. package/src/partial.ts +6 -1
  104. package/src/pnpm-workspace.ts +135 -0
  105. package/src/position.ts +23 -2
  106. package/src/print.ts +2 -0
  107. package/src/query.ts +26 -0
  108. package/src/references.ts +472 -40
  109. package/src/report.ts +56 -6
  110. package/src/reporters/annotations.ts +12 -3
  111. package/src/reporters/template.ts +3 -0
  112. package/src/resolve.ts +65 -11
  113. package/src/roots.ts +70 -9
  114. package/src/run.ts +15 -5
  115. package/src/schema.ts +2 -0
  116. package/src/session.ts +218 -37
  117. package/src/setup-failure.ts +39 -0
  118. package/src/source-files.ts +4 -66
  119. package/src/stores.ts +9 -0
  120. package/src/sweep.ts +9 -2
  121. package/src/template-field.ts +42 -7
  122. package/src/test-support.ts +77 -0
  123. package/src/type-errors.ts +340 -0
  124. package/src/type-query-alias.ts +133 -0
  125. package/src/value-flow.ts +104 -0
  126. package/src/verbs/analyze.ts +38 -4
  127. package/src/verbs/explain.ts +4 -1
  128. package/src/verbs/print-projects.ts +17 -69
  129. package/src/verbs/print-retained.ts +7 -2
  130. package/src/verbs/print-roots.ts +7 -2
  131. package/src/verbs/verb.ts +6 -3
  132. package/src/version-range.ts +309 -0
  133. package/src/version.ts +1 -1
  134. package/src/workspace-manifests.ts +100 -0
  135. package/src/workspace-programs.ts +486 -0
  136. package/src/workspace-resolution.ts +393 -0
  137. package/src/workspace-run.ts +148 -0
  138. package/src/workspace.ts +463 -0
@@ -7,15 +7,19 @@
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, generatedFiles, 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 { generatedFile } from "./generated-file.js";
21
+ import { sourceFilesOf } from "./source-files.js";
22
+ import { detectExemptions, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
19
23
  import { heldByInclusion, sourceTree } from "./file-facts.js";
20
24
  import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
21
25
  import { NO_SELF_CHECK, unmatchedRootFindings, } from "./findings/self-check.js";
@@ -26,19 +30,23 @@ import { interfaceSatisfaction } from "./interface-satisfaction.js";
26
30
  import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
27
31
  import { inventory } from "./inventory.js";
28
32
  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";
33
+ import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
34
+ import { joinPath, relativePath, resolvePath } from "./paths.js";
35
+ import { byPosition, isComponentFile } from "./position.js";
36
+ import { SetupError, setupLine } from "./setup-failure.js";
37
+ import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
32
38
  import { reflectiveLookup } from "./reflective-lookup.js";
33
39
  import { references, testFileRulesOf } from "./references.js";
34
40
  import { roots, unmatchedEverywhere } from "./roots.js";
35
41
  import { serializationContract } from "./serialization-contract.js";
36
- import { diagnosticsOf, runSession } from "./session.js";
42
+ import { supportReferences, testSupportFiles } from "./test-support.js";
43
+ import { diagnosticsOf, ownedAs, runSession } from "./session.js";
37
44
  import { accessorsOf, storesOf } from "./stores.js";
38
45
  import { isPartKind } from "./suppress.js";
39
46
  import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
40
47
  import { inlineDirectives, inlineSuppressions } from "./suppress-inline.js";
41
48
  import { readTemplates, templateField } from "./template-field.js";
49
+ import { ownErrors, workspaceRun } from "./workspace-run.js";
42
50
  /** The setting whose source decides where a finding about a configured root sits. */
43
51
  const ROOTS_SETTING = "roots.patterns";
44
52
  /**
@@ -63,12 +71,29 @@ function compare(a, b) {
63
71
  return a < b ? -1 : 1;
64
72
  }
65
73
  /**
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.
74
+ * The manifests the analysis reads, each once: the target's own, then every workspace
75
+ * member's below the target root.
76
+ */
77
+ function conventionManifests(host, targetRoot, memberDirs) {
78
+ const found = new Map();
79
+ if (host.kindOf(joinPath(targetRoot, "package.json")) === "file") {
80
+ found.set(targetRoot, { dir: targetRoot, path: "package.json" });
81
+ }
82
+ for (const dir of memberDirs) {
83
+ const below = relativePath(targetRoot, dir);
84
+ if (below !== undefined && !found.has(dir)) {
85
+ found.set(dir, { dir, path: `${below}/package.json` });
86
+ }
87
+ }
88
+ return [...found.values()];
89
+ }
90
+ /**
91
+ * Reads every project the scope discovers, and each declared consumer's, in one snapshot,
92
+ * each checked for errors first. `stage` is handed a target project and enumerates and roots
93
+ * it if it reads it; a consumer project is read for its references to the target. A type error
94
+ * in an own file skips the unit holding it. A guess that does not load or meets a setup failure
95
+ * is dropped into `notBuilt` (see {@link isGuess}); any other such project fails the run, as
96
+ * does a run whose every guess was dropped.
72
97
  */
73
98
  function readProjects(engine, host, scope, config, stage) {
74
99
  const targetRoot = scope.target.path;
@@ -76,14 +101,20 @@ function readProjects(engine, host, scope, config, stage) {
76
101
  const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
77
102
  const loads = consumerLoads(engine, host, scope, discovered.configFiles);
78
103
  const consumerOf = new Map(loads.flatMap((load) => load.configFiles.map((configFile) => [configFile, load.consumer])));
104
+ const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
105
+ const workspace = workspaceRun(engine, host, absoluteRoot, discovered.configFiles, readManifest(host, targetRoot));
106
+ const conventions = readConventions(host, (fileName, text) => engine.parseSourceFile(fileName, text), conventionManifests(host, absoluteRoot, workspace?.memberDirs ?? []), config.ts.disabledConventions);
79
107
  const options = {
80
- manifest: readManifest(host, targetRoot),
108
+ host,
109
+ manifest: workspace?.manifest ?? readManifest(host, targetRoot),
110
+ workspaceEntries: workspace?.entries,
81
111
  patterns: config.rootPatterns,
82
112
  entryFiles: config.ts.entryFiles,
113
+ conventions,
83
114
  testFiles: config.ts.testFiles,
115
+ parse: (fileName, text) => engine.parseSourceFile(fileName, text),
84
116
  publishedAPI: config.targetKind === "library",
85
117
  };
86
- const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
87
118
  const notBuilt = [...discovered.notBuilt];
88
119
  const dropped = (configFile, error) => {
89
120
  notBuilt.push({
@@ -93,10 +124,36 @@ function readProjects(engine, host, scope, config, stage) {
93
124
  });
94
125
  };
95
126
  const failures = [];
127
+ const unbuildable = [];
128
+ const setupDropped = [];
96
129
  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);
130
+ const setupMet = (configFile, met) => {
131
+ const [first] = met;
132
+ if (first !== undefined && isGuess(discovered, configFile)) {
133
+ dropped(configFile, setupLine(first));
134
+ setupDropped.push(...met);
135
+ }
136
+ else {
137
+ unbuildable.push(...met);
138
+ }
139
+ };
140
+ const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (opened) => {
141
+ const consumer = consumerOf.get(opened.configFile);
142
+ const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
143
+ if (setup !== undefined) {
144
+ setupMet(opened.configFile, [
145
+ { setupClass: "workspace-member-without-source", detail: setup },
146
+ ]);
147
+ return undefined;
148
+ }
149
+ const referenceOnly = workspace === undefined || consumer !== undefined
150
+ ? () => false
151
+ : workspace.referenceOnly(opened.configFile, opened.rootFiles);
152
+ const project = workspace === undefined || consumer !== undefined
153
+ ? opened
154
+ : ownedAs(opened, (file, fromExternalLibrary) => workspace.ownFile(file, fromExternalLibrary));
155
+ const raw = projectErrors(diagnosticsOf(project));
156
+ const errors = { load: raw.load, check: ownErrors(raw.check, referenceOnly) };
100
157
  const [unloaded] = errors.load;
101
158
  if (unloaded !== undefined &&
102
159
  consumer === undefined &&
@@ -104,8 +161,37 @@ function readProjects(engine, host, scope, config, stage) {
104
161
  dropped(project.configFile, renderDiagnostic(unloaded, (path) => relativePath(absoluteRoot, path) ?? path));
105
162
  return undefined;
106
163
  }
107
- if (errors.load.length > 0 || errors.check.length > 0) {
108
- failures.push(...errors.load, ...errors.check);
164
+ if (errors.load.length > 0) {
165
+ failures.push(...errors.load);
166
+ return undefined;
167
+ }
168
+ const reading = readErrors(project, errors.check, {
169
+ host,
170
+ targetRoot: consumer === undefined
171
+ ? absoluteRoot
172
+ : resolvePath(host.workingDirectory(), consumer.path),
173
+ paths: pathsOf(engine, project.configFile),
174
+ });
175
+ if (reading.failing.length > 0) {
176
+ failures.push(...reading.failing);
177
+ return undefined;
178
+ }
179
+ if (consumer !== undefined && reading.missing.length > 0) {
180
+ unbuildable.push(...reading.missing.map((one) => ({
181
+ setupClass: "missing-consumer",
182
+ detail: `the consumer ${consumer.path} does not build, its dependencies not installed: ${one.detail}`,
183
+ })));
184
+ return undefined;
185
+ }
186
+ if (reading.missing.length > 0) {
187
+ setupMet(project.configFile, reading.missing);
188
+ return undefined;
189
+ }
190
+ const unconventional = consumer === undefined
191
+ ? conventionFailuresFor(conventions, project.configFile, project.ownSourceFiles().map((file) => file.fileName))
192
+ : [];
193
+ if (unconventional.length > 0) {
194
+ setupMet(project.configFile, unconventional);
109
195
  return undefined;
110
196
  }
111
197
  if (consumer !== undefined) {
@@ -117,22 +203,53 @@ function readProjects(engine, host, scope, config, stage) {
117
203
  }
118
204
  return undefined;
119
205
  }
120
- const held = inventory(project, host, targetRoot);
121
- const rooted = roots(project, held, targetRoot, options);
122
206
  const configuration = ids.get(project.configFile) ?? project.configFile;
123
- return stage(project, { configuration, configFile: project.configFile, held, rooted });
124
- }, discovered.derived ? "record" : "refuse");
207
+ const read = () => {
208
+ const held = inventory(project, host, targetRoot);
209
+ const found = roots(project, held, targetRoot, options);
210
+ const kept = skippedRoots(project, held, reading.skips);
211
+ const rooted = kept.length === 0
212
+ ? found
213
+ : {
214
+ ...found,
215
+ liveUnderReachability: [
216
+ ...found.liveUnderReachability,
217
+ ...kept.map((id) => ({ id, kind: "type-error", source: "" })),
218
+ ],
219
+ };
220
+ return {
221
+ configuration,
222
+ configFile: project.configFile,
223
+ held,
224
+ rooted,
225
+ typeErrorSkips: reading.skips.map((one) => one.record),
226
+ skipped: skippedUnits(reading.skips, absoluteRoot),
227
+ referenceOnly: project
228
+ .ownSourceFiles()
229
+ .filter((file) => referenceOnly(file.fileName))
230
+ .flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
231
+ generated: generatedFiles(conventions, sourceFilesOf(project, targetRoot)),
232
+ };
233
+ };
234
+ return stage(project, { configuration, read });
235
+ }, discovered.derived ? "record" : "refuse", workspace?.programs);
125
236
  for (const configFile of session.unopened) {
126
237
  if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
127
238
  throw new DiscoveryError(`${configFile} opened no project`, []);
128
239
  }
129
240
  dropped(configFile, "the compiler opened no project for the configuration");
130
241
  }
242
+ if (unbuildable.length > 0) {
243
+ throw new SetupError(unbuildable);
244
+ }
131
245
  if (failures.length > 0) {
132
246
  throw new DiscoveryError(`${String(failures.length)} error(s), so no answer was produced`, failures);
133
247
  }
134
248
  const answers = session.projects.filter((answer) => answer !== undefined);
135
249
  const [first] = notBuilt;
250
+ if (answers.length === 0 && setupDropped.length > 0) {
251
+ throw new SetupError(setupDropped);
252
+ }
136
253
  if (answers.length === 0 && first !== undefined) {
137
254
  throw new DiscoveryError(`no configuration discovery derived could be built; ${first.id}: ${first.error}`, []);
138
255
  }
@@ -144,8 +261,19 @@ function readProjects(engine, host, scope, config, stage) {
144
261
  },
145
262
  notBuilt,
146
263
  unanswered: session.unanswered,
264
+ componentsUnread: workspace?.componentsUnread ?? [],
265
+ conventionsApplied: conventions.applied,
147
266
  };
148
267
  }
268
+ /**
269
+ * The projects the run the scope and the configuration describe analyzes, each read
270
+ * for the errors and setup failures that decide whether it is analyzed, dropped or
271
+ * fails the run, exactly as the analysis reads them.
272
+ */
273
+ export function runProjects(engine, host, scope, config) {
274
+ const read = readProjects(engine, host, scope, config, (_project, opened) => opened.configuration);
275
+ return { configurations: read.answers, notBuilt: read.notBuilt, unanswered: read.unanswered };
276
+ }
149
277
  /**
150
278
  * The root set of the run the scope and the configuration describe.
151
279
  *
@@ -154,7 +282,7 @@ function readProjects(engine, host, scope, config, stage) {
154
282
  */
155
283
  export function runRoots(engine, host, scope, config, provenance) {
156
284
  const targetRoot = scope.target.path;
157
- const read = readProjects(engine, host, scope, config, (_project, projectRead) => projectRead);
285
+ const read = readProjects(engine, host, scope, config, (_project, opened) => opened.read());
158
286
  const projects = read.answers;
159
287
  const configurations = [];
160
288
  const merged = new Map();
@@ -197,6 +325,7 @@ export function runRoots(engine, host, scope, config, provenance) {
197
325
  const DETECTORS = new Map([
198
326
  ["interface-satisfaction", interfaceSatisfaction],
199
327
  ["enum-group", enumGroup],
328
+ ["generated-file", generatedFile],
200
329
  ["template-field", templateField],
201
330
  ["reflective-lookup", reflectiveLookup],
202
331
  ["decorator", decorator],
@@ -217,36 +346,64 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
217
346
  ? { delimiters: config.analysis.templateDelimiters, files: [] }
218
347
  : readTemplates(host, targetRoot, config.analysis);
219
348
  const consumers = scope.consumers.map((consumer) => consumer.path);
220
- const read = readProjects(engine, host, scope, config, (project, projectRead) => {
349
+ const read = readProjects(engine, host, scope, config, (project, opened) => {
350
+ const projectRead = opened.read();
221
351
  const resolved = references(project, projectRead.held, targetRoot, {
222
352
  testFiles: config.ts.testFiles,
223
353
  });
354
+ const support = testSupportFiles(projectRead.held.symbols, resolved.references, projectRead.rooted.liveUnderReachability, resolved.testFilePaths);
355
+ const detected = detectExemptions({
356
+ project,
357
+ held: projectRead.held,
358
+ targetRoot,
359
+ templates,
360
+ ts: config.ts,
361
+ consumers,
362
+ ...(config.analysis.generatedFiles === "include"
363
+ ? {}
364
+ : { generated: projectRead.generated }),
365
+ }, detectors, {
366
+ disabled,
367
+ mode,
368
+ testFiles: new Set([...resolved.testFilePaths, ...support]),
369
+ });
370
+ const exempt = detected.records;
371
+ const made = supportReferences(resolved.references, support);
224
372
  const configured = {
225
373
  configuration: projectRead.configuration,
226
374
  symbols: projectRead.held.symbols,
227
- references: resolved.references,
375
+ references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
228
376
  roots: projectRead.rooted.liveUnderReachability,
229
377
  testFiles: resolved.testFilePaths,
378
+ ...(support.size === 0 ? {} : { supportFiles: [...support].sort() }),
379
+ ...(projectRead.referenceOnly.length === 0
380
+ ? {}
381
+ : { referenceOnly: projectRead.referenceOnly }),
230
382
  };
231
- const exempt = computeExemptions({ project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers }, detectors, {
232
- disabled,
233
- mode,
234
- testFiles: new Set(resolved.testFilePaths),
235
- });
236
383
  const unanswered = [
237
384
  ...projectRead.held.unanswered,
238
385
  ...projectRead.rooted.unanswered,
239
- ...resolved.references
386
+ ...made
240
387
  .filter((reference) => reference.resolution === "by-name")
241
388
  .map((reference) => reference.to),
242
389
  ...exempt.filter(isUnansweredRecord).map((record) => record.id),
243
390
  ];
391
+ const components = project.ownSourceFiles().filter(isComponentFile);
392
+ const pathOf = (file) => relativePath(targetRoot, file.fileName) ?? file.fileName;
244
393
  return {
245
394
  configFile: projectRead.configFile,
246
395
  configured,
247
396
  exempt,
248
397
  unanswered,
249
- extra: extra(project, projectRead, resolved.references),
398
+ typeErrorSkips: projectRead.typeErrorSkips,
399
+ skipped: projectRead.skipped,
400
+ componentFiles: components.map(pathOf),
401
+ componentWarnings: components.flatMap((file) => readComponent(file.originalText).warnings.map((warning) => ({
402
+ path: pathOf(file),
403
+ line: lineOf(file.originalText, warning.offset),
404
+ reason: warning.reason,
405
+ }))),
406
+ extra: extra(project, projectRead, made),
250
407
  };
251
408
  });
252
409
  const consumed = read.consumers.references;
@@ -272,8 +429,82 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
272
429
  consumers: read.consumers.loaded,
273
430
  notBuilt: read.notBuilt,
274
431
  unanswered: read.unanswered,
432
+ componentsUnread: read.componentsUnread,
433
+ conventionsApplied: read.conventionsApplied,
275
434
  };
276
435
  }
436
+ /**
437
+ * The run swept under one set of marks with the records that hold. A record a component
438
+ * file's markup holds counts only while the file is live, and what it holds may be what
439
+ * keeps the file live, so each file's markup joins only once a sweep without it finds the
440
+ * file live, until a sweep finds no further file live.
441
+ */
442
+ function holdingSweep(read, input) {
443
+ const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
444
+ let live = new Set();
445
+ for (;;) {
446
+ const swept = {
447
+ ...input,
448
+ exempt: holdingWhile(read.exempt, live),
449
+ unanswered: read.unansweredHeld,
450
+ };
451
+ const result = sweepMatrix(read.matrix, swept);
452
+ const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
453
+ if (next.size === live.size) {
454
+ return { swept, result };
455
+ }
456
+ live = next;
457
+ }
458
+ }
459
+ /**
460
+ * The test references exemption evidence written in a test file makes: each is a read of
461
+ * the declaration it would retain, made by the test file at the evidence's site.
462
+ */
463
+ function evidenceReferences(symbols, inTestFiles) {
464
+ if (inTestFiles.length === 0) {
465
+ return [];
466
+ }
467
+ const files = new Map(symbols
468
+ .filter((symbol) => symbol.kind === "file")
469
+ .map((symbol) => [symbol.position.path, symbol.id]));
470
+ return inTestFiles.flatMap((record) => {
471
+ const from = files.get(record.site.path);
472
+ return from === undefined
473
+ ? []
474
+ : [
475
+ {
476
+ from,
477
+ to: record.id,
478
+ position: record.site,
479
+ use: "read",
480
+ resolution: "syntax",
481
+ test: true,
482
+ },
483
+ ];
484
+ });
485
+ }
486
+ /**
487
+ * The declarations every reference the mode counts stores into, a loaded consumer's
488
+ * reference counting as a read: what the write-only kind would report but for a record.
489
+ */
490
+ function writtenOnly(read, mode) {
491
+ const written = new Set();
492
+ const readSome = new Set();
493
+ for (const { configured } of read.projects) {
494
+ for (const reference of configured.references) {
495
+ if (mode.production && reference.test) {
496
+ continue;
497
+ }
498
+ if (reference.use === "write" && reference.consumer === undefined) {
499
+ written.add(reference.to);
500
+ }
501
+ else {
502
+ readSome.add(reference.to);
503
+ }
504
+ }
505
+ }
506
+ return new Set([...written].filter((id) => !readSome.has(id)));
507
+ }
277
508
  /**
278
509
  * The sweep of a run read once, under one set of marks. Where a question went unanswered,
279
510
  * the run is swept once more without what that question could have kept live and without
@@ -282,8 +513,8 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
282
513
  * every declaration that makes such a reference.
283
514
  */
284
515
  function sweptOf(read, input) {
285
- const swept = { ...input, exempt: read.exempt, unanswered: read.unansweredHeld };
286
- const result = sweepMatrix(read.matrix, swept);
516
+ const { swept, result } = holdingSweep(read, input);
517
+ const exempt = swept.exempt ?? [];
287
518
  const held = new Set(read.unansweredHeld);
288
519
  if (held.size > 0) {
289
520
  // What a declaration reads where the checker did not answer is unknown, so a rule
@@ -302,7 +533,7 @@ function sweptOf(read, input) {
302
533
  })));
303
534
  const bare = {
304
535
  ...swept,
305
- exempt: read.exempt.filter((record) => !isUnansweredRecord(record)),
536
+ exempt: exempt.filter((record) => !isUnansweredRecord(record)),
306
537
  unanswered: [],
307
538
  };
308
539
  for (const candidate of sweepMatrix(answered, bare).candidates) {
@@ -316,10 +547,13 @@ function sweptOf(read, input) {
316
547
  return {
317
548
  matrix: read.matrix,
318
549
  sweep: result,
319
- retained: retainedIn(read.matrix, swept),
550
+ exempt,
551
+ retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
320
552
  notBuilt: read.notBuilt,
321
553
  unanswered: read.unanswered,
322
554
  heldByUnanswered: [...held],
555
+ skipped: read.projects.flatMap((one) => one.skipped),
556
+ componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(compare),
323
557
  };
324
558
  }
325
559
  /**
@@ -341,11 +575,12 @@ function readEmitterRun(engine, host, scope, config, mode, detectors) {
341
575
  const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
342
576
  deprecation: deprecatedDeclarations(project, read.held),
343
577
  accessors: accessorsOf(project, read.held),
344
- needs: projectNeeds(project, read.held, host),
578
+ needs: projectNeeds(project, read.held, host, targetRoot, (fileName, text) => engine.parseSourceFile(fileName, text)),
345
579
  implementations: implementations(project, read.held),
346
580
  heldByInclusion: heldByInclusion(project, targetRoot),
581
+ generated: [...read.generated.keys()],
347
582
  rooted: read.rooted,
348
- directives: inlineDirectives(project, read.held, targetRoot),
583
+ directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
349
584
  declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
350
585
  parts: projectParts(project, read.held, references, targetRoot),
351
586
  }));
@@ -371,12 +606,13 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
371
606
  stores: storesOf(projects.map((one) => ({
372
607
  references: one.configured.references,
373
608
  accessors: one.extra.accessors,
374
- })), read.exempt, input.mode),
609
+ })), swept.exempt, input.mode),
375
610
  dependencies: dependenciesOf(host, targetRoot, projects.map((one) => one.extra.needs), swept.sweep.candidates.map((candidate) => candidate.id)),
376
611
  implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
377
612
  files: {
378
613
  tree: sourceTree(host, targetRoot),
379
614
  heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
615
+ generated: new Set(projects.flatMap((one) => one.extra.generated)),
380
616
  },
381
617
  boundary: {
382
618
  consumers: {
@@ -387,7 +623,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
387
623
  edges: readEdgeSides(host, targetRoot),
388
624
  },
389
625
  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])),
626
+ 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
627
  };
392
628
  }
393
629
  /**
@@ -424,10 +660,18 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
424
660
  id: one.configured.configuration,
425
661
  configFile: one.configFile,
426
662
  })),
427
- testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles)),
663
+ testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles), read.projects.flatMap((one) => one.configured.supportFiles ?? [])),
664
+ typeErrorSkips: read.projects.flatMap((one) => one.typeErrorSkips),
665
+ componentWarnings: [
666
+ ...new Map(read.projects
667
+ .flatMap((one) => one.componentWarnings)
668
+ .map((one) => [`${one.path}:${String(one.line)}:${one.reason}`, one])).values(),
669
+ ],
428
670
  consumers: read.consumers,
429
671
  notBuilt: read.notBuilt,
430
672
  unanswered: read.unanswered,
673
+ componentsUnread: read.componentsUnread,
674
+ conventionsApplied: read.conventionsApplied,
431
675
  },
432
676
  configured: read.projects.map((one) => one.configured),
433
677
  symbols,
@@ -475,6 +719,22 @@ export function runAnalysis(engine, host, scope, config, provenance, mode, detec
475
719
  const inputs = reader.inputsUnder(readBaseline(host, scope.target.path, reader.symbols));
476
720
  return analysisOf(reader, inputs);
477
721
  }
722
+ /**
723
+ * One count per configuration of the run in which the checker left a question unanswered:
724
+ * the questions asked in its project, and the declarations of its own the run holds for them.
725
+ */
726
+ export function unansweredCounts(analysis) {
727
+ const held = new Set(analysis.swept.heldByUnanswered);
728
+ return analysis.run.projects.flatMap((project) => {
729
+ const questions = analysis.run.unanswered.filter((one) => one.configFile === project.configFile).length;
730
+ if (questions === 0) {
731
+ return [];
732
+ }
733
+ const symbols = analysis.configured.find((one) => one.configuration === project.id)?.symbols;
734
+ const declarations = new Set((symbols ?? []).map((one) => one.id).filter((id) => held.has(id)));
735
+ return [{ configuration: project.id, questions, declarations: declarations.size }];
736
+ });
737
+ }
478
738
  function analysisOf(reader, inputs) {
479
739
  return {
480
740
  result: findingsPass(inputs),