@cplieger/deadset-ts 8.0.0-dev.8 → 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 (75) hide show
  1. package/dist/src/analysis.js +84 -288
  2. package/dist/src/configuration-files.js +2 -9
  3. package/dist/src/consumers.js +1 -28
  4. package/dist/src/contract/contract.generated.js +20 -10
  5. package/dist/src/conventions.js +3 -8
  6. package/dist/src/failure.js +36 -0
  7. package/dist/src/fs/document.js +21 -28
  8. package/dist/src/fs/manifest.js +7 -0
  9. package/dist/src/load/classify.js +236 -0
  10. package/dist/src/load/diagnostics.js +79 -0
  11. package/dist/src/load/discover.js +230 -0
  12. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  13. package/dist/src/load/expected-module.js +136 -0
  14. package/dist/src/load/load.js +22 -0
  15. package/dist/src/load/module-lookup.js +37 -0
  16. package/dist/src/load/plan.js +155 -0
  17. package/dist/src/{test-globals.js → load/test-globals.js} +51 -56
  18. package/dist/src/load/test-joins.js +82 -0
  19. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +3 -3
  20. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +37 -40
  21. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +21 -59
  22. package/dist/src/{workspace-run.js → load/workspace-run.js} +4 -3
  23. package/dist/src/{workspace.js → load/workspace.js} +21 -17
  24. package/dist/src/outside-tests.js +1 -1
  25. package/dist/src/run.js +20 -26
  26. package/dist/src/scope.js +11 -19
  27. package/dist/src/session.js +22 -28
  28. package/dist/src/source-files.js +1 -1
  29. package/dist/src/type-errors.js +11 -216
  30. package/dist/src/verbs/analyze.js +90 -109
  31. package/dist/src/verbs/describe.js +5 -12
  32. package/dist/src/verbs/version.js +12 -2
  33. package/jsr.json +1 -1
  34. package/package.json +1 -1
  35. package/src/analysis.ts +68 -437
  36. package/src/configuration-files.ts +8 -10
  37. package/src/consumers.ts +2 -53
  38. package/src/contract/contract.generated.ts +20 -10
  39. package/src/conventions.ts +6 -12
  40. package/src/failure.ts +63 -0
  41. package/src/fs/document.ts +23 -27
  42. package/src/fs/manifest.ts +8 -0
  43. package/src/load/classify.ts +352 -0
  44. package/src/load/diagnostics.ts +141 -0
  45. package/src/load/discover.ts +378 -0
  46. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  47. package/src/load/expected-module.ts +188 -0
  48. package/src/load/load.ts +61 -0
  49. package/src/load/module-lookup.ts +50 -0
  50. package/src/load/plan.ts +258 -0
  51. package/src/{test-globals.ts → load/test-globals.ts} +59 -65
  52. package/src/load/test-joins.ts +98 -0
  53. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +4 -4
  54. package/src/{workspace-programs.ts → load/workspace-programs.ts} +59 -53
  55. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +31 -78
  56. package/src/{workspace-run.ts → load/workspace-run.ts} +14 -11
  57. package/src/{workspace.ts → load/workspace.ts} +28 -21
  58. package/src/outside-tests.ts +1 -1
  59. package/src/partial.ts +1 -1
  60. package/src/run.ts +26 -25
  61. package/src/scope.ts +15 -21
  62. package/src/session.ts +52 -53
  63. package/src/source-files.ts +1 -1
  64. package/src/type-errors.ts +18 -288
  65. package/src/verbs/analyze.ts +129 -136
  66. package/src/verbs/describe.ts +4 -11
  67. package/src/verbs/version.ts +13 -2
  68. package/dist/src/base-url.js +0 -18
  69. package/dist/src/discover.js +0 -374
  70. package/dist/src/setup-failure.js +0 -19
  71. package/dist/src/test-joins.js +0 -101
  72. package/src/base-url.ts +0 -24
  73. package/src/discover.ts +0 -548
  74. package/src/setup-failure.ts +0 -37
  75. package/src/test-joins.ts +0 -118
@@ -7,24 +7,20 @@
7
7
  */
8
8
  import { ModuleKind, ModuleResolutionKind } from "@typescript/native/sync";
9
9
  import { isStringLiteralLikeNode } from "@typescript/native/ast";
