@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
@@ -0,0 +1,486 @@
1
+ /**
2
+ * The programs of a workspace's configurations, each reading the members it imports
3
+ * from source. Each round builds the programs, answers their member imports, and gives
4
+ * the answers back as static resolutions, which the compiler reads as the program's own
5
+ * files rather than a library's, until a round adds none. A configuration with no
6
+ * member import keeps the project the compiler opens for it.
7
+ */
8
+
9
+ import { ModuleKind, ModuleResolutionKind } from "@typescript/native/unstable/sync";
10
+ import type {
11
+ CreateSnapshotProgramParams,
12
+ ModuleResolutionEntry,
13
+ ModuleResolver,
14
+ Program,
15
+ ResolutionMode,
16
+ } from "@typescript/native/unstable/sync";
17
+ import { isStringLiteralLikeNode } from "@typescript/native/unstable/ast";
18
+ import type { StringLiteralLikeNode } from "@typescript/native/unstable/ast";
19
+ import type { Host } from "./host.ts";
20
+ import { dirnamePath, joinPath, resolvePath } from "./paths.ts";
21
+ import type { Engine } from "./session.ts";
22
+ import type { WorkspaceResolver } from "./workspace-resolution.ts";
23
+
24
+ /** One member import no member source answers for. */
25
+ export interface UnresolvedImport {
26
+ /** The importing file, absolute. */
27
+ readonly file: string;
28
+ readonly specifier: string;
29
+ readonly member: string;
30
+ readonly subpath: string;
31
+ readonly reason: string;
32
+ }
33
+
34
+ /** What reading one configuration's member imports did. */
35
+ export interface MemberReading {
36
+ readonly configFile: string;
37
+ /** The files the configuration itself compiles, absolute. */
38
+ readonly fileNames: readonly string[];
39
+ /** The static resolutions its program is built with; none for a configuration opened as a project. */
40
+ readonly entries: readonly ModuleResolutionEntry[];
41
+ readonly unresolved: readonly UnresolvedImport[];
42
+ /**
43
+ * Why the configuration's component files are not read, where the snapshots read
44
+ * component files and its program reads none.
45
+ */
46
+ readonly componentsUnread: string | undefined;
47
+ }
48
+
49
+ /** How a snapshot reads one configuration that imports a member. */
50
+ export type WorkspaceProgram =
51
+ | { readonly kind: "program"; readonly params: CreateSnapshotProgramParams }
52
+ /** A configuration only the snapshot holds, at a path no file has. */
53
+ | { readonly kind: "configuration"; readonly file: string; readonly text: string };
54
+
55
+ /** The programs one run builds instead of opening projects, with what reading them did. */
56
+ export interface WorkspacePrograms {
57
+ /** Per configuration that imports a member, how the snapshot reads it. */
58
+ readonly programs: ReadonlyMap<string, WorkspaceProgram>;
59
+ readonly readings: readonly MemberReading[];
60
+ }
61
+
62
+ type Parsed = ReturnType<Engine["parseConfigFile"]>;
63
+
64
+ /** The most questions one round trip carries. */
65
+ const BATCH = 2048;
66
+
67
+ /**
68
+ * The options a configuration's program is built with: its own, less the ones that
69
+ * lay out an emitted program's inputs and outputs. The analysis emits nothing, and a
70
+ * member's source is outside the importer's root directory by construction, which
71
+ * the compiler refuses under such a layout.
72
+ */
73
+ function analysisOptions(parsed: Parsed): CreateSnapshotProgramParams["compilerOptions"] {
74
+ const {
75
+ composite,
76
+ rootDir: _rootDir,
77
+ outDir: _outDir,
78
+ declarationDir: _declarationDir,
79
+ tsBuildInfoFile: _tsBuildInfoFile,
80
+ ...kept
81
+ } = parsed.options;
82
+ return composite === true ? { ...kept, declaration: true } : kept;
83
+ }
84
+
85
+ /**
86
+ * The references a built program can carry: only where every referenced
87
+ * configuration reads and is composite with output, because the compiler reports a
88
+ * broken reference at syntax a built program does not have.
89
+ */
90
+ function carriedReferences(
91
+ engine: Engine,
92
+ parsed: Parsed,
93
+ ): NonNullable<Parsed["projectReferences"]> {
94
+ const references = parsed.projectReferences ?? [];
95
+ for (const reference of references) {
96
+ // A reference that names a directory names the `tsconfig.json` in it.
97
+ const configFile = reference.path.endsWith(".json")
98
+ ? reference.path
99
+ : joinPath(reference.path, "tsconfig.json");
100
+ let options: Parsed["options"];
101
+ try {
102
+ options = engine.parseConfigFile(configFile).options;
103
+ } catch {
104
+ return [];
105
+ }
106
+ if (options.composite !== true || options.noEmit === true) {
107
+ return [];
108
+ }
109
+ }
110
+ return references;
111
+ }
112
+
113
+ /** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
114
+ function conditionsOf(parsed: Parsed, mode: ResolutionMode | undefined): ReadonlySet<string> {
115
+ const resolution = parsed.options.moduleResolution;
116
+ const nodeLike =
117
+ resolution === ModuleResolutionKind.Node16 || resolution === ModuleResolutionKind.NodeNext;
118
+ return new Set([
119
+ "types",
120
+ mode === ModuleKind.CommonJS ? "require" : "import",
121
+ ...(nodeLike ? ["node"] : []),
122
+ ...(parsed.options.customConditions ?? []),
123
+ "default",
124
+ ]);
125
+ }
126
+
127
+ function resolutionMode(kind: ModuleKind): ResolutionMode | undefined {
128
+ return kind === ModuleKind.CommonJS || kind === ModuleKind.ESNext ? kind : undefined;
129
+ }
130
+
131
+ /** One configuration's state across the rounds. */
132
+ interface Reading {
133
+ readonly configFile: string;
134
+ readonly parsed: Parsed;
135
+ readonly references: NonNullable<Parsed["projectReferences"]>;
136
+ readonly entries: ModuleResolutionEntry[];
137
+ readonly seen: Set<string>;
138
+ /** Per member specifier, every file an import of it is read as. */
139
+ readonly bound: Map<string, Set<string>>;
140
+ readonly unresolved: UnresolvedImport[];
141
+ }
142
+
143
+ /** The directories the compiler leaves out of a configuration that names no `exclude`. */
144
+ const DEFAULT_EXCLUDES = ["node_modules", "bower_components", "jspm_packages"];
145
+
146
+ /** Why one configuration's answers cannot be `paths`: a specifier read as two files. */
147
+ function unstatable(reading: Reading): string | undefined {
148
+ const answered = new Set(reading.entries.map((entry) => entry.moduleName));
149
+ for (const [specifier, files] of reading.bound) {
150
+ if (answered.has(specifier) && files.size > 1) {
151
+ return `the imports of ${specifier} are read as ${String(files.size)} different files, which a configuration cannot state`;
152
+ }
153
+ }
154
+ return undefined;
155
+ }
156
+
157
+ /** The path beside `configFile` its written configuration takes, which no file has. */
158
+ function writtenPath(host: Host, configFile: string): string {
159
+ const stem = configFile.replace(/\.json$/u, "");
160
+ for (let counter = 0; ; counter += 1) {
161
+ const path = `${stem}.deadset-ts${counter === 0 ? "" : `-${String(counter)}`}.json`;
162
+ if (host.kindOf(path) === "absent") {
163
+ return path;
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * The configuration that reads as one configuration's program: it extends it, lays out
170
+ * no output for the reason {@link analysisOptions} gives, maps each answered specifier
171
+ * to its file, and carries the references the program does. The compiler's default
172
+ * `exclude` names the output directories this drops, so where the file list moves it is
173
+ * restated; a list that still differs, or answers `paths` cannot state, are a reason.
174
+ */
175
+ function configurationOf(
176
+ engine: Engine,
177
+ host: Host,
178
+ reading: Reading,
179
+ ): WorkspaceProgram | { readonly kind: "unread"; readonly reason: string } {
180
+ const reason = unstatable(reading);
181
+ if (reason !== undefined) {
182
+ return { kind: "unread", reason };
183
+ }
184
+ const { options } = reading.parsed;
185
+ const raw: unknown = (options as Record<string, unknown>)["pathsBasePath"];
186
+ const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
187
+ const paths: Record<string, string[]> = {};
188
+ for (const [key, targets] of Object.entries(options.paths ?? {})) {
189
+ paths[key] = targets.map((target) => resolvePath(base, target));
190
+ }
191
+ for (const entry of reading.entries) {
192
+ const [file] = reading.bound.get(entry.moduleName) ?? [];
193
+ if (file !== undefined) {
194
+ paths[entry.moduleName] = [file];
195
+ }
196
+ }
197
+ const json = (exclude: readonly string[] | undefined): Record<string, unknown> => ({
198
+ extends: reading.configFile,
199
+ compilerOptions: {
200
+ rootDir: null,
201
+ outDir: null,
202
+ declarationDir: null,
203
+ tsBuildInfoFile: null,
204
+ ...(options.composite === true ? { composite: false, declaration: true } : {}),
205
+ paths,
206
+ },
207
+ ...(reading.references.length > 0
208
+ ? { references: reading.references.map((reference) => ({ path: reference.path })) }
209
+ : {}),
210
+ ...(exclude === undefined ? {} : { exclude }),
211
+ });
212
+ const file = writtenPath(host, reading.configFile);
213
+ const expected = [...reading.parsed.fileNames].sort().join("\u0000");
214
+ const listed = (value: Record<string, unknown>): string | undefined => {
215
+ try {
216
+ return [...engine.parseConfigJson(value, file).fileNames].sort().join("\u0000");
217
+ } catch {
218
+ return undefined;
219
+ }
220
+ };
221
+ let value = json(undefined);
222
+ if (listed(value) !== expected) {
223
+ value = json([
224
+ ...DEFAULT_EXCLUDES,
225
+ ...[options.outDir, options.declarationDir].filter((dir) => dir !== undefined),
226
+ ]);
227
+ if (listed(value) !== expected) {
228
+ return {
229
+ kind: "unread",
230
+ reason: "its file list cannot be restated without its output directories",
231
+ };
232
+ }
233
+ }
234
+ return { kind: "configuration", file, text: `${JSON.stringify(value, null, 2)}\n` };
235
+ }
236
+
237
+ /** The program one configuration is built as, under the answers it holds so far. */
238
+ function programOf(
239
+ engine: Engine,
240
+ reading: Reading,
241
+ ): { readonly params: CreateSnapshotProgramParams; readonly resolver: ModuleResolver } {
242
+ const compilerOptions = analysisOptions(reading.parsed);
243
+ const resolver = engine.createModuleResolver(compilerOptions, reading.entries);
244
+ return {
245
+ resolver,
246
+ params: {
247
+ rootFiles: reading.parsed.fileNames,
248
+ compilerOptions,
249
+ options: {
250
+ moduleResolver: resolver,
251
+ configFileParsingDiagnostics: reading.parsed.errors,
252
+ ...(reading.references.length > 0 ? { projectReferences: reading.references } : {}),
253
+ },
254
+ },
255
+ };
256
+ }
257
+
258
+ function chunks<T>(items: readonly T[]): readonly (readonly T[])[] {
259
+ const found: T[][] = [];
260
+ for (let at = 0; at < items.length; at += BATCH) {
261
+ found.push(items.slice(at, at + BATCH));
262
+ }
263
+ return found;
264
+ }
265
+
266
+ /** Asks one question of every item, in round trips of at most {@link BATCH}. */
267
+ function batched<T, A>(
268
+ engine: Engine,
269
+ items: readonly T[],
270
+ question: (item: T) => Parameters<Engine["batch"]>[0][number],
271
+ ): A[] {
272
+ return chunks(items).flatMap((run) => engine.batch(run.map(question)) as A[]);
273
+ }
274
+
275
+ /** One round over one program: answers the member imports no earlier round asked about. */
276
+ function scan(
277
+ engine: Engine,
278
+ program: Program,
279
+ reading: Reading,
280
+ resolver: WorkspaceResolver,
281
+ ): number {
282
+ const names = program.getSourceFileNames().filter((name) => !name.includes("/node_modules/"));
283
+ const files = batched<string, ReturnType<Program["getSourceFile"]>>(engine, names, (name) =>
284
+ program.getSourceFile.gen(name),
285
+ );
286
+ const usages: { readonly file: string; readonly node: StringLiteralLikeNode }[] = [];
287
+ files.forEach((file, at) => {
288
+ const name = names[at];
289
+ if (file === undefined || name === undefined) {
290
+ return;
291
+ }
292
+ for (const node of file.imports) {
293
+ if (isStringLiteralLikeNode(node) && resolver.names(node.text)) {
294
+ usages.push({ file: name, node });
295
+ }
296
+ }
297
+ });
298
+ const modes = batched<(typeof usages)[number], ModuleKind>(engine, usages, (usage) =>
299
+ program.getModeForUsageLocation.gen(usage.file, usage.node),
300
+ );
301
+ const asked: {
302
+ readonly usage: (typeof usages)[number];
303
+ readonly mode: ResolutionMode | undefined;
304
+ }[] = [];
305
+ usages.forEach((usage, at) => {
306
+ const mode = resolutionMode(modes[at] ?? ModuleKind.None);
307
+ const key = [usage.node.text, dirnamePath(usage.file), String(mode)].join("\u0000");
308
+ if (!reading.seen.has(key)) {
309
+ reading.seen.add(key);
310
+ asked.push({ usage, mode });
311
+ }
312
+ });
313
+ const answers = batched<
314
+ (typeof asked)[number],
315
+ ReturnType<Program["getResolvedModuleFromModuleSpecifier"]>
316
+ >(engine, asked, ({ usage }) =>
317
+ program.getResolvedModuleFromModuleSpecifier.gen(usage.node, usage.file),
318
+ );
319
+ let added = 0;
320
+ asked.forEach(({ usage, mode }, at) => {
321
+ const answered = answers[at];
322
+ const specifier = usage.node.text;
323
+ const containingDirectory = dirnamePath(usage.file);
324
+ const compiled =
325
+ answered?.resolvedFileName === undefined || answered.resolvedFileName === ""
326
+ ? undefined
327
+ : answered;
328
+ const answer = resolver.resolve(
329
+ specifier,
330
+ containingDirectory,
331
+ conditionsOf(reading.parsed, mode),
332
+ compiled,
333
+ );
334
+ if (answer.kind !== "unresolved") {
335
+ const bound = reading.bound.get(specifier) ?? new Set<string>();
336
+ bound.add(answer.kind === "source" ? answer.file : (compiled?.resolvedFileName ?? ""));
337
+ reading.bound.set(specifier, bound);
338
+ }
339
+ if (answer.kind === "source") {
340
+ reading.entries.push({
341
+ moduleName: specifier,
342
+ containingDirectory,
343
+ resolutionMode: mode,
344
+ result: { resolvedFileName: answer.file },
345
+ });
346
+ added += 1;
347
+ } else if (answer.kind === "unresolved") {
348
+ reading.unresolved.push({
349
+ file: usage.file,
350
+ specifier,
351
+ member: answer.member,
352
+ subpath: answer.subpath,
353
+ reason: answer.reason,
354
+ });
355
+ }
356
+ });
357
+ return added;
358
+ }
359
+
360
+ /** One configuration as one round builds it. */
361
+ type Built =
362
+ | {
363
+ readonly kind: "program";
364
+ readonly params: CreateSnapshotProgramParams;
365
+ readonly resolver: ModuleResolver;
366
+ }
367
+ | { readonly kind: "configuration"; readonly file: string; readonly text: string };
368
+
369
+ /**
370
+ * Answers the member imports of every reading `build` describes, round by round, all
371
+ * the readings still adding answers built in one snapshot per round. A reading `build`
372
+ * gives no description for leaves the rounds.
373
+ */
374
+ function readRounds(
375
+ engine: Engine,
376
+ readings: readonly Reading[],
377
+ resolver: WorkspaceResolver,
378
+ build: (reading: Reading) => Built | undefined,
379
+ ): void {
380
+ let pending = readings;
381
+ while (pending.length > 0) {
382
+ const built = pending.flatMap((reading) => {
383
+ const one = build(reading);
384
+ return one === undefined ? [] : [{ reading, one }];
385
+ });
386
+ const programs = built.flatMap(({ one }) => (one.kind === "program" ? [one.params] : []));
387
+ const written = new Map(
388
+ built.flatMap(({ one }) =>
389
+ one.kind === "configuration" ? [[one.file, one.text] as const] : [],
390
+ ),
391
+ );
392
+ const snapshot = engine.createSnapshot([...written.keys()], programs, written);
393
+ try {
394
+ const created = snapshot.operation.createdPrograms ?? [];
395
+ const opened = new Map<string | undefined, Program>(
396
+ snapshot.getProjects().map((project) => [project.configFileName, project.program]),
397
+ );
398
+ let at = 0;
399
+ pending = built.flatMap(({ reading, one }) => {
400
+ const program = one.kind === "program" ? created[at++] : opened.get(one.file);
401
+ return program !== undefined && scan(engine, program, reading, resolver) > 0
402
+ ? [reading]
403
+ : [];
404
+ });
405
+ } finally {
406
+ snapshot.dispose();
407
+ for (const { one } of built) {
408
+ if (one.kind === "program") {
409
+ one.resolver[Symbol.dispose]();
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Reads every configuration's member imports. Where the snapshots read component files,
418
+ * the rounds go on over each reading's configuration, which reads them, for the imports
419
+ * only they make.
420
+ */
421
+ export function workspacePrograms(
422
+ engine: Engine,
423
+ host: Host,
424
+ configFiles: readonly string[],
425
+ resolver: WorkspaceResolver,
426
+ ): WorkspacePrograms {
427
+ const readings: Reading[] = configFiles.flatMap((configFile) => {
428
+ let parsed: Parsed;
429
+ try {
430
+ parsed = engine.parseConfigFile(configFile);
431
+ } catch {
432
+ // A configuration that does not read is opened as a project, which states why.
433
+ return [];
434
+ }
435
+ return [
436
+ {
437
+ configFile,
438
+ parsed,
439
+ references: carriedReferences(engine, parsed),
440
+ entries: [],
441
+ seen: new Set<string>(),
442
+ bound: new Map<string, Set<string>>(),
443
+ unresolved: [],
444
+ },
445
+ ];
446
+ });
447
+ readRounds(engine, readings, resolver, (reading) => ({
448
+ kind: "program",
449
+ ...programOf(engine, reading),
450
+ }));
451
+ if (engine.readsComponents) {
452
+ readRounds(engine, readings, resolver, (reading) => {
453
+ const one = configurationOf(engine, host, reading);
454
+ return one.kind === "configuration" ? one : undefined;
455
+ });
456
+ }
457
+ const programs = new Map<string, WorkspaceProgram>();
458
+ const unread = new Map<string, string>();
459
+ for (const reading of readings) {
460
+ if (reading.entries.length === 0) {
461
+ continue;
462
+ }
463
+ const one = engine.readsComponents ? configurationOf(engine, host, reading) : undefined;
464
+ if (one !== undefined && one.kind !== "unread") {
465
+ programs.set(reading.configFile, one);
466
+ continue;
467
+ }
468
+ programs.set(reading.configFile, {
469
+ kind: "program",
470
+ params: programOf(engine, reading).params,
471
+ });
472
+ if (one !== undefined) {
473
+ unread.set(reading.configFile, one.reason);
474
+ }
475
+ }
476
+ return {
477
+ programs,
478
+ readings: readings.map((reading) => ({
479
+ configFile: reading.configFile,
480
+ fileNames: reading.parsed.fileNames,
481
+ entries: reading.entries,
482
+ unresolved: reading.unresolved,
483
+ componentsUnread: unread.get(reading.configFile),
484
+ })),
485
+ };
486
+ }