@cplieger/deadset-ts 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/component-mapper.js +5 -0
  3. package/dist/bin/mapper-connection.js +101 -0
  4. package/dist/bin/node-host.js +24 -2
  5. package/dist/src/analysis.js +229 -36
  6. package/dist/src/component-files.js +612 -0
  7. package/dist/src/component-layer.js +132 -0
  8. package/dist/src/config.js +9 -2
  9. package/dist/src/configuration-files.js +186 -0
  10. package/dist/src/conformance-record.js +2 -2
  11. package/dist/src/convention-rows.js +383 -0
  12. package/dist/src/conventions.js +456 -0
  13. package/dist/src/dependencies.js +30 -3
  14. package/dist/src/discover.js +10 -3
  15. package/dist/src/emit-map.js +72 -0
  16. package/dist/src/entry-point-gaps.js +81 -35
  17. package/dist/src/entry-points.js +47 -32
  18. package/dist/src/exempt.js +11 -3
  19. package/dist/src/findings/completion.js +19 -29
  20. package/dist/src/findings/emitters.js +22 -1
  21. package/dist/src/findings/unused-declarations.js +7 -2
  22. package/dist/src/graph.js +13 -5
  23. package/dist/src/inventory.js +4 -2
  24. package/dist/src/kinds.js +2 -2
  25. package/dist/src/manifest.js +37 -11
  26. package/dist/src/matrix.js +21 -13
  27. package/dist/src/partial.js +3 -1
  28. package/dist/src/pnpm-workspace.js +120 -0
  29. package/dist/src/position.js +17 -2
  30. package/dist/src/print.js +2 -0
  31. package/dist/src/references.js +209 -30
  32. package/dist/src/report.js +26 -6
  33. package/dist/src/reporters/annotations.js +10 -3
  34. package/dist/src/reporters/template.js +3 -0
  35. package/dist/src/resolve.js +31 -6
  36. package/dist/src/roots.js +37 -8
  37. package/dist/src/run.js +13 -3
  38. package/dist/src/schema.js +2 -0
  39. package/dist/src/session.js +141 -34
  40. package/dist/src/setup-failure.js +23 -0
  41. package/dist/src/source-files.js +4 -61
  42. package/dist/src/sweep.js +4 -2
  43. package/dist/src/template-field.js +38 -6
  44. package/dist/src/test-support.js +59 -0
  45. package/dist/src/type-errors.js +252 -0
  46. package/dist/src/type-query-alias.js +92 -0
  47. package/dist/src/verbs/analyze.js +24 -4
  48. package/dist/src/verbs/explain.js +4 -1
  49. package/dist/src/verbs/print-projects.js +17 -56
  50. package/dist/src/verbs/print-retained.js +7 -2
  51. package/dist/src/verbs/print-roots.js +7 -2
  52. package/dist/src/version-range.js +255 -0
  53. package/dist/src/version.js +1 -1
  54. package/dist/src/workspace-manifests.js +68 -0
  55. package/dist/src/workspace-programs.js +342 -0
  56. package/dist/src/workspace-resolution.js +307 -0
  57. package/dist/src/workspace-run.js +75 -0
  58. package/dist/src/workspace.js +358 -0
  59. package/jsr.json +1 -1
  60. package/package.json +2 -1
  61. package/src/analysis.ts +394 -70
  62. package/src/component-files.ts +718 -0
  63. package/src/component-layer.ts +160 -0
  64. package/src/config.ts +11 -2
  65. package/src/configuration-files.ts +235 -0
  66. package/src/conformance-record.ts +2 -2
  67. package/src/convention-rows.ts +440 -0
  68. package/src/conventions.ts +640 -0
  69. package/src/dependencies.ts +36 -2
  70. package/src/discover.ts +10 -6
  71. package/src/emit-map.ts +93 -0
  72. package/src/entry-point-gaps.ts +92 -38
  73. package/src/entry-points.ts +66 -36
  74. package/src/exempt.ts +21 -3
  75. package/src/findings/completion.ts +24 -33
  76. package/src/findings/emitters.ts +32 -1
  77. package/src/findings/unused-declarations.ts +9 -2
  78. package/src/graph.ts +15 -4
  79. package/src/host.ts +23 -0
  80. package/src/index.ts +1 -1
  81. package/src/inventory.ts +13 -2
  82. package/src/kinds.ts +2 -2
  83. package/src/manifest.ts +47 -14
  84. package/src/matrix.ts +43 -15
  85. package/src/partial.ts +6 -1
  86. package/src/pnpm-workspace.ts +135 -0
  87. package/src/position.ts +23 -2
  88. package/src/print.ts +2 -0
  89. package/src/references.ts +227 -28
  90. package/src/report.ts +56 -6
  91. package/src/reporters/annotations.ts +12 -3
  92. package/src/reporters/template.ts +3 -0
  93. package/src/resolve.ts +59 -6
  94. package/src/roots.ts +60 -9
  95. package/src/run.ts +15 -5
  96. package/src/schema.ts +2 -0
  97. package/src/session.ts +218 -37
  98. package/src/setup-failure.ts +39 -0
  99. package/src/source-files.ts +4 -66
  100. package/src/sweep.ts +9 -2
  101. package/src/template-field.ts +42 -7
  102. package/src/test-support.ts +77 -0
  103. package/src/type-errors.ts +340 -0
  104. package/src/type-query-alias.ts +133 -0
  105. package/src/verbs/analyze.ts +38 -4
  106. package/src/verbs/explain.ts +4 -1
  107. package/src/verbs/print-projects.ts +17 -69
  108. package/src/verbs/print-retained.ts +7 -2
  109. package/src/verbs/print-roots.ts +7 -2
  110. package/src/verbs/verb.ts +6 -3
  111. package/src/version-range.ts +309 -0
  112. package/src/version.ts +1 -1
  113. package/src/workspace-manifests.ts +100 -0
  114. package/src/workspace-programs.ts +486 -0
  115. package/src/workspace-resolution.ts +393 -0
  116. package/src/workspace-run.ts +148 -0
  117. package/src/workspace.ts +463 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Whether a version satisfies a dependency range, under the grammar npm, pnpm and yarn
