@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

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 (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -1,47 +1,34 @@
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 } from "./discover.js";
7
+ import { configFilesIn } from "./discover.js";
8
+ import { failureExit } from "../failure.js";
10
9
  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";
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, 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";
14
17
  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
18
  /** The patterns one directory declares a workspace with, or `undefined` where it declares none. */
37
19
  function declarationAt(host, dir) {
38
20
  const pnpm = joinPath(dir, PNPM_WORKSPACE);
39
21
  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`, []);
22
+ const read = yamlKey(host.readFile(pnpm), "packages");
23
+ if (read.kind === "absent") {
24
+ return { file: pnpm, patterns: [] };
43
25
  }
44
- return { file: pnpm, patterns: read.kind === "listed" ? read.patterns : [] };
26
+ const listed = read.kind === "value" ? read.value : undefined;
27
+ if (!Array.isArray(listed) || !listed.every((entry) => typeof entry === "string")) {
28
+ const reason = read.kind === "refused" ? read.reason : "packages is not 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`);
30
+ }
31
+ return { file: pnpm, patterns: listed };
45
32
  }
46
33
  const manifest = manifestAt(host, dir);
47
34
  if (manifest === undefined || !Object.hasOwn(manifest, "workspaces")) {
@@ -50,7 +37,7 @@ function declarationAt(host, dir) {
50
37
  const declared = manifest["workspaces"];
51
38
  const list = isRecord(declared) ? declared["packages"] : declared;
52
39
  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`, []);
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`);
54
41
  }
55
42
  return { file: joinPath(dir, MANIFEST), patterns: list };
56
43
  }
@@ -62,20 +49,6 @@ function declaresWorkspace(host, dir) {
62
49
  const manifest = manifestAt(host, dir);
63
50
  return manifest !== undefined && Object.hasOwn(manifest, "workspaces");
64
51
  }
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
52
  /**
80
53
  * The segments of one pattern below the workspace root, `.` and empty segments
81
54
  * dropped and `..` taken back, or `undefined` where it leaves the root.
@@ -96,52 +69,6 @@ function segmentsOf(pattern) {
96
69
  }
97
70
  return segments;
98
71
  }
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
72
  /**
146
73
  * Every directory below `dir` one pattern names. `**` does not descend into a
147
74
  * directory that declares a workspace of its own, whose packages are that
@@ -155,7 +82,7 @@ function expand(host, dir, pattern, into) {
155
82
  }
156
83
  if (head === "**") {
157
84
  expand(host, dir, rest, into);
158
- for (const name of subdirectories(host, dir)) {
85
+ for (const name of directoriesIn(host, dir)) {
159
86
  if (name.startsWith(".")) {
160
87
  continue;
161
88
  }
@@ -166,12 +93,12 @@ function expand(host, dir, pattern, into) {
166
93
  }
167
94
  if (!head.includes("*") && !head.includes("?")) {
168
95
  const child = joinPath(dir, head);
169
- if (head !== "node_modules" && host.kindOf(child) === "directory") {
96
+ if (head !== INSTALLED_PACKAGES && host.kindOf(child) === "directory") {
170
97
  expand(host, child, rest, into);
171
98
  }
172
99
  return;
173
100
  }
174
- for (const name of subdirectories(host, dir)) {
101
+ for (const name of directoriesIn(host, dir)) {
175
102
  if (segmentMatches(head, name)) {
176
103
  expand(host, joinPath(dir, name), rest, into);
177
104
  }
@@ -208,10 +135,10 @@ function namesDirectory(root, patterns, dir) {
208
135
  if (below === undefined) {
209
136
  return false;
210
137
  }
211
- const path = below.split("/");
212
- if (path.includes("node_modules")) {
138
+ if (inInstalledPackages(below)) {
213
139
  return false;
214
140
  }
141
+ const path = below.split("/");
215
142
  let named = false;
216
143
  for (const written of patterns) {
217
144
  const negated = written.startsWith("!");
@@ -238,18 +165,7 @@ function layoutOf(configFile, parsed) {
238
165
  return { outDir: parsed.outDir, declarationDir: parsed.declarationDir, sourceDirs };
239
166
  }
240
167
  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) => {
168
+ return configFilesIn(host, dir).flatMap((name) => {
253
169
  const configFile = joinPath(dir, name);
254
170
  const parsed = parse(configFile);
255
171
  return parsed === undefined
@@ -267,31 +183,27 @@ function configurationsOf(host, dir, parse) {
267
183
  * highest directory below it, at or above the target root, that holds a manifest.
268
184
  */
