@cplieger/deadset-ts 8.0.0-dev.7 → 8.0.0-dev.9

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 (137) hide show
  1. package/dist/bin/node-host.js +21 -3
  2. package/dist/src/analysis.js +94 -299
  3. package/dist/src/config.js +6 -83
  4. package/dist/src/configuration-files.js +2 -9
  5. package/dist/src/configured-declarations.js +15 -20
  6. package/dist/src/consumers.js +3 -30
  7. package/dist/src/contract/config.generated.js +544 -0
  8. package/dist/src/contract/contract.generated.js +20 -10
  9. package/dist/src/contract/report.generated.js +3 -2
  10. package/dist/src/conventions.js +3 -8
  11. package/dist/src/edges.js +36 -112
  12. package/dist/src/exempt-classes.js +0 -2
  13. package/dist/src/exempt.js +1 -1
  14. package/dist/src/failure.js +36 -0
  15. package/dist/src/findings/completion.js +5 -4
  16. package/dist/src/findings/emitters.js +1 -1
  17. package/dist/src/findings/intra-function.js +1 -1
  18. package/dist/src/findings/non-code-artifacts.js +2 -2
  19. package/dist/src/findings/unused-declarations.js +1 -1
  20. package/dist/src/findings/visibility-narrowing.js +2 -2
  21. package/dist/src/findings-pass.js +1 -1
  22. package/dist/src/framework-lifecycle.js +7 -7
  23. package/dist/src/fs/document.js +52 -0
  24. package/dist/src/fs/manifest.js +7 -0
  25. package/dist/src/injection-container.js +1 -1
  26. package/dist/src/json-document.js +194 -67
  27. package/dist/src/kinds.js +0 -7
  28. package/dist/src/load/classify.js +236 -0
  29. package/dist/src/load/diagnostics.js +79 -0
  30. package/dist/src/load/discover.js +230 -0
  31. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  32. package/dist/src/load/expected-module.js +136 -0
  33. package/dist/src/load/load.js +22 -0
  34. package/dist/src/load/module-lookup.js +37 -0
  35. package/dist/src/load/plan.js +155 -0
  36. package/dist/src/{test-globals.js → load/test-globals.js} +51 -56
  37. package/dist/src/load/test-joins.js +82 -0
  38. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +3 -3
  39. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +37 -40
  40. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +21 -59
  41. package/dist/src/{workspace-run.js → load/workspace-run.js} +4 -3
  42. package/dist/src/{workspace.js → load/workspace.js} +21 -17
  43. package/dist/src/outside-tests.js +1 -1
  44. package/dist/src/print.js +10 -79
  45. package/dist/src/report-schema.js +3 -224
  46. package/dist/src/resolve.js +245 -715
  47. package/dist/src/run.js +59 -72
  48. package/dist/src/schema.js +320 -344
  49. package/dist/src/scope.js +37 -113
  50. package/dist/src/serialization-contract.js +1 -1
  51. package/dist/src/session.js +22 -28
  52. package/dist/src/source-files.js +1 -1
  53. package/dist/src/suppress-file.js +15 -17
  54. package/dist/src/suppress.js +3 -3
  55. package/dist/src/template-field.js +4 -4
  56. package/dist/src/type-errors.js +11 -216
  57. package/dist/src/usage.js +10 -0
  58. package/dist/src/verbs/analyze.js +100 -122
  59. package/dist/src/verbs/describe.js +5 -12
  60. package/dist/src/verbs/explain.js +3 -3
  61. package/dist/src/verbs/print-projects.js +2 -2
  62. package/dist/src/verbs/version.js +12 -2
  63. package/jsr.json +1 -1
  64. package/package.json +1 -1
  65. package/src/analysis.ts +95 -460
  66. package/src/config.ts +31 -298
  67. package/src/configuration-files.ts +8 -10
  68. package/src/configured-declarations.ts +18 -23
  69. package/src/consumers.ts +6 -57
  70. package/src/contract/config.generated.ts +798 -0
  71. package/src/contract/contract.generated.ts +20 -10
  72. package/src/contract/report.generated.ts +3 -2
  73. package/src/conventions.ts +6 -12
  74. package/src/edges.ts +39 -124
  75. package/src/exempt-classes.ts +0 -5
  76. package/src/exempt.ts +4 -4
  77. package/src/failure.ts +63 -0
  78. package/src/findings/completion.ts +5 -6
  79. package/src/findings/emitter.ts +2 -2
  80. package/src/findings/emitters.ts +1 -1
  81. package/src/findings/intra-function.ts +1 -1
  82. package/src/findings/non-code-artifacts.ts +4 -4
  83. package/src/findings/unused-declarations.ts +1 -1
  84. package/src/findings/visibility-narrowing.ts +2 -2
  85. package/src/findings-pass.ts +3 -3
  86. package/src/framework-lifecycle.ts +7 -7
  87. package/src/fs/document.ts +70 -0
  88. package/src/fs/manifest.ts +8 -0
  89. package/src/host.ts +12 -5
  90. package/src/injection-container.ts +1 -1
  91. package/src/json-document.ts +236 -76
  92. package/src/kinds.ts +0 -8
  93. package/src/load/classify.ts +352 -0
  94. package/src/load/diagnostics.ts +141 -0
  95. package/src/load/discover.ts +378 -0
  96. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  97. package/src/load/expected-module.ts +188 -0
  98. package/src/load/load.ts +61 -0
  99. package/src/load/module-lookup.ts +50 -0
  100. package/src/load/plan.ts +258 -0
  101. package/src/{test-globals.ts → load/test-globals.ts} +59 -65
  102. package/src/load/test-joins.ts +98 -0
  103. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +4 -4
  104. package/src/{workspace-programs.ts → load/workspace-programs.ts} +59 -53
  105. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +31 -78
  106. package/src/{workspace-run.ts → load/workspace-run.ts} +14 -11
  107. package/src/{workspace.ts → load/workspace.ts} +28 -21
  108. package/src/outside-tests.ts +1 -1
  109. package/src/partial.ts +1 -1
  110. package/src/print.ts +11 -92
  111. package/src/report-schema.ts +3 -299
  112. package/src/resolve.ts +281 -1105
  113. package/src/run.ts +68 -80
  114. package/src/schema.ts +391 -389
  115. package/src/scope.ts +45 -131
  116. package/src/serialization-contract.ts +1 -1
  117. package/src/session.ts +52 -53
  118. package/src/source-files.ts +1 -1
  119. package/src/suppress-file.ts +15 -20
  120. package/src/suppress.ts +3 -3
  121. package/src/template-field.ts +10 -7
  122. package/src/type-errors.ts +18 -288
  123. package/src/usage.ts +10 -0
  124. package/src/verbs/analyze.ts +139 -157
  125. package/src/verbs/describe.ts +4 -11
  126. package/src/verbs/explain.ts +3 -7
  127. package/src/verbs/print-projects.ts +2 -2
  128. package/src/verbs/verb.ts +2 -2
  129. package/src/verbs/version.ts +13 -2
  130. package/dist/src/base-url.js +0 -18
  131. package/dist/src/discover.js +0 -374
  132. package/dist/src/setup-failure.js +0 -19
  133. package/dist/src/test-joins.js +0 -101
  134. package/src/base-url.ts +0 -24
  135. package/src/discover.ts +0 -548
  136. package/src/setup-failure.ts +0 -37
  137. package/src/test-joins.ts +0 -118