3
+ * share: `||` alternatives of hyphen ranges or comparator runs, `x` and missing parts
4
+ * standing for any value, and a prerelease admitted only where a comparator names one
5
+ * of the same version. A range this grammar does not read is satisfied by nothing.
6
+ */
7
+ const NUMBER = /^(?:0|[1-9]\d*)$/u;
8
+ const IDENTIFIER = /^[0-9A-Za-z-]+$/u;
9
+ const WILDCARD = new Set(["x", "X", "*"]);
10
+ /** The prerelease identifiers after a hyphen, or `undefined` where one is malformed. */
11
+ function prereleaseOf(text) {
12
+ if (text === undefined) {
13
+ return [];
14
+ }
15
+ const parts = text.split(".");
16
+ return parts.every((part) => IDENTIFIER.test(part)) ? parts : undefined;
17
+ }
18
+ /**
19
+ * One version as a range writes it, a leading `v` or `=` allowed, or `undefined`
20
+ * where it is not one.
21
+ */
22
+ function partialOf(text) {
23
+ const match = /^[v=]?([^.+-]+)(?:\.([^.+-]+)(?:\.([^.+-]+)(?:-([^+]+))?)?)?(?:\+[0-9A-Za-z.-]+)?$/u.exec(text);
24
+ if (match === null) {
25
+ return undefined;
26
+ }
27
+ const parts = [];
28
+ let wild = false;
29
+ for (const part of [match[1], match[2], match[3]]) {
30
+ if (part === undefined || WILDCARD.has(part)) {
31
+ wild = true;
32
+ parts.push(undefined);
33
+ continue;
34
+ }
35
+ if (wild || !NUMBER.test(part)) {
36
+ return undefined;
37
+ }
38
+ parts.push(Number(part));
39
+ }
40
+ const prerelease = prereleaseOf(match[4]);
41
+ if (prerelease === undefined || (prerelease.length > 0 && wild)) {
42
+ return undefined;
43
+ }
44
+ return { major: parts[0], minor: parts[1], patch: parts[2], prerelease };
45
+ }
46
+ /** One exact version, or `undefined` where the text is not one. */
47
+ function versionOf(text) {
48
+ const partial = partialOf(text.trim());
49
+ if (partial?.major === undefined || partial.minor === undefined || partial.patch === undefined) {
50
+ return undefined;
51
+ }
52
+ return {
53
+ major: partial.major,
54
+ minor: partial.minor,
55
+ patch: partial.patch,
56
+ prerelease: partial.prerelease,
57
+ };
58
+ }
59
+ function exact(major, minor, patch, prerelease = []) {
60
+ return { major, minor, patch, prerelease };
61
+ }
62
+ /** The lowest prerelease of one version, which bounds a range from above. */
63
+ function floorOf(major, minor, patch) {
64
+ return exact(major, minor, patch, ["0"]);
65
+ }
66
+ function compareIdentifiers(a, b) {
67
+ const numericA = NUMBER.test(a);
68
+ const numericB = NUMBER.test(b);
69
+ if (numericA && numericB) {
70
+ return Math.sign(Number(a) - Number(b));
71
+ }
72
+ if (numericA !== numericB) {
73
+ return numericA ? -1 : 1;
74
+ }
75
+ return a === b ? 0 : a < b ? -1 : 1;
76
+ }
77
+ /** Two versions ordered by precedence. */
78
+ function compare(a, b) {
79
+ const core = Math.sign(a.major - b.major) || Math.sign(a.minor - b.minor) || Math.sign(a.patch - b.patch);
80
+ if (core !== 0) {
81
+ return core;
82
+ }
83
+ if (a.prerelease.length === 0 || b.prerelease.length === 0) {
84
+ return Math.sign(b.prerelease.length - a.prerelease.length);
85
+ }
86
+ for (let at = 0; at < Math.max(a.prerelease.length, b.prerelease.length); at += 1) {
87
+ const left = a.prerelease[at];
88
+ const right = b.prerelease[at];
89
+ if (left === undefined || right === undefined) {
90
+ return left === undefined ? -1 : 1;
91
+ }
92
+ const order = compareIdentifiers(left, right);
93
+ if (order !== 0) {
94
+ return order;
95
+ }
96
+ }
97
+ return 0;
98
+ }
99
+ function passes(version, comparator) {
100
+ const order = compare(version, comparator.version);
101
+ switch (comparator.operator) {
102
+ case "<":
103
+ return order < 0;
104
+ case "<=":
105
+ return order <= 0;
106
+ case ">":
107
+ return order > 0;
108
+ case ">=":
109
+ return order >= 0;
110
+ case "=":
111
+ return order === 0;
112
+ }
113
+ }
114
+ /** The comparators a version with missing parts stands for: any value of each. */
115
+ function anyOf(partial) {
116
+ const { major, minor } = partial;
117
+ if (major === undefined) {
118
+ return [{ operator: ">=", version: exact(0, 0, 0) }];
119
+ }
120
+ if (minor === undefined) {
121
+ return [
122
+ { operator: ">=", version: exact(major, 0, 0) },
123
+ { operator: "<", version: floorOf(major + 1, 0, 0) },
124
+ ];
125
+ }
126
+ if (partial.patch === undefined) {
127
+ return [
128
+ { operator: ">=", version: exact(major, minor, 0) },
129
+ { operator: "<", version: floorOf(major, minor + 1, 0) },
130
+ ];
131
+ }
132
+ return [{ operator: "=", version: exact(major, minor, partial.patch, partial.prerelease) }];
133
+ }
134
+ /** The lowest version a partial version stands for. */
135
+ function lowest(partial) {
136
+ return exact(partial.major ?? 0, partial.minor ?? 0, partial.patch ?? 0, partial.prerelease);
137
+ }
138
+ function tilde(partial) {
139
+ const { major, minor } = partial;
140
+ if (major === undefined || minor === undefined) {
141
+ return anyOf(partial);
142
+ }
143
+ return [
144
+ { operator: ">=", version: lowest(partial) },
145
+ { operator: "<", version: floorOf(major, minor + 1, 0) },
146
+ ];
147
+ }
148
+ function caret(partial) {
149
+ const { major, minor, patch } = partial;
150
+ if (major === undefined) {
151
+ return anyOf(partial);
152
+ }
153
+ const from = { operator: ">=", version: lowest(partial) };
154
+ if (major > 0 || minor === undefined) {
155
+ return [from, { operator: "<", version: floorOf(major + 1, 0, 0) }];
156
+ }
157
+ if (minor > 0 || patch === undefined) {
158
+ return [from, { operator: "<", version: floorOf(0, minor + 1, 0) }];
159
+ }
160
+ return [from, { operator: "<", version: floorOf(0, 0, patch + 1) }];
161
+ }
162
+ /** The comparators one primitive comparison stands for, a partial version filled in. */
163
+ function primitive(operator, partial) {
164
+ const { major, minor, patch } = partial;
165
+ if (major === undefined) {
166
+ return operator === "<" || operator === ">"
167
+ ? [{ operator: "<", version: floorOf(0, 0, 0) }]
168
+ : [{ operator: ">=", version: exact(0, 0, 0) }];
169
+ }
170
+ if (minor !== undefined && patch !== undefined) {
171
+ return [{ operator, version: lowest(partial) }];
172
+ }
173
+ const next = minor === undefined ? exact(major + 1, 0, 0) : exact(major, minor + 1, 0);
174
+ switch (operator) {
175
+ case ">":
176
+ return [{ operator: ">=", version: next }];
177
+ case ">=":
178
+ return [{ operator: ">=", version: lowest(partial) }];
179
+ case "<":
180
+ return [{ operator: "<", version: floorOf(major, minor ?? 0, 0) }];
181
+ case "<=":
182
+ return [{ operator: "<", version: floorOf(next.major, next.minor, 0) }];
183
+ }
184
+ }
185
+ /** The comparators one written comparator stands for, or `undefined` where it is none. */
186
+ function comparatorsOf(token) {
187
+ const match = /^(<=|>=|<|>|=|~>|~|\^)?(.*)$/u.exec(token);
188
+ const operator = match?.[1] ?? "";
189
+ const partial = partialOf(match?.[2] ?? "");
190
+ if (partial === undefined) {
191
+ return undefined;
192
+ }
193
+ switch (operator) {
194
+ case "~":
195
+ case "~>":
196
+ return tilde(partial);
197
+ case "^":
198
+ return caret(partial);
199
+ case "<":
200
+ case "<=":
201
+ case ">":
202
+ case ">=":
203
+ return primitive(operator, partial);
204
+ default:
205
+ return anyOf(partial);
206
+ }
207
+ }
208
+ /** The comparators of one alternative, or `undefined` where it does not read. */
209
+ function alternativeOf(text) {
210
+ const spaced = text.trim().replaceAll(/(<=|>=|<|>|=|~>|~|\^)\s+/gu, "$1");
211
+ if (spaced === "") {
212
+ return anyOf({ prerelease: [] });
213
+ }
214
+ const hyphen = /^(\S+)\s+-\s+(\S+)$/u.exec(spaced);
215
+ if (hyphen !== null) {
216
+ const from = partialOf(hyphen[1] ?? "");
217
+ const to = partialOf(hyphen[2] ?? "");
218
+ if (from === undefined || to === undefined) {
219
+ return undefined;
220
+ }
221
+ return [...primitive(">=", from), ...(to.major === undefined ? [] : primitive("<=", to))];
222
+ }
223
+ const found = [];
224
+ for (const token of spaced.split(/\s+/u)) {
225
+ const comparators = comparatorsOf(token);
226
+ if (comparators === undefined) {
227
+ return undefined;
228
+ }
229
+ found.push(...comparators);
230
+ }
231
+ return found;
232
+ }
233
+ /** Whether one alternative admits one version, its prerelease rule included. */
234
+ function admits(alternative, version) {
235
+ if (!alternative.every((comparator) => passes(version, comparator))) {
236
+ return false;
237
+ }
238
+ if (version.prerelease.length === 0) {
239
+ return true;
240
+ }
241
+ return alternative.some(({ version: bound }) => bound.prerelease.length > 0 &&
242
+ bound.major === version.major &&
243
+ bound.minor === version.minor &&
244
+ bound.patch === version.patch);
245
+ }
246
+ /** Whether `version` satisfies `range`; `false` where either does not read. */
247
+ export function satisfies(version, range) {
248
+ const parsed = versionOf(version);
249
+ if (parsed === undefined) {
250
+ return false;
251
+ }
252
+ const alternatives = range.split("||").map(alternativeOf);
253
+ return (alternatives.every((alternative) => alternative !== undefined) &&
254
+ alternatives.some((alternative) => admits(alternative, parsed)));
255
+ }
@@ -4,4 +4,4 @@
4
4
  * independently of the analyzer's own version, which is the one the host answers