269
185
  function nearestDeclaration(host, targetRoot) {
270
- let dir = targetRoot;
271
186
  let highestPackage;
272
- for (;;) {
187
+ for (const dir of ancestors(targetRoot, repositoryOf(host, targetRoot)?.root)) {
273
188
  const declared = declarationAt(host, dir);
274
189
  if (declared !== undefined) {
275
190
  return { root: dir, ...declared, highestPackage };
276
191
  }
277
- if (host.kindOf(joinPath(dir, MANIFEST)) === "file") {
192
+ if (holdsManifest(host, dir)) {
278
193
  highestPackage = dir;
279
194
  }
280
- const up = dirnamePath(dir);
281
- if (up === dir || host.kindOf(joinPath(dir, ".git")) !== "absent") {
282
- return undefined;
283
- }
284
- dir = up;
285
195
  }
196
+ return undefined;
286
197
  }
287
198
  /**
288
199
  * The workspace the target sits in, or `undefined` where none holds it: no directory
289
200
  * from the target root up to its repository root declares one with a member beside
290
201
  * its root, or the nearest one does not name the highest package below it, at or
291
202
  * above the target root, among its members. `parse` reads one compiler configuration,
292
- * 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.
293
205
  */
294
- export function findWorkspace(host, targetRoot, parse) {
206
+ export function findWorkspace(host, targetRoot, parse, known) {
295
207
  const declared = nearestDeclaration(host, targetRoot);
296
208
  if (declared === undefined) {
297
209
  return undefined;
@@ -300,6 +212,9 @@ export function findWorkspace(host, targetRoot, parse) {
300
212
  !namesDirectory(declared.root, declared.patterns, declared.highestPackage)) {
301
213
  return undefined;
302
214
  }
215
+ if (known?.root === declared.root) {
216
+ return known;
217
+ }
303
218
  const members = [];
304
219
  for (const dir of memberDirectories(host, declared.root, declared.patterns)) {
305
220
  const manifest = manifestAt(host, dir);
@@ -320,7 +235,10 @@ export function findWorkspace(host, targetRoot, parse) {
320
235
  if (members.length < 2) {
321
236
  return undefined;
322
237
  }
323
- members.sort((a, b) => a.dir === declared.root ? -1 : b.dir === declared.root ? 1 : a.dir < b.dir ? -1 : 1);
238
+ members.sort((a, b) => a.dir === declared.root ? -1 : b.dir === declared.root ? 1 : bytewise(a.dir, b.dir));
239
+ return assembled(host, declared.root, members);
240
+ }
241
+ function assembled(host, root, members) {
324
242
  const named = new Map();
325
243
  for (const member of members) {
326
244
  named.set(member.name, [...(named.get(member.name) ?? []), member]);
@@ -332,11 +250,12 @@ export function findWorkspace(host, targetRoot, parse) {
332
250
  return scopes.get(dir);
333
251
  }
334
252
  const up = dirnamePath(dir);
335
- const found = host.kindOf(joinPath(dir, MANIFEST)) === "file" ? dir : up === dir ? undefined : scopeOf(up);
253
+ const found = holdsManifest(host, dir) ? dir : up === dir ? undefined : scopeOf(up);
336
254
  scopes.set(dir, found);
337
255
  return found;
338
256
  };
339
257
  return {
258
+ root,
340
259
  members,
341
260
  named,
342
261
  memberOf: (path) => {
@@ -345,6 +264,25 @@ export function findWorkspace(host, targetRoot, parse) {
345
264
  },
346
265
  };
347
266
  }
267
+ /**
268
+ * The workspace with the programs the run forms put first among each member's configurations,
269
+ * by the member's directory: a formed program has no file on disk to list, so its file list
270
+ * is what makes a member's JavaScript its source.
271
+ */
272
+ export function withFormed(host, workspace, formed) {
273
+ return assembled(host, workspace.root, workspace.members.map((member) => {
274
+ const own = formed.get(member.dir) ?? [];
275
+ return own.length === 0
276
+ ? member
277
+ : {
278
+ ...member,
279
+ configurations: [
280
+ ...own.map((layout) => ({ fileNames: new Set(layout.fileNames) })),
281
+ ...member.configurations,
282
+ ],
283
+ };
284
+ }));
285
+ }
348
286
  /**
349
287
  * Whether one configuration holds one file only for the references it makes: the file
350
288
  * belongs to a workspace package, the root included, and the configuration does not
@@ -5,13 +5,12 @@
5
5
  * directory's manifest, {@link packageEntries} those of the packages below the target root.
6
6
  */
7
7
  import { isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
8
+ import { isRecord } from "./json-document.js";
9
+ import { MANIFEST, manifestAt, readJSON } from "./fs/manifest.js";
10
+ import { scriptTokens } from "./text/shell.js";
8
11
  /** The manifest's own name. */
9
- const MANIFEST = "package.json";
10
12
  /** The three members that name an entry point beside `exports` and `bin`. */
11
13
  const LEGACY_MEMBERS = ["main", "module", "types"];
12
- function isRecord(value) {
13
- return typeof value === "object" && value !== null && !Array.isArray(value);
14
- }
15
14
  /**
16
15
  * Whether one member's value names a file of this package rather than another
17
16
  * package or a URL. A target written with no leading dot is a path too, which is
@@ -23,31 +22,32 @@ function isTarget(value, requireDot) {
23
22
  }
24
23
  return !requireDot || value.startsWith("./");
25
24
  }
26
- /** Where a script's command line splits into tokens: white space and the shell's operators. */
27
- const SCRIPT_SEPARATORS = /\s+|&&|\|\||;|\|/u;
28
- /** A token written inside one pair of quotes. */
29
- const QUOTED = /^(["'])(.*)\1$/su;
30
- /**
31
- * Every token one script's command line holds, each with one pair of enclosing quotes
32
- * removed. A token holding a wildcard is a pattern rather than a path, and names no
33
- * entry point.
34
- */
35
- export function scriptTokens(command) {
36
- return command
37
- .split(SCRIPT_SEPARATORS)
38
- .map((token) => token.replace(QUOTED, "$2"))
39
- .filter((token) => token !== "" && !token.includes("*"));
40
- }
41
25
  /** One member's spelling below another: `exports` then `exports["."]`. */
42
26
  function below(member, key) {
43
27
  return `${member}[${JSON.stringify(key)}]`;
44
28
  }
45
29
  /**
46
- * Every target one `exports` value carries, at the member each is written under.
47
- *
48
- * The value nests strings, subpath or condition objects and alternative arrays to
49
- * any depth; a `null` blocks a subpath. Every level is walked, because each
50
- * condition names its own entry file, and a key's meaning is never read.
30
+ * The command strings one manifest runs, each at the member it is written under: each of its
31
+ * `scripts`, and the `exec` command of nodemon's configuration, in the manifest's `nodemonConfig`
32
+ * or the `nodemon.json` beside it, which nodemon runs the watched script with:
33
+ * https://github.com/remy/nodemon#config-files
34
+ */
35
+ export function manifestCommands(host, dir, manifest) {
36
+ const scripts = manifest["scripts"];
37
+ const nodemon = {
38
+ nodemonConfig: manifest["nodemonConfig"],
39
+ "nodemon.json": readJSON(host, joinPath(dir, "nodemon.json")),
40
+ };
41
+ const written = [
42
+ ...Object.entries(isRecord(scripts) ? scripts : {}).map(([name, value]) => [below("scripts", name), value]),
43
+ ...Object.entries(nodemon).map(([member, config]) => [below(member, "exec"), isRecord(config) ? config["exec"] : undefined]),
44
+ ];
45
+ return written.flatMap(([member, command]) => typeof command === "string" ? [{ member, command }] : []);
46
+ }
47
+ /**
48
+ * Every target one `exports` value carries, at the member each is written under. Every level of
49
+ * strings, subpath or condition objects and alternative arrays is walked, because each condition
50
+ * names its own entry file; a `null` blocks a subpath, and a key's meaning is never read.
51
51
  */
52
52
  function exportTargets(value, member, found) {
53
53
  if (typeof value === "string") {
@@ -69,28 +69,6 @@ function exportTargets(value, member, found) {
69
69
  exportTargets(held, below(member, key), found);
70
70
  }
71
71
  }
72
- /** The manifest at the target root, decoded, or undefined where there is none to read. */
73
- function manifestOf(host, targetRoot) {
74
- let text;
75
- try {
76
- text = host.readFile(joinPath(targetRoot, MANIFEST));
77
- }
78
- catch {
79
- return undefined;
80
- }
81
- let value;
82
- try {
83
- value = JSON.parse(text);
84
- }
85
- catch {
86
- // A manifest this program cannot read names no entry point, which is the same
87
- // answer as an absent one. What a manifest's contents say is not refused here:
88
- // the package manager owns that refusal, and an analysis of a tree it rejects
89
- // is not the run's subject.
90
- return undefined;
91
- }
92
- return isRecord(value) ? value : undefined;
93
- }
94
72
  /**
95
73
  * What the manifest in `targetRoot` declares about where its package is entered.
96
74
  *
@@ -99,7 +77,7 @@ function manifestOf(host, targetRoot) {
99
77
  * it and there is one such configuration per project, while there is one manifest.
100
78
  */
101
79
  export function readManifest(host, targetRoot) {
102
- const manifest = manifestOf(host, targetRoot);
80
+ const manifest = manifestAt(host, targetRoot);
103
81
  if (manifest === undefined) {
104
82
  return { entries: [], declaresExports: false };
105
83
  }
@@ -110,6 +88,24 @@ export function readManifest(host, targetRoot) {
110
88
  found.push({ member, path: value, role: "import", published: true });
111
89
  }
112
90
  }
91
+ // A `browser` string replaces `main` in a browser bundle, and an object replaces each
92
+ // module it maps with the file it names: https://github.com/defunctzombie/package-browser-field-spec
93
+ const browser = manifest["browser"];
94
+ if (typeof browser === "string" && isTarget(browser, false)) {
95
+ found.push({ member: "browser", path: browser, role: "import", published: true });
96
+ }
97
+ else if (isRecord(browser)) {
98
+ for (const [replaced, value] of Object.entries(browser)) {
99
+ if (typeof value === "string" && isTarget(value, true)) {
100
+ found.push({
101
+ member: below("browser", replaced),
102
+ path: value,
103
+ role: "import",
104
+ published: true,
105
+ });
106
+ }
107
+ }
108
+ }
113
109
  const bin = manifest["bin"];
114
110
  if (typeof bin === "string" && isTarget(bin, false)) {
115
111
  found.push({ member: "bin", path: bin, role: "run", published: false });
@@ -128,16 +124,15 @@ export function readManifest(host, targetRoot) {
128
124
  if (typeof command !== "string") {
129
125
  continue;
130
126
  }
131
- for (const token of new Set(scriptTokens(command))) {
132
- found.push({
133
- member: below("scripts", name),
134
- path: token,
135
- role: "script",
136
- published: false,
137
- });
127
+ const member = below("scripts", name);
128
+ for (const path of new Set(scriptTokens(command))) {
129
+ found.push({ member, path, role: "script", published: false });
138
130
  }
139
131
  }
140
132
  }
133
+ for (const { member, command } of manifestCommands(host, targetRoot, manifest)) {
134
+ found.push({ member, path: ".", role: "script", published: false, command });
135
+ }
141
136
  return {
142
137
  entries: found.map((entry) => ({
143
138
  ...entry,
@@ -70,11 +70,13 @@ export function matrixOf(per) {
70
70
  }
71
71
  });
72
72
  symbols.sort((a, b) => byPosition(a.position, b.position));
73
+ const testUsed = new Set(per.flatMap((one) => one.testUsedFiles ?? []));
73
74
  return {
74
75
  configurations: per.map((one) => one.configuration),
75
- graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles)),
76
- union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))], [...new Set(per.flatMap((one) => one.supportFiles ?? []))]),
76
+ graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles, one.ambient)),
77
+ union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))], [...new Set(per.flatMap((one) => one.supportFiles ?? []))], new Set(per.flatMap((one) => [...(one.ambient ?? [])]))),
77
78
  heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
79
+ testUsed: symbols.map((symbol) => testUsed.has(symbol.position.path)),
78
80
  dependents,
79
81
  };
80
82
  }
@@ -100,29 +102,36 @@ export function sweepMatrix(matrix, input) {
100
102
  new Map((per[index]?.candidates ?? []).map((candidate) => [candidate.id, candidate])),
101
103
  ]));
