@cplieger/deadset-ts 4.0.1 → 5.1.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 (138) 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 +301 -41
  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 +290 -0
  10. package/dist/src/conformance-record.js +3 -9
  11. package/dist/src/contextual-uses.js +240 -0
  12. package/dist/src/convention-rows.js +389 -0
  13. package/dist/src/conventions.js +565 -0
  14. package/dist/src/dependencies.js +36 -3
  15. package/dist/src/destructuring.js +6 -6
  16. package/dist/src/discover.js +10 -3
  17. package/dist/src/emit-map.js +72 -0
  18. package/dist/src/entry-point-gaps.js +81 -35
  19. package/dist/src/entry-points.js +50 -32
  20. package/dist/src/exempt-classes.js +6 -1
  21. package/dist/src/exempt.js +32 -20
  22. package/dist/src/findings/completion.js +46 -32
  23. package/dist/src/findings/emitters.js +22 -1
  24. package/dist/src/findings/interfaces.js +6 -2
  25. package/dist/src/findings/reads-and-writes.js +53 -9
  26. package/dist/src/findings/unused-declarations.js +47 -3
  27. package/dist/src/generated-file.js +24 -0
  28. package/dist/src/graph.js +13 -5
  29. package/dist/src/interface-satisfaction.js +2 -68
  30. package/dist/src/inventory.js +8 -3
  31. package/dist/src/kinds.js +6 -5
  32. package/dist/src/manifest.js +37 -11
  33. package/dist/src/matrix.js +21 -13
  34. package/dist/src/partial.js +3 -1
  35. package/dist/src/pnpm-workspace.js +120 -0
  36. package/dist/src/position.js +17 -2
  37. package/dist/src/print.js +2 -0
  38. package/dist/src/query.js +15 -0
  39. package/dist/src/references.js +404 -36
  40. package/dist/src/report.js +26 -6
  41. package/dist/src/reporters/annotations.js +10 -3
  42. package/dist/src/reporters/template.js +3 -0
  43. package/dist/src/resolve.js +37 -11
  44. package/dist/src/roots.js +37 -8
  45. package/dist/src/run.js +13 -3
  46. package/dist/src/schema.js +2 -0
  47. package/dist/src/session.js +141 -34
  48. package/dist/src/setup-failure.js +23 -0
  49. package/dist/src/source-files.js +4 -61
  50. package/dist/src/stores.js +3 -0
  51. package/dist/src/sweep.js +4 -2
  52. package/dist/src/template-field.js +38 -6
  53. package/dist/src/test-support.js +59 -0
  54. package/dist/src/type-errors.js +252 -0
  55. package/dist/src/type-query-alias.js +92 -0
  56. package/dist/src/value-flow.js +74 -0
  57. package/dist/src/verbs/analyze.js +24 -4
  58. package/dist/src/verbs/explain.js +4 -1
  59. package/dist/src/verbs/print-projects.js +17 -56
  60. package/dist/src/verbs/print-retained.js +7 -2
  61. package/dist/src/verbs/print-roots.js +7 -2
  62. package/dist/src/version-range.js +255 -0
  63. package/dist/src/version.js +1 -1
  64. package/dist/src/workspace-manifests.js +68 -0
  65. package/dist/src/workspace-programs.js +342 -0
  66. package/dist/src/workspace-resolution.js +307 -0
  67. package/dist/src/workspace-run.js +75 -0
  68. package/dist/src/workspace.js +358 -0
  69. package/jsr.json +1 -1
  70. package/package.json +3 -2
  71. package/src/analysis.ts +475 -72
  72. package/src/component-files.ts +718 -0
  73. package/src/component-layer.ts +160 -0
  74. package/src/config.ts +11 -2
  75. package/src/configuration-files.ts +370 -0
  76. package/src/conformance-record.ts +3 -10
  77. package/src/contextual-uses.ts +354 -0
  78. package/src/convention-rows.ts +448 -0
  79. package/src/conventions.ts +788 -0
  80. package/src/dependencies.ts +49 -2
  81. package/src/destructuring.ts +16 -6
  82. package/src/discover.ts +10 -6
  83. package/src/emit-map.ts +93 -0
  84. package/src/entry-point-gaps.ts +92 -38
  85. package/src/entry-points.ts +79 -36
  86. package/src/exempt-classes.ts +6 -1
  87. package/src/exempt.ts +69 -20
  88. package/src/file-facts.ts +2 -0
  89. package/src/findings/completion.ts +55 -36
  90. package/src/findings/emitters.ts +32 -1
  91. package/src/findings/interfaces.ts +9 -2
  92. package/src/findings/reads-and-writes.ts +70 -9
  93. package/src/findings/unused-declarations.ts +60 -3
  94. package/src/generated-file.ts +27 -0
  95. package/src/graph.ts +15 -4
  96. package/src/host.ts +23 -0
  97. package/src/index.ts +1 -1
  98. package/src/interface-satisfaction.ts +1 -92
  99. package/src/inventory.ts +17 -3
  100. package/src/kinds.ts +7 -5
  101. package/src/manifest.ts +47 -14
  102. package/src/matrix.ts +43 -15
  103. package/src/partial.ts +6 -1
  104. package/src/pnpm-workspace.ts +135 -0
  105. package/src/position.ts +23 -2
  106. package/src/print.ts +2 -0
  107. package/src/query.ts +26 -0
  108. package/src/references.ts +472 -40
  109. package/src/report.ts +56 -6
  110. package/src/reporters/annotations.ts +12 -3
  111. package/src/reporters/template.ts +3 -0
  112. package/src/resolve.ts +65 -11
  113. package/src/roots.ts +70 -9
  114. package/src/run.ts +15 -5
  115. package/src/schema.ts +2 -0
  116. package/src/session.ts +218 -37
  117. package/src/setup-failure.ts +39 -0
  118. package/src/source-files.ts +4 -66
  119. package/src/stores.ts +9 -0
  120. package/src/sweep.ts +9 -2
  121. package/src/template-field.ts +42 -7
  122. package/src/test-support.ts +77 -0
  123. package/src/type-errors.ts +340 -0
  124. package/src/type-query-alias.ts +133 -0
  125. package/src/value-flow.ts +104 -0
  126. package/src/verbs/analyze.ts +38 -4
  127. package/src/verbs/explain.ts +4 -1
  128. package/src/verbs/print-projects.ts +17 -69
  129. package/src/verbs/print-retained.ts +7 -2
  130. package/src/verbs/print-roots.ts +7 -2
  131. package/src/verbs/verb.ts +6 -3
  132. package/src/version-range.ts +309 -0
  133. package/src/version.ts +1 -1
  134. package/src/workspace-manifests.ts +100 -0
  135. package/src/workspace-programs.ts +486 -0
  136. package/src/workspace-resolution.ts +393 -0
  137. package/src/workspace-run.ts +148 -0
  138. package/src/workspace.ts +463 -0