@@ -16,13 +16,14 @@ import type {
16
16
  } from "@typescript/native/sync";
17
17
  import { isStringLiteralLikeNode } from "@typescript/native/ast";
18
18
  import type { StringLiteralLikeNode } from "@typescript/native/ast";
19
- import { baseUrlPaths } from "./base-url.ts";
20
- import type { Host } from "./host.ts";
21
- import { dirnamePath, resolvePath } from "./paths.ts";
22
- import type { Engine } from "./session.ts";
19
+ import type { Host } from "../host.ts";
20
+ import { dirnamePath, resolvePath } from "../paths.ts";
21
+ import type { Engine, ProgramBuild } from "../session.ts";
23
22
  import type { WorkspaceResolver } from "./workspace-resolution.ts";
24
- import { bytewise } from "./text/compare.ts";
25
- import { PRODUCT } from "./product.ts";
23
+ import { bytewise } from "../text/compare.ts";
24
+ import { PRODUCT } from "../product.ts";
25
+ import type { Parsed } from "./discover.ts";
26
+ import type { Build, CompilerOptions } from "./plan.ts";
26
27
 
27
28
  /** One member import no member source answers for. */
28
29
  interface UnresolvedImport {
@@ -45,32 +46,33 @@ export interface MemberReading {
45
46
  readonly componentsUnread: string | undefined;
46
47
  }
47
48
 
48
- /** How a snapshot reads one configuration that imports a member. */
49
- export type WorkspaceProgram =
50
- | { readonly kind: "program"; readonly params: CreateSnapshotProgramParams }
51
- /** A configuration only the snapshot holds, at a path no file has. */
52
- | { readonly kind: "configuration"; readonly file: string; readonly text: string };
49
+ /** One configuration as a written configuration or a described program. */
50
+ type Written = Exclude<Build, { readonly kind: "project" }>;
53
51
 
54
52
  /** The programs one run builds instead of opening projects, with what reading them did. */
55
53
  interface WorkspacePrograms {
56
54
  /** Per configuration that imports a member, how the snapshot reads it. */
57
- readonly programs: ReadonlyMap<string, WorkspaceProgram>;
55
+ readonly programs: ReadonlyMap<string, Written>;
58
56
  readonly readings: readonly MemberReading[];
59
57
  }
60
58
 
61
- type Parsed = ReturnType<Engine["parseConfigFile"]>;
59
+ /** One configuration the rounds read: its parse and the options the plan builds it with. */
60
+ export interface PlannedConfiguration {
61
+ readonly configFile: string;
62
+ readonly parsed: Parsed;
63
+ readonly options: CompilerOptions;
64
+ }
62
65
 
63
66
  /** The most questions one round trip carries. */
64
67
  const BATCH = 2048;
65
68
 
66
69
  /**
67
- * The options a configuration's program is built with: its own, less the ones that
68
- * lay out an emitted program's inputs and outputs, and with `paths` read against the
69
- * effective `baseUrl` as {@link baseUrlPaths} states. 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.
70
+ * The options a configuration's program is built with: the plan's, less the ones that lay
71
+ * out an emitted program's inputs and outputs. The analysis emits nothing, and a member's
72
+ * source is outside the importer's root directory by construction, which the compiler
73
+ * refuses under such a layout.
72
74
  */
73
- function analysisOptions(parsed: Parsed): CreateSnapshotProgramParams["compilerOptions"] {
75
+ function analysisOptions(options: CompilerOptions): CreateSnapshotProgramParams["compilerOptions"] {
74
76
  const {
75
77
  composite,
76
78
  rootDir: _rootDir,
@@ -78,10 +80,8 @@ function analysisOptions(parsed: Parsed): CreateSnapshotProgramParams["compilerO
78
80
  declarationDir: _declarationDir,
79
81
  tsBuildInfoFile: _tsBuildInfoFile,
80
82
  ...kept
81
- } = parsed.options;
82
- const paths = baseUrlPaths(parsed.options);
83
- const options = paths === undefined ? kept : { ...kept, paths };
84
- return composite === true ? { ...options, declaration: true } : options;
83
+ } = options;
84
+ return composite === true ? { ...kept, declaration: true } : kept;
85
85
  }
86
86
 
87
87
  /** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
@@ -103,9 +103,7 @@ function resolutionMode(kind: ModuleKind): ResolutionMode | undefined {
103
103
  }
104
104
 
105
105
  /** One configuration's state across the rounds. */
106
- interface Reading {
107
- readonly configFile: string;
108
- readonly parsed: Parsed;
106
+ interface Reading extends PlannedConfiguration {
109
107
  readonly entries: ModuleResolutionEntry[];
110
108
  readonly seen: Set<string>;
111
109
  /** Per member specifier, every file an import of it is read as. */
@@ -149,16 +147,17 @@ function configurationOf(
149
147
  engine: Engine,
150
148
  host: Host,
151
149
  reading: Reading,
152
- ): WorkspaceProgram | { readonly kind: "unread"; readonly reason: string } {
150
+ ): Written | { readonly kind: "unread"; readonly reason: string } {
153
151
  const reason = unstatable(reading);
154
152
  if (reason !== undefined) {
155
153
  return { kind: "unread", reason };
156
154
  }
157
- const { options, projectReferences: references = [] } = reading.parsed;
155
+ const { options } = reading;
156
+ const references = reading.parsed.projectReferences ?? [];
158
157
  const raw: unknown = (options as Record<string, unknown>)["pathsBasePath"];
159
158
  const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
160
159
  const paths: Record<string, string[]> = {};
161
- for (const [key, targets] of Object.entries(baseUrlPaths(options) ?? options.paths ?? {})) {
160
+ for (const [key, targets] of Object.entries(options.paths ?? {})) {
162
161
  paths[key] = targets.map((target) => resolvePath(base, target));
163
162
  }
164
163
  for (const entry of reading.entries) {
@@ -204,7 +203,7 @@ function configurationOf(
204
203
  };
205
204
  }
206
205
  }
207
- return { kind: "configuration", file, text: `${JSON.stringify(value, null, 2)}\n` };
206
+ return { kind: "configuration", value };
208
207
  }
209
208
 
210
209
  /** The program one configuration is built as, under the answers it holds so far. */
@@ -212,7 +211,7 @@ function programOf(
212
211
  engine: Engine,
213
212
  reading: Reading,
214
213
  ): { readonly params: CreateSnapshotProgramParams; readonly resolver: ModuleResolver } {
215
- const compilerOptions = analysisOptions(reading.parsed);
214
+ const compilerOptions = analysisOptions(reading.options);
216
215
  const references = reading.parsed.projectReferences ?? [];
217
216
  const resolver = engine.createModuleResolver(compilerOptions, reading.entries);
218
217
  return {
@@ -338,6 +337,23 @@ type Built =
338
337
  }
339
338
  | { readonly kind: "configuration"; readonly file: string; readonly text: string };
340
339
 
340
+ function configurationText(value: Readonly<Record<string, unknown>>): string {
341
+ return `${JSON.stringify(value, null, 2)}\n`;
342
+ }
343
+
344
+ /** One configuration value as the snapshot holds it, beside the configuration it stands for. */
345
+ export function writtenConfiguration(
346
+ host: Host,
347
+ configFile: string,
348
+ value: Readonly<Record<string, unknown>>,
349
+ ): Extract<ProgramBuild, { readonly kind: "configuration" }> {
350
+ return {
351
+ kind: "configuration",
352
+ file: writtenPath(host, configFile),
353
+ text: configurationText(value),
354
+ };
355
+ }
356
+
341
357
  /**
342
358
  * Answers the member imports of every reading `build` describes, round by round, all
343
359
  * the readings still adding answers built in one snapshot per round. A reading `build`
@@ -393,28 +409,16 @@ function readRounds(
393
409
  export function workspacePrograms(
394
410
  engine: Engine,
395
411
  host: Host,
396
- configFiles: readonly string[],
412
+ configurations: readonly PlannedConfiguration[],
397
413
  resolver: WorkspaceResolver,
398
414
  ): WorkspacePrograms {
399
- const readings: Reading[] = configFiles.flatMap((configFile) => {
400
- let parsed: Parsed;
401
- try {
402
- parsed = engine.parseConfigFile(configFile);
403
- } catch {
404
- // A configuration that does not read is opened as a project, which states why.
405
- return [];
406
- }
407
- return [
408
- {
409
- configFile,
410
- parsed,
411
- entries: [],
412
- seen: new Set<string>(),
413
- bound: new Map<string, Set<string>>(),
414
- unresolved: [],
415
- },
416
- ];
417
- });
415
+ const readings: Reading[] = configurations.map((configuration) => ({
416
+ ...configuration,
417
+ entries: [],
418
+ seen: new Set<string>(),
419
+ bound: new Map<string, Set<string>>(),
420
+ unresolved: [],
421
+ }));
418
422
  readRounds(engine, readings, resolver, (reading) => ({
419
423
  kind: "program",
420
424
  ...programOf(engine, reading),
@@ -422,10 +426,12 @@ export function workspacePrograms(
422
426
  if (engine.readsComponents) {
423
427
  readRounds(engine, readings, resolver, (reading) => {
424
428
  const one = configurationOf(engine, host, reading);
425
- return one.kind === "configuration" ? one : undefined;
429
+ return one.kind === "configuration"
430
+ ? writtenConfiguration(host, reading.configFile, one.value)
431
+ : undefined;
426
432
  });
427
433
  }
428
- const programs = new Map<string, WorkspaceProgram>();
434
+ const programs = new Map<string, Written>();
429
435
  const unread = new Map<string, string>();
430
436
  for (const reading of readings) {
431
437
  if (reading.entries.length === 0) {
@@ -7,20 +7,20 @@
7
7
  */
8
8
 
9
9
  import { emittedFrom } from "./emit-map.ts";
10
- import { gitIgnored } from "./git-ignore.ts";
11
- import type { Host } from "./host.ts";
12
- import {
13
- ancestors,
14
- dirnamePath,
15
- isAbsolutePath,
16
- joinPath,
17
- relativePath,
18
- resolvePath,
19
- } from "./paths.ts";
20
- import { satisfies } from "./version-range.ts";
10
+ import { packageOfSpecifier } from "../dependencies.ts";
11
+ import { gitIgnored } from "../git-ignore.ts";
12
+ import type { Host } from "../host.ts";
13
+ import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
14
+ import { satisfies } from "../version-range.ts";
21
15
  import type { Workspace, WorkspaceMember } from "./workspace.ts";
22
- import { isRecord } from "./json-document.ts";
23
- import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.ts";
16
+ import { isRecord } from "../json-document.ts";
17
+ import { MANIFEST } from "../fs/manifest.ts";
18
+ import {
19
+ dependencySpecifiers,
20
+ installedPackage,
21
+ manifestsAbove,
22
+ type ManifestAt,
23
+ } from "./module-lookup.ts";
24
24
 
25
25
  /** What the compiler's own resolution answered for one import. */
26
26
  export interface DefaultResolution {
@@ -45,35 +45,9 @@ export type MemberResolution =
45
45
  /** The manifest members that name an entry point for the subpath `.` without `exports`. */
46
46
  const LEGACY_MEMBERS = ["types", "typings", "module", "main"] as const;
47
47
 
48
- /** The dependency members a manifest declares other packages under. */
49
- const DEPENDENCY_MEMBERS = [
50
- "dependencies",
51
- "devDependencies",
52
- "peerDependencies",
53
- "optionalDependencies",
54
- ] as const;
55
-
56
48
  const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
57
49
  const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
58
50
 
59
- /** The package name a bare specifier names, or `undefined` for any other specifier. */
60
- export function packageNameOf(specifier: string): string | undefined {
61
- if (
62
- specifier === "" ||
63
- specifier.startsWith(".") ||
64
- specifier.startsWith("#") ||
65
- isAbsolutePath(specifier) ||
66
- specifier.includes(":")
67
- ) {
68
- return undefined;
69
- }
70
- const [first, second] = specifier.split("/");
71
- if (specifier.startsWith("@")) {
72
- return second === undefined || second === "" ? undefined : `${first ?? ""}/${second}`;
73
- }
74
- return first;
75
- }
76
-
77
51
  function inside(dir: string, path: string): boolean {
78
52
  return relativePath(dir, path) !== undefined;
79
53
  }
@@ -215,51 +189,32 @@ type Linked =
215
189
  /** The member imports of one workspace, read from the members' sources. */
216
190
  export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceResolver {
217
191
  const links = new Map<string, string | undefined>();
218
- /** The directory `node_modules/<name>` names from one directory, links followed. */
192
+ /** The directory the package `name` installed for one directory is, links followed. */
219
193
  const linkedFrom = (name: string, dir: string): string | undefined => {
220
194
  const key = `${name}\u0000${dir}`;
221
- if (links.has(key)) {
222
- return links.get(key);
223
- }
224
- const candidate = joinPath(dir, INSTALLED_PACKAGES, name);
225
- let found: string | undefined;
226
- if (host.kindOf(candidate) === "absent") {
227
- const up = dirnamePath(dir);
228
- found = up === dir ? undefined : linkedFrom(name, up);
229
- } else {
195
+ if (!links.has(key)) {
196
+ const installed = installedPackage(host, dir, name);
197
+ let found: string | undefined;
230
198
  try {
231
- found = host.realPath(candidate);
199
+ found = installed === undefined ? undefined : host.realPath(installed);
232
200
  } catch {
233
201
  found = undefined;
234
202
  }
203
+ links.set(key, found);
235
204
  }
236
- links.set(key, found);
237
- return found;
205
+ return links.get(key);
238
206
  };
239
207
 
240
208
  const root = workspace.members[0]?.dir;
241
209
  const ignored = root === undefined ? () => false : gitIgnored(host, root);
242
- const manifests = new Map<
243
- string,
244
- { dir: string; manifest: Record<string, unknown> } | undefined
245
- >();
210
+ const manifests = new Map<string, ManifestAt | undefined>();
246
211
  /** The nearest manifest at or above one directory, up to the workspace's root. */
247
- const nearestManifest = (
248
- dir: string,
249
- ): { dir: string; manifest: Record<string, unknown> } | undefined => {
250
- if (manifests.has(dir)) {
251
- return manifests.get(dir);
212
+ const nearestManifest = (dir: string): ManifestAt | undefined => {
213
+ if (!manifests.has(dir)) {
214
+ const [nearest] = manifestsAbove(host, dir, root ?? dir);
215
+ manifests.set(dir, nearest);
252
216
  }
253
- const manifest = manifestAt(host, dir);
254
- const up = dirnamePath(dir);
255
- const found =
256
- manifest !== undefined
257
- ? { dir, manifest }
258
- : up === dir || root === undefined || !inside(root, up)
259
- ? undefined
260
- : nearestManifest(up);
261
- manifests.set(dir, found);
262
- return found;
217
+ return manifests.get(dir);
263
218
  };
264
219
 
265
220
  const linkOf = (candidates: readonly WorkspaceMember[], fromDir: string): Linked => {
@@ -276,11 +231,9 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
276
231
  // A package below the root that no workspace lists depends on a member as a member does.
277
232
  const owner = scope ?? nearestManifest(fromDir);
278
233
  const manifest = owner?.manifest;
279
- const declared = DEPENDENCY_MEMBERS.flatMap((field) => {
280
- const held = manifest?.[field];
281
- const specifier = isRecord(held) ? held[name] : undefined;
282
- return typeof specifier === "string" ? [specifier] : [];
283
- });
234
+ const declared = (manifest === undefined ? [] : dependencySpecifiers(manifest, name)).filter(
235
+ (specifier) => typeof specifier === "string",
236
+ );
284
237
  const matched = candidates.filter((member) =>
285
238
  declared.some((specifier) => dependsOn(specifier, member, owner?.dir ?? fromDir)),
286
239
  );
@@ -383,11 +336,11 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
383
336
  workspace,
384
337
  sourceOf,
385
338
  names: (specifier) => {
386
- const name = packageNameOf(specifier);
339
+ const name = packageOfSpecifier(specifier);
387
340
  return name !== undefined && workspace.named.has(name);
388
341
  },
389
342
  resolve(specifier, fromDir, conditions, answered) {
390
- const name = packageNameOf(specifier);
343
+ const name = packageOfSpecifier(specifier);
391
344
  const candidates = name === undefined ? undefined : workspace.named.get(name);
392
345
  if (name === undefined || candidates === undefined) {
393
346
  return { kind: "default" };
@@ -5,24 +5,26 @@
5
5
  * the members' manifests name. A target in no workspace reads none of it.
6
6
  */
7
7
 
8
- import type { LocatedDiagnostic } from "./discover.ts";
9
- import type { Host } from "./host.ts";
10
- import type { Manifest, ManifestEntry } from "./manifest.ts";
11
- import { relativePath } from "./paths.ts";
12
- import type { Engine } from "./session.ts";
13
- import { findWorkspace, referenceOnlyIn, type ParsedLayout } from "./workspace.ts";
8
+ import type { Host } from "../host.ts";
9
+ import type { Manifest, ManifestEntry } from "../manifest.ts";
10
+ import { relativePath } from "../paths.ts";
11
+ import type { Engine } from "../session.ts";
12
+ import type { LocatedDiagnostic } from "./diagnostics.ts";
13
+ import type { Build } from "./plan.ts";
14
+ import { findWorkspace, referenceOnlyIn, type ParsedLayout, type Workspace } from "./workspace.ts";
14
15
  import { memberEntries, targetManifestIn } from "./workspace-manifests.ts";
15
16
  import {
16
17
  workspacePrograms,
17
18
  type MemberReading,
18
- type WorkspaceProgram,
19
+ type PlannedConfiguration,
19
20
  } from "./workspace-programs.ts";
20
21
  import { workspaceResolver } from "./workspace-resolution.ts";
21
22
 
22
23
  /** One run's workspace. */
23
- interface WorkspaceRun {
24
+ export interface WorkspaceRun {
25
+ readonly workspace: Workspace;
24
26
  /** Per configuration that imports a member, how the snapshot reads it. */
25
- readonly programs: ReadonlyMap<string, WorkspaceProgram>;
27
+ readonly programs: ReadonlyMap<string, Exclude<Build, { readonly kind: "project" }>>;
26
28
  /**
27
29
  * Per configuration whose component files the run cannot read, the reason, in the
28
30
  * order the configurations are named.
@@ -96,7 +98,7 @@ export function workspaceRun(
96
98
  engine: Engine,
97
99
  host: Host,
98
100
  targetRoot: string,
99
- configFiles: readonly string[],
101
+ configurations: readonly PlannedConfiguration[],
100
102
  manifest: Manifest,
101
103
  ): WorkspaceRun | undefined {
102
104
  const workspace = findWorkspace(host, targetRoot, (configFile) => layoutOf(engine, configFile));
@@ -104,7 +106,7 @@ export function workspaceRun(
104
106
  return undefined;
105
107
  }
106
108
  const resolver = workspaceResolver(host, workspace);
107
- const read = workspacePrograms(engine, host, configFiles, resolver);
109
+ const read = workspacePrograms(engine, host, configurations, resolver);
108
110
  const readings = new Map(read.readings.map((reading) => [reading.configFile, reading]));
109
111
  const referenceOnly = (
110
112
  configFile: string,
@@ -115,6 +117,7 @@ export function workspaceRun(
115
117
  new Set([...(readings.get(configFile)?.fileNames ?? []), ...compiled]),
116
118
  );
117
119
  return {
120
+ workspace,
118
121
  programs: read.programs,
119
122
  componentsUnread: read.readings.flatMap(({ configFile, componentsUnread }) =>
120
123
  componentsUnread === undefined ? [] : [{ configFile, reason: componentsUnread }],
@@ -1,29 +1,28 @@
1
1
  /**
2
2
  * The workspace a target sits in: the packages `pnpm-workspace.yaml` or a manifest's
3
- * `workspaces` declares in the nearest directory at or above the target root, up to
4
- * its repository root, where its patterns name the package the target is in, each
5
- * with the compiler configurations in its directory. A
6
- * member is the workspace root or a directory the patterns name that holds a named
7
- * manifest, never one below `node_modules`.
3
+ * `workspaces` declares in the nearest directory at or above the target root, up to its
4
+ * repository root, where its patterns name the package the target is in, each with the
5
+ * compiler configurations in its directory.
8
6
  */
9
7
 
10
- import { DiscoveryError, isConfigFile } from "./discover.ts";
8
+ import { isConfigFile } from "./discover.ts";
9
+ import { failureExit } from "../failure.ts";
11
10
  import type { EmitLayout } from "./emit-map.ts";
12
11
  import { commonDirectory } from "./emit-map.ts";
13
- import type { Host } from "./host.ts";
14
- import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.ts";
15
- import { bytewise } from "./text/compare.ts";
16
- import { isRecord } from "./json-document.ts";
17
- import { directoriesIn, filesIn, repositoryOf } from "./fs/tree.ts";
12
+ import type { Host } from "../host.ts";
13
+ import { ancestors, dirnamePath, joinPath, relativePath } from "../paths.ts";
14
+ import { bytewise } from "../text/compare.ts";
15
+ import { isRecord } from "../json-document.ts";
16
+ import { directoriesIn, filesIn, repositoryOf } from "../fs/tree.ts";
18
17
  import {
19
18
  holdsManifest,
20
19
  inInstalledPackages,
21
20
  INSTALLED_PACKAGES,
22
21
  MANIFEST,
23
22
  manifestAt,
24
- } from "./fs/manifest.ts";
25
- import { braceExpanded, pathMatches, segmentMatches } from "./text/glob.ts";
26
- import { yamlKey } from "./text/yaml.ts";
23
+ } from "../fs/manifest.ts";
24
+ import { braceExpanded, pathMatches, segmentMatches } from "../text/glob.ts";
25
+ import { yamlKey } from "../text/yaml.ts";
27
26
 
28
27
  /** What one compiler configuration of a member says about where it reads and writes. */
29
28
  export interface ParsedLayout {
@@ -56,6 +55,8 @@ export interface WorkspaceMember {
56
55
 
57
56
  /** The workspace one target sits in. */
58
57
  export interface Workspace {
58
+ /** The directory that declares the workspace, absolute. */
59
+ readonly root: string;
59
60
  /** Every member, the root first, then by directory. */
60
61
  readonly members: readonly WorkspaceMember[];
61
62
  /** The members one package name names; two members may share a name. */
@@ -84,9 +85,9 @@ function declarationAt(
84
85
  if (!Array.isArray(listed) || !listed.every((entry) => typeof entry === "string")) {
85
86
  const reason =
86
87
  read.kind === "refused" ? read.reason : "packages is not a sequence of strings";
87
- throw new DiscoveryError(
88
- `${pnpm}: ${reason}; the workspace's packages are read from a sequence of strings`,
89
- [],
88
+ throw failureExit(
89
+ "no-program",
90
+ `${pnpm}: ${reason}; the workspace's packages are read from a sequence of strings: correct the declaration`,
90
91
  );
91
92
  }
92
93
  return { file: pnpm, patterns: listed };
@@ -98,9 +99,9 @@ function declarationAt(
98
99
  const declared = manifest["workspaces"];
99
100
  const list = isRecord(declared) ? declared["packages"] : declared;
100
101
  if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) {
101
- throw new DiscoveryError(
102
- `${joinPath(dir, MANIFEST)}: workspaces is neither an array of strings nor an object whose packages is one`,
103
- [],
102
+ throw failureExit(
103
+ "no-program",
104
+ `${joinPath(dir, MANIFEST)}: workspaces is neither an array of strings nor an object whose packages is one: correct the declaration`,
104
105
  );
105
106
  }
106
107
  return { file: joinPath(dir, MANIFEST), patterns: list };
@@ -291,12 +292,14 @@ function nearestDeclaration(
291
292
  * from the target root up to its repository root declares one with a member beside
292
293
  * its root, or the nearest one does not name the highest package below it, at or
293
294
  * above the target root, among its members. `parse` reads one compiler configuration,
294
- * or answers `undefined` where it does not read.
295
+ * or answers `undefined` where it does not read. `known`, a workspace already read, is the
296
+ * answer where the nearest declaration is its own, so its members are not listed again.
295
297
  */
296
298
  export function findWorkspace(
297
299
  host: Host,
298
300
  targetRoot: string,
299
301
  parse: (configFile: string) => ParsedLayout | undefined,
302
+ known?: Workspace,
300
303
  ): Workspace | undefined {
301
304
  const declared = nearestDeclaration(host, targetRoot);
302
305
  if (declared === undefined) {
@@ -308,6 +311,9 @@ export function findWorkspace(
308
311
  ) {
309
312
  return undefined;
310
313
  }
314
+ if (known?.root === declared.root) {
315
+ return known;
316
+ }
311
317
  const members: WorkspaceMember[] = [];
312
318
  for (const dir of memberDirectories(host, declared.root, declared.patterns)) {
313
319
  const manifest = manifestAt(host, dir);
@@ -347,6 +353,7 @@ export function findWorkspace(
347
353
  return found;
348
354
  };
349
355
  return {
356
+ root: declared.root,
350
357
  members,
351
358
  named,
352
359
  memberOf: (path) => {
@@ -20,7 +20,7 @@ import {
20
20
  type SourceFile,
21
21
  } from "@typescript/native/ast";
22
22
  import { RESOLVED_EXTENSIONS, type ParseFile } from "./configuration-files.ts";
23
- import { emittedFrom } from "./emit-map.ts";
23
+ import { emittedFrom } from "./load/emit-map.ts";
24
24
  import type { Host } from "./host.ts";
25
25
  import type { Inventory } from "./inventory.ts";
26
26
  import { namesImported } from "./module-declarations.ts";
package/src/partial.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { NotBuilt } from "./discover.ts";
1
+ import type { NotBuilt } from "./load/discover.ts";
2
2
  import { relativePath } from "./paths.ts";
3
3
  import type { UnansweredQuestion } from "./query.ts";
4
4
  import { bytewise } from "./text/compare.ts";
package/src/print.ts CHANGED
@@ -1,97 +1,16 @@
1
- import {
2
- renderOrigin,
3
- type Config,
4
- type DeclarationEntry,
5
- type Provenance,
6
- type Provider,
7
- } from "./config.ts";
1
+ import { renderOrigin, type Provenance, type ResolvedConfig } from "./config.ts";
8
2
  import { bytewise } from "./text/compare.ts";
9
3
 
10
- /** One declaration entry as a document writes it: the members of its shape alone. */
11
- function declarationOf(entry: DeclarationEntry): Record<string, string> {
12
- switch (entry.shape) {
13
- case "symbol":
14
- return { symbol: entry.symbol };
15
- case "module":
16
- return { module: entry.module, name: entry.name };
17
- case "global":
18
- return { global: entry.global };
19
- }
20
- }
21
-
22
- /** One provider entry as a document writes it: the members of its shape alone. */
23
- function providerOf(entry: Provider): Record<string, unknown> {
24
- const installed = { name: entry.name, languages: entry.languages, command: entry.command };
25
- if (entry.shape === "installed") {
26
- return installed;
27
- }
28
- return { ...installed, source: entry.source, version: entry.version, digest: entry.digest };
29
- }
30
-
31
4
  /**
32
- * The resolved configuration and its provenance as one JSON object, indented,
33
- * ending in a newline.
34
- *
35
- * The output is an instance of the closed key list, so reading it back as a
36
- * repository configuration resolves to the same configuration: the provenance
37
- * object is a declared key resolution ignores. Keys are written in the schema's
38
- * order and the severity and provenance objects in ascending key order, so one
39
- * configuration renders to one string whatever order a source named its settings
40
- * in.
5
+ * The resolved configuration and its provenance as one JSON object, indented, ending
6
+ * in a newline: the configuration in the order resolution gives it, then the provenance
7
+ * object in ascending key order. Read back as a repository configuration it resolves to
8
+ * the same configuration, because resolution checks the provenance object and then
9
+ * ignores it.
41
10
  */
42
- export function printConfig(config: Config, provenance: Provenance): string {
43
- const document = {
44
- contract_version: config.contractVersion,
45
- target: { kind: config.targetKind },
46
- analysis: {
47
- languages: config.analysis.languages,
48
- min_confidence: config.analysis.minConfidence,
49
- generated_files: config.analysis.generatedFiles,
50
- consumer_tests: config.analysis.consumerTests,
51
- configurations: config.analysis.configurations.map((entry) =>
52
- entry.shape === "project"
53
- ? { id: entry.id, project: entry.project }
54
- : { id: entry.id, os: entry.os, arch: entry.arch, tags: entry.tags },
55
- ),
56
- matrix: { complete: config.analysis.matrixComplete },
57
- template_dirs: config.analysis.templateDirs,
58
- template_delimiters: {
59
- left: config.analysis.templateDelimiters.left,
60
- right: config.analysis.templateDelimiters.right,
61
- },
62
- },
63
- consumers: { complete: config.consumersComplete },
64
- roots: { patterns: config.rootPatterns },
65
- severity: Object.fromEntries([...config.severity].sort(([a], [b]) => bytewise(a, b))),
66
- exemptions: { disabled: config.exemptionsDisabled },
67
- reporters: {
68
- formats: config.reporters.formats,
69
- sort: config.reporters.sort,
70
- cascade: config.reporters.cascade,
71
- max_findings: config.reporters.maxFindings,
72
- fail_on: config.reporters.failOn,
73
- },
74
- providers: { analyzers: config.providers.analyzers.map(providerOf) },
75
- go: {},
76
- ts: {
77
- test_files: config.ts.testFiles,
78
- entry_files: config.ts.entryFiles,
79
- component_extensions: config.ts.componentExtensions,
80
- disabled_conventions: config.ts.disabledConventions,
81
- injection_registrations: config.ts.injectionRegistrations.map(declarationOf),
82
- lifecycle_contracts: config.ts.lifecycleContracts.map((entry) => ({
83
- components: entry.components.map(declarationOf),
84
- bases: entry.bases.map(declarationOf),
85
- members: entry.members,
86
- })),
87
- serializers: config.ts.serializers.map(declarationOf),
88
- roots_complete: config.ts.rootsComplete,
89
- },
90
- provenance: Object.fromEntries(
91
- [...provenance]
92
- .sort(([a], [b]) => bytewise(a, b))
93
- .map(([path, origin]) => [path, renderOrigin(origin)]),
94
- ),
95
- };
96
- return `${JSON.stringify(document, undefined, 2)}\n`;
11
+ export function printConfig(config: ResolvedConfig, provenance: Provenance): string {
12
+ const origins = [...provenance]
13
+ .sort(([a], [b]) => bytewise(a, b))
14
+ .map(([path, origin]): [string, string] => [path, renderOrigin(origin)]);
15
+ return `${JSON.stringify({ ...config, provenance: Object.fromEntries(origins) }, undefined, 2)}\n`;
97
16
  }