@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,307 @@
1
+ /**
2
+ * Which file an import of a workspace package's name is read from. The package is the
3
+ * one `node_modules` links the importer to or, with no link, the one the importer's
4
+ * manifest depends on. A source file the compiler reaches stands; any other file is read
5
+ * back to the source it is compiled from or to a manifest target that is source, a file
6
+ * the package's configurations neither compile nor write is read as installed, and an
7
+ * import nothing answers for is unresolved.
8
+ */
9
+ import { emittedFrom } from "./emit-map.js";
10
+ import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
+ import { satisfies } from "./version-range.js";
12
+ /** The manifest members that name an entry point for the subpath `.` without `exports`. */
13
+ const LEGACY_MEMBERS = ["types", "typings", "module", "main"];
14
+ /** The dependency members a manifest declares other packages under. */
15
+ const DEPENDENCY_MEMBERS = [
16
+ "dependencies",
17
+ "devDependencies",
18
+ "peerDependencies",
19
+ "optionalDependencies",
20
+ ];
21
+ const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
22
+ const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ /** The package name a bare specifier names, or `undefined` for any other specifier. */
27
+ export function packageNameOf(specifier) {
28
+ if (specifier === "" ||
29
+ specifier.startsWith(".") ||
30
+ specifier.startsWith("#") ||
31
+ isAbsolutePath(specifier) ||
32
+ specifier.includes(":")) {
33
+ return undefined;
34
+ }
35
+ const [first, second] = specifier.split("/");
36
+ if (specifier.startsWith("@")) {
37
+ return second === undefined || second === "" ? undefined : `${first ?? ""}/${second}`;
38
+ }
39
+ return first;
40
+ }
41
+ function inside(dir, path) {
42
+ return path === dir || relativePath(dir, path) !== undefined;
43
+ }
44
+ /** The `exports` entry one subpath selects, with what a pattern's `*` matched. */
45
+ function exportsEntry(exportsField, subpath) {
46
+ const map = isRecord(exportsField) && Object.keys(exportsField).some((key) => key.startsWith("."))
47
+ ? exportsField
48
+ : { ".": exportsField };
49
+ if (Object.hasOwn(map, subpath)) {
50
+ return { value: map[subpath] };
51
+ }
52
+ let best;
53
+ for (const key of Object.keys(map)) {
54
+ const star = key.indexOf("*");
55
+ if (star < 0 || key.slice(star + 1).includes("*")) {
56
+ continue;
57
+ }
58
+ const prefix = key.slice(0, star);
59
+ const suffix = key.slice(star + 1);
60
+ if (subpath.length >= key.length &&
61
+ subpath.startsWith(prefix) &&
62
+ subpath.endsWith(suffix) &&
63
+ (best === undefined ||
64
+ prefix.length > best.prefix ||
65
+ (prefix.length === best.prefix && key.length > best.key.length))) {
66
+ best = {
67
+ key,
68
+ prefix: prefix.length,
69
+ match: subpath.slice(prefix.length, subpath.length - suffix.length),
70
+ };
71
+ }
72
+ }
73
+ return best === undefined ? undefined : { value: map[best.key], match: best.match };
74
+ }
75
+ /** The leaf a condition set selects from one `exports` value, in the order Node reads it. */
76
+ function selected(value, conditions) {
77
+ if (typeof value === "string") {
78
+ return value;
79
+ }
80
+ if (Array.isArray(value)) {
81
+ for (const alternative of value) {
82
+ const found = selected(alternative, conditions);
83
+ if (found !== undefined) {
84
+ return found;
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ if (!isRecord(value)) {
90
+ return undefined;
91
+ }
92
+ for (const [key, held] of Object.entries(value)) {
93
+ if (conditions.has(key)) {
94
+ return selected(held, conditions);
95
+ }
96
+ }
97
+ return undefined;
98
+ }
99
+ /** Every leaf one `exports` value carries, in document order. */
100
+ function leaves(value) {
101
+ if (typeof value === "string") {
102
+ return [value];
103
+ }
104
+ if (Array.isArray(value)) {
105
+ return value.flatMap(leaves);
106
+ }
107
+ return isRecord(value) ? Object.values(value).flatMap(leaves) : [];
108
+ }
109
+ /** Whether one dependency specifier names one member, written from one directory. */
110
+ function dependsOn(specifier, member, fromDir) {
111
+ const atPath = (path) => resolvePath(fromDir, path) === member.dir;
112
+ const inRange = (range) => member.version !== undefined && satisfies(member.version, range);
113
+ if (specifier.startsWith("workspace:")) {
114
+ const rest = specifier.slice("workspace:".length);
115
+ if (rest.startsWith(".") || rest.startsWith("/")) {
116
+ return atPath(rest);
117
+ }
118
+ const alias = /^((?:@[^/@]+\/)?[^/@]+)@(.*)$/u.exec(rest);
119
+ const range = alias === null ? rest : (alias[2] ?? "");
120
+ if (alias !== null && alias[1] !== member.name) {
121
+ return false;
122
+ }
123
+ return range === "*" || range === "^" || range === "~" || inRange(range);
124
+ }
125
+ for (const protocol of ["file:", "link:"]) {
126
+ if (specifier.startsWith(protocol)) {
127
+ return atPath(specifier.slice(protocol.length));
128
+ }
129
+ }
130
+ return inRange(specifier);
131
+ }
132
+ /** The member imports of one workspace, read from the members' sources. */
133
+ export function workspaceResolver(host, workspace) {
134
+ const links = new Map();
135
+ /** The directory `node_modules/<name>` names from one directory, links followed. */
136
+ const linkedFrom = (name, dir) => {
137
+ const key = `${name}\u0000${dir}`;
138
+ if (links.has(key)) {
139
+ return links.get(key);
140
+ }
141
+ const candidate = joinPath(dir, "node_modules", name);
142
+ let found;
143
+ if (host.kindOf(candidate) === "absent") {
144
+ const up = dirnamePath(dir);
145
+ found = up === dir ? undefined : linkedFrom(name, up);
146
+ }
147
+ else {
148
+ try {
149
+ found = host.realPath(candidate);
150
+ }
151
+ catch {
152
+ found = undefined;
153
+ }
154
+ }
155
+ links.set(key, found);
156
+ return found;
157
+ };
158
+ const linkOf = (candidates, fromDir) => {
159
+ const name = candidates[0]?.name ?? "";
160
+ const linked = linkedFrom(name, fromDir);
161
+ if (linked !== undefined) {
162
+ const member = candidates.find((one) => one.dir === linked);
163
+ return member === undefined ? { kind: "none" } : { kind: "member", member };
164
+ }
165
+ const scope = workspace.memberOf(joinPath(fromDir, "package.json"));
166
+ if (scope?.name === name) {
167
+ return { kind: "member", member: scope };
168
+ }
169
+ const manifest = scope?.manifest;
170
+ const declared = DEPENDENCY_MEMBERS.flatMap((field) => {
171
+ const held = manifest?.[field];
172
+ const specifier = isRecord(held) ? held[name] : undefined;
173
+ return typeof specifier === "string" ? [specifier] : [];
174
+ });
175
+ const matched = candidates.filter((member) => declared.some((specifier) => dependsOn(specifier, member, scope?.dir ?? fromDir)));
176
+ const [one, two] = matched;
177
+ if (one === undefined) {
178
+ return { kind: "none" };
179
+ }
180
+ return two === undefined
181
+ ? { kind: "member", member: one }
182
+ : { kind: "ambiguous", dirs: matched.map((member) => member.dir) };
183
+ };
184
+ /** The files a member's configurations compile, which is what makes a file there source. */
185
+ const compiled = (member, path) => member.configurations.some((configuration) => configuration.fileNames.has(path));
186
+ /**
187
+ * The member's source one of its paths is, or is compiled from: a TypeScript source
188
+ * file, the source an output path maps back to, or a file one of the member's
189
+ * configurations compiles. `undefined` where none is.
190
+ */
191
+ const sourceOf = (member, path) => {
192
+ if (SOURCE.test(path) && host.kindOf(path) === "file") {
193
+ return path;
194
+ }
195
+ for (const { layout } of member.configurations) {
196
+ const candidates = layout === undefined ? [] : emittedFrom(path, layout);
197
+ const found = candidates.find((candidate) => host.kindOf(candidate) === "file");
198
+ if (found !== undefined) {
199
+ return found;
200
+ }
201
+ }
202
+ if (OUTPUT.test(path)) {
203
+ const beside = emittedFrom(path, { sourceDirs: [] });
204
+ const found = beside.find((candidate) => host.kindOf(candidate) === "file");
205
+ if (found !== undefined) {
206
+ return found;
207
+ }
208
+ }
209
+ return compiled(member, path) && host.kindOf(path) === "file" ? path : undefined;
210
+ };
211
+ /** The targets a member's manifest names for one subpath, the selected one first. */
212
+ const targetsOf = (member, subpath, conditions) => {
213
+ const exportsField = member.manifest["exports"];
214
+ if (exportsField !== undefined) {
215
+ const entry = exportsEntry(exportsField, subpath);
216
+ if (entry === undefined) {
217
+ return [];
218
+ }
219
+ const first = selected(entry.value, conditions);
220
+ const written = [...(first === undefined ? [] : [first]), ...leaves(entry.value)];
221
+ const found = written
222
+ .filter((target) => target.startsWith("./"))
223
+ .map((target) => entry.match === undefined ? target : target.replaceAll("*", entry.match));
224
+ return [...new Set(found)];
225
+ }
226
+ if (subpath === ".") {
227
+ return LEGACY_MEMBERS.flatMap((field) => {
228
+ const value = member.manifest[field];
229
+ return typeof value === "string" ? [value] : [];
230
+ });
231
+ }
232
+ return [
233
+ subpath,
234
+ `${subpath}.d.ts`,
235
+ `${subpath}.js`,
236
+ `${subpath}/index.d.ts`,
237
+ `${subpath}/index.js`,
238
+ ];
239
+ };
240
+ return {
241
+ workspace,
242
+ sourceOf,
243
+ names: (specifier) => {
244
+ const name = packageNameOf(specifier);
245
+ return name !== undefined && workspace.named.has(name);
246
+ },
247
+ resolve(specifier, fromDir, conditions, answered) {
248
+ const name = packageNameOf(specifier);
249
+ const candidates = name === undefined ? undefined : workspace.named.get(name);
250
+ if (name === undefined || candidates === undefined) {
251
+ return { kind: "default" };
252
+ }
253
+ const subpath = specifier === name ? "." : `.${specifier.slice(name.length)}`;
254
+ const linked = linkOf(candidates, fromDir);
255
+ if (linked.kind === "none") {
256
+ return { kind: "default" };
257
+ }
258
+ if (linked.kind === "ambiguous") {
259
+ return {
260
+ kind: "unresolved",
261
+ member: name,
262
+ subpath,
263
+ reason: `${String(linked.dirs.length)} workspace packages are named ${name} (${linked.dirs.join(", ")}) and nothing links the importer to one of them; install the workspace, or declare the dependency with a range only one of them satisfies`,
264
+ };
265
+ }
266
+ const { member } = linked;
267
+ // A member no compiler configuration builds is read as the package manager
268
+ // installed it, wherever the compiler's resolution found it.
269
+ if (answered !== undefined &&
270
+ (member.configurations.length === 0 || !inside(member.dir, answered.resolvedFileName))) {
271
+ return { kind: "default" };
272
+ }
273
+ if (answered !== undefined) {
274
+ const source = sourceOf(member, answered.resolvedFileName);
275
+ if (source !== undefined) {
276
+ return source === answered.resolvedFileName && answered.isExternalLibraryImport !== true
277
+ ? { kind: "default" }
278
+ : { kind: "source", file: source, member: member.name };
279
+ }
280
+ }
281
+ const targets = targetsOf(member, subpath, conditions);
282
+ for (const target of targets) {
283
+ const source = sourceOf(member, joinPath(member.dir, target));
284
+ if (source !== undefined) {
285
+ return { kind: "source", file: source, member: member.name };
286
+ }
287
+ }
288
+ // A file the member's configurations neither compile nor write, a hand-written
289
+ // declaration file or a bundler's output, is read as the package manager installed it.
290
+ if (answered !== undefined) {
291
+ return { kind: "default" };
292
+ }
293
+ const named = targets.length === 0 ? "no target" : targets.join(", ");
294
+ const built = targets.some((target) => host.kindOf(joinPath(member.dir, target)) === "file");
295
+ return {
296
+ kind: "unresolved",
297
+ member: name,
298
+ subpath,
299
+ reason: built
300
+ ? `its manifest names ${named}, output no tsconfig*.json of the package maps back to source, and nothing installed links the importer to it; install the workspace, or point an exports condition the importing configuration's customConditions lists at its source`
301
+ : member.configurations.length === 0
302
+ ? `the package has no tsconfig*.json and its manifest names ${named}, which does not exist; build the package`
303
+ : `its manifest names ${named}, which does not exist and which no tsconfig*.json of the package compiles to; build the package, give the tsconfig.json that compiles it an outDir and rootDir that map those targets, or point an exports condition the importing configuration's customConditions lists at its source`,
304
+ };
305
+ },
306
+ };
307
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * What one run reads of the workspace its target sits in: the programs that read
3
+ * member imports from source, the configurations whose member imports have no source,
4
+ * the files each configuration holds only for their references, and the entry points
5
+ * the members' manifests name. A target in no workspace reads none of it.
6
+ */
7
+ import { relativePath } from "./paths.js";
8
+ import { findWorkspace, referenceOnlyIn } from "./workspace.js";
9
+ import { memberEntries, targetManifestIn } from "./workspace-manifests.js";
10
+ import { workspacePrograms, } from "./workspace-programs.js";
11
+ import { workspaceResolver } from "./workspace-resolution.js";
12
+ function layoutOf(engine, configFile) {
13
+ try {
14
+ const parsed = engine.parseConfigFile(configFile);
15
+ return {
16
+ fileNames: parsed.fileNames,
17
+ outDir: parsed.options.outDir,
18
+ declarationDir: parsed.options.declarationDir,
19
+ rootDir: parsed.options.rootDir,
20
+ };
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ /**
27
+ * The setup failure one configuration's unresolved member imports state, with the fix.
28
+ * An import in a file the configuration holds only for its references is that file's
29
+ * own configuration's to answer for, like any other error there.
30
+ */
31
+ function unbuildableReason(reading, referenceOnly, targetRoot) {
32
+ const own = reading.unresolved.filter((one) => !referenceOnly(one.file));
33
+ const [first] = own;
34
+ if (first === undefined) {
35
+ return undefined;
36
+ }
37
+ const where = relativePath(targetRoot, first.file) ?? first.file;
38
+ const more = own.length > 1 ? ` (and ${String(own.length - 1)} more)` : "";
39
+ return `${where} imports ${first.specifier}, a package of the workspace with no source to read: ${first.reason}${more}`;
40
+ }
41
+ /**
42
+ * The workspace the target at `targetRoot` sits in, read for the configurations named,
43
+ * or `undefined` where the target sits in none.
44
+ */
45
+ export function workspaceRun(engine, host, targetRoot, configFiles, manifest) {
46
+ const workspace = findWorkspace(host, targetRoot, (configFile) => layoutOf(engine, configFile));
47
+ if (workspace === undefined) {
48
+ return undefined;
49
+ }
50
+ const resolver = workspaceResolver(host, workspace);
51
+ const read = workspacePrograms(engine, host, configFiles, resolver);
52
+ const readings = new Map(read.readings.map((reading) => [reading.configFile, reading]));
53
+ const referenceOnly = (configFile, compiled = []) => referenceOnlyIn(workspace, new Set([...(readings.get(configFile)?.fileNames ?? []), ...compiled]));
54
+ return {
55
+ programs: read.programs,
56
+ componentsUnread: read.readings.flatMap(({ configFile, componentsUnread }) => componentsUnread === undefined ? [] : [{ configFile, reason: componentsUnread }]),
57
+ unbuildable: (configFile) => {
58
+ const reading = readings.get(configFile);
59
+ return reading === undefined
60
+ ? undefined
61
+ : unbuildableReason(reading, referenceOnly(configFile), targetRoot);
62
+ },
63
+ referenceOnly,
64
+ ownFile: (file, fromExternalLibrary) => workspace.memberOf(file) !== undefined
65
+ ? relativePath(targetRoot, file) !== undefined
66
+ : !fromExternalLibrary,
67
+ manifest: targetManifestIn(resolver, targetRoot, manifest),
68
+ entries: memberEntries(host, resolver, targetRoot),
69
+ memberDirs: workspace.members.map((member) => member.dir),
70
+ };
71
+ }
72
+ /** The errors one configuration answers for: none in a file it holds only for its references. */
73
+ export function ownErrors(errors, referenceOnly) {
74
+ return errors.filter(({ diagnostic }) => diagnostic.fileName === undefined || !referenceOnly(diagnostic.fileName));
75
+ }