@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/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,24 +24,37 @@ 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";
25
- import type { ParseFile } from "./configuration-files.ts";
34
+ import { isGlobalBlock } from "./augmentations.ts";
35
+ import {
36
+ filesNamedBy,
37
+ outsideChain,
38
+ relativeSpecifiers,
39
+ type ParseFile,
40
+ } from "./configuration-files.ts";
26
41
  import { conventionEntries, type Conventions } from "./conventions.ts";
27
42
  import { entryPoints, type EntryRule } from "./entry-points.ts";
28
- import { globExpression } from "./glob.ts";
29
43
  import type { Host } from "./host.ts";
30
44
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
31
45
  import type { Manifest, ManifestEntry } from "./manifest.ts";
32
- import { relativePath, resolvePath } from "./paths.ts";
46
+ import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.ts";
33
47
  import { byPosition } from "./position.ts";
34
48
  import { UNANSWERED } from "./query.ts";
35
49
  import type { ProjectView } from "./session.ts";
36
50
  import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
37
- import { sourceFilesOf } from "./source-files.ts";
51
+ import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
38
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";
39
58
 
40
59
  /** Why one declaration is a root. */
41
60
  export type RootKind =
@@ -62,6 +81,8 @@ export type RootKind =
62
81
  | "pattern"
63
82
  /** A declaration of a function or statement a type error skipped, and what it may have selected. */
64
83
  | "type-error"
84
+ /** An ambient global interface merging into one declared outside the target, and its holders. */
85
+ | "global-augmentation"
65
86
  /** A file a tool or the platform enters by its own convention, and what it exports where the tool reads it. */
66
87
  | EntryRule;
67
88
 
