@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,12 +1,13 @@
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
- import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyDeclaration, isSatisfiesExpression, isSetAccessorDeclaration, isSpreadElement, isStringLiteral, isTypeLiteralNode, isVariableDeclaration, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
9
+ import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isAwaitExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isElementAccessExpression, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNonNullExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isForInStatement, isPropertyAccessExpression, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isSetAccessorDeclaration, isSourceFile, isSpreadAssignment, isSpreadElement, isStringLiteral, isTypeAssertion, isTypeLiteralNode, isVariableDeclaration, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
10
+ import { parenthesesRemoved } from "./unwrap.js";
10
11
  import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
11
12
  import { aliasChains } from "./alias-chain.js";
12
13
  import { calleeName, resolvedTargets, valuesPassed } from "./calls.js";
@@ -14,17 +15,21 @@ import { classMembers, memberComponent, typesAt } from "./class-members.js";
14
15
  import { names, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
15
16
  import { nodeKey } from "./inventory.js";
16
17
  import { renderPosition } from "./position.js";
17
- import { must } from "./query.js";
18
+ import { must, Unanswerable } from "./query.js";
18
19
  import { erasedStores } from "./erased-stores.js";
19
20
  import { checkedTypes, CONVERSION_MEMBERS, valueReach } from "./value-reach.js";
20
21
  /** The serializer every run reads, whatever the configuration names. */
21
- const JSON_STRINGIFY = { shape: "global", global: "JSON.stringify" };
22
+ const JSON_STRINGIFY = { global: "JSON.stringify" };
22
23
  /** The methods a serializer and a formatter resolve by name on a value that left the analysis. */
23
24
  const CONVERSIONS = new Set(["toJSON", "toString"]);
24
25
  /** The method a serializer calls on a value that carries one, wherever the value goes. */
25
26
  const SERIALIZED_CONVERSION = "toJSON";
26
27
  /** The parameter types that keep nothing of the value they are given. */
27
28
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
29
+ /** The standard library's promise types, whose value is their one type argument. */
30
+ const PROMISES = new Set(["Promise", "PromiseLike"]);
31
+ /** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
32
+ const MENTION_DEPTH = 3;
28
33
  /** The wider of two reaches, where either may be absent. */
29
34
  function wider(a, b) {
30
35
  return a === "conversions" || b === "conversions" ? "conversions" : (a ?? b);
@@ -84,11 +89,6 @@ const FUNCTION_INTERFACES = new Set([
84
89
  "CallableFunction",
85
90
  "NewableFunction",
86
91
  ]);
87
- function unparenthesized(expression) {
88
- return isParenthesizedExpression(expression)
89
- ? unparenthesized(expression.expression)
90
- : expression;
91
- }
92
92
  /**
93
93
  * The invocation a call writes through a method of `Function.prototype`, read from
94
94
  * the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
@@ -97,12 +97,12 @@ function unparenthesized(expression) {
97
97
  * carries. Whether the method is the prototype's is resolution's to say.
98
98
  */
99
99
  function throughMethod(call) {
100
- const expression = unparenthesized(call.expression);
100
+ const expression = parenthesesRemoved(call.expression);
101
101
  if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
102
102
  const [, ...rest] = call.arguments;
103
103
  let passed = rest;
104
104
  if (expression.name.text === "apply") {
105
- const list = rest[0] === undefined ? undefined : unparenthesized(rest[0]);
105
+ const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
106
106
  passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
107
107
  }
108
108
  return {
@@ -111,7 +111,7 @@ function throughMethod(call) {
111
111
  };
112
112
  }
113
113
  if (isCallExpression(expression)) {
114
- const bound = unparenthesized(expression.expression);
114
+ const bound = parenthesesRemoved(expression.expression);
115
115
  if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
116
116
  return {
117
117
  method: bound.name,
@@ -133,9 +133,16 @@ function throughMethod(call) {
133
133
  function walkProject(project, held) {
134
134
  const sites = [];
135
135
  const parameters = [];
136
+ const initialized = new Set();
136
137
  for (const file of project.ownSourceFiles()) {
137
138
  const visit = (node, self) => {
138
139
  let inner = self;
140
+ if (isPropertyDeclaration(node) && node.initializer !== undefined) {
141
+ const id = held.declarations.get(nodeKey(file, node));
142
+ if (id !== undefined) {
143
+ initialized.add(id);
144
+ }
145
+ }
139
146
  if (isClassDeclaration(node) || isClassExpression(node)) {
140
147
  inner = held.declarations.get(nodeKey(file, node));
141
148
  }
@@ -169,11 +176,33 @@ function walkProject(project, held) {
169
176
  visit(child, undefined);
170
177
  });
171
178
  }
172
- return { sites, parameters };
179
+ return { sites, parameters, initialized };
180
+ }
181
+ /** The calls of `Object` that read every own property of their argument by name. */
182
+ const ENUMERATIONS = new Set([
183
+ "keys",
184
+ "entries",
185
+ "values",
186
+ "getOwnPropertyNames",
187
+ "assign",
188
+ ]);
189
+ /**
190
+ * Whether a parameter type keeps no member name of the value it is given beyond the
191
+ * properties it names: `unknown`, `any`, or an object type of a string index signature,
192
+ * under which every other member of the value is held.
193
+ */
194
+ function keepsNoName(project, type) {
195
+ if ((type.flags & ERASED) !== 0) {
196
+ return true;
197
+ }
198
+ if ((type.flags & TypeFlags.Object) === 0) {
199
+ return false;
200
+ }
201
+ return must(project.queries.indexInfosOf(type)).some((info) => (info.keyType.flags & TypeFlags.String) !== 0);
173
202
  }
174
203
  /**
175
- * The parameters that keep nothing of the value they are given: one typed `unknown` or
176
- * `any`, and a rest parameter whose elements are, each by its symbol.
204
+ * The parameters that keep no member name of the value they are given, as `keepsNoName`
205
+ * reads it, and a rest parameter whose elements keep none, each by its symbol.
177
206
  */
178
207
  function erasedParameters(project, parameters) {
179
208
  const types = typesAt(project, parameters.map((parameter) => parameter.name));
@@ -183,10 +212,10 @@ function erasedParameters(project, parameters) {
183
212
  return false;
184
213
  }
185
214
  if (!parameter.rest) {
186
- return (type.flags & ERASED) !== 0;
215
+ return keepsNoName(project, type);
187
216
  }
188
217
  const [element] = must(project.queries.typeArguments(type));
189
- return element !== undefined && (element.flags & ERASED) !== 0;
218
+ return element !== undefined && keepsNoName(project, element);
190
219
  });
191
220
  const bySymbol = new Map();
192
221
  project
@@ -200,6 +229,103 @@ function erasedParameters(project, parameters) {
200
229
  });
201
230
  return bySymbol;
202
231
  }
232
+ /** Whether one node is a function, whose body runs only where something calls it. */
233
+ function isFunctionNode(node) {
234
+ return (isFunctionDeclaration(node) ||
235
+ isFunctionExpression(node) ||
236
+ isArrowFunction(node) ||
237
+ isMethodDeclaration(node) ||
238
+ isGetAccessorDeclaration(node) ||
239
+ isSetAccessorDeclaration(node));
240
+ }
241
+ /** The name a function nested in another is called by: its own, or the variable it initializes. */
242
+ function nestedName(fn) {
243
+ if (isFunctionDeclaration(fn)) {
244
+ return fn.name?.text;
245
+ }
246
+ let at = fn.parent;
247
+ while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
248
+ at = at.parent;
249
+ }
250
+ return isVariableDeclaration(at) && isIdentifier(at.name) ? at.name.text : undefined;
251
+ }
252
+ /** The names one function's body writes as identifiers, other than the names its declarations bind. */
253
+ function namesWritten(fn) {
254
+ const found = new Set();
255
+ const visit = (node) => {
256
+ const declares = (isFunctionDeclaration(node.parent) || isVariableDeclaration(node.parent)) &&
257
+ node.parent.name === node;
258
+ if (isIdentifier(node) && !declares) {
259
+ found.add(node.text);
260
+ }
261
+ node.forEachChild(visit);
262
+ };
263
+ fn.forEachChild(visit);
264
+ return found;
265
+ }
266
+ /**
267
+ * The erased parameters their own function enumerates: one an `Object.keys`, `entries`,
268
+ * `values`, `getOwnPropertyNames` or `assign` call takes, `Object` the standard library's,
269
+ * or a `for…in` statement walks, which reads every property of the value by its name. A
270
+ * nested function is read as the function's own only where its name is written in it, or
271
+ * it has none, a callback or a function called where it is written.
272
+ */
273
+ function enumeratedParameters(project, erased) {
274
+ const asked = [];
275
+ for (const parameter of erased.values()) {
276
+ const fn = parameter.name.parent.parent;
277
+ const spelled = parameter.name.getText();
278
+ const written = namesWritten(fn);
279
+ const visit = (node) => {
280
+ if (isFunctionNode(node)) {
281
+ const name = nestedName(node);
282
+ if (name !== undefined && !written.has(name)) {
283
+ return;
284
+ }
285
+ }
286
+ const read = [];
287
+ let object;
288
+ if (isCallExpression(node)) {
289
+ const callee = node.expression;
290
+ if (isPropertyAccessExpression(callee) &&
291
+ isIdentifier(callee.expression) &&
292
+ callee.expression.text === "Object" &&
293
+ ENUMERATIONS.has(callee.name.text)) {
294
+ object = callee.expression;
295
+ // `Object.assign` reads its sources, every argument after the target.
296
+ read.push(...node.arguments.slice(...(callee.name.text === "assign" ? [1] : [0, 1])));
297
+ }
298
+ }
299
+ else if (isForInStatement(node)) {
300
+ read.push(node.expression);
301
+ }
302
+ for (const value of read.map(evaluated)) {
303
+ if (isIdentifier(value) && value.text === spelled) {
304
+ asked.push({ node: value, parameter, object });
305
+ }
306
+ }
307
+ node.forEachChild(visit);
308
+ };
309
+ fn.forEachChild(visit);
310
+ }
311
+ const symbols = project.symbolsAt(asked.map((one) => project.handle(one.node))).map(must);
312
+ const objects = asked.flatMap((one) => (one.object === undefined ? [] : [one.object]));
313
+ const own = project.ownPaths();
314
+ const standard = new Set(project
315
+ .symbolsAt(objects.map((node) => project.handle(node)))
316
+ .map(must)
317
+ .flatMap((symbol, at) => symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [objects[at]] : []));
318
+ return [
319
+ ...new Set(asked.flatMap((one, at) => {
320
+ const symbol = symbols[at];
321
+ return symbol !== undefined &&
322
+ erased.get(symbol.id) === one.parameter &&
323
+ (one.object === undefined || standard.has(one.object))
324
+ ? [one.parameter]
325
+ : [];
326
+ })),
327
+ ];
328
+ }
203
329
  /** Each place a call's argument passes one of the erased parameters it can see. */
204
330
  function handedOn(project, sites, erased, invocationOf) {
205
331
  const spellings = new Set([...erased.values()].map((parameter) => parameter.name.getText()));
@@ -283,8 +409,8 @@ function implementedOwners(project, inAnalysis) {
283
409
  * callee is a serializer an entry names; at the arguments its resolved signature types
284
410
  * `unknown` or `any` where its callee is declared outside the analysis or only ambiently
285
411
  * with no implementation in it; and, where its callee is a function of the project, at each
286
- * parameter the function hands on to a destination, holding back the widest of what those
287
- * destinations hold back, which is settled by widening until no parameter moves.
412
+ * parameter the function enumerates or hands on to a destination, holding back the widest
413
+ * of what those hold back, settled by widening until no parameter moves.
288
414
  */
289
415
  function destinations(input, chains, walked) {
290
416
  const { project, held } = input;
@@ -381,6 +507,28 @@ function destinations(input, chains, walked) {
381
507
  });
382
508
  return containing.length === 0 ? target.declarations : containing;
383
509
  };
510
+ /** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
511
+ const mentions = (type, parameter, depth) => {
512
+ // A conditional type an alias names, `Awaited<T>` among them, carries its arguments
513
+ // only on the alias the client's type object reports.
514
+ const aliased = type
515
+ .aliasTypeArguments;
516
+ if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
517
+ return true;
518
+ }
519
+ if (depth >= MENTION_DEPTH) {
520
+ return false;
521
+ }
522
+ const inner = [
523
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
524
+ ...must(queries.typeArguments(type)),
525
+ ...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
526
+ ...signature.parameters.map((_parameter, at) => must(queries.typeAtPosition(signature, at))),
527
+ ...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
528
+ ]),
529
+ ];
530
+ return inner.some((one) => mentions(one, parameter, depth + 1));
531
+ };
384
532
  const erasedArguments = new Map();
385
533
  const erasedAt = (call, argument) => {
386
534
  let indexes = erasedArguments.get(call);
@@ -394,10 +542,32 @@ function destinations(input, chains, walked) {
394
542
  ? []
395
543
  : must(queries.signaturesOf(type, SignatureKind.Call))
396
544
  : [resolvedOf(call)].filter((one) => one !== undefined);
397
- indexes = new Set(invoked.arguments.flatMap((_unused, index) => signatures.some((signature) => {
398
- const parameter = must(queries.parameterType(signature, index));
399
- return parameter !== undefined && (parameter.flags & ERASED) !== 0;
400
- })
545
+ // A type parameter of the callee that nothing constrains and its result does not name
546
+ // keeps nothing of the value either: the instantiated signature names the value's own
547
+ // type there instead, and a result that names it hands the value back.
548
+ const generic = invoked.indirect ? undefined : resolvedOf(call);
549
+ const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
550
+ const unconstrained = (index) => {
551
+ const parameter = declared === undefined ? undefined : must(queries.parameterType(declared, index));
552
+ const parts = parameter === undefined
553
+ ? []
554
+ : parameter.isUnionType()
555
+ ? must(queries.constituents(parameter))
556
+ : [parameter];
557
+ const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
558
+ return parts.some((part) => {
559
+ if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
560
+ return false;
561
+ }
562
+ const constraint = must(queries.constraintOf(part));
563
+ return constraint === undefined || (constraint.flags & ERASED) !== 0;
564
+ });
565
+ };
566
+ indexes = new Set(invoked.arguments.flatMap((_unused, index) => unconstrained(index) ||
567
+ signatures.some((signature) => {
568
+ const parameter = must(queries.parameterType(signature, index));
569
+ return parameter !== undefined && (parameter.flags & ERASED) !== 0;
570
+ })
401
571
  ? [index]
402
572
  : []));
403
573
  erasedArguments.set(call, indexes);
@@ -432,6 +602,9 @@ function destinations(input, chains, walked) {
432
602
  }
433
603
  return undefined;
434
604
  };
605
+ for (const parameter of enumeratedParameters(project, erased)) {
606
+ forwards.set(keyOf(parameter), "properties");
607
+ }
435
608
  const forwarded = handedOn(project, walked.sites, erased, invocation);
436
609
  for (let moved = true; moved;) {
437
610
  moved = false;
@@ -505,6 +678,14 @@ function flowsOf(project, held, sites, to, stores) {
505
678
  },
506
679
  ];
507
680
  });
681
+ const generic = typeParameterFlows(project, reach, sites, arguments_.flatMap((one) => {
682
+ const type = typeOf.get(one.value);
683
+ const parameters = type === undefined ? [] : typeParametersOf(project, type, 0);
684
+ const destination = parameters.length === 0 ? undefined : to.at(one.site.call, one.argument);
685
+ return destination === undefined
686
+ ? []
687
+ : parameters.map((parameter) => ({ parameter, to: destination }));
688
+ }));
508
689
  const left = stores.outside.flatMap((store) => {
509
690
  const reached = reach.reachedFrom(store.type);
510
691
  return reached.classes.size === 0 && reached.members.length === 0
@@ -519,7 +700,155 @@ function flowsOf(project, held, sites, to, stores) {
519
700
  },
520
701
  ];
521
702
  });
522
- return [...passed, ...left];
703
+ return [...passed, ...generic, ...left];
704
+ }
705
+ /** The type parameters one type names, through constituents and type arguments. */
706
+ function typeParametersOf(project, type, depth) {
707
+ if (type.isTypeParameter()) {
708
+ return [type];
709
+ }
710
+ if (depth >= MENTION_DEPTH) {
711
+ return [];
712
+ }
713
+ const { queries } = project;
714
+ return [
715
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
716
+ ...must(queries.typeArguments(type)),
717
+ ].flatMap((one) => typeParametersOf(project, one, depth + 1));
718
+ }
719
+ /**
720
+ * The type parameters one type names, by identifier: through constituents, type arguments,
721
+ * an alias's arguments and the parameters and results of its call signatures, so a callback
722
+ * parameter's result names the parameter its callee's result does.
723
+ */
724
+ function namedTypeParameters(project, type, depth) {
725
+ const { queries } = project;
726
+ const aliased = type.aliasTypeArguments;
727
+ if (type.isTypeParameter()) {
728
+ return [type.id];
729
+ }
730
+ if (depth >= MENTION_DEPTH) {
731
+ return aliased ?? [];
732
+ }
733
+ return [
734
+ ...(aliased ?? []),
735
+ ...[
736
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
737
+ ...must(queries.typeArguments(type)),
738
+ ...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
739
+ ...signature.parameters.flatMap((_parameter, at) => [
740
+ must(queries.typeAtPosition(signature, at)),
741
+ ]),
742
+ ...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
743
+ ]),
744
+ ].flatMap((one) => namedTypeParameters(project, one, depth + 1)),
745
+ ];
746
+ }
747
+ /**
748
+ * The types an instantiated type puts where a declared type names `parameter`: matched
749
+ * through type arguments, and in a union every constituent the declared union does not
750
+ * itself name.
751
+ */
752
+ function bound(project, declared, instantiated, parameter, depth) {
753
+ if (declared.id === parameter.id) {
754
+ return [instantiated];
755
+ }
756
+ if (depth >= MENTION_DEPTH) {
757
+ return [];
758
+ }
759
+ const { queries } = project;
760
+ if (declared.isUnionType()) {
761
+ const parts = must(queries.constituents(declared));
762
+ if (parts.some((part) => part.id === parameter.id)) {
763
+ const kept = new Set(parts.map((part) => part.id));
764
+ const given = instantiated.isUnionType()
765
+ ? must(queries.constituents(instantiated))
766
+ : [instantiated];
767
+ return given.filter((part) => !kept.has(part.id));
768
+ }
769
+ }
770
+ const declaredArguments = must(queries.typeArguments(declared));
771
+ const givenArguments = must(queries.typeArguments(instantiated));
772
+ return declaredArguments.length !== givenArguments.length
773
+ ? []
774
+ : declaredArguments.flatMap((one, at) => {
775
+ const given = givenArguments[at];
776
+ return given === undefined ? [] : bound(project, one, given, parameter, depth + 1);
777
+ });
778
+ }
779
+ /**
780
+ * The flows a type parameter of a function of the project carries where a value its type
781
+ * names reaches a destination: each call of the function passes its type argument there,
782
+ * written or inferred from the arguments, and a type argument that is a type parameter
783
+ * of the calling function is followed on to that function's calls, to a fixpoint.
784
+ */
785
+ function typeParameterFlows(project, reach, sites, pending) {
786
+ const { queries } = project;
787
+ const byName = new Map();
788
+ for (const site of sites) {
789
+ const name = calleeName(site.call.expression)?.getText();
790
+ if (name !== undefined) {
791
+ byName.set(name, [...(byName.get(name) ?? []), site]);
792
+ }
793
+ }
794
+ const found = [];
795
+ const seen = new Set();
796
+ for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
797
+ const { parameter, to } = next;
798
+ if (seen.has(parameter.id)) {
799
+ continue;
800
+ }
801
+ seen.add(parameter.id);
802
+ const symbol = must(queries.symbolOfType(parameter));
803
+ const [handle] = symbol?.declarations ?? [];
804
+ const declaration = handle === undefined ? undefined : project.declarationAt(handle)?.node;
805
+ const fn = declaration?.parent;
806
+ const index = fn === undefined || !isFunctionLikeDeclaration(fn)
807
+ ? -1
808
+ : (fn.typeParameters ?? []).findIndex((one) => one === declaration);
809
+ const name = fn === undefined
810
+ ? undefined
811
+ : isMethodDeclaration(fn) && isIdentifier(fn.name)
812
+ ? fn.name.text
813
+ : nestedName(fn);
814
+ if (symbol === undefined || fn === undefined || index < 0 || name === undefined) {
815
+ continue;
816
+ }
817
+ for (const site of byName.get(name) ?? []) {
818
+ const resolved = must(queries.resolvedSignature(site.call));
819
+ const declared = resolved === undefined ? undefined : must(queries.genericOf(resolved));
820
+ const called = declared?.declaration === undefined
821
+ ? undefined
822
+ : project.declarationAt(declared.declaration)?.node;
823
+ if (resolved === undefined || declared === undefined || called !== fn) {
824
+ continue;
825
+ }
826
+ const written = site.call.typeArguments?.[index];
827
+ const given = written === undefined
828
+ ? site.call.arguments.flatMap((_argument, at) => {
829
+ const from = must(queries.parameterType(declared, at));
830
+ const into = must(queries.parameterType(resolved, at));
831
+ return from === undefined || into === undefined
832
+ ? []
833
+ : bound(project, from, into, parameter, 0);
834
+ })
835
+ : typesAt(project, [written]).filter((type) => type !== undefined);
836
+ for (const type of given) {
837
+ const reached = reach.reachedFrom(type);
838
+ if (reached.classes.size > 0 || reached.members.length > 0) {
839
+ found.push({
840
+ site: site.call,
841
+ classes: reached.classes,
842
+ members: reached.members,
843
+ to,
844
+ detail: `passed to ${to.callee} through the type parameter ${symbol.name}`,
845
+ });
846
+ }
847
+ pending.push(...typeParametersOf(project, type, 0).map((one) => ({ parameter: one, to })));
848
+ }
849
+ }
850
+ }
851
+ return found;
523
852
  }
