@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11

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 (276) 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 +181 -341
  5. package/dist/src/augmentations.js +106 -13
  6. package/dist/src/automatic-types.js +10 -29
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +2 -3
  10. package/dist/src/config.js +6 -82
  11. package/dist/src/configuration-files.js +28 -176
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +1 -1
  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 +73 -3
  20. package/dist/src/conventions.js +36 -66
  21. package/dist/src/dependencies.js +103 -40
  22. package/dist/src/edges.js +38 -122
  23. package/dist/src/element-access.js +4 -6
  24. package/dist/src/entry-point-gaps.js +2 -8
  25. package/dist/src/entry-points.js +11 -27
  26. package/dist/src/enum-group.js +3 -10
  27. package/dist/src/erased-stores.js +4 -6
  28. package/dist/src/exempt-classes.js +1 -63
  29. package/dist/src/exempt.js +21 -40
  30. package/dist/src/failure.js +36 -0
  31. package/dist/src/file-facts.js +2 -25
  32. package/dist/src/findings/completion.js +139 -72
  33. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  34. package/dist/src/findings/emitters.js +5 -5
  35. package/dist/src/findings/interfaces.js +4 -5
  36. package/dist/src/findings/intra-function.js +13 -10
  37. package/dist/src/findings/non-code-artifacts.js +7 -8
  38. package/dist/src/findings/reads-and-writes.js +16 -13
  39. package/dist/src/findings/self-check.js +10 -12
  40. package/dist/src/findings/unused-declarations.js +41 -32
  41. package/dist/src/findings/visibility-narrowing.js +19 -16
  42. package/dist/src/findings-pass.js +8 -15
  43. package/dist/src/framework-lifecycle.js +7 -7
  44. package/dist/src/fs/document.js +52 -0
  45. package/dist/src/fs/manifest.js +72 -0
  46. package/dist/src/fs/tree.js +88 -0
  47. package/dist/src/generated-file.js +7 -4
  48. package/dist/src/git-ignore.js +39 -26
  49. package/dist/src/graph.js +5 -3
  50. package/dist/src/html-entries.js +6 -33
  51. package/dist/src/in-operands.js +2 -1
  52. package/dist/src/index.js +1 -1
  53. package/dist/src/injection-container.js +1 -1
  54. package/dist/src/intra-function-parts.js +63 -43
  55. package/dist/src/inventory.js +2 -21
  56. package/dist/src/javascript-file.js +33 -0
  57. package/dist/src/json-document.js +198 -67
  58. package/dist/src/keyof-comparisons.js +4 -6
  59. package/dist/src/kinds.js +3 -366
  60. package/dist/src/load/classify.js +270 -0
  61. package/dist/src/load/diagnostics.js +80 -0
  62. package/dist/src/load/discover.js +277 -0
  63. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  64. package/dist/src/load/expected-module.js +136 -0
  65. package/dist/src/load/load.js +27 -0
  66. package/dist/src/load/module-lookup.js +37 -0
  67. package/dist/src/load/plan.js +313 -0
  68. package/dist/src/load/specifiers.js +115 -0
  69. package/dist/src/load/synthesize.js +140 -0
  70. package/dist/src/load/test-globals.js +207 -0
  71. package/dist/src/load/test-joins.js +82 -0
  72. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  73. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  74. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
  75. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  76. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  77. package/dist/src/manifest.js +49 -54
  78. package/dist/src/matrix.js +76 -4
  79. package/dist/src/mdx-entries.js +3 -26
  80. package/dist/src/outside-tests.js +1 -1
  81. package/dist/src/partial.js +2 -1
  82. package/dist/src/paths.js +26 -15
  83. package/dist/src/position.js +2 -1
  84. package/dist/src/print.js +11 -79
  85. package/dist/src/product.js +13 -0
  86. package/dist/src/query.js +1 -0
  87. package/dist/src/ref.js +1 -0
  88. package/dist/src/references.js +48 -30
  89. package/dist/src/report-schema.js +51 -0
  90. package/dist/src/report.js +57 -41
  91. package/dist/src/reporters/annotations.js +15 -8
  92. package/dist/src/reporters/sarif.js +16 -10
  93. package/dist/src/reporters/template.js +4 -19
  94. package/dist/src/resolve.js +245 -729
  95. package/dist/src/roots.js +273 -87
  96. package/dist/src/run.js +66 -77
  97. package/dist/src/schema.js +320 -343
  98. package/dist/src/scope.js +38 -115
  99. package/dist/src/serialization-contract.js +595 -21
  100. package/dist/src/session.js +34 -29
  101. package/dist/src/source-files.js +1 -1
  102. package/dist/src/suppress-file.js +25 -28
  103. package/dist/src/suppress-inline.js +3 -2
  104. package/dist/src/suppress.js +33 -51
  105. package/dist/src/sweep.js +7 -9
  106. package/dist/src/symbol-keys.js +3 -5
  107. package/dist/src/template-field.js +9 -19
  108. package/dist/src/test-support.js +8 -11
  109. package/dist/src/text/compare.js +26 -0
  110. package/dist/src/text/glob.js +194 -0
  111. package/dist/src/text/named-files.js +54 -0
  112. package/dist/src/text/shell.js +159 -0
  113. package/dist/src/text/yaml.js +393 -0
  114. package/dist/src/type-errors.js +11 -232
  115. package/dist/src/unseen-loads.js +100 -0
  116. package/dist/src/unwrap.js +23 -0
  117. package/dist/src/usage.js +10 -0
  118. package/dist/src/verbs/analyze.js +166 -126
  119. package/dist/src/verbs/describe.js +9 -16
  120. package/dist/src/verbs/explain.js +10 -7
  121. package/dist/src/verbs/print-config.js +2 -1
  122. package/dist/src/verbs/print-projects.js +6 -4
  123. package/dist/src/verbs/print-retained.js +4 -2
  124. package/dist/src/verbs/print-roots.js +4 -2
  125. package/dist/src/verbs/verb.js +1 -5
  126. package/dist/src/verbs/version.js +14 -3
  127. package/dist/src/workflow-steps.js +62 -64
  128. package/jsr.json +1 -1
  129. package/package.json +5 -4
  130. package/src/analysis.ts +245 -502
  131. package/src/augmentations.ts +135 -17
  132. package/src/automatic-types.ts +11 -29
  133. package/src/build-scripts.ts +543 -0
  134. package/src/component-layer.ts +4 -2
  135. package/src/components.ts +2 -4
  136. package/src/config.ts +31 -302
  137. package/src/configuration-files.ts +34 -179
  138. package/src/configured-declarations.ts +18 -23
  139. package/src/conformance-record.ts +1 -1
  140. package/src/consumers.ts +6 -57
  141. package/src/contextual-uses.ts +64 -12
  142. package/src/contract/config.generated.ts +818 -0
  143. package/src/contract/contract.generated.ts +1308 -0
  144. package/src/contract/report.generated.ts +1237 -0
  145. package/src/convention-rows.ts +85 -3
  146. package/src/conventions.ts +46 -84
  147. package/src/dependencies.ts +118 -46
  148. package/src/edges.ts +41 -140
  149. package/src/element-access.ts +4 -7
  150. package/src/entry-point-gaps.ts +3 -9
  151. package/src/entry-points.ts +12 -38
  152. package/src/enum-group.ts +2 -11
  153. package/src/erased-stores.ts +3 -7
  154. package/src/exempt-classes.ts +1 -75
  155. package/src/exempt.ts +36 -50
  156. package/src/failure.ts +63 -0
  157. package/src/file-facts.ts +9 -24
  158. package/src/finding.ts +14 -9
  159. package/src/findings/boundary.ts +2 -0
  160. package/src/findings/completion.ts +181 -84
  161. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  162. package/src/findings/emitter.ts +8 -9
  163. package/src/findings/emitters.ts +7 -7
  164. package/src/findings/interfaces.ts +4 -6
  165. package/src/findings/intra-function.ts +13 -11
  166. package/src/findings/non-code-artifacts.ts +9 -11
  167. package/src/findings/reads-and-writes.ts +20 -16
  168. package/src/findings/self-check.ts +11 -17
  169. package/src/findings/unused-declarations.ts +45 -32
  170. package/src/findings/visibility-narrowing.ts +20 -17
  171. package/src/findings-pass.ts +10 -19
  172. package/src/framework-lifecycle.ts +7 -7
  173. package/src/fs/document.ts +70 -0
  174. package/src/fs/manifest.ts +92 -0
  175. package/src/fs/tree.ts +110 -0
  176. package/src/generated-file.ts +7 -4
  177. package/src/git-ignore.ts +50 -26
  178. package/src/graph.ts +5 -3
  179. package/src/host.ts +12 -5
  180. package/src/html-entries.ts +6 -33
  181. package/src/in-operands.ts +2 -1
  182. package/src/index.ts +1 -1
  183. package/src/injection-container.ts +1 -1
  184. package/src/intra-function-parts.ts +73 -44
  185. package/src/inventory.ts +2 -20
  186. package/src/javascript-file.ts +37 -0
  187. package/src/json-document.ts +241 -76
  188. package/src/keyof-comparisons.ts +3 -7
  189. package/src/kinds.ts +5 -413
  190. package/src/load/classify.ts +397 -0
  191. package/src/load/diagnostics.ts +144 -0
  192. package/src/load/discover.ts +455 -0
  193. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  194. package/src/load/expected-module.ts +188 -0
  195. package/src/load/load.ts +73 -0
  196. package/src/load/module-lookup.ts +50 -0
  197. package/src/load/plan.ts +504 -0
  198. package/src/load/specifiers.ts +168 -0
  199. package/src/load/synthesize.ts +211 -0
  200. package/src/load/test-globals.ts +251 -0
  201. package/src/load/test-joins.ts +98 -0
  202. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  203. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  204. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
  205. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  206. package/src/{workspace.ts → load/workspace.ts} +90 -134
  207. package/src/manifest.ts +81 -75
  208. package/src/matrix.ts +122 -4
  209. package/src/mdx-entries.ts +3 -26
  210. package/src/outside-tests.ts +1 -1
  211. package/src/partial.ts +3 -2
  212. package/src/paths.ts +27 -15
  213. package/src/position.ts +2 -1
  214. package/src/print.ts +12 -92
  215. package/src/product.ts +18 -0
  216. package/src/query.ts +11 -0
  217. package/src/ref.ts +1 -0
  218. package/src/references.ts +52 -31
  219. package/src/report-schema.ts +68 -0
  220. package/src/report.ts +101 -224
  221. package/src/reporters/annotations.ts +22 -11
  222. package/src/reporters/json.ts +1 -1
  223. package/src/reporters/reporter.ts +2 -4
  224. package/src/reporters/reporters.ts +1 -1
  225. package/src/reporters/sarif.ts +29 -16
  226. package/src/reporters/template.ts +4 -20
  227. package/src/reporters/text.ts +3 -3
  228. package/src/resolve.ts +283 -1114
  229. package/src/roots.ts +339 -104
  230. package/src/run.ts +75 -91
  231. package/src/schema.ts +391 -388
  232. package/src/scope.ts +46 -138
  233. package/src/serialization-contract.ts +763 -22
  234. package/src/session.ts +70 -54
  235. package/src/source-files.ts +1 -1
  236. package/src/suppress-file.ts +24 -33
  237. package/src/suppress-inline.ts +2 -2
  238. package/src/suppress.ts +43 -58
  239. package/src/sweep.ts +23 -9
  240. package/src/symbol-keys.ts +2 -5
  241. package/src/template-field.ts +15 -25
  242. package/src/test-support.ts +17 -13
  243. package/src/text/compare.ts +27 -0
  244. package/src/text/glob.ts +210 -0
  245. package/src/text/named-files.inputs.json +1522 -0
  246. package/src/text/named-files.ts +68 -0
  247. package/src/text/shell.ts +161 -0
  248. package/src/text/yaml.ts +423 -0
  249. package/src/type-errors.ts +18 -288
  250. package/src/unseen-loads.ts +134 -0
  251. package/src/unwrap.ts +34 -0
  252. package/src/usage.ts +10 -0
  253. package/src/verbs/analyze.ts +224 -162
  254. package/src/verbs/describe.ts +14 -15
  255. package/src/verbs/explain.ts +10 -11
  256. package/src/verbs/print-config.ts +2 -1
  257. package/src/verbs/print-projects.ts +6 -4
  258. package/src/verbs/print-retained.ts +4 -2
  259. package/src/verbs/print-roots.ts +4 -2
  260. package/src/verbs/verb.ts +2 -8
  261. package/src/verbs/version.ts +15 -3
  262. package/src/workflow-steps.ts +74 -64
  263. package/dist/src/base-url.js +0 -18
  264. package/dist/src/discover.js +0 -468
  265. package/dist/src/glob.js +0 -65
  266. package/dist/src/pnpm-workspace.js +0 -120
  267. package/dist/src/setup-failure.js +0 -23
  268. package/dist/src/test-joins.js +0 -101
  269. package/dist/src/version.js +0 -7
  270. package/src/base-url.ts +0 -24
  271. package/src/discover.ts +0 -653
  272. package/src/glob.ts +0 -67
  273. package/src/pnpm-workspace.ts +0 -135
  274. package/src/setup-failure.ts +0 -39
  275. package/src/test-joins.ts +0 -120
  276. package/src/version.ts +0 -7