@@ -82,14 +103,9 @@ export interface Root {
82
103
  /** One project's roots, and every configured string that named nothing in it. */
83
104
  export interface Roots {
84
105
  /**
85
- * Every root of this project, ordered by the site of the declaration it names,
86
- * then by kind, then by the string that named it. A declaration that is a root for
87
- * more than one reason appears once per reason, so an explanation can name every
88
- * reason it is live.
89
- *
90
- * The member says which relation the set feeds: every declaration here is live
91
- * under reachability. A root of a kind that names a caller is also live under
92
- * 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.
93
109
  */
94
110
  readonly liveUnderReachability: readonly Root[];
95
111
  /**
@@ -101,9 +117,22 @@ export interface Roots {
101
117
  /**
102
118
  * Every declaration of the project, where the checker left the module or export table
103
119
  * of an entry or a published file unanswered, since which of them that file exports is
104
- * 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.
105
122
  */
106
123
  readonly unanswered: readonly string[];
124
+ /** Every manifest entry naming a file to import or run that reads back to no file of this project. */
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[];
107
136
  }
108
137
 
109
138
  /** What decides the roots beside the declarations themselves. */
@@ -134,6 +163,8 @@ export interface RootOptions {
134
163
  * inside the target whose entries are published API whatever the target's kind.
135
164
  */
136
165
  readonly publishedMembers?: ReadonlySet<string> | undefined;
166
+ /** The files the configurations read from a file hold, so a file another configuration compiles is no outside file. */
167
+ readonly runFiles?: ReadonlySet<string> | undefined;
137
168
  }
138
169
 
139
170
  /** Whether one declaration is written below the directory of a published workspace member. */
@@ -171,11 +202,18 @@ const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
171
202
  /** The extension a source file's name drops. */
172
203
  const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
173
204
 
205
+ /** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
206
+ function ofPackage(host: Host, dir: string, file: string): boolean {
207
+ const between = ancestors(dirnamePath(file), dir);
208
+ return between.length > 0 && !between.slice(0, -1).some((at) => holdsManifest(host, at));
209
+ }
210
+
174
211
  /**
175
212
  * The rooted files one manifest target reads back to by name: its path below the
176
213
  * manifest's directory, past its first directory where it names one, without its output
177
- * extension, is the name, and a rooted file is one whose path below the same directory,
178
- * without its source extension, ends with that name at a directory boundary.
214
+ * extension, is the name, and a rooted file of the manifest's own package is one whose
215
+ * path below the same directory, without its source extension, ends with that name at a
216
+ * directory boundary.
179
217
  */
180
218
  function readBackByName(
181
219
  host: Host,
@@ -195,13 +233,52 @@ function readBackByName(
195
233
  const found = new Map<string, SourceFile>();
196
234
  for (const file of rooted) {
197
235
  const path = relativePath(dir, file.fileName)?.replace(SOURCE_EXTENSION, "");
198
- if (path !== undefined && (path === name || path.endsWith(`/${name}`))) {
236
+ if (
237
+ path !== undefined &&
238
+ (path === name || path.endsWith(`/${name}`)) &&
239
+ ofPackage(host, dir, file.fileName)
240
+ ) {
199
241
  found.set(file.fileName, file);
200
242
  }
201
243
  }
202
244
  return [...found.values()];
203
245
  }
204
246
 
247
+ /**
248
+ * The program files a manifest target no program holds reaches: the target is a file
249
+ * read as a configuration file outside the program is, and each file of the chain it
250
+ * starts loads the program files its relative specifiers name, read through the emit
251
+ * mapping, as a command's shim loads the compiled entry it runs.
252
+ */
253
+ function throughOutside(
254
+ options: RootOptions,
255
+ targetRoot: string,
256
+ files: SourceFiles,
257
+ entry: ManifestEntry,
258
+ ): readonly SourceFile[] {
259
+ const cwd = options.host.workingDirectory();
260
+ const path = resolvePath(cwd, entry.path);
261
+ if (
262
+ options.parse === undefined ||
263
+ options.runFiles?.has(path) === true ||
264
+ options.host.kindOf(path) !== "file"
265
+ ) {
266
+ return [];
267
+ }
268
+ const chain = outsideChain(options.host, targetRoot, files, options.parse, [
269
+ { path, manifestDir: resolvePath(cwd, entry.dir) },
270
+ ]);
271
+ const found = new Map<string, SourceFile>();
272
+ for (const { file } of chain) {
273
+ for (const specifier of relativeSpecifiers(file)) {
274
+ for (const named of filesNamedBy(files, dirnamePath(file.fileName), specifier)) {
275
+ found.set(named.fileName, named);
276
+ }
277
+ }
278
+ }
279
+ return [...found.values()];
280
+ }
281
+
205
282
  /** Whether a configured string carries either special character. */
206
283
  function isPattern(text: string): boolean {
207
284
  return text.includes("*") || text.includes("?");
@@ -254,14 +331,6 @@ export function matchRef(pattern: string, ref: string): boolean {
254
331
  return matchGlob(Array.from(pattern), Array.from(ref));
255
332
  }
256
333
 
257
- /** Two strings ordered bytewise, which is the order every set of a run is read in. */
258
- function compare(a: string, b: string): number {
259
- if (a === b) {
260
- return 0;
261
- }
262
- return a < b ? -1 : 1;
263
- }
264
-
265
334
  /** The members of one declaration, indexed by the container that holds them. */
266
335
  function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymbol[]> {
267
336
  const byParent = new Map<string, InventorySymbol[]>();
@@ -277,15 +346,60 @@ function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymb
277
346
  }
278
347
 
279
348
  /**
280
- * The roots one project holds, and every configured string that named nothing in it.
281
- *
282
- * A file a manifest member, an entry-file pattern or a tool's configuration names
283
- * is a root with everything its module exports: each name of the module's export
284
- * table, and where a name is a re-export, every link of the chain to the declaration
285
- * it carries, so a re-exported declaration is a root of the file that publishes it.
286
- * In a library, what a published file exports is rooted by the published API alone.
287
- * A file a runtime executes without reading its exports, a test file or a worker, is
288
- * 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.
289
403
  */
290
404
  export function roots<Brand>(
291
405
  project: ProjectView<Brand>,
@@ -307,9 +421,21 @@ export function roots<Brand>(
307
421
  found.set([id, kind, source].join("\u0000"), { id, kind, source });
308
422
  };
309
423
 
310
- const entered: { file: SourceFile; kind: RootKind; source: string; exports: boolean }[] = [];
311
- const enter = (file: SourceFile, kind: RootKind, source: string, exports: boolean): void => {
312
- entered.push({ file, kind, source, exports });
424
+ const entered: {
425
+ file: SourceFile;
426
+ kind: RootKind;
427
+ source: string;
428
+ exports: boolean;
429
+ names?: readonly string[];
430
+ }[] = [];
431
+ const enter = (
432
+ file: SourceFile,
433
+ kind: RootKind,
434
+ source: string,
435
+ exports: boolean,
436
+ names?: readonly string[],
437
+ ): void => {
438
+ entered.push({ file, kind, source, exports, ...(names === undefined ? {} : { names }) });
313
439
  };
314
440
 
315
441
  const points = entryPoints(
@@ -338,46 +464,96 @@ export function roots<Brand>(
338
464
  })),
339
465
  ];
340
466
  const scripted = entries.flatMap(({ entry }) =>
341
- entry.role === "script" ? files.named(entry.path) : [],
467
+ entry.role === "script" && entry.command === undefined ? files.named(entry.path) : [],
342
468
  );
343
469
  const readBackTo = [
344
470
  ...scripted,
345
471
  ...points.filter((point) => READ_BACK_RULES.has(point.rule)).map((point) => point.file),
346
472
  ];
473
+ const entryFilesOf = new Map<ManifestEntry, readonly SourceFile[]>();
347
474
  const entryFiles = (entry: ManifestEntry): readonly SourceFile[] => {
475
+ const known = entryFilesOf.get(entry);
476
+ if (known !== undefined) {
477
+ return known;
478
+ }
479
+ const found = readEntryFiles(entry);
480
+ entryFilesOf.set(entry, found);
481
+ return found;
482
+ };
483
+ const readEntryFiles = (entry: ManifestEntry): readonly SourceFile[] => {
348
484
  const named = files.named(entry.path);
349
- return named.length > 0 || entry.role === "script"
350
- ? named
351
- : readBackByName(options.host, entry, readBackTo);
485
+ if (named.length > 0 || entry.role === "script") {
486
+ return named;
487
+ }
488
+ const cwd = options.host.workingDirectory();
489
+ const shipped = files.namedAsOutput(resolvePath(cwd, entry.dir), resolvePath(cwd, entry.path));
490
+ if (shipped.length > 0) {
491
+ return shipped;
492
+ }
493
+ const byName = readBackByName(options.host, entry, readBackTo);
494
+ return byName.length > 0 ? byName : throughOutside(options, targetRoot, files, entry);
352
495
  };
353
496
  for (const { entry, publishes } of entries) {
354
497
  if (publishes) {
355
498
  published.push(...entryFiles(entry));
356
499
  }
357
500
  }
358
- const publishedNames = new Set(published.map((file) => file.fileName));
359
- for (const { entry } of entries) {
360
- for (const file of entryFiles(entry)) {
361
- const kind = ENTRY_KINDS[entry.role];
362
- enter(file, kind, entry.member, !publishedNames.has(file.fileName));
363
- }
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
+ );
364
506
  }
507
+ const publishedNames = new Set(published.map((file) => file.fileName));
508
+ const patterned: { file: SourceFile; pattern: string }[] = [];
365
509
  for (const pattern of options.entryFiles) {
366
510
  const expression = globExpression(pattern);
367
511
  for (const [path, file] of files.byPath) {
368
512
  if (expression.test(path)) {
369
- enter(file, "entry-file", pattern, true);
513
+ patterned.push({ file, pattern });
370
514
  }
371
515
  }
372
516
  }
517
+ const unmapped: ManifestEntry[] = [];
518
+ const mapped = patterned.map(({ file }) => file);
519
+ for (const { entry } of entries) {
520
+ if (entry.command !== undefined) {
521
+ continue;
522
+ }
523
+ const named = entryFiles(entry);
524
+ if (named.length === 0 && entry.role !== "script") {
525
+ unmapped.push(entry);
526
+ }
527
+ mapped.push(...named);
528
+ for (const file of named) {
529
+ const kind = ENTRY_KINDS[entry.role];
530
+ enter(file, kind, entry.member, !publishedNames.has(file.fileName));
531
+ }
532
+ }
533
+ const unmappedEntries = unmapped.filter(
534
+ (entry) => readBackByName(options.host, entry, mapped).length === 0,
535
+ );
536
+ for (const { file, pattern } of patterned) {
537
+ enter(file, "entry-file", pattern, true);
538
+ }
373
539
  for (const { file, row } of options.conventions === undefined
374
540
  ? []
375
541
  : conventionEntries(options.conventions, files)) {
376
542
  enter(file, "convention", row, true);
377
543
  }
378
544
  for (const point of points) {
379
- enter(point.file, point.rule, point.source, point.exports);
545
+ enter(point.file, point.rule, point.source, point.exports, point.names);
380
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
+ );
381
557
  for (const file of files.byPath.values()) {
382
558
  for (const alias of typeQueryAliases(file)) {
383
559
  const source = alias.type.getText().replace(/\s+/gu, " ");
@@ -390,9 +566,14 @@ export function roots<Brand>(
390
566
  }
391
567
  }
392
568
 
393
- const { tables: exportsOf, answered } = exportTables(project, held, [
569
+ const {
570
+ tables: exportsOf,
571
+ named,
572
+ answered,
573
+ } = exportTables(project, held, [
394
574
  ...entered.filter((entry) => entry.exports).map((entry) => entry.file),
395
575
  ...published,
576
+ ...heldFiles,
396
577
  ]);
397
578
  for (const entry of entered) {
398
579
  const id = fileIds.get(entry.file.fileName);
@@ -401,8 +582,13 @@ export function roots<Brand>(
401
582
  }
402
583
  add(id, entry.kind, entry.source);
403
584
  if (entry.exports) {
404
- for (const exported of exportsOf.get(entry.file.fileName) ?? []) {
405
- add(exported, entry.kind, entry.source);
585
+ const byName = named.get(entry.file.fileName);
586
+ const exported =
587
+ entry.names === undefined || byName === undefined
588
+ ? (exportsOf.get(entry.file.fileName) ?? [])
589
+ : entry.names.flatMap((name) => byName.get(name) ?? []);
590
+ for (const one of exported) {
591
+ add(one, entry.kind, entry.source);
406
592
  }
407
593
  }
408
594
  }
@@ -426,13 +612,23 @@ export function roots<Brand>(
426
612
  const left = sites.get(a.id);
427
613
  const right = sites.get(b.id);
428
614
  const site = left === undefined || right === undefined ? 0 : byPosition(left, right);
429
- 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);
430
616
  });
431
617
 
432
618
  return {
433
619
  liveUnderReachability: ordered,
434
620
  unmatched,
435
- 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
+ ],
630
+ unmappedEntries,
631
+ heldScripts,
436
632
  };
437
633
  }
438
634
 
@@ -448,12 +644,18 @@ function exportTables<Brand>(
448
644
  project: ProjectView<Brand>,
449
645
  held: Inventory,
450
646
  wanted: readonly SourceFile[],
451
- ): { readonly tables: ReadonlyMap<string, readonly string[]>; readonly answered: boolean } {
647
+ ): {
648
+ readonly tables: ReadonlyMap<string, readonly string[]>;
649
+ /** Per file whose table the checker answered, the declarations each export name reaches. */
650
+ readonly named: ReadonlyMap<string, ReadonlyMap<string, readonly string[]>>;
651
+ readonly answered: boolean;
652
+ } {
452
653
  const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
453
654
  const tables = new Map<string, readonly string[]>();
655
+ const named = new Map<string, Map<string, string[]>>();
454
656
  let answered = true;
455
657
  if (unique.length === 0) {
456
- return { tables, answered };
658
+ return { tables, named, answered };
457
659
  }
458
660
  const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
459
661
  const chains = aliasChains(project, held);
@@ -475,15 +677,18 @@ function exportTables<Brand>(
475
677
  return;
476
678
  }
477
679
  const ids = new Set<string>();
680
+ const byName = new Map<string, string[]>();
478
681
  for (const exported of table) {
479
682
  for (const target of chains.chainOf(exported)) {
480
683
  answered &&= !target.guessed;
481
684
  ids.add(target.id);
685
+ byName.set(exported.name, [...(byName.get(exported.name) ?? []), target.id]);
482
686
  }
483
687
  }
484
688
  tables.set(file.fileName, [...ids]);
689
+ named.set(file.fileName, byName);
485
690
  });
486
- return { tables, answered };
691
+ return { tables, named, answered };
487
692
  }
488
693
 
489
694
  /** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
@@ -533,12 +738,12 @@ function releaseTagged(
533
738
  const EXPORTING_CONTAINERS: ReadonlySet<string> = new Set(["file", "namespace"]);
534
739
 
535
740
  /**
536
- * Every declaration a consumer of the library can name: what the published files export,
537
- * and below each the members a consumer writes, a namespace's exported ones and any other
538
- * declaration's public and protected ones. A declaration a published one's type names is
539
- * published too, and so is an exported declaration a release tag marks. A type name the
540
- * checker leaves unanswered publishes every declaration spelled as it is, as `guessed`. A
541
- * 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.
542
747
  */
543
748
  function publishedAPI<Brand>(
544
749
  project: ProjectView<Brand>,
@@ -553,11 +758,13 @@ function publishedAPI<Brand>(
553
758
  const nodes = declarationNodes(held, byPath);
554
759
  const chains = aliasChains(project, held);
555
760
  const reached = new Set<string>();
761
+ const entered = new Set<string>();
556
762
  const guessed = new Set<string>();
557
763
  const pending: string[] = [];
558
764
  const reach = (id: string): void => {
559
- if (!reached.has(id) && byId.get(id)?.kind !== "file") {
560
- reached.add(id);
765
+ const file = byId.get(id)?.kind === "file";
766
+ if (!(file ? entered : reached).has(id)) {
767
+ (file ? entered : reached).add(id);
561
768
  pending.push(id);
562
769
  }
563
770
  };
@@ -566,56 +773,87 @@ function publishedAPI<Brand>(
566
773
  }
567
774
  for (const file of published) {
568
775
  for (const id of exportsOf.get(file.fileName) ?? []) {
569
- if (byId.get(id)?.kind === "file") {
570
- pending.push(id);
571
- } else {
572
- reach(id);
573
- }
776
+ reach(id);
574
777
  }
575
778
  }
576
- while (pending.length > 0) {
577
- const round: string[] = [];
578
- for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
579
- round.push(next);
580
- const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
581
- for (const member of children.get(next) ?? []) {
582
- const reachable = exporting
583
- ? member.exported
584
- : member.visibility !== "private" && member.visibility !== "private-name";
585
- 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") {
586
798
  reach(member.id);
587
799
  }
588
800
  }
589
801
  }
590
- const named = round.flatMap((id) => {
591
- const symbol = byId.get(id);
592
- return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
593
- });
594
- for (const symbol of inferredTypeSymbols(
595
- project,
596
- round.flatMap((id) => {
597
- const symbol = byId.get(id);
598
- const node = symbol === undefined ? undefined : nodes.nodeOf(symbol);
599
- return node === undefined ? [] : [node];
600
- }),
601
- )) {
602
- for (const target of chains.chainOf(symbol)) {
603
- reach(target.id);
604
- }
605
- }
606
- project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
607
- if (symbol === UNANSWERED) {
608
- for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
609
- guessed.add(id);
610
- 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
+ }
611
817
  }
612
- } 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) {
613
831
  for (const target of chains.chainOf(symbol)) {
614
832
  reach(target.id);
615
833
  }
616
834
  }