524
853
  /**
525
854
  * The serialization-contract detector: each data member and `toJSON` of each class,
@@ -542,6 +871,10 @@ export function serializationContract(input) {
542
871
  const holdsBack = (flags, name, to) => (flags & SymbolFlags.Property) !== 0 ||
543
872
  ((flags & CONVERSION_MEMBERS) !== 0 &&
544
873
  (name === SERIALIZED_CONVERSION || (to.reach === "conversions" && CONVERSIONS.has(name))));
874
+ // The receiver reads only what the program wrote, so a data member holds while written.
875
+ const evidence = (id, flags, detail, site) => (flags & SymbolFlags.Property) !== 0 && !walked.initialized.has(id)
876
+ ? { id, detail, site, whileWritten: true }
877
+ : { id, detail, site };
545
878
  const found = [];
546
879
  for (const flow of flows) {
547
880
  const site = renderPosition(flow.site.getSourceFile(), targetRoot, flow.site.getStart());
@@ -550,16 +883,303 @@ export function serializationContract(input) {
550
883
  const symbol = byId.get(member.id);
551
884
  const name = symbol === undefined ? "" : (memberComponent(symbol, byId) ?? "");
552
885
  if (holdsBack(member.flags, name, flow.to)) {
553
- found.push({ id: member.id, detail, site });
886
+ found.push(evidence(member.id, member.flags, detail, site));
554
887
  }
555
888
  }
556
889
  for (const member of flow.members) {
557
890
  if (holdsBack(member.flags, member.name, flow.to)) {
558
891
  for (const id of chains.declarationsOf(member)) {
559
- found.push({ id, detail, site });
892
+ found.push(evidence(id, member.flags, detail, site));
560
893
  }
561
894
  }
562
895
  }
563
896
  }
564
897
  return found;
565
898
  }
899
+ /**
900
+ * The data members the decodes and copies of `files` fill, as {@link Decoded} names them.
901
+ * A generic declared outside the analysis, or only ambiently, fills the type that a type
902
+ * parameter of its own no parameter names binds, where the awaited result carries that
903
+ * type through its data members rather than only naming it, as a store's methods do: no
904
+ * argument hands the value over, so it fills it where the analysis cannot see, and may
905
+ * read it. Where the checker leaves a question unanswered, every data member is `unanswered`.
906
+ */
907
+ export function decodedMembers(project, held, files, consumers) {
908
+ try {
909
+ return { ...decodedOf(project, held, files, consumers), unanswered: [] };
910
+ }
911
+ catch (error) {
912
+ if (!(error instanceof Unanswerable)) {
913
+ throw error;
914
+ }
915
+ return {
916
+ filled: [],
917
+ parsed: [],
918
+ copied: [],
919
+ unanswered: held.symbols
920
+ .filter((symbol) => symbol.kind === "class-member" || symbol.kind === "type-member")
921
+ .map((symbol) => symbol.id),
922
+ };
923
+ }
924
+ }
925
+ function decodedOf(project, held, files, consumers) {
926
+ const { queries } = project;
927
+ const own = project.ownPaths();
928
+ const inAnalysis = (path) => own.has(path) || consumers.some((root) => path === root || path.startsWith(`${root}/`));
929
+ const calls = [];
930
+ const untyped = [];
931
+ const spreads = [];
932
+ const noteRead = (node) => {
933
+ const at = readAs(node);
934
+ if (at.tops.length > 0 || at.asserted.length > 0) {
935
+ untyped.push(node);
936
+ }
937
+ };
938
+ const visit = (node) => {
939
+ if (isCallExpression(node)) {
940
+ calls.push(node);
941
+ noteRead(node);
942
+ }
943
+ else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) {
944
+ noteRead(node);
945
+ }
946
+ else if (isSpreadAssignment(node)) {
947
+ spreads.push(node);
948
+ }
949
+ node.forEachChild(visit);
950
+ };
951
+ for (const file of files) {
952
+ file.forEachChild(visit);
953
+ }
954
+ const filled = [];
955
+ const resolved = queries.resolvedSignatures(calls).map(must);
956
+ for (const signature of resolved) {
957
+ const declared = signature?.target === undefined ? undefined : must(queries.genericOf(signature));
958
+ const declaration = declared?.declaration;
959
+ const generic = declared?.typeParameters ?? [];
960
+ if (signature === undefined || declared === undefined || generic.length === 0) {
961
+ continue;
962
+ }
963
+ const node = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
964
+ if (declaration === undefined ||
965
+ (inAnalysis(declaration.path) &&
966
+ (node === undefined || (node.flags & NodeFlags.Ambient) === 0))) {
967
+ continue;
968
+ }
969
+ const result = must(queries.returnTypeOf(declared));
970
+ const given = must(queries.returnTypeOf(signature));
971
+ if (result === undefined || given === undefined) {
972
+ continue;
973
+ }
974
+ const passed = new Set(declared.parameters.flatMap((_parameter, at) => {
975
+ const type = must(queries.parameterType(declared, at));
976
+ return type === undefined ? [] : namedTypeParameters(project, type, 0);
977
+ }));
978
+ const types = typeParametersOf(project, result, 0)
979
+ .filter((parameter) => generic.includes(parameter.id) && !passed.has(parameter.id))
980
+ .flatMap((parameter) => bound(project, result, given, parameter, 0));
981
+ if (types.length > 0) {
982
+ filled.push({ carried: awaited(project, given), types });
983
+ }
984
+ }
985
+ const read = readTypes(project, untyped);
986
+ const copies = copiedNames(project, spreads, calls);
987
+ if (filled.length === 0 &&
988
+ read.parsed.length === 0 &&
989
+ read.made.length === 0 &&
990
+ copies.length === 0) {
991
+ return { filled: [], parsed: [], copied: [] };
992
+ }
993
+ const reach = valueReach(project, held);
994
+ const chains = aliasChains(project, held);
995
+ const members = (type) => {
996
+ const reached = reach.reachedFrom(type);
997
+ const ofClasses = classMembers(project, held, reached.classes, "instance");
998
+ return [
999
+ ...[...reached.classes].flatMap((id) => (ofClasses.get(id) ?? []).flatMap((member) => (member.flags & SymbolFlags.Property) !== 0 ? [member.id] : [])),
1000
+ ...reached.members.flatMap((member) => (member.flags & SymbolFlags.Property) !== 0 ? chains.declarationsOf(member) : []),
1001
+ ];
1002
+ };
1003
+ return {
1004
+ filled: [
1005
+ ...new Set([
1006
+ ...filled.flatMap(({ carried, types }) => {
1007
+ const carries = new Set(members(carried));
1008
+ return types.flatMap((type) => members(type).filter((id) => carries.has(id)));
1009
+ }),
1010
+ ...read.made.flatMap(members),
1011
+ ]),
1012
+ ],
1013
+ parsed: [...new Set(read.parsed.flatMap(members))],
1014
+ copied: [
1015
+ ...new Set(copies.flatMap(({ into, names }) => must(queries.constituents(into)).flatMap((part) => must(queries.propertiesOf(part)).flatMap((property) => (property.flags & SymbolFlags.Property) !== 0 && names.has(property.name)
1016
+ ? chains.declarationsOf(property)
1017
+ : [])))),
1018
+ ],
1019
+ };
1020
+ }
1021
+ /**
1022
+ * Where one value is read as a type: each `as` and angle-bracket assertion around it,
1023
+ * through parentheses, `await` and `!`, and the outermost expression unless a statement
1024
+ * of its own or the object or callee of a further access holds it. A `satisfies` check
1025
+ * is stepped through and types nothing, its expression's type being its operand's. A
1026
+ * callback's result is read as well where the call taking the callback is, which may
1027
+ * hand it on inside its result, as `map` does.
1028
+ */
1029
+ function readAs(value, depth = 0) {
1030
+ const asserted = [];
1031
+ let at = value;
1032
+ for (;;) {
1033
+ const parent = at.parent;
1034
+ if (isAsExpression(parent) || isTypeAssertion(parent)) {
1035
+ asserted.push(parent.type);
1036
+ }
1037
+ else if (!isParenthesizedExpression(parent) &&
1038
+ !isAwaitExpression(parent) &&
1039
+ !isNonNullExpression(parent) &&
1040
+ !isSatisfiesExpression(parent)) {
1041
+ break;
1042
+ }
1043
+ at = parent;
1044
+ }
1045
+ const parent = at.parent;
1046
+ const unused = isExpressionStatement(parent) ||
1047
+ ((isPropertyAccessExpression(parent) ||
1048
+ isElementAccessExpression(parent) ||
1049
+ isCallExpression(parent)) &&
1050
+ parent.expression === at);
1051
+ const call = depth < MENTION_DEPTH ? callTaking(at) : undefined;
1052
+ const outer = call === undefined ? undefined : readAs(call, depth + 1);
1053
+ return {
1054
+ asserted: [...asserted, ...(outer?.asserted ?? [])],
1055
+ tops: [...(unused ? [] : [at]), ...(outer?.tops ?? [])],
1056
+ };
1057
+ }
1058
+ /** The call one value is returned to as a callback's result, an argument of that call. */
1059
+ function callTaking(value) {
1060
+ const parent = value.parent;
1061
+ let callback;
1062
+ if (isArrowFunction(parent) && parent.body === value) {
1063
+ callback = parent;
1064
+ }
1065
+ else if (isReturnStatement(parent)) {
1066
+ let at = parent.parent;
1067
+ while (!isFunctionLikeDeclaration(at) && !isSourceFile(at)) {
1068
+ at = at.parent;
1069
+ }
1070
+ callback = isArrowFunction(at) || isFunctionExpression(at) ? at : undefined;
1071
+ }
1072
+ let at = callback?.parent;
1073
+ while (at !== undefined && isParenthesizedExpression(at)) {
1074
+ at = at.parent;
1075
+ }
1076
+ return at !== undefined && isCallExpression(at) && at.expression !== callback ? at : undefined;
1077
+ }
1078
+ /** The global the standard library declares one name of `JSON` or `Object` as. */
1079
+ function standardGlobal(callee, global, name) {
1080
+ return isPropertyAccessExpression(callee) &&
1081
+ isIdentifier(callee.expression) &&
1082
+ callee.expression.text === global &&
1083
+ callee.name.text === name
1084
+ ? callee.expression
1085
+ : undefined;
1086
+ }
1087
+ /**
1088
+ * The types each value of `values` typed `any` or `unknown` is read as ({@link readAs}):
1089
+ * `parsed` for a `JSON.parse` call of the standard library, `made` for any other call or
1090
+ * property, whose maker the analysis cannot see. `JSON` must be the standard library's
1091
+ * global.
1092
+ */
1093
+ function readTypes(project, values) {
1094
+ if (values.length === 0) {
1095
+ return { parsed: [], made: [] };
1096
+ }
1097
+ const own = project.ownPaths();
1098
+ const parses = values.map((value) => isCallExpression(value) ? standardGlobal(value.expression, "JSON", "parse") : undefined);
1099
+ const globals = parses.flatMap((global) => (global === undefined ? [] : [global]));
1100
+ const standard = new Set(project
1101
+ .symbolsAt(globals.map((global) => project.handle(global)))
1102
+ .map(must)
1103
+ .flatMap((symbol, at) => symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [globals[at]] : []));
1104
+ // An `await` around the value reads the value it resolves to.
1105
+ const valueTypes = typesAt(project, values.map((value) => (isAwaitExpression(value.parent) ? value.parent : value)));
1106
+ const sites = values.flatMap((value, at) => {
1107
+ const parse = parses[at];
1108
+ const parsed = parse !== undefined && standard.has(parse);
1109
+ const type = valueTypes[at];
1110
+ const untyped = type !== undefined && (type.flags & (TypeFlags.Any | TypeFlags.Unknown)) !== 0;
1111
+ return parsed || untyped ? [{ ...readAs(value), parsed }] : [];
1112
+ });
1113
+ // Each type node and each outermost expression, with whether a `JSON.parse` call is read as it.
1114
+ const typed = sites.flatMap((site) => site.asserted.map((node) => ({ node, parsed: site.parsed })));
1115
+ const placed = sites.flatMap((site) => site.tops.map((node) => ({ node, parsed: site.parsed })));
1116
+ const read = [
1117
+ ...typesAt(project, typed.map((one) => one.node)).map((type, at) => ({ type, parsed: typed[at]?.parsed })),
1118
+ ...project.queries
1119
+ .contextualTypes(placed.map((one) => one.node))
1120
+ .map(must)
1121
+ .map((type, at) => ({ type, parsed: placed[at]?.parsed })),
1122
+ ];
1123
+ const typesWhere = (parsed) => read.flatMap((one) => (one.type !== undefined && one.parsed === parsed ? [one.type] : []));
1124
+ return { parsed: typesWhere(true), made: typesWhere(false) };
1125
+ }
1126
+ /**
1127
+ * The copies the spreads and calls of `files` make: for each spread in an object literal,
1128
+ * the type the literal is read as, and for each `Object.assign` call of the standard
1129
+ * library, its first argument's type, with the member names of the copied values' types.
1130
+ */
1131
+ function copiedNames(project, spreads, calls) {
1132
+ const own = project.ownPaths();
1133
+ const assigns = calls.flatMap((call) => {
1134
+ const global = standardGlobal(call.expression, "Object", "assign");
1135
+ const [target, ...sources] = call.arguments;
1136
+ return global === undefined || target === undefined || sources.length === 0
1137
+ ? []
1138
+ : [{ global, target, sources: sources.filter((source) => !isSpreadElement(source)) }];
1139
+ });
1140
+ const standard = project
1141
+ .symbolsAt(assigns.map((one) => project.handle(one.global)))
1142
+ .map(must)
1143
+ .map((symbol) => symbol?.declarations.every((handle) => !own.has(handle.path)) === true);
1144
+ const assigned = assigns.filter((_one, at) => standard[at] === true);
1145
+ const literals = spreads.flatMap((spread) => isObjectLiteralExpression(spread.parent)
1146
+ ? [{ literal: spread.parent, source: spread.expression }]
1147
+ : []);
1148
+ const contexts = project.queries.contextualTypes(literals.map((one) => one.literal)).map(must);
1149
+ const targets = typesAt(project, assigned.map((one) => one.target));
1150
+ const spreadTypes = typesAt(project, literals.map((one) => one.source));
1151
+ const sourceTypes = assigned.map((one) => typesAt(project, one.sources));
1152
+ const namesOf = (types) => new Set(types.flatMap((type) => type === undefined
1153
+ ? []
1154
+ : must(project.queries.propertiesOf(type)).map((property) => property.name)));
1155
+ const found = [];
1156
+ const add = (into, names) => {
1157
+ if (into !== undefined && names.size > 0) {
1158
+ found.push({ into, names });
1159
+ }
1160
+ };
1161
+ contexts.forEach((into, at) => {
1162
+ add(into, namesOf([spreadTypes[at]]));
1163
+ });
1164
+ targets.forEach((into, at) => {
1165
+ add(into, namesOf(sourceTypes[at] ?? []));
1166
+ });
1167
+ return found;
1168
+ }
1169
+ /** The value a promise of the standard library resolves to, through nested promises. */
1170
+ function awaited(project, type) {
1171
+ const { queries } = project;
1172
+ let at = type;
1173
+ for (let depth = 0; depth < MENTION_DEPTH; depth += 1) {
1174
+ const symbol = must(queries.symbolOfType(at));
1175
+ const [inner] = must(queries.typeArguments(at));
1176
+ const standard = symbol !== undefined &&
1177
+ PROMISES.has(symbol.name) &&
1178
+ symbol.declarations.every((handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true);
1179
+ if (!standard || inner === undefined) {
1180
+ break;
1181
+ }
1182
+ at = inner;
1183
+ }
1184
+ return at;
1185
+ }