102
104
  const union = matrix.union;
103
- const dead = union.symbols.map(() => false);
104
- const testOfDeadCode = union.symbols.map(() => false);
105
105
  const heldBy = union.symbols.map(() => undefined);
106
- const unreferencedTest = union.symbols.map(() => false);
107
- const testReached = reachedWithTheTests(union, [
106
+ const held = [
108
107
  ...input.marked,
109
108
  ...(input.exempt ?? []).map((record) => record.id),
110
109
  ...(input.unanswered ?? []),
111
- ]);
112
- const judged = union.symbols.map((symbol, at) => {
110
+ ];
111
+ const testReached = reachedWithTheTests(union, held);
112
+ // A test uses what it names: a file a test reads through a star re-export or a
113
+ // namespace keeps the exports nothing names at their own class.
114
+ const testNamed = reachedWithTheTests(union, held, false);
115
+ const everywhere = union.symbols.map((symbol, at) => {
113
116
  const held = matrix.heldIn[at] ?? [];
114
117
  const found = held.map((configuration) => byConfiguration.get(configuration)?.get(symbol.id));
115
118
  return held.length === 0 || found.some((candidate) => candidate === undefined)
116
119
  ? undefined
117
120
  : found.filter((candidate) => candidate !== undefined);
118
121
  });
122
+ const compiled = compiledReferences(matrix, new Set(union.symbols.flatMap((symbol, at) => (everywhere[at] === undefined ? [] : [symbol.id]))), input.mode);
123
+ const judged = everywhere.map((found, at) => compiled.has(union.symbols[at]?.id ?? "") ? undefined : found);
119
124
  // A configuration that does not hold a test's subject's caller finds the subject dead,
120
125
  // so a test is of dead code only where every production subject it names is dead in
121
126
  // every configuration that holds it.
122
127
  const subjectsDead = (at) => (union.out[at] ?? []).every((edge) => union.test[edge.to] === true ||
123
128
  union.support[edge.to] === true ||
129
+ union.ambient[edge.to] === true ||
124
130
  union.subject[edge.to] !== true ||
125
131
  judged[edge.to] !== undefined);
132
+ const admittedAt = (at) => judged[at]?.every((candidate) => candidate.testOfDeadCode) === true && subjectsDead(at);
133
+ const kept = keptByTests(union, testReached, admittedAt);
134
+ const testUsed = testUsedReach(union, union.symbols.map((_symbol, at) => matrix.testUsed[at] === true && testNamed[at] === true), judged.map((one) => one !== undefined));
126
135
  const candidates = [];
127
136
  union.symbols.forEach((symbol, at) => {
128
137
  const all = judged[at];
@@ -130,15 +139,12 @@ export function sweepMatrix(matrix, input) {
130
139
  return;
131
140
  }
132
141
  const held = matrix.heldIn[at] ?? [];
133
- const admitted = all.every((candidate) => candidate.testOfDeadCode) && subjectsDead(at);
142
+ const admitted = admittedAt(at);
134
143
  const testRefs = union.made[at]?.test ?? 0;
135
144
  const heldByTests = heldBy[union.parent[at] ?? OUTSIDE] ??
136
145
  (union.support[at] === true && testRefs > 0 && !admitted ? symbol.id : undefined);
137
146
  const unreferenced = union.test[at] === true && !admitted && testReached[at] !== true;
138
- dead[at] = true;
139
- testOfDeadCode[at] = admitted;
140
147
  heldBy[at] = heldByTests;
141
- unreferencedTest[at] = unreferenced;
142
148
  const productionRefs = union.made[at]?.production ?? 0;
143
149
  const counted = all.every((candidate) => candidate.relation === "reference-counting");
144
150
  candidates.push({
@@ -153,15 +159,90 @@ export function sweepMatrix(matrix, input) {
153
159
  configurations: held,
154
160
  heldByTests,
155
161
  unreferencedTest: unreferenced,
162
+ testUsed: testUsed[at] === true,
163
+ keptByTests: kept[at] === true,
156
164
  });
157
165
  });
158
166
  const liveUnder = liveAnywhere(union.symbols, per);
159
167
  return {
160
168
  liveUnder,
161
169
  candidates,
162
- components: componentsOf(union, dead.map((one, at) => one && heldBy[at] === undefined), testOfDeadCode, unreferencedTest, positionsOf(union, matrix.dependents, liveUnder)),
170
+ components: deadComponents(matrix, { liveUnder, candidates }, new Set()),
163
171
  };
164
172
  }
173
+ /**
174
+ * Every declaration of `dead`, those dead in every configuration that holds them, that a
175
+ * configuration's graph references from a judged declaration outside `dead` or already
176
+ * kept, until none joins: deleting one would break that configuration's compile. A
177
+ * production run counts no reference a test file made.
178
+ */
179
+ function compiledReferences(matrix, dead, mode) {
180
+ const kept = new Set();
181
+ for (let changed = true; changed;) {
182
+ changed = false;
183
+ for (const graph of matrix.graphs) {
184
+ graph.out.forEach((edges, from) => {
185
+ const id = graph.symbols[from]?.id ?? "";
186
+ if (graph.subject[from] !== true || (dead.has(id) && !kept.has(id))) {
187
+ return;
188
+ }
189
+ for (const edge of edges) {
190
+ const to = graph.symbols[edge.to]?.id ?? "";
191
+ if ((!mode.production || !edge.test) && dead.has(to) && !kept.has(to)) {
192
+ kept.add(to);
193
+ changed = true;
194
+ }
195
+ }
196
+ });
197
+ }
198
+ }
199
+ return kept;
200
+ }
201
+ /** Per declaration of the union, whether one of `seeds` is it or reaches it through `dead` declarations. */
202
+ function testUsedReach(union, seeds, dead) {
203
+ // A file is no candidate, and its top level is what reaches what it alone calls.
204
+ const reached = seeds.map((seed, at) => seed && dead[at] === true);
205
+ const queue = seeds.flatMap((one, at) => (one ? [at] : []));
206
+ for (let at = queue.pop(); at !== undefined; at = queue.pop()) {
207
+ for (const edge of union.out[at] ?? []) {
208
+ if (dead[edge.to] === true && reached[edge.to] !== true) {
209
+ reached[edge.to] = true;
210
+ queue.push(edge.to);
211
+ }
212
+ }
213
+ }
214
+ return reached;
215
+ }
216
+ /** Per declaration of the union, whether test code `reached` holds and `admitted` rejects references it. */
217
+ function keptByTests(union, reached, admitted) {
218
+ const kept = union.symbols.map(() => false);
219
+ union.out.forEach((edges, from) => {
220
+ const testCode = union.test[from] === true || union.support[from] === true;
221
+ if (!testCode || reached[from] !== true || admitted(from)) {
222
+ return;
223
+ }
224
+ for (const edge of edges) {
225
+ if (edge.test) {
226
+ kept[edge.to] = true;
227
+ }
228
+ }
229
+ });
230
+ return kept;
231
+ }
232
+ /**
233
+ * The dead components of one sweep over the union, with the file declarations at `files`
234
+ * dead as well: a file that is deleted whole takes the declarations it holds and every
235
+ * dead declaration only its top level references with it.
236
+ */
237
+ export function deadComponents(matrix, result, files) {
238
+ const union = matrix.union;
239
+ const byId = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
240
+ const candidateAt = (at) => byId.get(union.symbols[at]?.id ?? "");
241
+ return componentsOf(union, union.symbols.map((_symbol, at) => {
242
+ const candidate = candidateAt(at);
243
+ return files.has(at) || (candidate !== undefined && candidate.heldByTests === undefined);
244
+ }), union.symbols.map((_symbol, at) => candidateAt(at)?.testOfDeadCode === true), union.symbols.map((_symbol, at) => candidateAt(at)?.unreferencedTest === true), positionsOf(union, matrix.dependents, result.liveUnder));
245
+ }
165
246
  /**
166
247
  * The file declarations at `paths`, below the target root, that one sweep holds live
167
248
  * under both relations, the rule a judged declaration is live by.