@@ -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
+ }
@@ -0,0 +1,358 @@
1
+ /**
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`.
8
+ */
9
+ import { DiscoveryError } from "./discover.js";
10
+ import { commonDirectory } from "./emit-map.js";
11
+ import { dirnamePath, joinPath, relativePath } from "./paths.js";
12
+ import { pnpmPackages } from "./pnpm-workspace.js";
13
+ const MANIFEST = "package.json";
14
+ const PNPM_WORKSPACE = "pnpm-workspace.yaml";
15
+ const CONFIG_FILE = /^tsconfig.*\.json$/u;
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ /** One manifest, decoded, or `undefined` where there is none to read. */
20
+ function manifestAt(host, dir) {
21
+ let text;
22
+ try {
23
+ text = host.readFile(joinPath(dir, MANIFEST));
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ try {
29
+ const value = JSON.parse(text);
30
+ return isRecord(value) ? value : undefined;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ /** The patterns one directory declares a workspace with, or `undefined` where it declares none. */
37
+ function declarationAt(host, dir) {
38
+ const pnpm = joinPath(dir, PNPM_WORKSPACE);
39
+ if (host.kindOf(pnpm) === "file") {
40
+ const read = pnpmPackages(host.readFile(pnpm));
41
+ if (read.kind === "refused") {
42
+ throw new DiscoveryError(`${pnpm}: ${read.reason}; the workspace's packages are read from a sequence of strings`, []);
43
+ }
44
+ return { file: pnpm, patterns: read.kind === "listed" ? read.patterns : [] };
45
+ }
46
+ const manifest = manifestAt(host, dir);
47
+ if (manifest === undefined || !Object.hasOwn(manifest, "workspaces")) {
48
+ return undefined;
49
+ }
50
+ const declared = manifest["workspaces"];
51
+ const list = isRecord(declared) ? declared["packages"] : declared;
52
+ if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) {
53
+ throw new DiscoveryError(`${joinPath(dir, MANIFEST)}: workspaces is neither an array of strings nor an object whose packages is one`, []);
54
+ }
55
+ return { file: joinPath(dir, MANIFEST), patterns: list };
56
+ }
57
+ /** Whether one directory declares a workspace of its own. */
58
+ function declaresWorkspace(host, dir) {
59
+ if (host.kindOf(joinPath(dir, PNPM_WORKSPACE)) === "file") {
60
+ return true;
61
+ }
62
+ const manifest = manifestAt(host, dir);
63
+ return manifest !== undefined && Object.hasOwn(manifest, "workspaces");
64
+ }
65
+ /** A pattern with every brace list expanded into its alternatives. */
66
+ function braceExpanded(pattern) {
67
+ const open = pattern.indexOf("{");
68
+ const close = open < 0 ? -1 : pattern.indexOf("}", open);
69
+ if (close < 0) {
70
+ return [pattern];
71
+ }
72
+ const before = pattern.slice(0, open);
73
+ const after = pattern.slice(close + 1);
74
+ return pattern
75
+ .slice(open + 1, close)
76
+ .split(",")
77
+ .flatMap((member) => braceExpanded(before + member + after));
78
+ }
79
+ /**
80
+ * The segments of one pattern below the workspace root, `.` and empty segments
81
+ * dropped and `..` taken back, or `undefined` where it leaves the root.
82
+ */
83
+ function segmentsOf(pattern) {
84
+ const segments = [];
85
+ for (const segment of pattern.split("/")) {
86
+ if (segment === "" || segment === ".") {
87
+ continue;
88
+ }
89
+ if (segment === "..") {
90
+ if (segments.pop() === undefined) {
91
+ return undefined;
92
+ }
93
+ continue;
94
+ }
95
+ segments.push(segment);
96
+ }
97
+ return segments;
98
+ }
99
+ /**
100
+ * Whether one directory name matches one pattern segment: `*` stands for any run of
101
+ * characters and `?` for one, and neither matches a leading dot.
102
+ */
103
+ function segmentMatches(segment, name) {
104
+ if (name.startsWith(".") && !segment.startsWith(".")) {
105
+ return false;
106
+ }
107
+ const source = Array.from(segment)
108
+ .map((character) => {
109
+ if (character === "*") {
110
+ return ".*";
111
+ }
112
+ if (character === "?") {
113
+ return ".";
114
+ }
115
+ return character.replaceAll(/[.+^${}()|[\]\\/]/gu, (c) => `\\${c}`);
116
+ })
117
+ .join("");
118
+ return new RegExp(`^${source}$`, "u").test(name);
119
+ }
120
+ /** Whether one path's segments match one pattern's, `**` standing for any run of segments. */
121
+ function pathMatches(pattern, path) {
122
+ const [head, ...rest] = pattern;
123
+ if (head === undefined) {
124
+ return path.length === 0;
125
+ }
126
+ if (head === "**") {
127
+ return (pathMatches(rest, path) ||
128
+ (path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1))));
129
+ }
130
+ const [name, ...below] = path;
131
+ return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
132
+ }
133
+ function subdirectories(host, dir) {
134
+ try {
135
+ return host
136
+ .readDirectory(dir)
137
+ .filter((entry) => entry.directory && entry.name !== "node_modules" && entry.name !== ".git")
138
+ .map((entry) => entry.name)
139
+ .sort();
140
+ }
141
+ catch {
142
+ return [];
143
+ }
144
+ }
145
+ /**
146
+ * Every directory below `dir` one pattern names. `**` does not descend into a
147
+ * directory that declares a workspace of its own, whose packages are that
148
+ * workspace's to declare.
149
+ */
150
+ function expand(host, dir, pattern, into) {
151
+ const [head, ...rest] = pattern;
152
+ if (head === undefined) {
153
+ into.add(dir);
154
+ return;
155
+ }
156
+ if (head === "**") {
157
+ expand(host, dir, rest, into);
158
+ for (const name of subdirectories(host, dir)) {
159
+ if (name.startsWith(".")) {
160
+ continue;
161
+ }
162
+ const child = joinPath(dir, name);
163
+ expand(host, child, declaresWorkspace(host, child) ? rest : pattern, into);
164
+ }
165
+ return;
166
+ }
167
+ if (!head.includes("*") && !head.includes("?")) {
168
+ const child = joinPath(dir, head);
169
+ if (head !== "node_modules" && host.kindOf(child) === "directory") {
170
+ expand(host, child, rest, into);
171
+ }
172
+ return;
173
+ }
174
+ for (const name of subdirectories(host, dir)) {
175
+ if (segmentMatches(head, name)) {
176
+ expand(host, joinPath(dir, name), rest, into);
177
+ }
178
+ }
179
+ }
180
+ /** The directories a workspace's patterns name, every `!` pattern applied after the rest. */
181
+ function memberDirectories(host, root, patterns) {
182
+ const included = new Set([root]);
183
+ const excluded = [];
184
+ for (const written of patterns) {
185
+ const negated = written.startsWith("!");
186
+ for (const pattern of braceExpanded(negated ? written.slice(1) : written)) {
187
+ const segments = segmentsOf(pattern);
188
+ if (segments === undefined) {
189
+ continue;
190
+ }
191
+ if (negated) {
192
+ excluded.push(segments);
193
+ }
194
+ else {
195
+ expand(host, root, segments, included);
196
+ }
197
+ }
198
+ }
199
+ return [...included].filter((dir) => {
200
+ const below = relativePath(root, dir);
201
+ const segments = below === undefined || below === "." || below === "" ? [] : below.split("/");
202
+ return dir === root || !excluded.some((pattern) => pathMatches(pattern, segments));
203
+ });
204
+ }
205
+ /** Whether a workspace's patterns name one directory below its root, read without listing any. */
206
+ function namesDirectory(root, patterns, dir) {
207
+ const below = relativePath(root, dir);
208
+ if (below === undefined || below === "" || below.startsWith("..")) {
209
+ return false;
210
+ }
211
+ const path = below.split("/");
212
+ if (path.includes("node_modules")) {
213
+ return false;
214
+ }
215
+ let named = false;
216
+ for (const written of patterns) {
217
+ const negated = written.startsWith("!");
218
+ const matched = braceExpanded(negated ? written.slice(1) : written).some((pattern) => {
219
+ const segments = segmentsOf(pattern);
220
+ return segments !== undefined && pathMatches(segments, path);
221
+ });
222
+ if (matched && negated) {
223
+ return false;
224
+ }
225
+ named ||= matched;
226
+ }
227
+ return named;
228
+ }
229
+ /** The layout one parsed configuration writes with, where it writes any output. */
230
+ function layoutOf(configFile, parsed) {
231
+ if (parsed.outDir === undefined && parsed.declarationDir === undefined) {
232
+ return undefined;
233
+ }
234
+ const inputs = parsed.fileNames.filter((file) => !/\.d\.[mc]?ts$/u.test(file));
235
+ const sourceDirs = parsed.rootDir === undefined
236
+ ? [...new Set([dirnamePath(configFile), commonDirectory(inputs)])].filter((dir) => dir !== "")
237
+ : [parsed.rootDir];
238
+ return { outDir: parsed.outDir, declarationDir: parsed.declarationDir, sourceDirs };
239
+ }
240
+ function configurationsOf(host, dir, parse) {
241
+ let names;
242
+ try {
243
+ names = host
244
+ .readDirectory(dir)
245
+ .filter((entry) => !entry.directory && CONFIG_FILE.test(entry.name))
246
+ .map((entry) => entry.name)
247
+ .sort();
248
+ }
249
+ catch {
250
+ return [];
251
+ }
252
+ return names.flatMap((name) => {
253
+ const configFile = joinPath(dir, name);
254
+ const parsed = parse(configFile);
255
+ return parsed === undefined
256
+ ? []
257
+ : [
258
+ {
259
+ configFile,
260
+ fileNames: new Set(parsed.fileNames),
261
+ layout: layoutOf(configFile, parsed),
262
+ },
263
+ ];
264
+ });
265
+ }
266
+ /**
267
+ * The directory declaring the workspace nearest the target root, its patterns, and the
268
+ * highest directory below it, at or above the target root, that holds a manifest.
269
+ */
270
+ function nearestDeclaration(host, targetRoot) {
271
+ let dir = targetRoot;
272
+ let highestPackage;
273
+ for (;;) {
274
+ const declared = declarationAt(host, dir);
275
+ if (declared !== undefined) {
276
+ return { root: dir, ...declared, highestPackage };
277
+ }
278
+ if (host.kindOf(joinPath(dir, MANIFEST)) === "file") {
279
+ highestPackage = dir;
280
+ }
281
+ const up = dirnamePath(dir);
282
+ if (up === dir || host.kindOf(joinPath(dir, ".git")) !== "absent") {
283
+ return undefined;
284
+ }
285
+ dir = up;
286
+ }
287
+ }
288
+ /**
289
+ * The workspace the target sits in, or `undefined` where none holds it: no directory
290
+ * from the target root up to its repository root declares one with a member beside
291
+ * its root, or the nearest one does not name the highest package below it, at or
292
+ * above the target root, among its members. `parse` reads one compiler configuration,
293
+ * or answers `undefined` where it does not read.
294
+ */
295
+ export function findWorkspace(host, targetRoot, parse) {
296
+ const declared = nearestDeclaration(host, targetRoot);
297
+ if (declared === undefined) {
298
+ return undefined;
299
+ }
300
+ if (declared.highestPackage !== undefined &&
301
+ !namesDirectory(declared.root, declared.patterns, declared.highestPackage)) {
302
+ return undefined;
303
+ }
304
+ const members = [];
305
+ for (const dir of memberDirectories(host, declared.root, declared.patterns)) {
306
+ const manifest = manifestAt(host, dir);
307
+ const name = manifest?.["name"];
308
+ if (manifest === undefined || typeof name !== "string" || name === "") {
309
+ continue;
310
+ }
311
+ const version = manifest["version"];
312
+ members.push({
313
+ name,
314
+ dir,
315
+ version: typeof version === "string" ? version : undefined,
316
+ private: manifest["private"] === true,
317
+ manifest,
318
+ configurations: configurationsOf(host, dir, parse),
319
+ });
320
+ }
321
+ if (members.length < 2) {
322
+ return undefined;
323
+ }
324
+ members.sort((a, b) => a.dir === declared.root ? -1 : b.dir === declared.root ? 1 : a.dir < b.dir ? -1 : 1);
325
+ const named = new Map();
326
+ for (const member of members) {
327
+ named.set(member.name, [...(named.get(member.name) ?? []), member]);
328
+ }
329
+ const byDir = new Map(members.map((member) => [member.dir, member]));
330
+ const scopes = new Map();
331
+ const scopeOf = (dir) => {
332
+ if (scopes.has(dir)) {
333
+ return scopes.get(dir);
334
+ }
335
+ const up = dirnamePath(dir);
336
+ const found = host.kindOf(joinPath(dir, MANIFEST)) === "file" ? dir : up === dir ? undefined : scopeOf(up);
337
+ scopes.set(dir, found);
338
+ return found;
339
+ };
340
+ return {
341
+ root: declared.root,
342
+ declaredIn: declared.file,
343
+ members,
344
+ named,
345
+ memberOf: (path) => {
346
+ const scope = scopeOf(dirnamePath(path));
347
+ return scope === undefined ? undefined : byDir.get(scope);
348
+ },
349
+ };
350
+ }
351
+ /**
352
+ * Whether one configuration holds one file only for the references it makes: the file
353
+ * belongs to a workspace package, the root included, and the configuration does not
354
+ * compile it itself. `compiles` is the configuration's own file list.
355
+ */
356
+ export function referenceOnlyIn(workspace, compiles) {
357
+ return (file) => !compiles.has(file) && workspace.memberOf(file) !== undefined;
358
+ }
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "4.0.1",
3
+ "version": "5.1.0",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "4.0.1",
3
+ "version": "5.1.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
@@ -23,6 +23,7 @@
23
23
  "typecheck": "tsc -p tsconfig.json",
24
24
  "typecheck:tests": "tsc -p tsconfig.test.json",
25
25
  "test": "vitest --run",
26
+ "test:starters": "vitest --run --config starters/vitest.config.ts",
26
27
  "lint": "eslint .",
27
28
  "lint:prettier": "prettier --check .",
28
29
  "lint:fix": "eslint . --fix && prettier --write ."
@@ -32,7 +33,7 @@
32
33
  },
33
34
  "devDependencies": {
34
35
  "@eslint/js": "10.0.1",
35
- "@types/node": "24.19.0",
36
+ "@types/node": "24.19.1",
36
37
  "eslint": "10.11.0",
37
38
  "fast-check": "4.10.2",
38
39
  "prettier": "3.9.9",