@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
package/src/manifest.ts CHANGED
@@ -7,45 +7,47 @@
7
7
 
8
8
  import type { Host } from "./host.ts";
9
9
  import { isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
10
+ import { isRecord } from "./json-document.ts";
11
+ import { MANIFEST, manifestAt, readJSON } from "./fs/manifest.ts";
12
+ import { scriptTokens } from "./text/shell.ts";
10
13
 
11
14
  /** The manifest's own name. */
12
- const MANIFEST = "package.json";
13
15
 
14
16
  /** The three members that name an entry point beside `exports` and `bin`. */
15
17
  const LEGACY_MEMBERS = ["main", "module", "types"] as const;
16
18
 
17
- /** What reaches one entry point the manifest names. */
19
+ /** What reaches one entry the manifest names. */
18
20
  type EntryRole =
19
21
  /** A consumer importing the package, which is a caller the analysis cannot see. */
20
22
  | "import"
21
23
  /** A shell running the package's command, which is a caller that exists. */
22
24
  | "run"
23
- /** A token of one of the package's scripts, which the package manager runs. */
25
+ /** A token of one of the package's scripts, or the whole of a command string the package runs. */
24
26
  | "script";
25
27
 
26
- /** One entry point the manifest names. */
28
+ /** One entry point the manifest names, or the whole of one command string the package runs. */
27
29
  export interface ManifestEntry {
28
30
  /**
29
- * The member that named it, spelled as a reader of the manifest reads one:
30
- * `main`, `bin["deadset-ts"]`, `exports["."]["import"]`. It is what a maintainer
31
- * edits, so it is what a root reports as the string that named it.
31
+ * The member that named it as a maintainer edits it, `main`, `bin["deadset-ts"]` or
32
+ * `exports["."]["import"]`: what a root reports as the string that named it.
32
33
  */
33
34
  readonly member: string;
34
- /** The target the member carries, resolved against the manifest's directory. */
35
+ /** The target the member carries, resolved against the manifest's directory, or that directory for a whole command string. */
35
36
  readonly path: string;
36
37
  readonly role: EntryRole;
37
38
  /**
38
- * Whether the member declares what a consumer may import, which is `exports` and
39
- * the three members that preceded it. A command is published too, and is not this:
40
- * `exports` is the surface a library's published API is read from.
39
+ * Whether the member declares what a consumer may import, the surface a library's published
40
+ * API is read from: `exports`, `browser` and the three members that preceded `exports`.
41
41
  */
42
42
  readonly published: boolean;
43
43
  /** The directory of the manifest that names it, which `path` was resolved against. */
44
44
  readonly dir: string;
45
- }
46
-
47
- function isRecord(value: unknown): value is Record<string, unknown> {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ /**
46
+ * On the entry of a whole command string, which names no entry point and whose path is the
47
+ * manifest's directory, that string: rooting enters no file for it and holds each file
48
+ * `spelledFiles` reads it to name.
49
+ */
50
+ readonly command?: string;
49
51
  }
50
52
 
51
53
  /**
@@ -60,35 +62,45 @@ function isTarget(value: string, requireDot: boolean): boolean {
60
62
  return !requireDot || value.startsWith("./");
61
63
  }
62
64
 
63
- /** Where a script's command line splits into tokens: white space and the shell's operators. */
64
- const SCRIPT_SEPARATORS = /\s+|&&|\|\||;|\|/u;
65
-
66
- /** A token written inside one pair of quotes. */
67
- const QUOTED = /^(["'])(.*)\1$/su;
68
-
69
- /**
70
- * Every token one script's command line holds, each with one pair of enclosing quotes
71
- * removed. A token holding a wildcard is a pattern rather than a path, and names no
72
- * entry point.
73
- */
74
- export function scriptTokens(command: string): readonly string[] {
75
- return command
76
- .split(SCRIPT_SEPARATORS)
77
- .map((token) => token.replace(QUOTED, "$2"))
78
- .filter((token) => token !== "" && !token.includes("*"));
79
- }
80
-
81
65
  /** One member's spelling below another: `exports` then `exports["."]`. */
82
66
  function below(member: string, key: string): string {
83
67
  return `${member}[${JSON.stringify(key)}]`;
84
68
  }
85
69
 
86
70
  /**
87
- * Every target one `exports` value carries, at the member each is written under.
88
- *
89
- * The value nests strings, subpath or condition objects and alternative arrays to
90
- * any depth; a `null` blocks a subpath. Every level is walked, because each
91
- * condition names its own entry file, and a key's meaning is never read.
71
+ * The command strings one manifest runs, each at the member it is written under: each of its
72
+ * `scripts`, and the `exec` command of nodemon's configuration, in the manifest's `nodemonConfig`
73
+ * or the `nodemon.json` beside it, which nodemon runs the watched script with:
74
+ * https://github.com/remy/nodemon#config-files
75
+ */
76
+ export function manifestCommands(
77
+ host: Host,
78
+ dir: string,
79
+ manifest: Record<string, unknown>,
80
+ ): readonly { readonly member: string; readonly command: string }[] {
81
+ const scripts = manifest["scripts"];
82
+ const nodemon = {
83
+ nodemonConfig: manifest["nodemonConfig"],
84
+ "nodemon.json": readJSON(host, joinPath(dir, "nodemon.json")),
85
+ };
86
+ const written = [
87
+ ...Object.entries(isRecord(scripts) ? scripts : {}).map(
88
+ ([name, value]) => [below("scripts", name), value] as const,
89
+ ),
90
+ ...Object.entries(nodemon).map(
91
+ ([member, config]) =>
92
+ [below(member, "exec"), isRecord(config) ? config["exec"] : undefined] as const,
93
+ ),
94
+ ];
95
+ return written.flatMap(([member, command]) =>
96
+ typeof command === "string" ? [{ member, command }] : [],
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Every target one `exports` value carries, at the member each is written under. Every level of
102
+ * strings, subpath or condition objects and alternative arrays is walked, because each condition
103
+ * names its own entry file; a `null` blocks a subpath, and a key's meaning is never read.
92
104
  */
93
105
  function exportTargets(value: unknown, member: string, found: Omit<ManifestEntry, "dir">[]): void {
94
106
  if (typeof value === "string") {
@@ -111,41 +123,18 @@ function exportTargets(value: unknown, member: string, found: Omit<ManifestEntry
111
123
  }
112
124
  }
113
125
 
114
- /** The manifest at the target root, decoded, or undefined where there is none to read. */
115
- function manifestOf(host: Host, targetRoot: string): Record<string, unknown> | undefined {
116
- let text: string;
117
- try {
118
- text = host.readFile(joinPath(targetRoot, MANIFEST));
119
- } catch {
120
- return undefined;
121
- }
122
- let value: unknown;
123
- try {
124
- value = JSON.parse(text);
125
- } catch {
126
- // A manifest this program cannot read names no entry point, which is the same
127
- // answer as an absent one. What a manifest's contents say is not refused here:
128
- // the package manager owns that refusal, and an analysis of a tree it rejects
129
- // is not the run's subject.
130
- return undefined;
131
- }
132
- return isRecord(value) ? value : undefined;
133
- }
134
-
135
126
  /** What one manifest declares about where its package is entered. */
136
127
  export interface Manifest {
137
128
  /**
138
- * Every entry point the manifest names, in the order the members are read: `main`,
139
- * `module`, `types`, then each `bin` command, then every target of `exports`, then
140
- * every token of each script, which names a file only where one is at its path. Each
141
- * path is absolute.
129
+ * Every entry the manifest names, by absolute path, in the order read: the entry points `main`,
130
+ * `module`, `types`, `browser` and each file it maps, each `bin` command, every `exports` target
131
+ * and per script each token, which names a file only where one is at its path; and per command
132
+ * string {@link manifestCommands} reads one entry of its whole text, which is no entry point.
142
133
  */
143
134
  readonly entries: readonly ManifestEntry[];
144
135
  /**
145
- * Whether an `exports` member is written at all. A package that declares one has
146
- * said exactly what a consumer may import, so its published API is what `exports`
147
- * reaches; a package that declares none publishes whatever the members that
148
- * preceded it name.
136
+ * Whether an `exports` member is written: then the published API is what `exports` reaches,
137
+ * and otherwise whatever the members that preceded it name.
149
138
  */
150
139
  readonly declaresExports: boolean;
151
140
  }
@@ -158,7 +147,7 @@ export interface Manifest {
158
147
  * it and there is one such configuration per project, while there is one manifest.
159
148
  */
160
149
  export function readManifest(host: Host, targetRoot: string): Manifest {
161
- const manifest = manifestOf(host, targetRoot);
150
+ const manifest = manifestAt(host, targetRoot);
162
151
  if (manifest === undefined) {
163
152
  return { entries: [], declaresExports: false };
164
153
  }
@@ -171,6 +160,24 @@ export function readManifest(host: Host, targetRoot: string): Manifest {
171
160
  }
172
161
  }
173
162
 
163
+ // A `browser` string replaces `main` in a browser bundle, and an object replaces each
164
+ // module it maps with the file it names: https://github.com/defunctzombie/package-browser-field-spec
165
+ const browser = manifest["browser"];
166
+ if (typeof browser === "string" && isTarget(browser, false)) {
167
+ found.push({ member: "browser", path: browser, role: "import", published: true });
168
+ } else if (isRecord(browser)) {
169
+ for (const [replaced, value] of Object.entries(browser)) {
170
+ if (typeof value === "string" && isTarget(value, true)) {
171
+ found.push({
172
+ member: below("browser", replaced),
173
+ path: value,
174
+ role: "import",
175
+ published: true,
176
+ });
177
+ }
178
+ }
179
+ }
180
+
174
181
  const bin = manifest["bin"];
175
182
  if (typeof bin === "string" && isTarget(bin, false)) {
176
183
  found.push({ member: "bin", path: bin, role: "run", published: false });
@@ -190,16 +197,15 @@ export function readManifest(host: Host, targetRoot: string): Manifest {
190
197
  if (typeof command !== "string") {
191
198
  continue;
192
199
  }
193
- for (const token of new Set(scriptTokens(command))) {
194
- found.push({
195
- member: below("scripts", name),
196
- path: token,
197
- role: "script",
198
- published: false,
199
- });
200
+ const member = below("scripts", name);
201
+ for (const path of new Set(scriptTokens(command))) {
202
+ found.push({ member, path, role: "script", published: false });
200
203
  }
201
204
  }
202
205
  }
206
+ for (const { member, command } of manifestCommands(host, targetRoot, manifest)) {
207
+ found.push({ member, path: ".", role: "script", published: false, command });
208
+ }
203
209
 
204
210
  return {
205
211
  entries: found.map((entry) => ({
package/src/matrix.ts CHANGED
@@ -30,6 +30,10 @@ export interface Configured {
30
30
  readonly testFiles: readonly string[];
31
31
  /** The paths of the test-support files, which only test code imports. */
32
32
  readonly supportFiles?: readonly string[] | undefined;
33
+ /** The paths of the files only tests reach that are no test-support file. */
34
+ readonly testUsedFiles?: readonly string[] | undefined;
35
+ /** The ids of the ambient declarations, which describe code the target does not implement. */
36
+ readonly ambient?: ReadonlySet<string> | undefined;
33
37
  /**
34
38
  * The paths of the workspace-package files the configuration holds but does not
35
39
  * compile itself. Their declarations count in its graph and its verdict on them is no
@@ -58,6 +62,11 @@ export interface Matrix {
58
62
  * whose project holds it, in the run's order.
59
63
  */
60
64
  readonly heldIn: readonly (readonly string[])[];
65
+ /**
66
+ * Per declaration of {@link Matrix.union}, whether a file only tests reach that is no
67
+ * test-support file holds it in some configuration: production code only tests use.
68
+ */
69
+ readonly testUsed: readonly boolean[];
61
70
  /** Each declaration's dependents, by id, as the first configuration holding it states them. */
62
71
  readonly dependents: ReadonlyMap<string, readonly string[]>;
63
72
  }
@@ -79,6 +88,17 @@ export interface MatrixCandidate extends Candidate {
79
88
  * shape nothing references would be.
80
89
  */
81
90
  readonly unreferencedTest: boolean;
91
+ /**
92
+ * Whether it is dead only because code only tests use is: production code only tests
93
+ * use holds it and the run that counts test references reaches it, or such a
94
+ * declaration reaches it through dead declarations.
95
+ */
96
+ readonly testUsed: boolean;
97
+ /**
98
+ * Whether test code the run that counts test references reaches, and that is no test of
99
+ * dead code, references it: a test that stays keeps it.
100
+ */
101
+ readonly keptByTests: boolean;
82
102
  }
83
103
 
84
104
  /** One run's sweep. */
@@ -156,11 +176,12 @@ export function matrixOf(per: readonly Configured[]): Matrix {
156
176
  }
157
177
  });
158
178
  symbols.sort((a, b) => byPosition(a.position, b.position));
179
+ const testUsed = new Set(per.flatMap((one) => one.testUsedFiles ?? []));
159
180
 
160
181
  return {
161
182
  configurations: per.map((one) => one.configuration),
162
183
  graphs: per.map((one) =>
163
- graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles),
184
+ graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles, one.ambient),
164
185
  ),
165
186
  union: graphOf(
166
187
  symbols,
@@ -168,8 +189,10 @@ export function matrixOf(per: readonly Configured[]): Matrix {
168
189
  per.flatMap((one) => one.roots),
169
190
  [...new Set(per.flatMap((one) => one.testFiles))],
170
191
  [...new Set(per.flatMap((one) => one.supportFiles ?? []))],
192
+ new Set(per.flatMap((one) => [...(one.ambient ?? [])])),
171
193
  ),
172
194
  heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
195
+ testUsed: symbols.map((symbol) => testUsed.has(symbol.position.path)),
173
196
  dependents,
174
197
  };
175
198
  }
@@ -199,22 +222,33 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
199
222
  );
200
223
 
201
224
  const union = matrix.union;
202
- const dead = union.symbols.map(() => false);
203
- const testOfDeadCode = union.symbols.map(() => false);
204
225
  const heldBy: (string | undefined)[] = union.symbols.map(() => undefined);
205
- const unreferencedTest = union.symbols.map(() => false);
206
- const testReached = reachedWithTheTests(union, [
226
+ const held = [
207
227
  ...input.marked,
208
228
  ...(input.exempt ?? []).map((record) => record.id),
209
229
  ...(input.unanswered ?? []),
210
- ]);
211
- const judged = union.symbols.map((symbol, at): readonly Candidate[] | undefined => {
230
+ ];
231
+ const testReached = reachedWithTheTests(union, held);
232
+ // A test uses what it names: a file a test reads through a star re-export or a
233
+ // namespace keeps the exports nothing names at their own class.
234
+ const testNamed = reachedWithTheTests(union, held, false);
235
+ const everywhere = union.symbols.map((symbol, at): readonly Candidate[] | undefined => {
212
236
  const held = matrix.heldIn[at] ?? [];
213
237
  const found = held.map((configuration) => byConfiguration.get(configuration)?.get(symbol.id));
214
238
  return held.length === 0 || found.some((candidate) => candidate === undefined)
215
239
  ? undefined
216
240
  : found.filter((candidate): candidate is Candidate => candidate !== undefined);
217
241
  });
242
+ const compiled = compiledReferences(
243
+ matrix,
244
+ new Set(
245
+ union.symbols.flatMap((symbol, at) => (everywhere[at] === undefined ? [] : [symbol.id])),
246
+ ),
247
+ input.mode,
248
+ );
249
+ const judged = everywhere.map((found, at) =>
250
+ compiled.has(union.symbols[at]?.id ?? "") ? undefined : found,
251
+ );
218
252
  // A configuration that does not hold a test's subject's caller finds the subject dead,
219
253
  // so a test is of dead code only where every production subject it names is dead in
220
254
  // every configuration that holds it.
@@ -223,9 +257,18 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
223
257
  (edge) =>
224
258
  union.test[edge.to] === true ||
225
259
  union.support[edge.to] === true ||
260
+ union.ambient[edge.to] === true ||
226
261
  union.subject[edge.to] !== true ||
227
262
  judged[edge.to] !== undefined,
228
263
  );
264
+ const admittedAt = (at: number): boolean =>
265
+ judged[at]?.every((candidate) => candidate.testOfDeadCode) === true && subjectsDead(at);
266
+ const kept = keptByTests(union, testReached, admittedAt);
267
+ const testUsed = testUsedReach(
268
+ union,
269
+ union.symbols.map((_symbol, at) => matrix.testUsed[at] === true && testNamed[at] === true),
270
+ judged.map((one) => one !== undefined),
271
+ );
229
272
  const candidates: MatrixCandidate[] = [];
230
273
  union.symbols.forEach((symbol, at) => {
231
274
  const all = judged[at];
@@ -233,16 +276,13 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
233
276
  return;
234
277
  }
235
278
  const held = matrix.heldIn[at] ?? [];
236
- const admitted = all.every((candidate) => candidate.testOfDeadCode) && subjectsDead(at);
279
+ const admitted = admittedAt(at);
237
280
  const testRefs = union.made[at]?.test ?? 0;
238
281
  const heldByTests =
239
282
  heldBy[union.parent[at] ?? OUTSIDE] ??
240
283
  (union.support[at] === true && testRefs > 0 && !admitted ? symbol.id : undefined);
241
284
  const unreferenced = union.test[at] === true && !admitted && testReached[at] !== true;
242
- dead[at] = true;
243
- testOfDeadCode[at] = admitted;
244
285
  heldBy[at] = heldByTests;
245
- unreferencedTest[at] = unreferenced;
246
286
  const productionRefs = union.made[at]?.production ?? 0;
247
287
  const counted = all.every((candidate) => candidate.relation === "reference-counting");
248
288
  candidates.push({
@@ -257,6 +297,8 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
257
297
  configurations: held,
258
298
  heldByTests,
259
299
  unreferencedTest: unreferenced,
300
+ testUsed: testUsed[at] === true,
301
+ keptByTests: kept[at] === true,
260
302
  });
261
303
  });
262
304
 
@@ -264,16 +306,110 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
264
306
  return {
265
307
  liveUnder,
266
308
  candidates,
267
- components: componentsOf(
268
- union,
269
- dead.map((one, at) => one && heldBy[at] === undefined),
270
- testOfDeadCode,
271
- unreferencedTest,
272
- positionsOf(union, matrix.dependents, liveUnder),
273
- ),
309
+ components: deadComponents(matrix, { liveUnder, candidates }, new Set()),
274
310
  };
275
311
  }
276
312
 
313
+ /**
314
+ * Every declaration of `dead`, those dead in every configuration that holds them, that a
315
+ * configuration's graph references from a judged declaration outside `dead` or already
316
+ * kept, until none joins: deleting one would break that configuration's compile. A
317
+ * production run counts no reference a test file made.
318
+ */
319
+ function compiledReferences(
320
+ matrix: Matrix,
321
+ dead: ReadonlySet<string>,
322
+ mode: SweepInput["mode"],
323
+ ): ReadonlySet<string> {
324
+ const kept = new Set<string>();
325
+ for (let changed = true; changed;) {
326
+ changed = false;
327
+ for (const graph of matrix.graphs) {
328
+ graph.out.forEach((edges, from) => {
329
+ const id = graph.symbols[from]?.id ?? "";
330
+ if (graph.subject[from] !== true || (dead.has(id) && !kept.has(id))) {
331
+ return;
332
+ }
333
+ for (const edge of edges) {
334
+ const to = graph.symbols[edge.to]?.id ?? "";
335
+ if ((!mode.production || !edge.test) && dead.has(to) && !kept.has(to)) {
336
+ kept.add(to);
337
+ changed = true;
338
+ }
339
+ }
340
+ });
341
+ }
342
+ }
343
+ return kept;
344
+ }
345
+
346
+ /** Per declaration of the union, whether one of `seeds` is it or reaches it through `dead` declarations. */
347
+ function testUsedReach(
348
+ union: Graph,
349
+ seeds: readonly boolean[],
350
+ dead: readonly boolean[],
351
+ ): readonly boolean[] {
352
+ // A file is no candidate, and its top level is what reaches what it alone calls.
353
+ const reached = seeds.map((seed, at) => seed && dead[at] === true);
354
+ const queue = seeds.flatMap((one, at) => (one ? [at] : []));
355
+ for (let at = queue.pop(); at !== undefined; at = queue.pop()) {
356
+ for (const edge of union.out[at] ?? []) {
357
+ if (dead[edge.to] === true && reached[edge.to] !== true) {
358
+ reached[edge.to] = true;
359
+ queue.push(edge.to);
360
+ }
361
+ }
362
+ }
363
+ return reached;
364
+ }
365
+
366
+ /** Per declaration of the union, whether test code `reached` holds and `admitted` rejects references it. */
367
+ function keptByTests(
368
+ union: Graph,
369
+ reached: readonly boolean[],
370
+ admitted: (at: number) => boolean,
371
+ ): readonly boolean[] {
372
+ const kept = union.symbols.map(() => false);
373
+ union.out.forEach((edges, from) => {
374
+ const testCode = union.test[from] === true || union.support[from] === true;
375
+ if (!testCode || reached[from] !== true || admitted(from)) {
376
+ return;
377
+ }
378
+ for (const edge of edges) {
379
+ if (edge.test) {
380
+ kept[edge.to] = true;
381
+ }
382
+ }
383
+ });
384
+ return kept;
385
+ }
386
+
387
+ /**
388
+ * The dead components of one sweep over the union, with the file declarations at `files`
389
+ * dead as well: a file that is deleted whole takes the declarations it holds and every
390
+ * dead declaration only its top level references with it.
391
+ */
392
+ export function deadComponents(
393
+ matrix: Matrix,
394
+ result: Pick<SweepResult, "liveUnder" | "candidates">,
395
+ files: ReadonlySet<number>,
396
+ ): readonly Component[] {
397
+ const union = matrix.union;
398
+ const byId = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
399
+ const candidateAt = (at: number): MatrixCandidate | undefined =>
400
+ byId.get(union.symbols[at]?.id ?? "");
401
+ return componentsOf(
402
+ union,
403
+ union.symbols.map((_symbol, at) => {
404
+ const candidate = candidateAt(at);
405
+ return files.has(at) || (candidate !== undefined && candidate.heldByTests === undefined);
406
+ }),
407
+ union.symbols.map((_symbol, at) => candidateAt(at)?.testOfDeadCode === true),
408
+ union.symbols.map((_symbol, at) => candidateAt(at)?.unreferencedTest === true),
409
+ positionsOf(union, matrix.dependents, result.liveUnder),
410
+ );
411
+ }
412
+
277
413
  /**
278
414
  * The file declarations at `paths`, below the target root, that one sweep holds live
279
415
  * under both relations, the rule a judged declaration is live by.