10
- import { baseUrlPaths } from "./base-url.js";
11
- import { dirnamePath, resolvePath } from "./paths.js";
12
- import { bytewise } from "./text/compare.js";
13
- import { PRODUCT } from "./product.js";
10
+ import { dirnamePath, resolvePath } from "../paths.js";
11
+ import { bytewise } from "../text/compare.js";
12
+ import { PRODUCT } from "../product.js";
14
13
  /** The most questions one round trip carries. */
15
14
  const BATCH = 2048;
16
15
  /**
17
- * The options a configuration's program is built with: its own, less the ones that
18
- * lay out an emitted program's inputs and outputs, and with `paths` read against the
19
- * effective `baseUrl` as {@link baseUrlPaths} states. The analysis emits nothing, and a
20
- * member's source is outside the importer's root directory by construction, which
21
- * the compiler refuses under such a layout.
16
+ * The options a configuration's program is built with: the plan's, less the ones that lay
17
+ * out an emitted program's inputs and outputs. The analysis emits nothing, and a member's
18
+ * source is outside the importer's root directory by construction, which the compiler
19
+ * refuses under such a layout.
22
20
  */
23
- function analysisOptions(parsed) {
24
- const { composite, rootDir: _rootDir, outDir: _outDir, declarationDir: _declarationDir, tsBuildInfoFile: _tsBuildInfoFile, ...kept } = parsed.options;
25
- const paths = baseUrlPaths(parsed.options);
26
- const options = paths === undefined ? kept : { ...kept, paths };
27
- return composite === true ? { ...options, declaration: true } : options;
21
+ function analysisOptions(options) {
22
+ const { composite, rootDir: _rootDir, outDir: _outDir, declarationDir: _declarationDir, tsBuildInfoFile: _tsBuildInfoFile, ...kept } = options;
23
+ return composite === true ? { ...kept, declaration: true } : kept;
28
24
  }
29
25
  /** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
30
26
  function conditionsOf(parsed, mode) {
@@ -75,11 +71,12 @@ function configurationOf(engine, host, reading) {
75
71
  if (reason !== undefined) {
76
72
  return { kind: "unread", reason };
77
73
  }
78
- const { options, projectReferences: references = [] } = reading.parsed;
74
+ const { options } = reading;
75
+ const references = reading.parsed.projectReferences ?? [];
79
76
  const raw = options["pathsBasePath"];
80
77
  const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
81
78
  const paths = {};
82
- for (const [key, targets] of Object.entries(baseUrlPaths(options) ?? options.paths ?? {})) {
79
+ for (const [key, targets] of Object.entries(options.paths ?? {})) {
83
80
  paths[key] = targets.map((target) => resolvePath(base, target));
84
81
  }
85
82
  for (const entry of reading.entries) {
@@ -126,11 +123,11 @@ function configurationOf(engine, host, reading) {
126
123
  };
127
124
  }
128
125
  }
129
- return { kind: "configuration", file, text: `${JSON.stringify(value, null, 2)}\n` };
126
+ return { kind: "configuration", value };
130
127
  }
131
128
  /** The program one configuration is built as, under the answers it holds so far. */
132
129
  function programOf(engine, reading) {
133
- const compilerOptions = analysisOptions(reading.parsed);
130
+ const compilerOptions = analysisOptions(reading.options);
134
131
  const references = reading.parsed.projectReferences ?? [];
135
132
  const resolver = engine.createModuleResolver(compilerOptions, reading.entries);
136
133
  return {
@@ -217,6 +214,17 @@ function scan(engine, program, reading, resolver) {
217
214
  });
218
215
  return added;
219
216
  }
217
+ function configurationText(value) {
218
+ return `${JSON.stringify(value, null, 2)}\n`;
219
+ }
220
+ /** One configuration value as the snapshot holds it, beside the configuration it stands for. */
221
+ export function writtenConfiguration(host, configFile, value) {
222
+ return {
223
+ kind: "configuration",
224
+ file: writtenPath(host, configFile),
225
+ text: configurationText(value),
226
+ };
227
+ }
220
228
  /**
221
229
  * Answers the member imports of every reading `build` describes, round by round, all
222
230
  * the readings still adding answers built in one snapshot per round. A reading `build`
@@ -258,27 +266,14 @@ function readRounds(engine, readings, resolver, build) {
258
266
  * the rounds go on over each reading's configuration, which reads them, for the imports
259
267
  * only they make.
260
268
  */
261
- export function workspacePrograms(engine, host, configFiles, resolver) {
262
- const readings = configFiles.flatMap((configFile) => {
263
- let parsed;
264
- try {
265
- parsed = engine.parseConfigFile(configFile);
266
- }
267
- catch {
268
- // A configuration that does not read is opened as a project, which states why.
269
- return [];
270
- }
271
- return [
272
- {
273
- configFile,
274
- parsed,
275
- entries: [],
276
- seen: new Set(),
277
- bound: new Map(),
278
- unresolved: [],
279
- },
280
- ];
281
- });
269
+ export function workspacePrograms(engine, host, configurations, resolver) {
270
+ const readings = configurations.map((configuration) => ({
271
+ ...configuration,
272
+ entries: [],
273
+ seen: new Set(),
274
+ bound: new Map(),
275
+ unresolved: [],
276
+ }));
282
277
  readRounds(engine, readings, resolver, (reading) => ({
283
278
  kind: "program",
284
279
  ...programOf(engine, reading),
@@ -286,7 +281,9 @@ export function workspacePrograms(engine, host, configFiles, resolver) {
286
281
  if (engine.readsComponents) {
287
282
  readRounds(engine, readings, resolver, (reading) => {
288
283
  const one = configurationOf(engine, host, reading);
289
- return one.kind === "configuration" ? one : undefined;
284
+ return one.kind === "configuration"
285
+ ? writtenConfiguration(host, reading.configFile, one.value)
286
+ : undefined;
290
287
  });
291
288
  }
292
289
  const programs = new Map();
@@ -6,37 +6,17 @@
6
6
  * the package neither compiles nor writes is read as installed, and the rest is unresolved.
7
7
  */
8
8
  import { emittedFrom } from "./emit-map.js";
9
- import { gitIgnored } from "./git-ignore.js";
10
- import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
11
- import { satisfies } from "./version-range.js";
12
- import { isRecord } from "./json-document.js";
13
- import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.js";
9
+ import { packageOfSpecifier } from "../dependencies.js";
10
+ import { gitIgnored } from "../git-ignore.js";
11
+ import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.js";
12
+ import { satisfies } from "../version-range.js";
13
+ import { isRecord } from "../json-document.js";
14
+ import { MANIFEST } from "../fs/manifest.js";
15
+ import { dependencySpecifiers, installedPackage, manifestsAbove, } from "./module-lookup.js";
14
16
  /** The manifest members that name an entry point for the subpath `.` without `exports`. */
15
17
  const LEGACY_MEMBERS = ["types", "typings", "module", "main"];
16
- /** The dependency members a manifest declares other packages under. */
17
- const DEPENDENCY_MEMBERS = [
18
- "dependencies",
19
- "devDependencies",
20
- "peerDependencies",
21
- "optionalDependencies",
22
- ];
23
18
  const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
24
19
  const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
25
- /** The package name a bare specifier names, or `undefined` for any other specifier. */
26
- export function packageNameOf(specifier) {
27
- if (specifier === "" ||
28
- specifier.startsWith(".") ||
29
- specifier.startsWith("#") ||
30
- isAbsolutePath(specifier) ||
31
- specifier.includes(":")) {
32
- return undefined;
33
- }
34
- const [first, second] = specifier.split("/");
35
- if (specifier.startsWith("@")) {
36
- return second === undefined || second === "" ? undefined : `${first ?? ""}/${second}`;
37
- }
38
- return first;
39
- }
40
20
  function inside(dir, path) {
41
21
  return relativePath(dir, path) !== undefined;
42
22
  }
@@ -139,46 +119,32 @@ function dependsOn(specifier, member, fromDir) {
139
119
  /** The member imports of one workspace, read from the members' sources. */
140
120
  export function workspaceResolver(host, workspace) {
141
121
  const links = new Map();
142
- /** The directory `node_modules/<name>` names from one directory, links followed. */
122
+ /** The directory the package `name` installed for one directory is, links followed. */
143
123
  const linkedFrom = (name, dir) => {
144
124
  const key = `${name}\u0000${dir}`;
145
- if (links.has(key)) {
146
- return links.get(key);
147
- }
148
- const candidate = joinPath(dir, INSTALLED_PACKAGES, name);
149
- let found;
150
- if (host.kindOf(candidate) === "absent") {
151
- const up = dirnamePath(dir);
152
- found = up === dir ? undefined : linkedFrom(name, up);
153
- }
154
- else {
125
+ if (!links.has(key)) {
126
+ const installed = installedPackage(host, dir, name);
127
+ let found;
155
128
  try {
156
- found = host.realPath(candidate);
129
+ found = installed === undefined ? undefined : host.realPath(installed);
157
130
  }
158
131
  catch {
159
132
  found = undefined;
160
133
  }
134
+ links.set(key, found);
161
135
  }
162
- links.set(key, found);
163
- return found;
136
+ return links.get(key);
164
137
  };
165
138
  const root = workspace.members[0]?.dir;
166
139
  const ignored = root === undefined ? () => false : gitIgnored(host, root);
167
140
  const manifests = new Map();
168
141
  /** The nearest manifest at or above one directory, up to the workspace's root. */
169
142
  const nearestManifest = (dir) => {
170
- if (manifests.has(dir)) {
171
- return manifests.get(dir);
143
+ if (!manifests.has(dir)) {
144
+ const [nearest] = manifestsAbove(host, dir, root ?? dir);
145
+ manifests.set(dir, nearest);
172
146
  }
173
- const manifest = manifestAt(host, dir);
174
- const up = dirnamePath(dir);
175
- const found = manifest !== undefined
176
- ? { dir, manifest }
177
- : up === dir || root === undefined || !inside(root, up)
178
- ? undefined
179
- : nearestManifest(up);
180
- manifests.set(dir, found);
181
- return found;
147
+ return manifests.get(dir);
182
148
  };
183
149
  const linkOf = (candidates, fromDir) => {
184
150
  const name = candidates[0]?.name ?? "";
@@ -194,11 +160,7 @@ export function workspaceResolver(host, workspace) {
194
160
  // A package below the root that no workspace lists depends on a member as a member does.
195
161
  const owner = scope ?? nearestManifest(fromDir);
196
162
  const manifest = owner?.manifest;
197
- const declared = DEPENDENCY_MEMBERS.flatMap((field) => {
198
- const held = manifest?.[field];
199
- const specifier = isRecord(held) ? held[name] : undefined;
200
- return typeof specifier === "string" ? [specifier] : [];
201
- });
163
+ const declared = (manifest === undefined ? [] : dependencySpecifiers(manifest, name)).filter((specifier) => typeof specifier === "string");
202
164
  const matched = candidates.filter((member) => declared.some((specifier) => dependsOn(specifier, member, owner?.dir ?? fromDir)));
203
165
  const [one, two] = matched;
204
166
  if (one === undefined) {
@@ -284,11 +246,11 @@ export function workspaceResolver(host, workspace) {
284
246
  workspace,
285
247
  sourceOf,
286
248
  names: (specifier) => {
287
- const name = packageNameOf(specifier);
249
+ const name = packageOfSpecifier(specifier);
288
250
  return name !== undefined && workspace.named.has(name);
289
251
  },
290
252
  resolve(specifier, fromDir, conditions, answered) {
291
- const name = packageNameOf(specifier);
253
+ const name = packageOfSpecifier(specifier);
292
254
  const candidates = name === undefined ? undefined : workspace.named.get(name);
293
255
  if (name === undefined || candidates === undefined) {
294
256
  return { kind: "default" };
@@ -4,7 +4,7 @@
4
4
  * the files each configuration holds only for their references, and the entry points
5
5
  * the members' manifests name. A target in no workspace reads none of it.
6
6
  */
7
- import { relativePath } from "./paths.js";
7
+ import { relativePath } from "../paths.js";
8
8
  import { findWorkspace, referenceOnlyIn } from "./workspace.js";
9
9
  import { memberEntries, targetManifestIn } from "./workspace-manifests.js";
10
10
  import { workspacePrograms, } from "./workspace-programs.js";
@@ -42,16 +42,17 @@ function unbuildableReason(reading, referenceOnly, targetRoot) {
42
42
  * The workspace the target at `targetRoot` sits in, read for the configurations named,
43
43
  * or `undefined` where the target sits in none.
44
44
  */
45
- export function workspaceRun(engine, host, targetRoot, configFiles, manifest) {
45
+ export function workspaceRun(engine, host, targetRoot, configurations, manifest) {
46
46
  const workspace = findWorkspace(host, targetRoot, (configFile) => layoutOf(engine, configFile));
47
47
  if (workspace === undefined) {
48
48
  return undefined;
49
49
  }
50
50
  const resolver = workspaceResolver(host, workspace);
51
- const read = workspacePrograms(engine, host, configFiles, resolver);
51
+ const read = workspacePrograms(engine, host, configurations, resolver);
52
52
  const readings = new Map(read.readings.map((reading) => [reading.configFile, reading]));
53
53
  const referenceOnly = (configFile, compiled = []) => referenceOnlyIn(workspace, new Set([...(readings.get(configFile)?.fileNames ?? []), ...compiled]));
54
54
  return {
55
+ workspace,
55
56
  programs: read.programs,
56
57
  componentsUnread: read.readings.flatMap(({ configFile, componentsUnread }) => componentsUnread === undefined ? [] : [{ configFile, reason: componentsUnread }]),
57
58
  unbuildable: (configFile) => {
@@ -1,20 +1,19 @@
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
- import { DiscoveryError, isConfigFile } from "./discover.js";
7
+ import { isConfigFile } from "./discover.js";
8
+ import { failureExit } from "../failure.js";
10
9
  import { commonDirectory } from "./emit-map.js";
11
- import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.js";
12
- import { bytewise } from "./text/compare.js";
13
- import { isRecord } from "./json-document.js";
14
- import { directoriesIn, filesIn, repositoryOf } from "./fs/tree.js";
15
- import { holdsManifest, inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, manifestAt, } from "./fs/manifest.js";
16
- import { braceExpanded, pathMatches, segmentMatches } from "./text/glob.js";
17
- import { yamlKey } from "./text/yaml.js";
10
+ import { ancestors, dirnamePath, joinPath, relativePath } from "../paths.js";
11
+ import { bytewise } from "../text/compare.js";
12
+ import { isRecord } from "../json-document.js";
13
+ import { directoriesIn, filesIn, repositoryOf } from "../fs/tree.js";
14
+ import { holdsManifest, inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, manifestAt, } from "../fs/manifest.js";
15
+ import { braceExpanded, pathMatches, segmentMatches } from "../text/glob.js";
16
+ import { yamlKey } from "../text/yaml.js";
18
17
  const PNPM_WORKSPACE = "pnpm-workspace.yaml";
19
18
  /** The patterns one directory declares a workspace with, or `undefined` where it declares none. */
20
19
  function declarationAt(host, dir) {
@@ -27,7 +26,7 @@ function declarationAt(host, dir) {
27
26
  const listed = read.kind === "value" ? read.value : undefined;
28
27
  if (!Array.isArray(listed) || !listed.every((entry) => typeof entry === "string")) {
29
28
  const reason = read.kind === "refused" ? read.reason : "packages is not a sequence of strings";
30
- throw new DiscoveryError(`${pnpm}: ${reason}; the workspace's packages are read from a sequence of strings`, []);
29
+ throw failureExit("no-program", `${pnpm}: ${reason}; the workspace's packages are read from a sequence of strings: correct the declaration`);
31
30
  }
32
31
  return { file: pnpm, patterns: listed };
33
32
  }
@@ -38,7 +37,7 @@ function declarationAt(host, dir) {
38
37
  const declared = manifest["workspaces"];
39
38
  const list = isRecord(declared) ? declared["packages"] : declared;
40
39
  if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) {
41
- throw new DiscoveryError(`${joinPath(dir, MANIFEST)}: workspaces is neither an array of strings nor an object whose packages is one`, []);
40
+ throw failureExit("no-program", `${joinPath(dir, MANIFEST)}: workspaces is neither an array of strings nor an object whose packages is one: correct the declaration`);
42
41
  }
43
42
  return { file: joinPath(dir, MANIFEST), patterns: list };
44
43
  }
@@ -201,9 +200,10 @@ function nearestDeclaration(host, targetRoot) {
201
200
  * from the target root up to its repository root declares one with a member beside
202
201
  * its root, or the nearest one does not name the highest package below it, at or
203
202
  * above the target root, among its members. `parse` reads one compiler configuration,
204
- * or answers `undefined` where it does not read.
203
+ * or answers `undefined` where it does not read. `known`, a workspace already read, is the
204
+ * answer where the nearest declaration is its own, so its members are not listed again.
205
205
  */
206
- export function findWorkspace(host, targetRoot, parse) {
206
+ export function findWorkspace(host, targetRoot, parse, known) {
207
207
  const declared = nearestDeclaration(host, targetRoot);
208
208
  if (declared === undefined) {
209
209
  return undefined;
@@ -212,6 +212,9 @@ export function findWorkspace(host, targetRoot, parse) {
212
212
  !namesDirectory(declared.root, declared.patterns, declared.highestPackage)) {
213
213
  return undefined;
214
214
  }
215
+ if (known?.root === declared.root) {
216
+ return known;
217
+ }
215
218
  const members = [];
216
219
  for (const dir of memberDirectories(host, declared.root, declared.patterns)) {
217
220
  const manifest = manifestAt(host, dir);
@@ -249,6 +252,7 @@ export function findWorkspace(host, targetRoot, parse) {
249
252
  return found;
250
253
  };
251
254
  return {
255
+ root: declared.root,
252
256
  members,
253
257
  named,
254
258
  memberOf: (path) => {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
11
- import { emittedFrom } from "./emit-map.js";
11
+ import { emittedFrom } from "./load/emit-map.js";
12
12
  import { namesImported } from "./module-declarations.js";
13
13
  import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
14
14
  import { renderPosition } from "./position.js";
package/dist/src/run.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { CONFIG_MAX_BYTES, SETTINGS } from "./contract/config.generated.js";
2
2
  import { EXIT_FAILURE, EXIT_USAGE } from "./contract/contract.generated.js";
3
3
  import { REPOSITORY_DOCUMENT } from "./config.js";
4
- import { DiscoveryError, renderDiagnostic } from "./discover.js";
5
- import { readTargetDocument, TargetDocumentError } from "./fs/document.js";
4
+ import { readTargetDocument } from "./fs/document.js";
6
5
  import { joinPath, resolvePath } from "./paths.js";
7
- import { readScope, ScopeError, scopeForDir } from "./scope.js";
8
- import { SetupError, setupLine } from "./setup-failure.js";
6
+ import { renderDiagnostic } from "./load/diagnostics.js";
7
+ import { FailureExit, failureLines, productFault } from "./failure.js";
8
+ import { readScope, scopeForDir } from "./scope.js";
9
9
  import { UsageError } from "./usage.js";
10
10
  import { openEngine } from "./session.js";
11
11
  import { printConfigVerb } from "./verbs/print-config.js";
@@ -106,13 +106,8 @@ function documentAt(host, label) {
106
106
  function repositoryAt(host, target) {
107
107
  const label = joinPath(target, REPOSITORY_DOCUMENT);
108
108
  const root = resolvePath(host.workingDirectory(), target);
109
- try {
110
- const text = readTargetDocument(host, root, REPOSITORY_DOCUMENT, label);
111
- return text === undefined ? undefined : { text, label };
112
- }
113
- catch (error) {
114
- throw error instanceof TargetDocumentError ? new UsageError(error.message) : error;
115
- }
109
+ const text = readTargetDocument(host, root, REPOSITORY_DOCUMENT, label, (named, reason) => new UsageError(`${named}: ${reason}`));
110
+ return text === undefined ? undefined : { text, label };
116
111
  }
117
112
  /**
118
113
  * The configuration sources one invocation carries: the repository configuration at
@@ -202,29 +197,28 @@ function invoke(verb, own, args, streams, openClient) {
202
197
  return refuse(error, streams.err);
203
198
  }
204
199
  }
205
- /** Turns one refusal into the exit code the Contract's table gives it. */
200
+ /**
201
+ * Turns one refusal into the exit code the Contract's table gives it. An error no class names
202
+ * is a fault of this analyzer, printed with its stack for the maintainers it asks the user to
203
+ * report it to.
204
+ */
206
205
  function refuse(error, err) {
207
206
  if (error instanceof UsageError) {
208
207
  diagnose(err, error.message);
209
208
  err.write(USAGE);
210
209
  return EXIT_USAGE;
211
210
  }
212
- if (error instanceof DiscoveryError) {
213
- for (const diagnostic of error.diagnostics) {
214
- err.write(`${renderDiagnostic(diagnostic)}\n`);
211
+ if (!(error instanceof FailureExit)) {
212
+ if (error instanceof Error && error.stack !== undefined) {
213
+ err.write(`${error.stack}\n`);
215
214
  }
216
- diagnose(err, error.message);
217
- return EXIT_FAILURE;
215
+ return refuse(productFault(`meets an error none of its classes names: ${message(error)}`), err);
218
216
  }
219
- if (error instanceof SetupError) {
220
- for (const failure of error.failures) {
221
- err.write(`${setupLine(failure)}\n`);
222
- }
223
- return EXIT_FAILURE;
217
+ for (const diagnostic of error.diagnostics) {
218
+ err.write(`${renderDiagnostic(diagnostic)}\n`);
224
219
  }
225
- if (error instanceof ScopeError || error instanceof TargetDocumentError) {
226
- diagnose(err, error.message);
227
- return EXIT_FAILURE;
220
+ for (const line of failureLines(error.failures)) {
221
+ err.write(`${line}\n`);
228
222
  }
229
- throw error;
223
+ return EXIT_FAILURE;
230
224
  }
package/dist/src/scope.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { SCOPE_SCHEMA } from "./contract/config.generated.js";
2
2
  import { dotted, locatedFault, placeIn, readStrict, StrictJSONError } from "./json-document.js";
3
3
  import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
4
+ import { MARKERS } from "./load/discover.js";
5
+ import { failureExit } from "./failure.js";
4
6
  import { message } from "./product.js";
5
7
  import { firstRefusal } from "./schema.js";
6
8
  import { UsageError } from "./usage.js";
@@ -14,23 +16,13 @@ const MAX_DOCUMENT_LENGTH = 1 << 20;
14
16
  * so a document past the bound is past it within these.
15
17
  */
16
18
  const MAX_DOCUMENT_BYTES = 3 * (MAX_DOCUMENT_LENGTH + 1);
17
- /**
18
- * A scope that cannot be read as one. It ends the run with the failure code: the run
19
- * could not learn what to analyze, so it produced no answer.
20
- */
21
- export class ScopeError extends Error {
22
- constructor(message) {
23
- super(message);
24
- this.name = "ScopeError";
25
- }
26
- }
27
19
  function refusal(file, place, reason) {
28
- return new ScopeError(`scope: ${file}: ${place === "" ? "" : `${place}: `}${reason}`);
20
+ return new UsageError(`scope: ${file}: ${place === "" ? "" : `${place}: `}${reason}`);
29
21
  }
30
22
  /**
31
23
  * Decodes the scope document at `file`, held to the Contract's scope schema, a relative
32
- * path resolved against the document's own directory. A document that cannot be read is
33
- * a usage error, one that is refused a scope error. `workspace` is read by Go consumers
24
+ * path resolved against the document's own directory. A document that cannot be read or is
25
+ * refused is a usage error, as any document the invocation names. `workspace` is read by Go consumers
34
26
  * alone: a TypeScript project resolves the target through its own compiler configuration.
35
27
  */
36
28
  export function readScope(host, file) {
@@ -43,7 +35,7 @@ export function readScope(host, file) {
43
35
  throw new UsageError(`scope: ${file}: ${message(error)}`);
44
36
  }
45
37
  if (text.length > MAX_DOCUMENT_LENGTH) {
46
- throw new ScopeError(`scope: ${file} is longer than ${String(MAX_DOCUMENT_LENGTH)} characters`);
38
+ throw new UsageError(`scope: ${file} is longer than ${String(MAX_DOCUMENT_LENGTH)} characters`);
47
39
  }
48
40
  let value;
49
41
  try {
@@ -67,15 +59,15 @@ export function readScope(host, file) {
67
59
  });
68
60
  return { target: moduleOf(declared.target), consumers: (declared.consumers ?? []).map(moduleOf) };
69
61
  }
70
- /** The single-package scope one directory is, when no scope document is given. */
62
+ /**
63
+ * The single-package scope one directory is, when no scope document is given. A target that is
64
+ * no directory holds no program.
65
+ */
71
66
  export function scopeForDir(host, dir) {
72
67
  const path = resolvePath(host.workingDirectory(), dir);
73
68
  const kind = host.kindOf(path);
74
- if (kind === "absent") {
75
- throw new ScopeError(`scope: ${path} does not exist`);
76
- }
77
69
  if (kind !== "directory") {
78
- throw new ScopeError(`scope: ${path} is not a directory`);
70
+ throw failureExit("no-program", `the target ${path} ${kind === "absent" ? "does not exist" : "is not a directory"}, so it holds no compiler configuration, no file named ${MARKERS.join(", ")}: name the directory that holds one`);
79
71
  }
80
72
  return { target: { id: "", path }, consumers: [] };
81
73
  }
@@ -1,6 +1,5 @@
1
1
  import { API, SymbolFlags } from "@typescript/native/sync";
2
2
  import { componentLayer, mapperManifest } from "./component-layer.js";
3
- import { DiscoveryError } from "./discover.js";
4
3
  import { joinPath } from "./paths.js";
5
4
  import { queriesOf, UnansweredLog, } from "./query.js";
6
5
  import { MANIFEST } from "./fs/manifest.js";
@@ -253,14 +252,13 @@ function projectsOf(snapshot, built, written) {
253
252
  return opened;
254
253
  }
255
254
  /**
256
- * Runs `visit` over every project the compiler configurations name, inside one never updated
257
- * snapshot of one client, and answers in their order. A configuration `programs` describes is
258
- * built as that program, or opened from the configuration it writes. One the snapshot holds
259
- * nothing for ends the run under `"refuse"` and is named in the result under `"record"`.
260
- * `first` runs over every opened project before the first visit and `last` after the last,
261
- * both while the client answers; the snapshot and the client are then released.
255
+ * Opens every project the compiler configurations name in one never updated snapshot of one
256
+ * client, and hands them to `use` while the client answers; the snapshot and the client are
257
+ * then released. A configuration `options.programs` describes is built as that program, or
258
+ * opened from the configuration it writes. One the snapshot holds nothing for has no view.
262
259
  */
263
- export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map(), last = () => undefined, first = () => undefined) {
260
+ export function openSession(engine, configFiles, options, use) {
261
+ const programs = options.programs ?? new Map();
264
262
  try {
265
263
  const built = [];
266
264
  const params = [];
@@ -282,30 +280,13 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
282
280
  const opened = projectsOf(snapshot, built, written);
283
281
  const log = new UnansweredLog();
284
282
  const views = [];
285
- const missing = [];
286
283
  for (const configFile of configFiles) {
287
284
  const project = opened.get(configFile);
288
- if (project === undefined) {
289
- if (unopened === "refuse") {
290
- throw new DiscoveryError(`${configFile} opened no project`, []);
291
- }
292
- missing.push(configFile);
293
- continue;
285
+ if (project !== undefined) {
286
+ views.push(viewOf(project, configFile, engine, log));
294
287
  }
295
- views.push(viewOf(project, configFile, engine, log));
296
288
  }
297
- first(views.map((view) => ({
298
- configFile: view.configFile,
299
- fileNames: () => view.ownSourceFiles().map((file) => file.fileName),
300
- })));
301
- const projects = views.map((view) => visit(view));
302
- last();
303
- return {
304
- projects,
305
- unopened: missing,
306
- timing: engine.getTimingInfo(),
307
- unanswered: log.questions,
308
- };
289
+ return use({ views }, () => log.questions);
309
290
  }
310
291
  finally {
311
292
  snapshot.dispose();
@@ -315,3 +296,16 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
315
296
  engine.close();
316
297
  }
317
298
  }
299
+ /**
300
+ * Runs `visit` over the project {@link openSession} opens for each configuration, and answers
301
+ * in their order; a configuration the snapshot opens nothing for throws before any visit.
302
+ */
303
+ export function runSession(engine, configFiles, visit) {
304
+ return openSession(engine, configFiles, {}, (opened) => {
305
+ const unopened = configFiles.find((configFile) => !opened.views.some((view) => view.configFile === configFile));
306
+ if (unopened !== undefined) {
307
+ throw new Error(`${unopened} opened no project`);
308
+ }
309
+ return { projects: opened.views.map((view) => visit(view)), timing: engine.getTimingInfo() };
310
+ });
311
+ }
@@ -3,7 +3,7 @@
3
3
  * in a document or a string names one: as the file itself, or as a file the
4
4
  * compiler emits from it.
5
5
  */
6
- import { commonDirectory, emittedFrom } from "./emit-map.js";
6
+ import { commonDirectory, emittedFrom } from "./load/emit-map.js";
7
7
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
8
8
  /** The one character a subpath pattern of a manifest's `exports` holds. */
9
9
  const SUBPATH_WILDCARD = "*";