@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
@@ -7,11 +7,12 @@
7
7
  * written for the runner, as one that imports it is.
8
8
  */
9
9
  import { isAsExpression, isBinaryExpression, isCallExpression, isExportAssignment, isExpressionStatement, isIdentifier, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, SyntaxKind, } from "@typescript/native/ast";
10
- import { ancestors, dirnamePath, joinPath } from "./paths.js";
11
- import { UNANSWERED } from "./query.js";
12
- import { writtenPath } from "./workspace-programs.js";
13
- import { filesIn } from "./fs/tree.js";
14
- import { holdsManifest, INSTALLED_PACKAGES } from "./fs/manifest.js";
10
+ import { isRecord } from "../json-document.js";
11
+ import { ancestors, dirnamePath, joinPath } from "../paths.js";
12
+ import { UNANSWERED } from "../query.js";
13
+ import { filesIn } from "../fs/tree.js";
14
+ import { holdsManifest, INSTALLED_PACKAGES } from "../fs/manifest.js";
15
+ import { isTargetConfiguration } from "./discover.js";
15
16
  /** The test runner whose configuration injects the globals, and the `types` entry declaring them. */
16
17
  export const GLOBALS_RUNNER = "vitest";
17
18
  const GLOBALS_TYPES = "vitest/globals";
@@ -117,67 +118,61 @@ function typesOf(value) {
117
118
  return Array.isArray(value) && value.every((one) => typeof one === "string") ? value : undefined;
118
119
  }
119
120
  /**
120
- * How the snapshot reads each configuration of `configFiles` whose nearest manifest's
121
- * runner configuration injects the globals and that can resolve their declarations: as
122
- * `programs` reads it, else as itself, with the globals' declarations added to its `types`.
123
- * A configuration the compiler cannot read is left for its project to refuse.
121
+ * The plans with the globals' declarations added to the `types` of each target configuration
122
+ * whose nearest manifest's runner configuration injects them and that can resolve them.
124
123
  */
