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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -1,36 +1,46 @@
1
1
  /**
2
2
  * The serialization-contract class: the data members and `toJSON` of every type a value's
3
3
  * static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
4
- * names, and `toString` too where it reaches an `unknown` or `any` parameter of a function
5
- * outside the analysis or an erased property of a type the program does not declare. A
6
- * function of the project handing such a parameter to a destination is one, to a fixpoint,
7
- * and a function invoked through `call`, `apply` or `bind` is the callee.
4
+ * names, and `toString` too where it reaches an erased parameter of a function outside the
5
+ * analysis, or an erased property of a type the program does not declare. A function of
6
+ * the project handing such a parameter to a destination is one, to a fixpoint, and a
7
+ * function invoked through `call`, `apply` or `bind` is the callee.
8
8
  */
9
9
 
10
10
  import {
11
11
  isArrayLiteralExpression,
12
12
  isArrowFunction,
13
13
  isAsExpression,
14
+ isAwaitExpression,
14
15
  isCallExpression,
15
16
  isClassDeclaration,
16
17
  isClassExpression,
17
18
  isClassStaticBlockDeclaration,
19
+ isElementAccessExpression,
20
+ isExpressionStatement,
18
21
  isFunctionDeclaration,
19
22
  isFunctionExpression,
23
+ isFunctionLikeDeclaration,
20
24
  isGetAccessorDeclaration,
21
25
  isIdentifier,
22
26
  isInterfaceDeclaration,
23
27
  isMethodDeclaration,
28
+ isNonNullExpression,
24
29
  isNoSubstitutionTemplateLiteral,
25
30
  isNumericLiteral,
26
31
  isObjectLiteralExpression,
27
32
  isParenthesizedExpression,
33
+ isForInStatement,
28
34
  isPropertyAccessExpression,
29
35
  isPropertyDeclaration,
36
+ isReturnStatement,
30
37
  isSatisfiesExpression,
31
38
  isSetAccessorDeclaration,
39
+ isSourceFile,
40
+ isSpreadAssignment,
32
41
  isSpreadElement,
33
42
  isStringLiteral,
43
+ isTypeAssertion,
34
44
  isTypeLiteralNode,
35
45
  isVariableDeclaration,
36
46
  ModifierFlags,
@@ -40,7 +50,10 @@ import {
40
50
  type Expression,
41
51
  type Node,
42
52
  type ParameterDeclaration,
53
+ type SourceFile,
54
+ type SpreadAssignment,
43
55
  } from "@typescript/native/ast";
56
+ import { parenthesesRemoved } from "./unwrap.ts";
44
57
  import {
45
58
  SignatureKind,
46
59
  SymbolFlags,
@@ -62,14 +75,14 @@ import {
62
75
  } from "./configured-declarations.ts";
63
76
  import type { DetectorInput, Evidence } from "./exempt.ts";
64
77
  import { nodeKey, type Inventory } from "./inventory.ts";
65
- import { renderPosition } from "./position.ts";
66
- import { must } from "./query.ts";
78
+ import { renderPosition, type Position } from "./position.ts";
79
+ import { must, Unanswerable } from "./query.ts";
67
80
  import type { ProjectView } from "./session.ts";
68
81
  import { erasedStores, type ErasedStores } from "./erased-stores.ts";
69
82
  import { checkedTypes, CONVERSION_MEMBERS, valueReach, type Reached } from "./value-reach.ts";
70
83
 
71
84
  /** The serializer every run reads, whatever the configuration names. */
72
- const JSON_STRINGIFY: DeclarationEntry = { shape: "global", global: "JSON.stringify" };
85
+ const JSON_STRINGIFY: DeclarationEntry = { global: "JSON.stringify" };
73
86
 
74
87
  /** The methods a serializer and a formatter resolve by name on a value that left the analysis. */
75
88
  const CONVERSIONS: ReadonlySet<string> = new Set(["toJSON", "toString"]);
@@ -80,6 +93,12 @@ const SERIALIZED_CONVERSION = "toJSON";
80
93
  /** The parameter types that keep nothing of the value they are given. */
81
94
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
82
95
 
96
+ /** The standard library's promise types, whose value is their one type argument. */
97
+ const PROMISES: ReadonlySet<string> = new Set(["Promise", "PromiseLike"]);
98
+
99
+ /** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
100
+ const MENTION_DEPTH = 3;
101
+
83
102
  /**
84
103
  * What a destination holds back of a class: its properties, or its properties and its
85
104
  * two conversion methods. The second holds back everything the first does.
@@ -209,12 +228,6 @@ interface MethodForm {
209
228
  readonly invocation: Invocation;
210
229
  }
211
230
 
212
- function unparenthesized(expression: Expression): Expression {
213
- return isParenthesizedExpression(expression)
214
- ? unparenthesized(expression.expression)
215
- : expression;
216
- }
217
-
218
231
  /**
219
232
  * The invocation a call writes through a method of `Function.prototype`, read from
220
233
  * the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
@@ -223,12 +236,12 @@ function unparenthesized(expression: Expression): Expression {
223
236
  * carries. Whether the method is the prototype's is resolution's to say.
224
237
  */
225
238
  function throughMethod(call: CallExpression): MethodForm | undefined {
226
- const expression = unparenthesized(call.expression);
239
+ const expression = parenthesesRemoved(call.expression);
227
240
  if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
228
241
  const [, ...rest] = call.arguments;
229
242
  let passed: readonly Expression[] = rest;
230
243
  if (expression.name.text === "apply") {
231
- const list = rest[0] === undefined ? undefined : unparenthesized(rest[0]);
244
+ const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
232
245
  passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
233
246
  }
234
247
  return {
@@ -237,7 +250,7 @@ function throughMethod(call: CallExpression): MethodForm | undefined {
237
250
  };
238
251
  }
239
252
  if (isCallExpression(expression)) {
240
- const bound = unparenthesized(expression.expression);
253
+ const bound = parenthesesRemoved(expression.expression);
241
254
  if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
242
255
  return {
243
256
  method: bound.name,
@@ -256,6 +269,8 @@ function throughMethod(call: CallExpression): MethodForm | undefined {
256
269
  interface Walked {
257
270
  readonly sites: readonly Site[];
258
271
  readonly parameters: readonly Parameter[];
272
+ /** The class properties whose declaration writes an initializer, by identifier. */
273
+ readonly initialized: ReadonlySet<string>;
259
274
  }
260
275
 
261
276
  /**
@@ -266,9 +281,16 @@ interface Walked {
266
281
  function walkProject<Brand>(project: ProjectView<Brand>, held: Inventory): Walked {
267
282
  const sites: Site[] = [];
268
283
  const parameters: Parameter[] = [];
284
+ const initialized = new Set<string>();
269
285
  for (const file of project.ownSourceFiles()) {
270
286
  const visit = (node: Node, self: string | undefined): void => {
271
287
  let inner = self;
288
+ if (isPropertyDeclaration(node) && node.initializer !== undefined) {
289
+ const id = held.declarations.get(nodeKey(file, node));
290
+ if (id !== undefined) {
291
+ initialized.add(id);
292
+ }
293
+ }
272
294
  if (isClassDeclaration(node) || isClassExpression(node)) {
273
295
  inner = held.declarations.get(nodeKey(file, node));
274
296
  } else if (resetsThis(node)) {
@@ -301,12 +323,38 @@ function walkProject<Brand>(project: ProjectView<Brand>, held: Inventory): Walke
301
323
  visit(child, undefined);
302
324
  });
303
325
  }
304
- return { sites, parameters };
326
+ return { sites, parameters, initialized };
327
+ }
328
+
329
+ /** The calls of `Object` that read every own property of their argument by name. */
330
+ const ENUMERATIONS: ReadonlySet<string> = new Set([
331
+ "keys",
332
+ "entries",
333
+ "values",
334
+ "getOwnPropertyNames",
335
+ "assign",
336
+ ]);
337
+
338
+ /**
339
+ * Whether a parameter type keeps no member name of the value it is given beyond the
340
+ * properties it names: `unknown`, `any`, or an object type of a string index signature,
341
+ * under which every other member of the value is held.
342
+ */
343
+ function keepsNoName<Brand>(project: ProjectView<Brand>, type: Type): boolean {
344
+ if ((type.flags & ERASED) !== 0) {
345
+ return true;
346
+ }
347
+ if ((type.flags & TypeFlags.Object) === 0) {
348
+ return false;
349
+ }
350
+ return must(project.queries.indexInfosOf(type)).some(
351
+ (info) => (info.keyType.flags & TypeFlags.String) !== 0,
352
+ );
305
353
  }
306
354
 
307
355
  /**
308
- * The parameters that keep nothing of the value they are given: one typed `unknown` or
309
- * `any`, and a rest parameter whose elements are, each by its symbol.
356
+ * The parameters that keep no member name of the value they are given, as `keepsNoName`
357
+ * reads it, and a rest parameter whose elements keep none, each by its symbol.
310
358
  */
311
359
  function erasedParameters<Brand>(
312
360
  project: ProjectView<Brand>,
@@ -322,10 +370,10 @@ function erasedParameters<Brand>(
322
370
  return false;
323
371
  }
324
372
  if (!parameter.rest) {
325
- return (type.flags & ERASED) !== 0;
373
+ return keepsNoName(project, type);
326
374
  }
327
375
  const [element] = must(project.queries.typeArguments(type));
328
- return element !== undefined && (element.flags & ERASED) !== 0;
376
+ return element !== undefined && keepsNoName(project, element);
329
377
  });
330
378
  const bySymbol = new Map<number, Parameter>();
331
379
  project
@@ -340,6 +388,124 @@ function erasedParameters<Brand>(
340
388
  return bySymbol;
341
389
  }
342
390
 
391
+ /** Whether one node is a function, whose body runs only where something calls it. */
392
+ function isFunctionNode(node: Node): boolean {
393
+ return (
394
+ isFunctionDeclaration(node) ||
395
+ isFunctionExpression(node) ||
396
+ isArrowFunction(node) ||
397
+ isMethodDeclaration(node) ||
398
+ isGetAccessorDeclaration(node) ||
399
+ isSetAccessorDeclaration(node)
400
+ );
401
+ }
402
+
403
+ /** The name a function nested in another is called by: its own, or the variable it initializes. */
404
+ function nestedName(fn: Node): string | undefined {
405
+ if (isFunctionDeclaration(fn)) {
406
+ return fn.name?.text;
407
+ }
408
+ let at = fn.parent;
409
+ while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
410
+ at = at.parent;
411
+ }
412
+ return isVariableDeclaration(at) && isIdentifier(at.name) ? at.name.text : undefined;
413
+ }
414
+
415
+ /** The names one function's body writes as identifiers, other than the names its declarations bind. */
416
+ function namesWritten(fn: Node): ReadonlySet<string> {
417
+ const found = new Set<string>();
418
+ const visit = (node: Node): void => {
419
+ const declares =
420
+ (isFunctionDeclaration(node.parent) || isVariableDeclaration(node.parent)) &&
421
+ node.parent.name === node;
422
+ if (isIdentifier(node) && !declares) {
423
+ found.add(node.text);
424
+ }
425
+ node.forEachChild(visit);
426
+ };
427
+ fn.forEachChild(visit);
428
+ return found;
429
+ }
430
+
431
+ /**
432
+ * The erased parameters their own function enumerates: one an `Object.keys`, `entries`,
433
+ * `values`, `getOwnPropertyNames` or `assign` call takes, `Object` the standard library's,
434
+ * or a `for…in` statement walks, which reads every property of the value by its name. A
435
+ * nested function is read as the function's own only where its name is written in it, or
436
+ * it has none, a callback or a function called where it is written.
437
+ */
438
+ function enumeratedParameters<Brand>(
439
+ project: ProjectView<Brand>,
440
+ erased: ReadonlyMap<number, Parameter>,
441
+ ): readonly Parameter[] {
442
+ const asked: {
443
+ readonly node: Node;
444
+ readonly parameter: Parameter;
445
+ readonly object: Node | undefined;
446
+ }[] = [];
447
+ for (const parameter of erased.values()) {
448
+ const fn = parameter.name.parent.parent;
449
+ const spelled = parameter.name.getText();
450
+ const written = namesWritten(fn);
451
+ const visit = (node: Node): void => {
452
+ if (isFunctionNode(node)) {
453
+ const name = nestedName(node);
454
+ if (name !== undefined && !written.has(name)) {
455
+ return;
456
+ }
457
+ }
458
+ const read: Expression[] = [];
459
+ let object: Node | undefined;
460
+ if (isCallExpression(node)) {
461
+ const callee = node.expression;
462
+ if (
463
+ isPropertyAccessExpression(callee) &&
464
+ isIdentifier(callee.expression) &&
465
+ callee.expression.text === "Object" &&
466
+ ENUMERATIONS.has(callee.name.text)
467
+ ) {
468
+ object = callee.expression;
469
+ // `Object.assign` reads its sources, every argument after the target.
470
+ read.push(...node.arguments.slice(...(callee.name.text === "assign" ? [1] : [0, 1])));
471
+ }
472
+ } else if (isForInStatement(node)) {
473
+ read.push(node.expression);
474
+ }
475
+ for (const value of read.map(evaluated)) {
476
+ if (isIdentifier(value) && value.text === spelled) {
477
+ asked.push({ node: value, parameter, object });
478
+ }
479
+ }
480
+ node.forEachChild(visit);
481
+ };
482
+ fn.forEachChild(visit);
483
+ }
484
+ const symbols = project.symbolsAt(asked.map((one) => project.handle(one.node))).map(must);
485
+ const objects = asked.flatMap((one) => (one.object === undefined ? [] : [one.object]));
486
+ const own = project.ownPaths();
487
+ const standard = new Set(
488
+ project
489
+ .symbolsAt(objects.map((node) => project.handle(node)))
490
+ .map(must)
491
+ .flatMap((symbol, at) =>
492
+ symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [objects[at]] : [],
493
+ ),
494
+ );
495
+ return [
496
+ ...new Set(
497
+ asked.flatMap((one, at) => {
498
+ const symbol = symbols[at];
499
+ return symbol !== undefined &&
500
+ erased.get(symbol.id) === one.parameter &&
501
+ (one.object === undefined || standard.has(one.object))
502
+ ? [one.parameter]
503
+ : [];
504
+ }),
505
+ ),
506
+ ];
507
+ }
508
+
343
509
  /** Each place a call's argument passes one of the erased parameters it can see. */
344
510
  function handedOn<Brand>(
345
511
  project: ProjectView<Brand>,
@@ -449,8 +615,8 @@ interface Destinations {
449
615
  * callee is a serializer an entry names; at the arguments its resolved signature types
450
616
  * `unknown` or `any` where its callee is declared outside the analysis or only ambiently
451
617
  * with no implementation in it; and, where its callee is a function of the project, at each
452
- * parameter the function hands on to a destination, holding back the widest of what those
453
- * destinations hold back, which is settled by widening until no parameter moves.
618
+ * parameter the function enumerates or hands on to a destination, holding back the widest
619
+ * of what those hold back, settled by widening until no parameter moves.
454
620
  */
455
621
  function destinations<Brand>(
456
622
  input: DetectorInput<Brand>,
@@ -564,6 +730,30 @@ function destinations<Brand>(
564
730
  });
565
731
  return containing.length === 0 ? target.declarations : containing;
566
732
  };
733
+ /** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
734
+ const mentions = (type: Type, parameter: Type, depth: number): boolean => {
735
+ // A conditional type an alias names, `Awaited<T>` among them, carries its arguments
736
+ // only on the alias the client's type object reports.
737
+ const aliased = (type as { readonly aliasTypeArguments?: readonly number[] })
738
+ .aliasTypeArguments;
739
+ if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
740
+ return true;
741
+ }
742
+ if (depth >= MENTION_DEPTH) {
743
+ return false;
744
+ }
745
+ const inner = [
746
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
747
+ ...must(queries.typeArguments(type)),
748
+ ...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
749
+ ...signature.parameters.map((_parameter, at) =>
750
+ must(queries.typeAtPosition(signature, at)),
751
+ ),
752
+ ...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
753
+ ]),
754
+ ];
755
+ return inner.some((one) => mentions(one, parameter, depth + 1));
756
+ };
567
757
  const erasedArguments = new Map<CallExpression, ReadonlySet<number>>();
568
758
  const erasedAt = (call: CallExpression, argument: number): boolean => {
569
759
  let indexes = erasedArguments.get(call);
@@ -577,8 +767,32 @@ function destinations<Brand>(
577
767
  ? []
578
768
  : must(queries.signaturesOf(type, SignatureKind.Call))
579
769
  : [resolvedOf(call)].filter((one) => one !== undefined);
770
+ // A type parameter of the callee that nothing constrains and its result does not name
771
+ // keeps nothing of the value either: the instantiated signature names the value's own
772
+ // type there instead, and a result that names it hands the value back.
773
+ const generic = invoked.indirect ? undefined : resolvedOf(call);
774
+ const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
775
+ const unconstrained = (index: number): boolean => {
776
+ const parameter =
777
+ declared === undefined ? undefined : must(queries.parameterType(declared, index));
778
+ const parts =
779
+ parameter === undefined
780
+ ? []
781
+ : parameter.isUnionType()
782
+ ? must(queries.constituents(parameter))
783
+ : [parameter];
784
+ const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
785
+ return parts.some((part) => {
786
+ if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
787
+ return false;
788
+ }
789
+ const constraint = must(queries.constraintOf(part));
790
+ return constraint === undefined || (constraint.flags & ERASED) !== 0;
791
+ });
792
+ };
580
793
  indexes = new Set(
581
794
  invoked.arguments.flatMap((_unused, index) =>
795
+ unconstrained(index) ||
582
796
  signatures.some((signature) => {
583
797
  const parameter = must(queries.parameterType(signature, index));
584
798
  return parameter !== undefined && (parameter.flags & ERASED) !== 0;
@@ -623,6 +837,9 @@ function destinations<Brand>(
623
837
  return undefined;
624
838
  };
625
839
 
840
+ for (const parameter of enumeratedParameters(project, erased)) {
841
+ forwards.set(keyOf(parameter), "properties");
842
+ }
626
843
  const forwarded = handedOn(project, walked.sites, erased, invocation);
627
844
  for (let moved = true; moved;) {
628
845
  moved = false;
@@ -725,6 +942,19 @@ function flowsOf<Brand>(
725
942
  },
726
943
  ];
727
944
  });
945
+ const generic = typeParameterFlows(
946
+ project,
947
+ reach,
948
+ sites,
949
+ arguments_.flatMap((one) => {
950
+ const type = typeOf.get(one.value);
951
+ const parameters = type === undefined ? [] : typeParametersOf(project, type, 0);
952
+ const destination = parameters.length === 0 ? undefined : to.at(one.site.call, one.argument);
953
+ return destination === undefined
954
+ ? []
955
+ : parameters.map((parameter) => ({ parameter, to: destination }));
956
+ }),
957
+ );
728
958
  const left = stores.outside.flatMap((store) => {
729
959
  const reached = reach.reachedFrom(store.type);
730
960
  return reached.classes.size === 0 && reached.members.length === 0
@@ -739,7 +969,178 @@ function flowsOf<Brand>(
739
969
  },
740
970
  ];
741
971
  });
742
- return [...passed, ...left];
972
+ return [...passed, ...generic, ...left];
973
+ }
974
+
975
+ /** The type parameters one type names, through constituents and type arguments. */
976
+ function typeParametersOf<Brand>(project: ProjectView<Brand>, type: Type, depth: number): Type[] {
977
+ if (type.isTypeParameter()) {
978
+ return [type];
979
+ }
980
+ if (depth >= MENTION_DEPTH) {
981
+ return [];
982
+ }
983
+ const { queries } = project;
984
+ return [
985
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
986
+ ...must(queries.typeArguments(type)),
987
+ ].flatMap((one) => typeParametersOf(project, one, depth + 1));
988
+ }
989
+
990
+ /**
991
+ * The type parameters one type names, by identifier: through constituents, type arguments,
992
+ * an alias's arguments and the parameters and results of its call signatures, so a callback
993
+ * parameter's result names the parameter its callee's result does.
994
+ */
995
+ function namedTypeParameters<Brand>(
996
+ project: ProjectView<Brand>,
997
+ type: Type,
998
+ depth: number,
999
+ ): readonly number[] {
1000
+ const { queries } = project;
1001
+ const aliased = (type as { readonly aliasTypeArguments?: readonly number[] }).aliasTypeArguments;
1002
+ if (type.isTypeParameter()) {
1003
+ return [type.id];
1004
+ }
1005
+ if (depth >= MENTION_DEPTH) {
1006
+ return aliased ?? [];
1007
+ }
1008
+ return [
1009
+ ...(aliased ?? []),
1010
+ ...[
1011
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
1012
+ ...must(queries.typeArguments(type)),
1013
+ ...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
1014
+ ...signature.parameters.flatMap((_parameter, at) => [
1015
+ must(queries.typeAtPosition(signature, at)),
1016
+ ]),
1017
+ ...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
1018
+ ]),
1019
+ ].flatMap((one) => namedTypeParameters(project, one, depth + 1)),
1020
+ ];
1021
+ }
1022
+
1023
+ /**
1024
+ * The types an instantiated type puts where a declared type names `parameter`: matched
1025
+ * through type arguments, and in a union every constituent the declared union does not
1026
+ * itself name.
1027
+ */
1028
+ function bound<Brand>(
1029
+ project: ProjectView<Brand>,
1030
+ declared: Type,
1031
+ instantiated: Type,
1032
+ parameter: Type,
1033
+ depth: number,
1034
+ ): Type[] {
1035
+ if (declared.id === parameter.id) {
1036
+ return [instantiated];
1037
+ }
1038
+ if (depth >= MENTION_DEPTH) {
1039
+ return [];
1040
+ }
1041
+ const { queries } = project;
1042
+ if (declared.isUnionType()) {
1043
+ const parts = must(queries.constituents(declared));
1044
+ if (parts.some((part) => part.id === parameter.id)) {
1045
+ const kept = new Set(parts.map((part) => part.id));
1046
+ const given = instantiated.isUnionType()
1047
+ ? must(queries.constituents(instantiated))
1048
+ : [instantiated];
1049
+ return given.filter((part) => !kept.has(part.id));
1050
+ }
1051
+ }
1052
+ const declaredArguments = must(queries.typeArguments(declared));
1053
+ const givenArguments = must(queries.typeArguments(instantiated));
1054
+ return declaredArguments.length !== givenArguments.length
1055
+ ? []
1056
+ : declaredArguments.flatMap((one, at) => {
1057
+ const given = givenArguments[at];
1058
+ return given === undefined ? [] : bound(project, one, given, parameter, depth + 1);
1059
+ });
1060
+ }
1061
+
1062
+ /**
1063
+ * The flows a type parameter of a function of the project carries where a value its type
1064
+ * names reaches a destination: each call of the function passes its type argument there,
1065
+ * written or inferred from the arguments, and a type argument that is a type parameter
1066
+ * of the calling function is followed on to that function's calls, to a fixpoint.
1067
+ */
1068
+ function typeParameterFlows<Brand>(
1069
+ project: ProjectView<Brand>,
1070
+ reach: ReturnType<typeof valueReach>,
1071
+ sites: readonly Site[],
1072
+ pending: { readonly parameter: Type; readonly to: Destination }[],
1073
+ ): readonly Flow[] {
1074
+ const { queries } = project;
1075
+ const byName = new Map<string, Site[]>();
1076
+ for (const site of sites) {
1077
+ const name = calleeName(site.call.expression)?.getText();
1078
+ if (name !== undefined) {
1079
+ byName.set(name, [...(byName.get(name) ?? []), site]);
1080
+ }
1081
+ }
1082
+ const found: Flow[] = [];
1083
+ const seen = new Set<number>();
1084
+ for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
1085
+ const { parameter, to } = next;
1086
+ if (seen.has(parameter.id)) {
1087
+ continue;
1088
+ }
1089
+ seen.add(parameter.id);
1090
+ const symbol = must(queries.symbolOfType(parameter));
1091
+ const [handle] = symbol?.declarations ?? [];
1092
+ const declaration = handle === undefined ? undefined : project.declarationAt(handle)?.node;
1093
+ const fn = declaration?.parent;
1094
+ const index =
1095
+ fn === undefined || !isFunctionLikeDeclaration(fn)
1096
+ ? -1
1097
+ : (fn.typeParameters ?? []).findIndex((one) => one === declaration);
1098
+ const name =
1099
+ fn === undefined
1100
+ ? undefined
1101
+ : isMethodDeclaration(fn) && isIdentifier(fn.name)
1102
+ ? fn.name.text
1103
+ : nestedName(fn);
1104
+ if (symbol === undefined || fn === undefined || index < 0 || name === undefined) {
1105
+ continue;
1106
+ }
1107
+ for (const site of byName.get(name) ?? []) {
1108
+ const resolved = must(queries.resolvedSignature(site.call));
1109
+ const declared = resolved === undefined ? undefined : must(queries.genericOf(resolved));
1110
+ const called =
1111
+ declared?.declaration === undefined
1112
+ ? undefined
1113
+ : project.declarationAt(declared.declaration)?.node;
1114
+ if (resolved === undefined || declared === undefined || called !== fn) {
1115
+ continue;
1116
+ }
1117
+ const written = site.call.typeArguments?.[index];
1118
+ const given =
1119
+ written === undefined
1120
+ ? site.call.arguments.flatMap((_argument, at) => {
1121
+ const from = must(queries.parameterType(declared, at));
1122
+ const into = must(queries.parameterType(resolved, at));
1123
+ return from === undefined || into === undefined
1124
+ ? []
1125
+ : bound(project, from, into, parameter, 0);
1126
+ })
1127
+ : typesAt(project, [written]).filter((type) => type !== undefined);
1128
+ for (const type of given) {
1129
+ const reached = reach.reachedFrom(type);
1130
+ if (reached.classes.size > 0 || reached.members.length > 0) {
1131
+ found.push({
1132
+ site: site.call,
1133
+ classes: reached.classes,
1134
+ members: reached.members,
1135
+ to,
1136
+ detail: `passed to ${to.callee} through the type parameter ${symbol.name}`,
1137
+ });
1138
+ }
1139
+ pending.push(...typeParametersOf(project, type, 0).map((one) => ({ parameter: one, to })));
1140
+ }
1141
+ }
1142
+ }
1143
+ return found;
743
1144
  }
744
1145
 
745
1146
  /**
@@ -775,6 +1176,11 @@ export function serializationContract<Brand>(input: DetectorInput<Brand>): reado
775
1176
  (flags & SymbolFlags.Property) !== 0 ||
776
1177
  ((flags & CONVERSION_MEMBERS) !== 0 &&
777
1178
  (name === SERIALIZED_CONVERSION || (to.reach === "conversions" && CONVERSIONS.has(name))));
1179
+ // The receiver reads only what the program wrote, so a data member holds while written.
1180
+ const evidence = (id: string, flags: SymbolFlags, detail: string, site: Position): Evidence =>
1181
+ (flags & SymbolFlags.Property) !== 0 && !walked.initialized.has(id)
1182
+ ? { id, detail, site, whileWritten: true }
1183
+ : { id, detail, site };
778
1184
  const found: Evidence[] = [];
779
1185
  for (const flow of flows) {
780
1186
  const site = renderPosition(flow.site.getSourceFile(), targetRoot, flow.site.getStart());
@@ -783,16 +1189,402 @@ export function serializationContract<Brand>(input: DetectorInput<Brand>): reado
783
1189
  const symbol = byId.get(member.id);
784
1190
  const name = symbol === undefined ? "" : (memberComponent(symbol, byId) ?? "");
785
1191
  if (holdsBack(member.flags, name, flow.to)) {
786
- found.push({ id: member.id, detail, site });
1192
+ found.push(evidence(member.id, member.flags, detail, site));
787
1193
  }
788
1194
  }
789
1195
  for (const member of flow.members) {
790
1196
  if (holdsBack(member.flags, member.name, flow.to)) {
791
1197
  for (const id of chains.declarationsOf(member)) {
792
- found.push({ id, detail, site });
1198
+ found.push(evidence(id, member.flags, detail, site));
793
1199
  }
794
1200
  }
795
1201
  }
796
1202
  }
797
1203
  return found;
798
1204
  }
1205
+
1206
+ /** The data members the decodes and copies of a project's production code fill. */
1207
+ export interface Decoded {
1208
+ /**
1209
+ * Those a generic declared outside the analysis fills, which it may read, and those of
1210
+ * a value another call or a property types `any` or `unknown`, whose maker it cannot see.
1211
+ */
1212
+ readonly filled: readonly string[];
1213
+ /** Those a `JSON.parse` call of the standard library fills. */
1214
+ readonly parsed: readonly string[];
1215
+ /** Those a spread or `Object.assign` copies from a value whose type has a member of the name. */
1216
+ readonly copied: readonly string[];
1217
+ /** Every data member of the project, where the checker left a question unanswered. */
1218
+ readonly unanswered: readonly string[];
1219
+ }
1220
+
1221
+ /**
1222
+ * The data members the decodes and copies of `files` fill, as {@link Decoded} names them.
1223
+ * A generic declared outside the analysis, or only ambiently, fills the type that a type
1224
+ * parameter of its own no parameter names binds, where the awaited result carries that
1225
+ * type through its data members rather than only naming it, as a store's methods do: no
1226
+ * argument hands the value over, so it fills it where the analysis cannot see, and may
1227
+ * read it. Where the checker leaves a question unanswered, every data member is `unanswered`.
1228
+ */
1229
+ export function decodedMembers<Brand>(
1230
+ project: ProjectView<Brand>,
1231
+ held: Inventory,
1232
+ files: readonly SourceFile[],
1233
+ consumers: readonly string[],
1234
+ ): Decoded {
1235
+ try {
1236
+ return { ...decodedOf(project, held, files, consumers), unanswered: [] };
1237
+ } catch (error: unknown) {
1238
+ if (!(error instanceof Unanswerable)) {
1239
+ throw error;
1240
+ }
1241
+ return {
1242
+ filled: [],
1243
+ parsed: [],
1244
+ copied: [],
1245
+ unanswered: held.symbols
1246
+ .filter((symbol) => symbol.kind === "class-member" || symbol.kind === "type-member")
1247
+ .map((symbol) => symbol.id),
1248
+ };
1249
+ }
1250
+ }
1251
+
1252
+ function decodedOf<Brand>(
1253
+ project: ProjectView<Brand>,
1254
+ held: Inventory,
1255
+ files: readonly SourceFile[],
1256
+ consumers: readonly string[],
1257
+ ): Omit<Decoded, "unanswered"> {
1258
+ const { queries } = project;
1259
+ const own = project.ownPaths();
1260
+ const inAnalysis = (path: string): boolean =>
1261
+ own.has(path) || consumers.some((root) => path === root || path.startsWith(`${root}/`));
1262
+ const calls: CallExpression[] = [];
1263
+ const untyped: Expression[] = [];
1264
+ const spreads: SpreadAssignment[] = [];
1265
+ const noteRead = (node: Expression): void => {
1266
+ const at = readAs(node);
1267
+ if (at.tops.length > 0 || at.asserted.length > 0) {
1268
+ untyped.push(node);
1269
+ }
1270
+ };
1271
+ const visit = (node: Node): void => {
1272
+ if (isCallExpression(node)) {
1273
+ calls.push(node);
1274
+ noteRead(node);
1275
+ } else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) {
1276
+ noteRead(node);
1277
+ } else if (isSpreadAssignment(node)) {
1278
+ spreads.push(node);
1279
+ }
1280
+ node.forEachChild(visit);
1281
+ };
1282
+ for (const file of files) {
1283
+ file.forEachChild(visit);
1284
+ }
1285
+ const filled: { readonly carried: Type; readonly types: readonly Type[] }[] = [];
1286
+ const resolved = queries.resolvedSignatures(calls).map(must);
1287
+ for (const signature of resolved) {
1288
+ const declared =
1289
+ signature?.target === undefined ? undefined : must(queries.genericOf(signature));
1290
+ const declaration = declared?.declaration;
1291
+ const generic = declared?.typeParameters ?? [];
1292
+ if (signature === undefined || declared === undefined || generic.length === 0) {
1293
+ continue;
1294
+ }
1295
+ const node = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
1296
+ if (
1297
+ declaration === undefined ||
1298
+ (inAnalysis(declaration.path) &&
1299
+ (node === undefined || (node.flags & NodeFlags.Ambient) === 0))
1300
+ ) {
1301
+ continue;
1302
+ }
1303
+ const result = must(queries.returnTypeOf(declared));
1304
+ const given = must(queries.returnTypeOf(signature));
1305
+ if (result === undefined || given === undefined) {
1306
+ continue;
1307
+ }
1308
+ const passed = new Set(
1309
+ declared.parameters.flatMap((_parameter, at) => {
1310
+ const type = must(queries.parameterType(declared, at));
1311
+ return type === undefined ? [] : namedTypeParameters(project, type, 0);
1312
+ }),
1313
+ );
1314
+ const types = typeParametersOf(project, result, 0)
1315
+ .filter((parameter) => generic.includes(parameter.id) && !passed.has(parameter.id))
1316
+ .flatMap((parameter) => bound(project, result, given, parameter, 0));
1317
+ if (types.length > 0) {
1318
+ filled.push({ carried: awaited(project, given), types });
1319
+ }
1320
+ }
1321
+ const read = readTypes(project, untyped);
1322
+ const copies = copiedNames(project, spreads, calls);
1323
+ if (
1324
+ filled.length === 0 &&
1325
+ read.parsed.length === 0 &&
1326
+ read.made.length === 0 &&
1327
+ copies.length === 0
1328
+ ) {
1329
+ return { filled: [], parsed: [], copied: [] };
1330
+ }
1331
+ const reach = valueReach(project, held);
1332
+ const chains = aliasChains(project, held);
1333
+ const members = (type: Type): readonly string[] => {
1334
+ const reached = reach.reachedFrom(type);
1335
+ const ofClasses = classMembers(project, held, reached.classes, "instance");
1336
+ return [
1337
+ ...[...reached.classes].flatMap((id) =>
1338
+ (ofClasses.get(id) ?? []).flatMap((member) =>
1339
+ (member.flags & SymbolFlags.Property) !== 0 ? [member.id] : [],
1340
+ ),
1341
+ ),
1342
+ ...reached.members.flatMap((member) =>
1343
+ (member.flags & SymbolFlags.Property) !== 0 ? chains.declarationsOf(member) : [],
1344
+ ),
1345
+ ];
1346
+ };
1347
+ return {
1348
+ filled: [
1349
+ ...new Set([
1350
+ ...filled.flatMap(({ carried, types }) => {
1351
+ const carries = new Set(members(carried));
1352
+ return types.flatMap((type) => members(type).filter((id) => carries.has(id)));
1353
+ }),
1354
+ ...read.made.flatMap(members),
1355
+ ]),
1356
+ ],
1357
+ parsed: [...new Set(read.parsed.flatMap(members))],
1358
+ copied: [
1359
+ ...new Set(
1360
+ copies.flatMap(({ into, names }) =>
1361
+ must(queries.constituents(into)).flatMap((part) =>
1362
+ must(queries.propertiesOf(part)).flatMap((property) =>
1363
+ (property.flags & SymbolFlags.Property) !== 0 && names.has(property.name)
1364
+ ? chains.declarationsOf(property)
1365
+ : [],
1366
+ ),
1367
+ ),
1368
+ ),
1369
+ ),
1370
+ ],
1371
+ };
1372
+ }
1373
+
1374
+ /** Where a value is read as a type: the assertions around it, and the positions it is written in. */
1375
+ interface ReadAs {
1376
+ /** The type nodes of the `as` and angle-bracket assertions around the value. */
1377
+ readonly asserted: readonly Node[];
1378
+ /** The outermost expressions whose contextual types the value is read as. */
1379
+ readonly tops: readonly Expression[];
1380
+ }
1381
+
1382
+ /**
1383
+ * Where one value is read as a type: each `as` and angle-bracket assertion around it,
1384
+ * through parentheses, `await` and `!`, and the outermost expression unless a statement
1385
+ * of its own or the object or callee of a further access holds it. A `satisfies` check
1386
+ * is stepped through and types nothing, its expression's type being its operand's. A
1387
+ * callback's result is read as well where the call taking the callback is, which may
1388
+ * hand it on inside its result, as `map` does.
1389
+ */
1390
+ function readAs(value: Expression, depth = 0): ReadAs {
1391
+ const asserted: Node[] = [];
1392
+ let at: Expression = value;
1393
+ for (;;) {
1394
+ const parent = at.parent;
1395
+ if (isAsExpression(parent) || isTypeAssertion(parent)) {
1396
+ asserted.push(parent.type);
1397
+ } else if (
1398
+ !isParenthesizedExpression(parent) &&
1399
+ !isAwaitExpression(parent) &&
1400
+ !isNonNullExpression(parent) &&
1401
+ !isSatisfiesExpression(parent)
1402
+ ) {
1403
+ break;
1404
+ }
1405
+ at = parent;
1406
+ }
1407
+ const parent = at.parent;
1408
+ const unused =
1409
+ isExpressionStatement(parent) ||
1410
+ ((isPropertyAccessExpression(parent) ||
1411
+ isElementAccessExpression(parent) ||
1412
+ isCallExpression(parent)) &&
1413
+ parent.expression === at);
1414
+ const call = depth < MENTION_DEPTH ? callTaking(at) : undefined;
1415
+ const outer = call === undefined ? undefined : readAs(call, depth + 1);
1416
+ return {
1417
+ asserted: [...asserted, ...(outer?.asserted ?? [])],
1418
+ tops: [...(unused ? [] : [at]), ...(outer?.tops ?? [])],
1419
+ };
1420
+ }
1421
+
1422
+ /** The call one value is returned to as a callback's result, an argument of that call. */
1423
+ function callTaking(value: Expression): CallExpression | undefined {
1424
+ const parent = value.parent;
1425
+ let callback: Node | undefined;
1426
+ if (isArrowFunction(parent) && parent.body === value) {
1427
+ callback = parent;
1428
+ } else if (isReturnStatement(parent)) {
1429
+ let at: Node = parent.parent;
1430
+ while (!isFunctionLikeDeclaration(at) && !isSourceFile(at)) {
1431
+ at = at.parent;
1432
+ }
1433
+ callback = isArrowFunction(at) || isFunctionExpression(at) ? at : undefined;
1434
+ }
1435
+ let at = callback?.parent;
1436
+ while (at !== undefined && isParenthesizedExpression(at)) {
1437
+ at = at.parent;
1438
+ }
1439
+ return at !== undefined && isCallExpression(at) && at.expression !== callback ? at : undefined;
1440
+ }
1441
+
1442
+ /** The global the standard library declares one name of `JSON` or `Object` as. */
1443
+ function standardGlobal(callee: Expression, global: string, name: string): Node | undefined {
1444
+ return isPropertyAccessExpression(callee) &&
1445
+ isIdentifier(callee.expression) &&
1446
+ callee.expression.text === global &&
1447
+ callee.name.text === name
1448
+ ? callee.expression
1449
+ : undefined;
1450
+ }
1451
+
1452
+ /**
1453
+ * The types each value of `values` typed `any` or `unknown` is read as ({@link readAs}):
1454
+ * `parsed` for a `JSON.parse` call of the standard library, `made` for any other call or
1455
+ * property, whose maker the analysis cannot see. `JSON` must be the standard library's
1456
+ * global.
1457
+ */
1458
+ function readTypes<Brand>(
1459
+ project: ProjectView<Brand>,
1460
+ values: readonly Expression[],
1461
+ ): { readonly parsed: readonly Type[]; readonly made: readonly Type[] } {
1462
+ if (values.length === 0) {
1463
+ return { parsed: [], made: [] };
1464
+ }
1465
+ const own = project.ownPaths();
1466
+ const parses = values.map((value) =>
1467
+ isCallExpression(value) ? standardGlobal(value.expression, "JSON", "parse") : undefined,
1468
+ );
1469
+ const globals = parses.flatMap((global) => (global === undefined ? [] : [global]));
1470
+ const standard = new Set(
1471
+ project
1472
+ .symbolsAt(globals.map((global) => project.handle(global)))
1473
+ .map(must)
1474
+ .flatMap((symbol, at) =>
1475
+ symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [globals[at]] : [],
1476
+ ),
1477
+ );
1478
+ // An `await` around the value reads the value it resolves to.
1479
+ const valueTypes = typesAt(
1480
+ project,
1481
+ values.map((value) => (isAwaitExpression(value.parent) ? value.parent : value)),
1482
+ );
1483
+ const sites = values.flatMap((value, at) => {
1484
+ const parse = parses[at];
1485
+ const parsed = parse !== undefined && standard.has(parse);
1486
+ const type = valueTypes[at];
1487
+ const untyped = type !== undefined && (type.flags & (TypeFlags.Any | TypeFlags.Unknown)) !== 0;
1488
+ return parsed || untyped ? [{ ...readAs(value), parsed }] : [];
1489
+ });
1490
+ // Each type node and each outermost expression, with whether a `JSON.parse` call is read as it.
1491
+ const typed = sites.flatMap((site) =>
1492
+ site.asserted.map((node) => ({ node, parsed: site.parsed })),
1493
+ );
1494
+ const placed = sites.flatMap((site) => site.tops.map((node) => ({ node, parsed: site.parsed })));
1495
+ const read = [
1496
+ ...typesAt(
1497
+ project,
1498
+ typed.map((one) => one.node),
1499
+ ).map((type, at) => ({ type, parsed: typed[at]?.parsed })),
1500
+ ...project.queries
1501
+ .contextualTypes(placed.map((one) => one.node))
1502
+ .map(must)
1503
+ .map((type, at) => ({ type, parsed: placed[at]?.parsed })),
1504
+ ];
1505
+ const typesWhere = (parsed: boolean): Type[] =>
1506
+ read.flatMap((one) => (one.type !== undefined && one.parsed === parsed ? [one.type] : []));
1507
+ return { parsed: typesWhere(true), made: typesWhere(false) };
1508
+ }
1509
+
1510
+ /**
1511
+ * The copies the spreads and calls of `files` make: for each spread in an object literal,
1512
+ * the type the literal is read as, and for each `Object.assign` call of the standard
1513
+ * library, its first argument's type, with the member names of the copied values' types.
1514
+ */
1515
+ function copiedNames<Brand>(
1516
+ project: ProjectView<Brand>,
1517
+ spreads: readonly SpreadAssignment[],
1518
+ calls: readonly CallExpression[],
1519
+ ): readonly { readonly into: Type; readonly names: ReadonlySet<string> }[] {
1520
+ const own = project.ownPaths();
1521
+ const assigns = calls.flatMap((call) => {
1522
+ const global = standardGlobal(call.expression, "Object", "assign");
1523
+ const [target, ...sources] = call.arguments;
1524
+ return global === undefined || target === undefined || sources.length === 0
1525
+ ? []
1526
+ : [{ global, target, sources: sources.filter((source) => !isSpreadElement(source)) }];
1527
+ });
1528
+ const standard = project
1529
+ .symbolsAt(assigns.map((one) => project.handle(one.global)))
1530
+ .map(must)
1531
+ .map((symbol) => symbol?.declarations.every((handle) => !own.has(handle.path)) === true);
1532
+ const assigned = assigns.filter((_one, at) => standard[at] === true);
1533
+ const literals = spreads.flatMap((spread) =>
1534
+ isObjectLiteralExpression(spread.parent)
1535
+ ? [{ literal: spread.parent, source: spread.expression }]
1536
+ : [],
1537
+ );
1538
+ const contexts = project.queries.contextualTypes(literals.map((one) => one.literal)).map(must);
1539
+ const targets = typesAt(
1540
+ project,
1541
+ assigned.map((one) => one.target),
1542
+ );
1543
+ const spreadTypes = typesAt(
1544
+ project,
1545
+ literals.map((one) => one.source),
1546
+ );
1547
+ const sourceTypes = assigned.map((one) => typesAt(project, one.sources));
1548
+ const namesOf = (types: readonly (Type | undefined)[]): ReadonlySet<string> =>
1549
+ new Set(
1550
+ types.flatMap((type) =>
1551
+ type === undefined
1552
+ ? []
1553
+ : must(project.queries.propertiesOf(type)).map((property) => property.name),
1554
+ ),
1555
+ );
1556
+ const found: { readonly into: Type; readonly names: ReadonlySet<string> }[] = [];
1557
+ const add = (into: Type | undefined, names: ReadonlySet<string>): void => {
1558
+ if (into !== undefined && names.size > 0) {
1559
+ found.push({ into, names });
1560
+ }
1561
+ };
1562
+ contexts.forEach((into, at) => {
1563
+ add(into, namesOf([spreadTypes[at]]));
1564
+ });
1565
+ targets.forEach((into, at) => {
1566
+ add(into, namesOf(sourceTypes[at] ?? []));
1567
+ });
1568
+ return found;
1569
+ }
1570
+
1571
+ /** The value a promise of the standard library resolves to, through nested promises. */
1572
+ function awaited<Brand>(project: ProjectView<Brand>, type: Type): Type {
1573
+ const { queries } = project;
1574
+ let at = type;
1575
+ for (let depth = 0; depth < MENTION_DEPTH; depth += 1) {
1576
+ const symbol = must(queries.symbolOfType(at));
1577
+ const [inner] = must(queries.typeArguments(at));
1578
+ const standard =
1579
+ symbol !== undefined &&
1580
+ PROMISES.has(symbol.name) &&
1581
+ symbol.declarations.every(
1582
+ (handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true,
1583
+ );
1584
+ if (!standard || inner === undefined) {
1585
+ break;
1586
+ }
1587
+ at = inner;
1588
+ }
1589
+ return at;
1590
+ }