617
- });
618
- }
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());
619
857
  return { ids: [...reached], guessed: [...guessed] };
620
858
  }
621
859
 
@@ -625,24 +863,22 @@ function declarationNodes(
625
863
  byPath: ReadonlyMap<string, SourceFile>,
626
864
  ): {
627
865
  typeNamesOf(symbol: InventorySymbol): readonly Node[];
628
- nodeOf(symbol: InventorySymbol): Node | undefined;
866
+ nodesOf(symbol: InventorySymbol): readonly Node[];
629
867
  } {
630
- const walked = new Map<string, { byId: Map<string, Node>; nodes: Set<Node> }>();
631
- const nodesIn = (path: string): { byId: Map<string, Node>; nodes: Set<Node> } => {
868
+ const walked = new Map<string, { byId: Map<string, Node[]>; nodes: Set<Node> }>();
869
+ const nodesIn = (path: string): { byId: Map<string, Node[]>; nodes: Set<Node> } => {
632
870
  const known = walked.get(path);
633
871
  if (known !== undefined) {
634
872
  return known;
635
873
  }
636
- const found = { byId: new Map<string, Node>(), nodes: new Set<Node>() };
874
+ const found = { byId: new Map<string, Node[]>(), nodes: new Set<Node>() };
637
875
  const file = byPath.get(path);
638
876
  if (file !== undefined) {
639
877
  const visit = (node: Node): void => {
640
878
  const id = held.declarations.get(nodeKey(file, node));
641
879
  if (id !== undefined) {
642
880
  found.nodes.add(node);
643
- if (!found.byId.has(id)) {
644
- found.byId.set(id, node);
645
- }
881
+ found.byId.set(id, [...(found.byId.get(id) ?? []), node]);
646
882
  }
647
883
  node.forEachChild(visit);
648
884
  };
@@ -654,10 +890,9 @@ function declarationNodes(
654
890
  return {
655
891
  typeNamesOf: (symbol) => {
656
892
  const { byId, nodes } = nodesIn(symbol.position.path);
657
- const declaration = byId.get(symbol.id);
658
- return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
893
+ return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
659
894
  },
660
- nodeOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id),
895
+ nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
661
896
  };
662
897
  }
663
898
 
@@ -665,30 +900,139 @@ function declarationNodes(
665
900
  const INFERRED_DEPTH = 4;
666
901
 
667
902
  /**
668
- * The declarations of the types the compiler infers where a declaration writes none: the
669
- * type of a variable or a property with no annotation, and the result of a function or a
670
- * method with no result annotation, each with the types its type arguments and its union
671
- * or intersection constituents name, to a fixed depth. A question the checker leaves
672
- * 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.
673
1005
  */
674
1006
  function inferredTypeSymbols<Brand>(
675
1007
  project: ProjectView<Brand>,
676
1008
  declarations: readonly Node[],
677
- ): readonly TSSymbol[] {
1009
+ ): { readonly types: readonly TSSymbol[]; readonly members: readonly TSSymbol[] } {
678
1010
  const { queries } = project;
679
- const valued = declarations.flatMap((node) =>
1011
+ const valued: Node[] = declarations.flatMap((node) =>
680
1012
  (isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
681
1013
  node.type === undefined &&
682
1014
  !isFunctionLikeDeclaration(node.initializer ?? node)
683
1015
  ? [node.name]
684
1016
  : [],
685
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
+ });
686
1028
  const types: Type[] = [];
687
- queries.typesAt(valued).forEach((type) => {
1029
+ const returnedTypes: Type[] = [];
1030
+ queries.typesAt([...valued, ...returned]).forEach((type, at) => {
688
1031
  if (type !== UNANSWERED && type !== undefined) {
689
- types.push(type);
1032
+ (at < valued.length ? types : returnedTypes).push(type);
690
1033
  }
691
1034
  });
1035
+ types.push(...returnedTypes);
692
1036
  for (const node of declarations) {
693
1037
  const signature = isFunctionLikeDeclaration(node)
694
1038
  ? node
@@ -731,7 +1075,19 @@ function inferredTypeSymbols<Brand>(
731
1075
  types.forEach((type) => {
732
1076
  visit(type, 0);
733
1077
  });
734
- 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()] };
735
1091
  }
736
1092
 
737
1093
  /**
@@ -793,13 +1149,10 @@ function configured(
793
1149
  }
794
1150
 
795
1151
  /**
796
- * Every configured string that named no declaration in ANY project of the run, in
797
- * configuration order and each once.
798
- *
799
- * A finding about a configured root is about the configuration and not about a
800
- * declaration, so there is one per run: a string that names a declaration of one
801
- * project has named something, and the projects it named nothing in are not a
802
- * 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.
803
1156
  */
804
1157
  export function unmatchedEverywhere(
805
1158
  patterns: readonly string[],