125
- export function withTestGlobals(engine, host, parse, configFiles, programs) {
126
- const found = new Map(programs);
124
+ export function testGlobals(host, parse, plans) {
127
125
  const injects = new Map();
128
- for (const configFile of configFiles) {
129
- const dir = manifestDir(host, configFile);
130
- if (dir === undefined || !globalsInstalled(host, configFile)) {
131
- continue;
126
+ return plans.map((plan) => {
127
+ const dir = manifestDir(host, plan.configFile);
128
+ if (!isTargetConfiguration(plan) ||
129
+ dir === undefined ||
130
+ !globalsInstalled(host, plan.configFile)) {
131
+ return plan;
132
132
  }
133
133
  if (!injects.has(dir)) {
134
134
  injects.set(dir, injectsGlobals(host, parse, dir));
135
135
  }
136
+ const base = typesOf(plan.options.types) ?? [];
137
+ const { build } = plan;
136
138
  if (injects.get(dir) !== true) {
137
- continue;
138
- }
139
- let base;
140
- try {
141
- base = typesOf(engine.parseConfigFile(configFile).options.types) ?? [];
142
- }
143
- catch {
144
- continue;
145
- }
146
- const held = found.get(configFile);
147
- if (held?.kind === "program") {
148
- const listed = typesOf(held.params.compilerOptions.types) ?? base;
149
- if (!listed.includes(GLOBALS_TYPES)) {
150
- found.set(configFile, {
151
- kind: "program",
152
- params: {
153
- ...held.params,
154
- compilerOptions: { ...held.params.compilerOptions, types: [...listed, GLOBALS_TYPES] },
139
+ return plan;
140
+ }
141
+ if (build.kind === "program") {
142
+ const listed = typesOf(build.params.compilerOptions.types) ?? base;
143
+ return listed.includes(GLOBALS_TYPES)
144
+ ? plan
145
+ : {
146
+ ...plan,
147
+ build: {
148
+ kind: "program",
149
+ params: {
150
+ ...build.params,
151
+ compilerOptions: {
152
+ ...build.params.compilerOptions,
153
+ types: [...listed, GLOBALS_TYPES],
154
+ },
155
+ },
155
156
  },
156
- });
157
- }
158
- continue;
157
+ };
159
158
  }
160
- let written = { extends: configFile };
161
- if (held !== undefined) {
162
- try {
163
- written = JSON.parse(held.text);
164
- }
165
- catch {
166
- continue;
167
- }
168
- }
169
- const options = (written["compilerOptions"] ?? {});
159
+ const written = build.kind === "configuration" ? build.value : { extends: plan.configFile };
160
+ const held = written["compilerOptions"];
161
+ const options = isRecord(held) ? held : {};
170
162
  const listed = typesOf(options["types"]) ?? base;
171
- if (listed.includes(GLOBALS_TYPES)) {
172
- continue;
173
- }
174
- found.set(configFile, {
175
- kind: "configuration",
176
- file: held?.file ?? writtenPath(host, configFile),
177
- text: `${JSON.stringify({ ...written, compilerOptions: { ...options, types: [...listed, GLOBALS_TYPES] } }, null, 2)}\n`,
178
- });
179
- }
180
- return found;
163
+ return listed.includes(GLOBALS_TYPES)
164
+ ? plan
165
+ : {
166
+ ...plan,
167
+ build: {
168
+ kind: "configuration",
169
+ value: {
170
+ ...written,
171
+ compilerOptions: { ...options, types: [...listed, GLOBALS_TYPES] },
172
+ },
173
+ },
174
+ };
175
+ });
181
176
  }
182
177
  /**
183
178
  * Whether one file names a global the runner injects, which is how a file written for the
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The test files a derived matrix holds although no configuration's file list names them.
3
+ * The test runner compiles such a file with the options of the nearest compiler
4
+ * configuration at or above it, so the file joins that configuration's program as a file
5
+ * its list names, unless a `package.json` below that configuration's directory makes the
6
+ * file another package's, which its own test runner compiles.
7
+ */
8
+ import { sourcePaths } from "../file-facts.js";
9
+ import { ancestors, dirnamePath, joinPath } from "../paths.js";
10
+ import { bytewise } from "../text/compare.js";
11
+ import { holdsManifest } from "../fs/manifest.js";
12
+ const PREFERRED = "/tsconfig.json";
13
+ /** The configurations of one directory in the order a file joins them: `tsconfig.json` first. */
14
+ function byPreference(a, b) {
15
+ const first = a.endsWith(PREFERRED) ? 0 : 1;
16
+ const second = b.endsWith(PREFERRED) ? 0 : 1;
17
+ return first - second || bytewise(a, b);
18
+ }
19
+ /**
20
+ * Per configuration of `configurations`, the absolute paths of the test files that join it:
21
+ * each file below `root` a pattern of `patterns` names, that no configuration's file list
22
+ * holds, joins the configuration nearest at or above its directory when no `package.json`
23
+ * sits between them.
24
+ */
25
+ export function joinedTestFiles(host, root, configurations, patterns) {
26
+ const listed = new Set();
27
+ const byDir = new Map();
28
+ for (const { configFile, parsed } of configurations) {
29
+ parsed.fileNames.forEach((name) => listed.add(name));
30
+ const dir = dirnamePath(configFile);
31
+ byDir.set(dir, [...(byDir.get(dir) ?? []), configFile].sort(byPreference));
32
+ }
33
+ const joins = new Map();
34
+ for (const path of sourcePaths(host, root)) {
35
+ const file = joinPath(root, path);
36
+ if (listed.has(file) || !patterns.some((pattern) => pattern.test(path))) {
37
+ continue;
38
+ }
39
+ for (const dir of ancestors(dirnamePath(file), root)) {
40
+ const [nearest] = byDir.get(dir) ?? [];
41
+ if (nearest !== undefined) {
42
+ joins.set(nearest, [...(joins.get(nearest) ?? []), file]);
43
+ break;
44
+ }
45
+ if (holdsManifest(host, dir)) {
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ return joins;
51
+ }
52
+ /**
53
+ * The plans with each joined test file added to the configuration it joins: to a described
54
+ * program's root files, or to the file list of a configuration only the snapshot holds, which
55
+ * lists every file the configuration lists and the joined ones.
56
+ */
57
+ export function joinedTests(plans, joins) {
58
+ return plans.map((plan) => {
59
+ const files = joins.get(plan.configFile);
60
+ const { build } = plan;
61
+ if (files === undefined) {
62
+ return plan;
63
+ }
64
+ if (build.kind === "program") {
65
+ return {
66
+ ...plan,
67
+ build: {
68
+ kind: "program",
69
+ params: { ...build.params, rootFiles: [...(build.params.rootFiles ?? []), ...files] },
70
+ },
71
+ };
72
+ }
73
+ const written = build.kind === "configuration" ? build.value : { extends: plan.configFile };
74
+ return {
75
+ ...plan,
76
+ build: {
77
+ kind: "configuration",
78
+ value: { ...written, files: [...plan.parsed.fileNames, ...files], include: [] },
79
+ },
80
+ };
81
+ });
82
+ }
@@ -6,9 +6,9 @@
6
6
  * workspace imports, so only its commands are entry points.
7
7
  */
8
8
  import { emittedFrom } from "./emit-map.js";
9
- import { readManifest } from "./manifest.js";
10
- import { joinPath, relativePath } from "./paths.js";
11
- import { MANIFEST } from "./fs/manifest.js";
9
+ import { readManifest } from "../manifest.js";
10
+ import { joinPath, relativePath } from "../paths.js";
11
+ import { MANIFEST } from "../fs/manifest.js";
12
12
  /** The one character a subpath pattern of a manifest's `exports` holds. */
13
13
  const SUBPATH_WILDCARD = "*";
14
14
  /** The paths one written target stands for in its member's sources, itself included. */
@@ -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";