package/src/roots.ts CHANGED
@@ -1,16 +1,22 @@
1
1
  /**
2
- * The root set of one project: every declaration the analysis keeps live without a
3
- * reference, and why.
4
- *
5
- * Every root seeds REACHABILITY, and no root counts as a reference. A root of a kind
6
- * that names a caller is live under reference counting as well, which the sweep
7
- * decides; the published API only supposes a consumer, so a published export is live
8
- * under reachability and still a candidate under reference counting.
2
+ * The root set of one project: every declaration kept live without a reference, and why.
3
+ * Every root seeds reachability and none counts as a reference; a root of a kind that
4
+ * names a caller is live under reference counting too, which the sweep decides, so a
5
+ * published export, which only supposes a consumer, stays a reference-counting candidate.
9
6
  */
10
7
 
11
8
  import {
9
+ isArrowFunction,
10
+ isAssertionExpression,
12
11
  isExpressionWithTypeArguments,
13
12
  isFunctionLikeDeclaration,
13
+ isIdentifier,
14
+ isMethodDeclaration,
15
+ isObjectLiteralExpression,
16
+ isParenthesizedExpression,
17
+ isPropertyAssignment,
18
+ isReturnStatement,
19
+ isSatisfiesExpression,
14
20
  isPropertyAccessExpression,
15
21
  isQualifiedName,
16
22
  isTypeQueryNode,
@@ -18,10 +24,14 @@ import {
18
24
  isPropertyDeclaration,
19
25
  isVariableDeclaration,
20
26
  isVariableStatement,
27
+ SyntaxKind,
28
+ type Expression,
21
29
  type Node,
30
+ type ObjectLiteralExpression,
22
31
  type SourceFile,
23
32
  } from "@typescript/native/ast";
24
33
  import { aliasChains } from "./alias-chain.ts";
34
+ import { isGlobalBlock } from "./augmentations.ts";
25
35
  import {
26
36
  filesNamedBy,
27
37
  outsideChain,
@@ -30,17 +40,21 @@ import {
30
40
  } from "./configuration-files.ts";
31
41
  import { conventionEntries, type Conventions } from "./conventions.ts";
32
42
  import { entryPoints, type EntryRule } from "./entry-points.ts";
33
- import { globExpression } from "./glob.ts";
34
43
  import type { Host } from "./host.ts";
35
44
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
36
45
  import type { Manifest, ManifestEntry } from "./manifest.ts";
37
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
46
+ import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.ts";
38
47
  import { byPosition } from "./position.ts";
39
48
  import { UNANSWERED } from "./query.ts";
40
49
  import type { ProjectView } from "./session.ts";
41
50
  import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
42
51
  import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
43
52
  import { typeQueryAliases } from "./type-query-alias.ts";
53
+ import { bytewise } from "./text/compare.ts";
54
+ import { holdsManifest } from "./fs/manifest.ts";
55
+ import { globExpression } from "./text/glob.ts";
56
+ import { spelledFiles } from "./text/named-files.ts";
57
+ import { commandDirectories, workflowCommands } from "./workflow-steps.ts";
44
58
 
45
59
  /** Why one declaration is a root. */
46
60
  export type RootKind =
@@ -67,6 +81,8 @@ export type RootKind =
67
81
  | "pattern"
68
82
  /** A declaration of a function or statement a type error skipped, and what it may have selected. */
69
83
  | "type-error"
84
+ /** An ambient global interface merging into one declared outside the target, and its holders. */
85
+ | "global-augmentation"
70
86
  /** A file a tool or the platform enters by its own convention, and what it exports where the tool reads it. */
71
87
  | EntryRule;
72
88
 
@@ -87,14 +103,9 @@ export interface Root {
87
103
  /** One project's roots, and every configured string that named nothing in it. */
88
104
  export interface Roots {
89
105
  /**
90
- * Every root of this project, ordered by the site of the declaration it names,
91
- * then by kind, then by the string that named it. A declaration that is a root for
92
- * more than one reason appears once per reason, so an explanation can name every
93
- * reason it is live.
94
- *
95
- * The member says which relation the set feeds: every declaration here is live
96
- * under reachability. A root of a kind that names a caller is also live under
97
- * reference counting, which the sweep decides.
106
+ * Every root of this project, live under reachability, ordered by the site of the
107
+ * declaration it names, then by kind, then by the string that named it. A declaration
108
+ * rooted for several reasons appears once per reason, so an explanation names each.
98
109
  */
99
110
  readonly liveUnderReachability: readonly Root[];
100
111
  /**
@@ -106,11 +117,22 @@ export interface Roots {
106
117
  /**
107
118
  * Every declaration of the project, where the checker left the module or export table
108
119
  * of an entry or a published file unanswered, since which of them that file exports is
109
- * unknown; otherwise each one an unanswered published type name may name.
120
+ * unknown; otherwise each one an unanswered published type name may name. Beside them, each
121
+ * file of {@link heldScripts} and every declaration its export table names.
110
122
  */
111
123
  readonly unanswered: readonly string[];
112
124
  /** Every manifest entry naming a file to import or run that reads back to no file of this project. */
113
125
  readonly unmappedEntries: readonly ManifestEntry[];
126
+ /** Every package whose command strings name, as `spelledFiles` reads them, an own file no root enters with all it exports. */
127
+ readonly heldScripts: readonly HeldScripts[];
128
+ }
129
+
130
+ /** One package's command strings, and the own files they name that no root enters with all they export. */
131
+ export interface HeldScripts {
132
+ /** The package's directory, absolute. */
133
+ readonly dir: string;
134
+ /** Each file, by its path below the target root. */
135
+ readonly files: readonly string[];
114
136
  }
115
137
 
116
138
  /** What decides the roots beside the declarations themselves. */
@@ -141,7 +163,7 @@ export interface RootOptions {
141
163
  * inside the target whose entries are published API whatever the target's kind.
142
164
  */
143
165
  readonly publishedMembers?: ReadonlySet<string> | undefined;
144
- /** Every file a program of the run holds, so a file another configuration compiles is no outside file. */
166
+ /** The files the configurations read from a file hold, so a file another configuration compiles is no outside file. */
145
167
  readonly runFiles?: ReadonlySet<string> | undefined;
146
168
  }
147
169
 
@@ -182,15 +204,8 @@ const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
182
204
 
183
205
  /** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
184
206
  function ofPackage(host: Host, dir: string, file: string): boolean {
185
- for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
186
- if (
187
- relativePath(dir, at) === undefined ||
188
- host.kindOf(joinPath(at, "package.json")) === "file"
189
- ) {
190
- return false;
191
- }
192
- }
193
- return true;
207
+ const between = ancestors(dirnamePath(file), dir);
208
+ return between.length > 0 && !between.slice(0, -1).some((at) => holdsManifest(host, at));
194
209
  }
195
210
 
196
211
  /**
@@ -316,14 +331,6 @@ export function matchRef(pattern: string, ref: string): boolean {
316
331
  return matchGlob(Array.from(pattern), Array.from(ref));
317
332
  }
318
333
 
319
- /** Two strings ordered bytewise, which is the order every set of a run is read in. */
320
- function compare(a: string, b: string): number {
321
- if (a === b) {
322
- return 0;
323
- }
324
- return a < b ? -1 : 1;
325
- }
326
-
327
334
  /** The members of one declaration, indexed by the container that holds them. */
328
335
  function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymbol[]> {
329
336
  const byParent = new Map<string, InventorySymbol[]>();
@@ -339,15 +346,60 @@ function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymb
339
346
  }
340
347
 
341
348
  /**
342
- * The roots one project holds, and every configured string that named nothing in it.
343
- *
344
- * A file a manifest member, an entry-file pattern or a tool's configuration names
345
- * is a root with everything its module exports: each name of the module's export
346
- * table, and where a name is a re-export, every link of the chain to the declaration
347
- * it carries, so a re-exported declaration is a root of the file that publishes it.
348
- * In a library, what a published file exports is rooted by the published API alone.
349
- * A file a runtime executes without reading its exports, a test file or a worker, is
350
- * a root by itself.
349
+ * Per package, the own files its command strings name as plain text, but those a root enters
350
+ * with all they export: a command can use no more of such a file than the root keeps live. A
351
+ * manifest's are its entries' whole command strings, and the target root's package has the
352
+ * workflow steps, git hooks and makefile recipes beside its own, with the directories its
353
+ * workflow steps and lefthook commands run in, where a `.` names that directory's index file.
354
+ */
355
+ function scriptHolds(
356
+ entries: readonly ManifestEntry[],
357
+ files: SourceFiles,
358
+ entered: readonly {
359
+ readonly file: SourceFile;
360
+ readonly exports: boolean;
361
+ readonly names?: readonly string[];
362
+ }[],
363
+ host: Host,
364
+ targetRoot: string,
365
+ ): readonly HeldScripts[] {
366
+ const rooted = new Set(
367
+ entered.filter((one) => one.exports && one.names === undefined).map((one) => one.file.fileName),
368
+ );
369
+ const spelled = [...files.byName.values()]
370
+ .filter((file) => !rooted.has(file.fileName))
371
+ .map((file) => ({ file, paths: [file.fileName, ...files.writtenAs(file)] }));
372
+ const cwd = host.workingDirectory();
373
+ const commands = new Map<string, string[]>([
374
+ [
375
+ resolvePath(cwd, targetRoot),
376
+ [...workflowCommands(host, targetRoot), ...commandDirectories(host, targetRoot)],
377
+ ],
378
+ ]);
379
+ for (const entry of entries) {
380
+ if (entry.command !== undefined) {
381
+ const dir = resolvePath(cwd, entry.dir);
382
+ commands.set(dir, [...(commands.get(dir) ?? []), entry.command]);
383
+ }
384
+ }
385
+ const held: HeldScripts[] = [];
386
+ for (const [dir, texts] of commands) {
387
+ const named = spelledFiles(texts.join("\n"), dir, spelled);
388
+ if (named.length > 0) {
389
+ const paths = named.map((file) => relativePath(targetRoot, file.fileName) ?? file.fileName);
390
+ held.push({ dir, files: paths });
391
+ }
392
+ }
393
+ return held;
394
+ }
395
+
396
+ /**
397
+ * The roots one project holds, and every configured string that named nothing in it. A
398
+ * file a manifest member, an entry-file pattern or a tool's configuration names is a root
399
+ * with each name of its export table and every link of a re-export chain to the
400
+ * declaration it carries; in a library, the published API alone roots what a published
401
+ * file exports. A file a runtime executes without reading its exports, a test file or a
402
+ * worker, is a root by itself.
351
403
  */
352
404
  export function roots<Brand>(
353
405
  project: ProjectView<Brand>,
@@ -412,7 +464,7 @@ export function roots<Brand>(
412
464
  })),
413
465
  ];
414
466
  const scripted = entries.flatMap(({ entry }) =>
415
- entry.role === "script" ? files.named(entry.path) : [],
467
+ entry.role === "script" && entry.command === undefined ? files.named(entry.path) : [],
416
468
  );
417
469
  const readBackTo = [
418
470
  ...scripted,
@@ -446,6 +498,12 @@ export function roots<Brand>(
446
498
  published.push(...entryFiles(entry));
447
499
  }
448
500
  }
501
+ if (options.publishedAPI) {
502
+ // A library's bundle hands what its entry exports to every program that loads it.
503
+ published.push(
504
+ ...points.filter((point) => point.rule === "bundler-entry").map((point) => point.file),
505
+ );
506
+ }
449
507
  const publishedNames = new Set(published.map((file) => file.fileName));
450
508
  const patterned: { file: SourceFile; pattern: string }[] = [];
451
509
  for (const pattern of options.entryFiles) {
@@ -459,6 +517,9 @@ export function roots<Brand>(
459
517
  const unmapped: ManifestEntry[] = [];
460
518
  const mapped = patterned.map(({ file }) => file);
461
519
  for (const { entry } of entries) {
520
+ if (entry.command !== undefined) {
521
+ continue;
522
+ }
462
523
  const named = entryFiles(entry);
463
524
  if (named.length === 0 && entry.role !== "script") {
464
525
  unmapped.push(entry);
@@ -483,6 +544,16 @@ export function roots<Brand>(
483
544
  for (const point of points) {
484
545
  enter(point.file, point.rule, point.source, point.exports, point.names);
485
546
  }
547
+ const heldScripts = scriptHolds(
548
+ entries.map(({ entry }) => entry),
549
+ files,
550
+ entered,
551
+ options.host,
552
+ targetRoot,
553
+ );
554
+ const heldFiles = [...new Set(heldScripts.flatMap((one) => one.files))].flatMap(
555
+ (path) => files.byPath.get(path) ?? [],
556
+ );
486
557
  for (const file of files.byPath.values()) {
487
558
  for (const alias of typeQueryAliases(file)) {
488
559
  const source = alias.type.getText().replace(/\s+/gu, " ");
@@ -502,6 +573,7 @@ export function roots<Brand>(
502
573
  } = exportTables(project, held, [
503
574
  ...entered.filter((entry) => entry.exports).map((entry) => entry.file),
504
575
  ...published,
576
+ ...heldFiles,
505
577
  ]);
506
578
  for (const entry of entered) {
507
579
  const id = fileIds.get(entry.file.fileName);
@@ -540,14 +612,23 @@ export function roots<Brand>(
540
612
  const left = sites.get(a.id);
541
613
  const right = sites.get(b.id);
542
614
  const site = left === undefined || right === undefined ? 0 : byPosition(left, right);
543
- return site || compare(a.kind, b.kind) || compare(a.source, b.source);
615
+ return site || bytewise(a.kind, b.kind) || bytewise(a.source, b.source);
544
616
  });
545
617
 
546
618
  return {
547
619
  liveUnderReachability: ordered,
548
620
  unmatched,
549
- unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
621
+ unanswered: [
622
+ ...(answered ? api.guessed : held.symbols.map((symbol) => symbol.id)),
623
+ // A command runs a held file and may read any export of it, so the file, which reaches its
624
+ // top level, and its exports are held; a declaration neither reaches is still dead.
625
+ ...heldFiles.flatMap((file) => {
626
+ const id = fileIds.get(file.fileName);
627
+ return [...(id === undefined ? [] : [id]), ...(exportsOf.get(file.fileName) ?? [])];
628
+ }),
629
+ ],
550
630
  unmappedEntries,
631
+ heldScripts,
551
632
  };
552
633
  }
553
634
 
@@ -657,12 +738,12 @@ function releaseTagged(
657
738
  const EXPORTING_CONTAINERS: ReadonlySet<string> = new Set(["file", "namespace"]);
658
739
 
659
740
  /**
660
- * Every declaration a consumer of the library can name: what the published files export,
661
- * and below each the members a consumer writes, a namespace's exported ones and any other
662
- * declaration's public and protected ones. A declaration a published one's type names is
663
- * published too, and so is an exported declaration a release tag marks. A type name the
664
- * checker leaves unanswered publishes every declaration spelled as it is, as `guessed`. A
665
- * published file is not itself published.
741
+ * Every declaration a consumer of the library can name, a published file itself excluded:
742
+ * what the published files export, with a namespace's exported members and any other
743
+ * declaration's public and protected ones; a declaration a published one's type names; an
744
+ * exported declaration a release tag marks; each declaration a `declare global` block holds
745
+ * in a published file or one holding a published declaration; and, as `guessed`, each
746
+ * declaration spelled as a type name the checker leaves unanswered.
666
747
  */
667
748
  function publishedAPI<Brand>(
668
749
  project: ProjectView<Brand>,
@@ -695,48 +776,84 @@ function publishedAPI<Brand>(
695
776
  reach(id);
696
777
  }
697
778
  }
698
- while (pending.length > 0) {
699
- const round: string[] = [];
700
- for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
701
- round.push(next);
702
- const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
703
- for (const member of children.get(next) ?? []) {
704
- const reachable = exporting
705
- ? member.exported
706
- : member.visibility !== "private" && member.visibility !== "private-name";
707
- if (member.kind !== "type-parameter" && reachable) {
779
+ const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
780
+ const blocks = held.symbols.filter((one) => isGlobalBlock(one, held, files));
781
+ const fileOf = (id: string): string | undefined => {
782
+ let at = byId.get(id);
783
+ while (at !== undefined && at.kind !== "file") {
784
+ at = byId.get(at.parent);
785
+ }
786
+ return at?.id;
787
+ };
788
+ const publishedFiles = published.flatMap(
789
+ (file) => held.declarations.get(nodeKey(file, file)) ?? [],
790
+ );
791
+ const globalsReached = (): boolean => {
792
+ const typed = new Set([...publishedFiles, ...[...reached].flatMap((id) => fileOf(id) ?? [])]);
793
+ const before = pending.length;
794
+ for (const block of blocks.filter((one) => typed.has(one.parent))) {
795
+ reach(block.id);
796
+ for (const member of children.get(block.id) ?? []) {
797
+ if (member.kind !== "type-parameter") {
708
798
  reach(member.id);
709
799
  }
710
800
  }
711
801
  }
712
- const named = round.flatMap((id) => {
713
- const symbol = byId.get(id);
714
- return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
715
- });
716
- for (const symbol of inferredTypeSymbols(
717
- project,
718
- round.flatMap((id) => {
719
- const symbol = byId.get(id);
720
- return symbol === undefined ? [] : nodes.nodesOf(symbol);
721
- }),
722
- )) {
723
- for (const target of chains.chainOf(symbol)) {
724
- reach(target.id);
725
- }
726
- }
727
- project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
728
- if (symbol === UNANSWERED) {
729
- for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
730
- guessed.add(id);
731
- reach(id);
802
+ return pending.length > before;
803
+ };
804
+ do {
805
+ while (pending.length > 0) {
806
+ const round: string[] = [];
807
+ for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
808
+ round.push(next);
809
+ const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
810
+ for (const member of children.get(next) ?? []) {
811
+ const reachable = exporting
812
+ ? member.exported
813
+ : member.visibility !== "private" && member.visibility !== "private-name";
814
+ if (member.kind !== "type-parameter" && reachable) {
815
+ reach(member.id);
816
+ }
732
817
  }
733
- } else if (symbol !== undefined) {
818
+ }
819
+ const named = round.flatMap((id) => {
820
+ const symbol = byId.get(id);
821
+ return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
822
+ });
823
+ const inferred = inferredTypeSymbols(
824
+ project,
825
+ round.flatMap((id) => {
826
+ const symbol = byId.get(id);
827
+ return symbol === undefined ? [] : nodes.nodesOf(symbol);
828
+ }),
829
+ );
830
+ for (const symbol of inferred.types) {
734
831
  for (const target of chains.chainOf(symbol)) {
735
832
  reach(target.id);
736
833
  }
737
834
  }
738
- });
739
- }
835
+ for (const symbol of inferred.members) {
836
+ for (const target of chains.chainOf(symbol)) {
837
+ const member = byId.get(target.id);
838
+ if (member?.visibility === "public" || member?.visibility === "protected") {
839
+ reach(target.id);
840
+ }
841
+ }
842
+ }
843
+ project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
844
+ if (symbol === UNANSWERED) {
845
+ for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
846
+ guessed.add(id);
847
+ reach(id);
848
+ }
849
+ } else if (symbol !== undefined) {
850
+ for (const target of chains.chainOf(symbol)) {
851
+ reach(target.id);
852
+ }
853
+ }
854
+ });
855
+ }
856
+ } while (globalsReached());
740
857
  return { ids: [...reached], guessed: [...guessed] };
741
858
  }
742
859
 
@@ -783,30 +900,139 @@ function declarationNodes(
783
900
  const INFERRED_DEPTH = 4;
784
901
 
785
902
  /**
786
- * The declarations of the types the compiler infers where a declaration writes none: the
787
- * type of a variable or a property with no annotation, and the result of a function or a
788
- * method with no result annotation, each with the types its type arguments and its union
789
- * or intersection constituents name, to a fixed depth. A question the checker leaves
790
- * unanswered names nothing.
903
+ * The values one function body returns, each through the type assertions around it: the
904
+ * value a caller receives carries the operand's type whatever type the assertion or the
905
+ * signature names. A nested function's returns are its own.
906
+ */
907
+ function returnedValues(body: Node): readonly Expression[] {
908
+ const found: Expression[] = [];
909
+ const add = (value: Expression): void => {
910
+ for (let at: Expression | undefined = value; at !== undefined;) {
911
+ found.push(at);
912
+ at =
913
+ isAssertionExpression(at) || isSatisfiesExpression(at) || isParenthesizedExpression(at)
914
+ ? at.expression
915
+ : undefined;
916
+ }
917
+ };
918
+ if (!isFunctionLikeDeclaration(body.parent) || body.parent.body !== body) {
919
+ return found;
920
+ }
921
+ if (isArrowFunction(body.parent) && body.kind !== SyntaxKind.Block) {
922
+ add(body as Expression);
923
+ return found;
924
+ }
925
+ const visit = (node: Node): void => {
926
+ if (isFunctionLikeDeclaration(node)) {
927
+ return;
928
+ }
929
+ if (isReturnStatement(node) && node.expression !== undefined) {
930
+ add(node.expression);
931
+ }
932
+ node.forEachChild(visit);
933
+ };
934
+ body.forEachChild(visit);
935
+ return found;
936
+ }
937
+
938
+ /** The object literal one returned value is, written there or as the initializer of a variable `body` declares. */
939
+ function returnedLiteral(value: Expression, body: Node): ObjectLiteralExpression | undefined {
940
+ if (isObjectLiteralExpression(value)) {
941
+ return value;
942
+ }
943
+ if (!isIdentifier(value)) {
944
+ return undefined;
945
+ }
946
+ let found: ObjectLiteralExpression | undefined;
947
+ const visit = (node: Node): void => {
948
+ if (
949
+ isVariableDeclaration(node) &&
950
+ isIdentifier(node.name) &&
951
+ node.name.text === value.text &&
952
+ node.initializer !== undefined
953
+ ) {
954
+ let at: Expression = node.initializer;
955
+ while (
956
+ isAssertionExpression(at) ||
957
+ isSatisfiesExpression(at) ||
958
+ isParenthesizedExpression(at)
959
+ ) {
960
+ at = at.expression;
961
+ }
962
+ found = isObjectLiteralExpression(at) ? at : found;
963
+ }
964
+ if (!isFunctionLikeDeclaration(node)) {
965
+ node.forEachChild(visit);
966
+ }
967
+ };
968
+ body.forEachChild(visit);
969
+ return found;
970
+ }
971
+
972
+ /**
973
+ * The values one function body returns, and those each method of an object literal it
974
+ * returns returns, to a fixed depth: a caller holding the literal calls its methods, and
975
+ * receives what they return.
976
+ */
977
+ function returnedThroughLiterals(body: Node, depth = 0): readonly Expression[] {
978
+ const values = returnedValues(body);
979
+ if (depth >= INFERRED_DEPTH) {
980
+ return values;
981
+ }
982
+ const found = [...values];
983
+ for (const value of values) {
984
+ for (const property of returnedLiteral(value, body)?.properties ?? []) {
985
+ const fn = isMethodDeclaration(property)
986
+ ? property
987
+ : isPropertyAssignment(property) && isFunctionLikeDeclaration(property.initializer)
988
+ ? property.initializer
989
+ : undefined;
990
+ if (fn?.body !== undefined) {
991
+ found.push(...returnedThroughLiterals(fn.body, depth + 1));
992
+ }
993
+ }
994
+ }
995
+ return found;
996
+ }
997
+
998
+ /**
999
+ * The declarations of the types the compiler infers where a declaration writes none, and
1000
+ * of the values a function returns: the type of a variable or a property with no
1001
+ * annotation, the result of a function or a method with no result annotation, and the type
1002
+ * of each value its body returns, each with the types its type arguments and its union or
1003
+ * intersection constituents name, to a fixed depth; and as `members` the properties of each
1004
+ * returned value's type. A question the checker leaves unanswered names nothing.
791
1005
  */
792
1006
  function inferredTypeSymbols<Brand>(
793
1007
  project: ProjectView<Brand>,
794
1008
  declarations: readonly Node[],
795
- ): readonly TSSymbol[] {
1009
+ ): { readonly types: readonly TSSymbol[]; readonly members: readonly TSSymbol[] } {
796
1010
  const { queries } = project;
797
- const valued = declarations.flatMap((node) =>
1011
+ const valued: Node[] = declarations.flatMap((node) =>
798
1012
  (isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
799
1013
  node.type === undefined &&
800
1014
  !isFunctionLikeDeclaration(node.initializer ?? node)
801
1015
  ? [node.name]
802
1016
  : [],
803
1017
  );
1018
+ const returned = declarations.flatMap((node) => {
1019
+ const signature = isFunctionLikeDeclaration(node)
1020
+ ? node
1021
+ : isVariableDeclaration(node) &&
1022
+ node.initializer !== undefined &&
1023
+ isFunctionLikeDeclaration(node.initializer)
1024
+ ? node.initializer
1025
+ : undefined;
1026
+ return signature?.body === undefined ? [] : returnedThroughLiterals(signature.body);
1027
+ });
804
1028
  const types: Type[] = [];
805
- queries.typesAt(valued).forEach((type) => {
1029
+ const returnedTypes: Type[] = [];
1030
+ queries.typesAt([...valued, ...returned]).forEach((type, at) => {
806
1031
  if (type !== UNANSWERED && type !== undefined) {
807
- types.push(type);
1032
+ (at < valued.length ? types : returnedTypes).push(type);
808
1033
  }
809
1034
  });
1035
+ types.push(...returnedTypes);
810
1036
  for (const node of declarations) {
811
1037
  const signature = isFunctionLikeDeclaration(node)
812
1038
  ? node
@@ -849,7 +1075,19 @@ function inferredTypeSymbols<Brand>(
849
1075
  types.forEach((type) => {
850
1076
  visit(type, 0);
851
1077
  });
852
- return [...found.values()];
1078
+ // A returned value carries each property of its type, one an anonymous type declares or
1079
+ // an intersection merges included, which no type name reaches.
1080
+ const members = new Map<number, TSSymbol>();
1081
+ for (const type of returnedTypes) {
1082
+ const parts = queries.constituents(type);
1083
+ for (const one of [type, ...(parts === UNANSWERED ? [] : parts)]) {
1084
+ const properties = queries.propertiesOf(one);
1085
+ for (const property of properties === UNANSWERED ? [] : properties) {
1086
+ members.set(property.id, property);
1087
+ }
1088
+ }
1089
+ }
1090
+ return { types: [...found.values()], members: [...members.values()] };
853
1091
  }
854
1092
 
855
1093
  /**
@@ -911,13 +1149,10 @@ function configured(
911
1149
  }
912
1150
 
913
1151
  /**
914
- * Every configured string that named no declaration in ANY project of the run, in
915
- * configuration order and each once.
916
- *
917
- * A finding about a configured root is about the configuration and not about a
918
- * declaration, so there is one per run: a string that names a declaration of one
919
- * project has named something, and the projects it named nothing in are not a
920
- * finding. A run with no project names nothing with any of them.
1152
+ * Every configured string that named no declaration in any project of the run, in
1153
+ * configuration order and each once. The finding is about the configuration, so a string
1154
+ * naming a declaration of one project has named something, and a run with no project
1155
+ * names nothing with any of them.
921
1156
  */
922
1157
  export function unmatchedEverywhere(
923
1158
  patterns: readonly string[],