5
5
  * from the package the analyzer was installed from.
6
6
  */
7
- export const CONTRACT_VERSION = "4.0.0";
7
+ export const CONTRACT_VERSION = "5.1.0";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The entry points the manifests of a workspace name, read back to the members'
3
+ * sources. Each member's manifest is read the way the target's own is, and each target
4
+ * is read back to the source file its member's configurations compile it from. A
5
+ * member whose manifest forbids publishing it exports nothing anyone outside the
6
+ * workspace imports, so only its commands are entry points.
7
+ */
8
+ import { emittedFrom } from "./emit-map.js";
9
+ import { readManifest } from "./manifest.js";
10
+ import { joinPath, relativePath } from "./paths.js";
11
+ /** The one character a subpath pattern of a manifest's `exports` holds. */
12
+ const SUBPATH_WILDCARD = "*";
13
+ /** The paths one written target stands for in its member's sources, itself included. */
14
+ function sourcesOf(resolver, member, path) {
15
+ if (!path.includes(SUBPATH_WILDCARD)) {
16
+ const source = resolver.sourceOf(member, path);
17
+ return source === undefined ? [path] : [source];
18
+ }
19
+ const patterns = member.configurations.flatMap(({ layout }) => layout === undefined ? [] : emittedFrom(path, layout));
20
+ return [...new Set([path, ...patterns])];
21
+ }
22
+ /** One manifest's entries with every target read back to its member's sources. */
23
+ function sourceEntries(resolver, member, entries, spelled) {
24
+ return entries.flatMap((entry) => sourcesOf(resolver, member, entry.path).map((path) => ({
25
+ ...entry,
26
+ member: spelled(entry),
27
+ path,
28
+ })));
29
+ }
30
+ /**
31
+ * The target's own manifest with its targets read back to the sources of the member
32
+ * the target is, where it is one.
33
+ */
34
+ export function targetManifestIn(resolver, targetRoot, manifest) {
35
+ const member = resolver.workspace.members.find((one) => one.dir === targetRoot);
36
+ return member === undefined
37
+ ? manifest
38
+ : {
39
+ ...manifest,
40
+ entries: sourceEntries(resolver, member, manifest.entries, (entry) => entry.member),
41
+ };
42
+ }
43
+ /**
44
+ * The entry points every other member's manifest names, each spelled as the member
45
+ * that named it under the manifest's path below the target root, a private member's
46
+ * `exports` left out: only the workspace imports a private package, and the workspace
47
+ * is analyzed. An entry is published where its member is not private and the member
48
+ * named in its own manifest is one a consumer imports.
49
+ */
50
+ export function memberEntries(host, resolver, targetRoot) {
51
+ return resolver.workspace.members.flatMap((member) => {
52
+ if (member.dir === targetRoot) {
53
+ return [];
54
+ }
55
+ const manifest = readManifest(host, member.dir);
56
+ const where = relativePath(targetRoot, joinPath(member.dir, "package.json")) ??
57
+ joinPath(member.dir, "package.json");
58
+ const kept = manifest.entries.flatMap((entry) => {
59
+ const exported = entry.member.startsWith("exports");
60
+ if (member.private && exported) {
61
+ return [];
62
+ }
63
+ const published = !member.private && entry.published && (manifest.declaresExports ? exported : true);
64
+ return [{ ...entry, published }];
65
+ });
66
+ return sourceEntries(resolver, member, kept, (entry) => `${where} ${entry.member}`);
67
+ });
68
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * The programs of a workspace's configurations, each reading the members it imports
3
+ * from source. Each round builds the programs, answers their member imports, and gives
4
+ * the answers back as static resolutions, which the compiler reads as the program's own
5
+ * files rather than a library's, until a round adds none. A configuration with no
6
+ * member import keeps the project the compiler opens for it.
7
+ */
8
+ import { ModuleKind, ModuleResolutionKind } from "@typescript/native/unstable/sync";
9
+ import { isStringLiteralLikeNode } from "@typescript/native/unstable/ast";
10
+ import { dirnamePath, joinPath, resolvePath } from "./paths.js";
11
+ /** The most questions one round trip carries. */
12
+ const BATCH = 2048;
13
+ /**
14
+ * The options a configuration's program is built with: its own, less the ones that
15
+ * lay out an emitted program's inputs and outputs. The analysis emits nothing, and a
16
+ * member's source is outside the importer's root directory by construction, which
17
+ * the compiler refuses under such a layout.
18
+ */
19
+ function analysisOptions(parsed) {
20
+ const { composite, rootDir: _rootDir, outDir: _outDir, declarationDir: _declarationDir, tsBuildInfoFile: _tsBuildInfoFile, ...kept } = parsed.options;
21
+ return composite === true ? { ...kept, declaration: true } : kept;
22
+ }
23
+ /**
24
+ * The references a built program can carry: only where every referenced
25
+ * configuration reads and is composite with output, because the compiler reports a
26
+ * broken reference at syntax a built program does not have.
27
+ */
28
+ function carriedReferences(engine, parsed) {
29
+ const references = parsed.projectReferences ?? [];
30
+ for (const reference of references) {
31
+ // A reference that names a directory names the `tsconfig.json` in it.
32
+ const configFile = reference.path.endsWith(".json")
33
+ ? reference.path
34
+ : joinPath(reference.path, "tsconfig.json");
35
+ let options;
36
+ try {
37
+ options = engine.parseConfigFile(configFile).options;
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ if (options.composite !== true || options.noEmit === true) {
43
+ return [];
44
+ }
45
+ }
46
+ return references;
47
+ }
48
+ /** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
49
+ function conditionsOf(parsed, mode) {
50
+ const resolution = parsed.options.moduleResolution;
51
+ const nodeLike = resolution === ModuleResolutionKind.Node16 || resolution === ModuleResolutionKind.NodeNext;
52
+ return new Set([
53
+ "types",
54
+ mode === ModuleKind.CommonJS ? "require" : "import",
55
+ ...(nodeLike ? ["node"] : []),
56
+ ...(parsed.options.customConditions ?? []),
57
+ "default",
58
+ ]);
59
+ }
60
+ function resolutionMode(kind) {
61
+ return kind === ModuleKind.CommonJS || kind === ModuleKind.ESNext ? kind : undefined;
62
+ }
63
+ /** The directories the compiler leaves out of a configuration that names no `exclude`. */
64
+ const DEFAULT_EXCLUDES = ["node_modules", "bower_components", "jspm_packages"];
65
+ /** Why one configuration's answers cannot be `paths`: a specifier read as two files. */
66
+ function unstatable(reading) {
67
+ const answered = new Set(reading.entries.map((entry) => entry.moduleName));
68
+ for (const [specifier, files] of reading.bound) {
69
+ if (answered.has(specifier) && files.size > 1) {
70
+ return `the imports of ${specifier} are read as ${String(files.size)} different files, which a configuration cannot state`;
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ /** The path beside `configFile` its written configuration takes, which no file has. */
76
+ function writtenPath(host, configFile) {
77
+ const stem = configFile.replace(/\.json$/u, "");
78
+ for (let counter = 0;; counter += 1) {
79
+ const path = `${stem}.deadset-ts${counter === 0 ? "" : `-${String(counter)}`}.json`;
80
+ if (host.kindOf(path) === "absent") {
81
+ return path;
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * The configuration that reads as one configuration's program: it extends it, lays out
87
+ * no output for the reason {@link analysisOptions} gives, maps each answered specifier
88
+ * to its file, and carries the references the program does. The compiler's default
89
+ * `exclude` names the output directories this drops, so where the file list moves it is
90
+ * restated; a list that still differs, or answers `paths` cannot state, are a reason.
91
+ */
92
+ function configurationOf(engine, host, reading) {
93
+ const reason = unstatable(reading);
94
+ if (reason !== undefined) {
95
+ return { kind: "unread", reason };
96
+ }
97
+ const { options } = reading.parsed;
98
+ const raw = options["pathsBasePath"];
99
+ const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
100
+ const paths = {};
101
+ for (const [key, targets] of Object.entries(options.paths ?? {})) {
102
+ paths[key] = targets.map((target) => resolvePath(base, target));
103
+ }
104
+ for (const entry of reading.entries) {
105
+ const [file] = reading.bound.get(entry.moduleName) ?? [];
106
+ if (file !== undefined) {
107
+ paths[entry.moduleName] = [file];
108
+ }
109
+ }
110
+ const json = (exclude) => ({
111
+ extends: reading.configFile,
112
+ compilerOptions: {
113
+ rootDir: null,
114
+ outDir: null,
115
+ declarationDir: null,
116
+ tsBuildInfoFile: null,
117
+ ...(options.composite === true ? { composite: false, declaration: true } : {}),
118
+ paths,
119
+ },
120
+ ...(reading.references.length > 0
121
+ ? { references: reading.references.map((reference) => ({ path: reference.path })) }
122
+ : {}),
123
+ ...(exclude === undefined ? {} : { exclude }),
124
+ });
125
+ const file = writtenPath(host, reading.configFile);
126
+ const expected = [...reading.parsed.fileNames].sort().join("\u0000");
127
+ const listed = (value) => {
128
+ try {
129
+ return [...engine.parseConfigJson(value, file).fileNames].sort().join("\u0000");
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
134
+ };
135
+ let value = json(undefined);
136
+ if (listed(value) !== expected) {
137
+ value = json([
138
+ ...DEFAULT_EXCLUDES,
139
+ ...[options.outDir, options.declarationDir].filter((dir) => dir !== undefined),
140
+ ]);
141
+ if (listed(value) !== expected) {
142
+ return {
143
+ kind: "unread",
144
+ reason: "its file list cannot be restated without its output directories",
145
+ };
146
+ }
147
+ }
148
+ return { kind: "configuration", file, text: `${JSON.stringify(value, null, 2)}\n` };
149
+ }
150
+ /** The program one configuration is built as, under the answers it holds so far. */
151
+ function programOf(engine, reading) {
152
+ const compilerOptions = analysisOptions(reading.parsed);
153
+ const resolver = engine.createModuleResolver(compilerOptions, reading.entries);
154
+ return {
155
+ resolver,
156
+ params: {
157
+ rootFiles: reading.parsed.fileNames,
158
+ compilerOptions,
159
+ options: {
160
+ moduleResolver: resolver,
161
+ configFileParsingDiagnostics: reading.parsed.errors,
162
+ ...(reading.references.length > 0 ? { projectReferences: reading.references } : {}),
163
+ },
164
+ },
165
+ };
166
+ }
167
+ function chunks(items) {
168
+ const found = [];
169
+ for (let at = 0; at < items.length; at += BATCH) {
170
+ found.push(items.slice(at, at + BATCH));
171
+ }
172
+ return found;
173
+ }
174
+ /** Asks one question of every item, in round trips of at most {@link BATCH}. */
175
+ function batched(engine, items, question) {
176
+ return chunks(items).flatMap((run) => engine.batch(run.map(question)));
177
+ }
178
+ /** One round over one program: answers the member imports no earlier round asked about. */
179
+ function scan(engine, program, reading, resolver) {
180
+ const names = program.getSourceFileNames().filter((name) => !name.includes("/node_modules/"));
181
+ const files = batched(engine, names, (name) => program.getSourceFile.gen(name));
182
+ const usages = [];
183
+ files.forEach((file, at) => {
184
+ const name = names[at];
185
+ if (file === undefined || name === undefined) {
186
+ return;
187
+ }
188
+ for (const node of file.imports) {
189
+ if (isStringLiteralLikeNode(node) && resolver.names(node.text)) {
190
+ usages.push({ file: name, node });
191
+ }
192
+ }
193
+ });
194
+ const modes = batched(engine, usages, (usage) => program.getModeForUsageLocation.gen(usage.file, usage.node));
195
+ const asked = [];
196
+ usages.forEach((usage, at) => {
197
+ const mode = resolutionMode(modes[at] ?? ModuleKind.None);
198
+ const key = [usage.node.text, dirnamePath(usage.file), String(mode)].join("\u0000");
199
+ if (!reading.seen.has(key)) {
200
+ reading.seen.add(key);
201
+ asked.push({ usage, mode });
202
+ }
203
+ });
204
+ const answers = batched(engine, asked, ({ usage }) => program.getResolvedModuleFromModuleSpecifier.gen(usage.node, usage.file));
205
+ let added = 0;
206
+ asked.forEach(({ usage, mode }, at) => {
207
+ const answered = answers[at];
208
+ const specifier = usage.node.text;
209
+ const containingDirectory = dirnamePath(usage.file);
210
+ const compiled = answered?.resolvedFileName === undefined || answered.resolvedFileName === ""
211
+ ? undefined
212
+ : answered;
213
+ const answer = resolver.resolve(specifier, containingDirectory, conditionsOf(reading.parsed, mode), compiled);
214
+ if (answer.kind !== "unresolved") {
215
+ const bound = reading.bound.get(specifier) ?? new Set();
216
+ bound.add(answer.kind === "source" ? answer.file : (compiled?.resolvedFileName ?? ""));
217
+ reading.bound.set(specifier, bound);
218
+ }
219
+ if (answer.kind === "source") {
220
+ reading.entries.push({
221
+ moduleName: specifier,
222
+ containingDirectory,
223
+ resolutionMode: mode,
224
+ result: { resolvedFileName: answer.file },
225
+ });
226
+ added += 1;
227
+ }
228
+ else if (answer.kind === "unresolved") {
229
+ reading.unresolved.push({
230
+ file: usage.file,
231
+ specifier,
232
+ member: answer.member,
233
+ subpath: answer.subpath,
234
+ reason: answer.reason,
235
+ });
236
+ }
237
+ });
238
+ return added;
239
+ }
240
+ /**
241
+ * Answers the member imports of every reading `build` describes, round by round, all
242
+ * the readings still adding answers built in one snapshot per round. A reading `build`
243
+ * gives no description for leaves the rounds.
244
+ */
245
+ function readRounds(engine, readings, resolver, build) {
246
+ let pending = readings;
247
+ while (pending.length > 0) {
248
+ const built = pending.flatMap((reading) => {
249
+ const one = build(reading);
250
+ return one === undefined ? [] : [{ reading, one }];
251
+ });
252
+ const programs = built.flatMap(({ one }) => (one.kind === "program" ? [one.params] : []));
253
+ const written = new Map(built.flatMap(({ one }) => one.kind === "configuration" ? [[one.file, one.text]] : []));
254
+ const snapshot = engine.createSnapshot([...written.keys()], programs, written);
255
+ try {
256
+ const created = snapshot.operation.createdPrograms ?? [];
257
+ const opened = new Map(snapshot.getProjects().map((project) => [project.configFileName, project.program]));
258
+ let at = 0;
259
+ pending = built.flatMap(({ reading, one }) => {
260
+ const program = one.kind === "program" ? created[at++] : opened.get(one.file);
261
+ return program !== undefined && scan(engine, program, reading, resolver) > 0
262
+ ? [reading]
263
+ : [];
264
+ });
265
+ }
266
+ finally {
267
+ snapshot.dispose();
268
+ for (const { one } of built) {
269
+ if (one.kind === "program") {
270
+ one.resolver[Symbol.dispose]();
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+ /**
277
+ * Reads every configuration's member imports. Where the snapshots read component files,
278
+ * the rounds go on over each reading's configuration, which reads them, for the imports
279
+ * only they make.
280
+ */
281
+ export function workspacePrograms(engine, host, configFiles, resolver) {
282
+ const readings = configFiles.flatMap((configFile) => {
283
+ let parsed;
284
+ try {
285
+ parsed = engine.parseConfigFile(configFile);
286
+ }
287
+ catch {
288
+ // A configuration that does not read is opened as a project, which states why.
289
+ return [];
290
+ }
291
+ return [
292
+ {
293
+ configFile,
294
+ parsed,
295
+ references: carriedReferences(engine, parsed),
296
+ entries: [],
297
+ seen: new Set(),
298
+ bound: new Map(),
299
+ unresolved: [],
300
+ },
301
+ ];
302
+ });
303
+ readRounds(engine, readings, resolver, (reading) => ({
304
+ kind: "program",
305
+ ...programOf(engine, reading),
306
+ }));
307
+ if (engine.readsComponents) {
308
+ readRounds(engine, readings, resolver, (reading) => {
309
+ const one = configurationOf(engine, host, reading);
310
+ return one.kind === "configuration" ? one : undefined;
311
+ });
312
+ }
313
+ const programs = new Map();
314
+ const unread = new Map();
315
+ for (const reading of readings) {
316
+ if (reading.entries.length === 0) {
317
+ continue;
318
+ }
319
+ const one = engine.readsComponents ? configurationOf(engine, host, reading) : undefined;
320
+ if (one !== undefined && one.kind !== "unread") {
321
+ programs.set(reading.configFile, one);
322
+ continue;
323
+ }
324
+ programs.set(reading.configFile, {
325
+ kind: "program",
326
+ params: programOf(engine, reading).params,
327
+ });
328
+ if (one !== undefined) {
329
+ unread.set(reading.configFile, one.reason);
330
+ }
331
+ }
332
+ return {
333
+ programs,
334
+ readings: readings.map((reading) => ({
335
+ configFile: reading.configFile,
336
+ fileNames: reading.parsed.fileNames,
337
+ entries: reading.entries,
338
+ unresolved: reading.unresolved,
339
+ componentsUnread: unread.get(reading.configFile),
340
+ })),
341
+ };
342
+ }