@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.20

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 (402) hide show
  1. package/README.md +3 -1
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/dist/bin/mapper-connection.js +2 -1
  4. package/dist/bin/node-host.js +24 -5
  5. package/dist/src/alias-chain.js +2 -7
  6. package/dist/src/amd.js +81 -0
  7. package/dist/src/analysis.js +269 -432
  8. package/dist/src/augmentations.js +64 -3
  9. package/dist/src/automatic-types.js +10 -29
  10. package/dist/src/bindings.js +382 -0
  11. package/dist/src/calls.js +279 -43
  12. package/dist/src/commands/held.js +499 -0
  13. package/dist/src/commands/read.js +161 -0
  14. package/dist/src/commands/shell.js +169 -0
  15. package/dist/src/commands/tools.js +1847 -0
  16. package/dist/src/component-files.js +3 -2
  17. package/dist/src/component-layer.js +4 -2
  18. package/dist/src/config.js +6 -82
  19. package/dist/src/conformance-record.js +1 -1
  20. package/dist/src/consumers.js +10 -38
  21. package/dist/src/contextual-uses.js +57 -26
  22. package/dist/src/contract/config.generated.js +561 -0
  23. package/dist/src/contract/contract.generated.js +1369 -0
  24. package/dist/src/contract/report.generated.js +866 -0
  25. package/dist/src/dependencies.js +164 -121
  26. package/dist/src/destructuring.js +1 -19
  27. package/dist/src/edges.js +38 -122
  28. package/dist/src/element-access.js +181 -168
  29. package/dist/src/entries/build-scripts.js +222 -0
  30. package/dist/src/{configuration-files.js → entries/configuration-files.js} +52 -256
  31. package/dist/src/{convention-rows.js → entries/convention-rows.js} +170 -30
  32. package/dist/src/entries/conventions.js +740 -0
  33. package/dist/src/{html-entries.js → entries/html-entries.js} +12 -42
  34. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +5 -28
  35. package/dist/src/entries/run-directories.js +77 -0
  36. package/dist/src/entries/sources.js +144 -0
  37. package/dist/src/entries/static-module.js +670 -0
  38. package/dist/src/entries/test-runners.js +650 -0
  39. package/dist/src/entries/workflow-steps.js +345 -0
  40. package/dist/src/enum-group.js +3 -10
  41. package/dist/src/erased-stores.js +4 -6
  42. package/dist/src/exempt-classes.js +1 -63
  43. package/dist/src/exempt.js +21 -66
  44. package/dist/src/exposure.js +400 -0
  45. package/dist/src/extensions.js +37 -0
  46. package/dist/src/facts.js +38 -0
  47. package/dist/src/failure.js +36 -0
  48. package/dist/src/file-facts.js +3 -26
  49. package/dist/src/findings/completion.js +109 -56
  50. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  51. package/dist/src/findings/emitters.js +27 -41
  52. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  53. package/dist/src/{in-operands.js → findings/in-operands.js} +3 -2
  54. package/dist/src/findings/interfaces.js +4 -5
  55. package/dist/src/findings/intra-function.js +16 -13
  56. package/dist/src/findings/non-code-artifacts.js +17 -12
  57. package/dist/src/findings/reads-and-writes.js +86 -27
  58. package/dist/src/findings/self-check.js +44 -30
  59. package/dist/src/findings/unused-declarations.js +29 -45
  60. package/dist/src/findings/visibility-narrowing.js +23 -31
  61. package/dist/src/findings-pass.js +13 -21
  62. package/dist/src/framework-lifecycle.js +46 -19
  63. package/dist/src/fs/document.js +52 -0
  64. package/dist/src/fs/manifest.js +67 -0
  65. package/dist/src/fs/tree.js +88 -0
  66. package/dist/src/generated-file.js +174 -28
  67. package/dist/src/git-ignore.js +39 -26
  68. package/dist/src/import-bindings.js +73 -28
  69. package/dist/src/index/sites.js +557 -0
  70. package/dist/src/index.js +2 -2
  71. package/dist/src/indexed-access.js +523 -478
  72. package/dist/src/injection-container.js +2 -2
  73. package/dist/src/interface-satisfaction.js +1 -1
  74. package/dist/src/intra-function-parts.js +63 -43
  75. package/dist/src/inventory/commonjs.js +507 -0
  76. package/dist/src/inventory/enumerate.js +167 -0
  77. package/dist/src/inventory/syntax.js +236 -0
  78. package/dist/src/inventory.js +323 -724
  79. package/dist/src/javascript-file.js +48 -0
  80. package/dist/src/json-document.js +198 -67
  81. package/dist/src/keyof-comparisons.js +126 -107
  82. package/dist/src/kinds.js +3 -366
  83. package/dist/src/{components.js → liveness/components.js} +2 -3
  84. package/dist/src/{graph.js → liveness/graph.js} +52 -28
  85. package/dist/src/liveness/matrix.js +99 -0
  86. package/dist/src/liveness/sweep.js +558 -0
  87. package/dist/src/load/classify.js +289 -0
  88. package/dist/src/load/diagnostics.js +87 -0
  89. package/dist/src/load/discover.js +285 -0
  90. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  91. package/dist/src/load/expected-module.js +136 -0
  92. package/dist/src/load/load.js +27 -0
  93. package/dist/src/load/module-lookup.js +37 -0
  94. package/dist/src/load/package-scope.js +56 -0
  95. package/dist/src/load/plan.js +313 -0
  96. package/dist/src/load/specifiers.js +113 -0
  97. package/dist/src/load/synthesize.js +152 -0
  98. package/dist/src/load/test-globals.js +207 -0
  99. package/dist/src/load/test-joins.js +82 -0
  100. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  101. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  102. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +24 -66
  103. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  104. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  105. package/dist/src/module-declarations.js +70 -85
  106. package/dist/src/overrides.js +134 -112
  107. package/dist/src/partial.js +2 -1
  108. package/dist/src/paths.js +82 -16
  109. package/dist/src/platform-calls.js +78 -0
  110. package/dist/src/platform-symbols.js +134 -0
  111. package/dist/src/position.js +43 -1
  112. package/dist/src/print.js +11 -79
  113. package/dist/src/product.js +13 -0
  114. package/dist/src/query.js +47 -0
  115. package/dist/src/ref.js +12 -20
  116. package/dist/src/references/read-rule.js +1 -0
  117. package/dist/src/references/read-rules.js +48 -0
  118. package/dist/src/references/sink.js +237 -0
  119. package/dist/src/references/sources.js +450 -0
  120. package/dist/src/references.js +117 -1074
  121. package/dist/src/report-schema.js +51 -0
  122. package/dist/src/report.js +57 -41
  123. package/dist/src/reporters/annotations.js +15 -8
  124. package/dist/src/reporters/sarif.js +16 -10
  125. package/dist/src/reporters/template.js +4 -19
  126. package/dist/src/resolve.js +246 -730
  127. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +22 -24
  128. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +6 -15
  129. package/dist/src/roots/manifest.js +216 -0
  130. package/dist/src/roots/published-reach.js +412 -0
  131. package/dist/src/roots/root-set.js +487 -0
  132. package/dist/src/roots/unseen-loads.js +236 -0
  133. package/dist/src/run.js +66 -77
  134. package/dist/src/schema.js +320 -343
  135. package/dist/src/scope.js +38 -115
  136. package/dist/src/serialization-contract.js +601 -22
  137. package/dist/src/session.js +37 -29
  138. package/dist/src/source-files.js +6 -10
  139. package/dist/src/stores.js +4 -2
  140. package/dist/src/suppress-file.js +25 -28
  141. package/dist/src/suppress-inline.js +36 -83
  142. package/dist/src/suppress.js +50 -71
  143. package/dist/src/symbol-keys.js +92 -88
  144. package/dist/src/template-field.js +9 -19
  145. package/dist/src/tests/file-class.js +76 -0
  146. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  147. package/dist/src/{test-support.js → tests/support.js} +5 -12
  148. package/dist/src/text/compare.js +26 -0
  149. package/dist/src/text/glob.js +194 -0
  150. package/dist/src/text/named-files.js +298 -0
  151. package/dist/src/text/yaml.js +684 -0
  152. package/dist/src/type-errors.js +11 -232
  153. package/dist/src/type-parameter-reads.js +178 -139
  154. package/dist/src/unimplemented-callees.js +262 -242
  155. package/dist/src/unwrap.js +23 -0
  156. package/dist/src/usage.js +10 -0
  157. package/dist/src/vendor/unbash/ansi-c.js +132 -0
  158. package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
  159. package/dist/src/vendor/unbash/arithmetic.js +591 -0
  160. package/dist/src/vendor/unbash/assignment.js +131 -0
  161. package/dist/src/vendor/unbash/chars.js +48 -0
  162. package/dist/src/vendor/unbash/command.js +41 -0
  163. package/dist/src/vendor/unbash/expansion.js +80 -0
  164. package/dist/src/vendor/unbash/glob.js +268 -0
  165. package/dist/src/vendor/unbash/heredoc.js +27 -0
  166. package/dist/src/vendor/unbash/internal-types.js +1 -0
  167. package/dist/src/vendor/unbash/lexer.js +4274 -0
  168. package/dist/src/vendor/unbash/limits.js +3 -0
  169. package/dist/src/vendor/unbash/parser.js +1366 -0
  170. package/dist/src/vendor/unbash/parts.js +57 -0
  171. package/dist/src/vendor/unbash/substitution-context.js +8 -0
  172. package/dist/src/vendor/unbash/traversal.js +212 -0
  173. package/dist/src/vendor/unbash/types.js +1 -0
  174. package/dist/src/vendor/unbash/word-value.js +61 -0
  175. package/dist/src/vendor/unbash/word.js +40 -0
  176. package/dist/src/verbs/analyze.js +194 -127
  177. package/dist/src/verbs/describe.js +13 -18
  178. package/dist/src/verbs/explain.js +85 -139
  179. package/dist/src/verbs/print-config.js +2 -1
  180. package/dist/src/verbs/print-projects.js +6 -4
  181. package/dist/src/verbs/print-retained.js +7 -5
  182. package/dist/src/verbs/print-roots.js +4 -2
  183. package/dist/src/verbs/verb.js +1 -5
  184. package/dist/src/verbs/version.js +14 -3
  185. package/jsr.json +2 -1
  186. package/package.json +14 -8
  187. package/src/alias-chain.ts +2 -9
  188. package/src/amd.ts +130 -0
  189. package/src/analysis.ts +424 -650
  190. package/src/augmentations.ts +93 -4
  191. package/src/automatic-types.ts +12 -30
  192. package/src/bindings.ts +460 -0
  193. package/src/calls.ts +338 -46
  194. package/src/commands/held.ts +632 -0
  195. package/src/commands/read.ts +242 -0
  196. package/src/commands/shell.ts +228 -0
  197. package/src/commands/tools.ts +2382 -0
  198. package/src/component-files.ts +4 -2
  199. package/src/component-layer.ts +4 -2
  200. package/src/config.ts +31 -302
  201. package/src/conformance-record.ts +1 -1
  202. package/src/consumers.ts +16 -66
  203. package/src/contextual-uses.ts +66 -34
  204. package/src/contract/config.generated.ts +821 -0
  205. package/src/contract/contract.generated.ts +1452 -0
  206. package/src/contract/report.generated.ts +1241 -0
  207. package/src/dependencies.ts +222 -127
  208. package/src/destructuring.ts +1 -31
  209. package/src/edges.ts +41 -140
  210. package/src/element-access.ts +190 -187
  211. package/src/entries/build-scripts.ts +292 -0
  212. package/src/{configuration-files.ts → entries/configuration-files.ts} +68 -278
  213. package/src/{convention-rows.ts → entries/convention-rows.ts} +227 -31
  214. package/src/{conventions.ts → entries/conventions.ts} +367 -583
  215. package/src/{html-entries.ts → entries/html-entries.ts} +22 -48
  216. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +7 -30
  217. package/src/entries/run-directories.ts +108 -0
  218. package/src/entries/sources.ts +266 -0
  219. package/src/entries/static-module.ts +858 -0
  220. package/src/entries/test-runners.ts +835 -0
  221. package/src/entries/workflow-steps.ts +432 -0
  222. package/src/enum-group.ts +2 -11
  223. package/src/erased-stores.ts +3 -7
  224. package/src/exempt-classes.ts +1 -75
  225. package/src/exempt.ts +70 -98
  226. package/src/exposure.ts +492 -0
  227. package/src/extensions.ts +45 -0
  228. package/src/facts.ts +46 -0
  229. package/src/failure.ts +63 -0
  230. package/src/file-facts.ts +15 -25
  231. package/src/finding.ts +10 -10
  232. package/src/findings/boundary.ts +11 -0
  233. package/src/findings/completion.ts +133 -64
  234. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  235. package/src/findings/emitter.ts +9 -10
  236. package/src/findings/emitters.ts +38 -52
  237. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  238. package/src/{in-operands.ts → findings/in-operands.ts} +4 -3
  239. package/src/findings/interfaces.ts +7 -9
  240. package/src/findings/intra-function.ts +16 -14
  241. package/src/findings/non-code-artifacts.ts +24 -15
  242. package/src/findings/reads-and-writes.ts +118 -41
  243. package/src/findings/self-check.ts +51 -38
  244. package/src/findings/unused-declarations.ts +31 -49
  245. package/src/findings/visibility-narrowing.ts +36 -40
  246. package/src/findings-pass.ts +15 -31
  247. package/src/framework-lifecycle.ts +47 -19
  248. package/src/fs/document.ts +70 -0
  249. package/src/fs/manifest.ts +96 -0
  250. package/src/fs/tree.ts +110 -0
  251. package/src/generated-file.ts +242 -35
  252. package/src/git-ignore.ts +50 -26
  253. package/src/host.ts +12 -5
  254. package/src/import-bindings.ts +98 -29
  255. package/src/index/sites.ts +781 -0
  256. package/src/index.ts +2 -2
  257. package/src/indexed-access.ts +570 -527
  258. package/src/injection-container.ts +2 -2
  259. package/src/interface-satisfaction.ts +2 -2
  260. package/src/intra-function-parts.ts +73 -44
  261. package/src/inventory/commonjs.ts +696 -0
  262. package/src/inventory/enumerate.ts +203 -0
  263. package/src/inventory/syntax.ts +311 -0
  264. package/src/inventory.ts +442 -895
  265. package/src/javascript-file.ts +54 -0
  266. package/src/json-document.ts +241 -76
  267. package/src/keyof-comparisons.ts +133 -122
  268. package/src/kinds.ts +5 -413
  269. package/src/{components.ts → liveness/components.ts} +4 -6
  270. package/src/{graph.ts → liveness/graph.ts} +100 -35
  271. package/src/liveness/matrix.ts +162 -0
  272. package/src/liveness/sweep.ts +855 -0
  273. package/src/load/classify.ts +433 -0
  274. package/src/load/diagnostics.ts +154 -0
  275. package/src/load/discover.ts +471 -0
  276. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  277. package/src/load/expected-module.ts +194 -0
  278. package/src/load/load.ts +73 -0
  279. package/src/load/module-lookup.ts +50 -0
  280. package/src/load/package-scope.ts +74 -0
  281. package/src/load/plan.ts +513 -0
  282. package/src/load/specifiers.ts +171 -0
  283. package/src/load/synthesize.ts +230 -0
  284. package/src/load/test-globals.ts +251 -0
  285. package/src/load/test-joins.ts +98 -0
  286. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  287. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  288. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +41 -77
  289. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  290. package/src/{workspace.ts → load/workspace.ts} +90 -134
  291. package/src/module-declarations.ts +74 -98
  292. package/src/overrides.ts +162 -134
  293. package/src/partial.ts +3 -2
  294. package/src/paths.ts +92 -16
  295. package/src/platform-calls.ts +119 -0
  296. package/src/platform-symbols.ts +189 -0
  297. package/src/position.ts +44 -1
  298. package/src/print.ts +12 -92
  299. package/src/product.ts +18 -0
  300. package/src/query.ts +92 -0
  301. package/src/ref.ts +12 -20
  302. package/src/references/read-rule.ts +60 -0
  303. package/src/references/read-rules.ts +55 -0
  304. package/src/references/sink.ts +314 -0
  305. package/src/references/sources.ts +632 -0
  306. package/src/references.ts +187 -1450
  307. package/src/report-schema.ts +68 -0
  308. package/src/report.ts +103 -226
  309. package/src/reporters/annotations.ts +22 -11
  310. package/src/reporters/json.ts +1 -1
  311. package/src/reporters/reporter.ts +2 -4
  312. package/src/reporters/reporters.ts +1 -1
  313. package/src/reporters/sarif.ts +29 -16
  314. package/src/reporters/template.ts +4 -20
  315. package/src/reporters/text.ts +3 -3
  316. package/src/resolve.ts +284 -1115
  317. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +27 -29
  318. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +7 -16
  319. package/src/roots/manifest.ts +303 -0
  320. package/src/roots/published-reach.ts +486 -0
  321. package/src/{roots.ts → roots/root-set.ts} +286 -469
  322. package/src/roots/unseen-loads.ts +345 -0
  323. package/src/run.ts +75 -91
  324. package/src/schema.ts +391 -388
  325. package/src/scope.ts +46 -138
  326. package/src/serialization-contract.ts +769 -23
  327. package/src/session.ts +87 -55
  328. package/src/source-files.ts +6 -11
  329. package/src/stores.ts +15 -3
  330. package/src/suppress-file.ts +24 -33
  331. package/src/suppress-inline.ts +55 -91
  332. package/src/suppress.ts +78 -79
  333. package/src/symbol-keys.ts +104 -107
  334. package/src/template-field.ts +15 -25
  335. package/src/tests/file-class.ts +135 -0
  336. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  337. package/src/{test-support.ts → tests/support.ts} +15 -21
  338. package/src/text/compare.ts +27 -0
  339. package/src/text/glob.ts +210 -0
  340. package/src/text/named-files.ts +388 -0
  341. package/src/text/yaml.ts +747 -0
  342. package/src/type-errors.ts +18 -288
  343. package/src/type-parameter-reads.ts +187 -158
  344. package/src/unimplemented-callees.ts +297 -280
  345. package/src/unwrap.ts +34 -0
  346. package/src/usage.ts +10 -0
  347. package/src/vendor/unbash/LICENSE +15 -0
  348. package/src/vendor/unbash/VENDORED.md +16 -0
  349. package/src/vendor/unbash/ansi-c.ts +132 -0
  350. package/src/vendor/unbash/arithmetic-parts.ts +53 -0
  351. package/src/vendor/unbash/arithmetic.ts +641 -0
  352. package/src/vendor/unbash/assignment.ts +163 -0
  353. package/src/vendor/unbash/chars.ts +48 -0
  354. package/src/vendor/unbash/command.ts +47 -0
  355. package/src/vendor/unbash/expansion.ts +144 -0
  356. package/src/vendor/unbash/glob.ts +284 -0
  357. package/src/vendor/unbash/heredoc.ts +34 -0
  358. package/src/vendor/unbash/internal-types.ts +94 -0
  359. package/src/vendor/unbash/lexer.ts +4352 -0
  360. package/src/vendor/unbash/limits.ts +3 -0
  361. package/src/vendor/unbash/parser.ts +1612 -0
  362. package/src/vendor/unbash/parts.ts +96 -0
  363. package/src/vendor/unbash/substitution-context.ts +20 -0
  364. package/src/vendor/unbash/traversal.ts +182 -0
  365. package/src/vendor/unbash/types.ts +660 -0
  366. package/src/vendor/unbash/word-value.ts +64 -0
  367. package/src/vendor/unbash/word.ts +50 -0
  368. package/src/verbs/analyze.ts +255 -164
  369. package/src/verbs/describe.ts +19 -17
  370. package/src/verbs/explain.ts +93 -169
  371. package/src/verbs/print-config.ts +2 -1
  372. package/src/verbs/print-projects.ts +6 -4
  373. package/src/verbs/print-retained.ts +7 -5
  374. package/src/verbs/print-roots.ts +4 -2
  375. package/src/verbs/verb.ts +2 -8
  376. package/src/verbs/version.ts +15 -3
  377. package/dist/src/base-url.js +0 -18
  378. package/dist/src/conventions.js +0 -937
  379. package/dist/src/discover.js +0 -468
  380. package/dist/src/entry-points.js +0 -529
  381. package/dist/src/glob.js +0 -65
  382. package/dist/src/manifest.js +0 -165
  383. package/dist/src/matrix.js +0 -246
  384. package/dist/src/pnpm-workspace.js +0 -120
  385. package/dist/src/roots.js +0 -672
  386. package/dist/src/setup-failure.js +0 -23
  387. package/dist/src/sweep.js +0 -224
  388. package/dist/src/test-joins.js +0 -101
  389. package/dist/src/version.js +0 -7
  390. package/dist/src/workflow-steps.js +0 -185
  391. package/src/base-url.ts +0 -24
  392. package/src/discover.ts +0 -653
  393. package/src/entry-points.ts +0 -687
  394. package/src/glob.ts +0 -67
  395. package/src/manifest.ts +0 -234
  396. package/src/matrix.ts +0 -423
  397. package/src/pnpm-workspace.ts +0 -135
  398. package/src/setup-failure.ts +0 -39
  399. package/src/sweep.ts +0 -347
  400. package/src/test-joins.ts +0 -120
  401. package/src/version.ts +0 -7
  402. package/src/workflow-steps.ts +0 -216
@@ -0,0 +1,858 @@
1
+ /**
2
+ * What a module or a configuration document writes out, read without running it, as one
3
+ * value model every entry source reads: a default export in each of its forms, the options
4
+ * a call of one module's export passes, and the top-level values names stand for. A value
5
+ * the text does not state is opaque, so no reader guesses it.
6
+ */
7
+
8
+ import {
9
+ isArrayLiteralExpression,
10
+ isBindingElement,
11
+ isExternalModuleReference,
12
+ isImportClause,
13
+ isImportEqualsDeclaration,
14
+ isImportSpecifier,
15
+ isStringLiteralLikeNode,
16
+ isCallExpression,
17
+ isClassDeclaration,
18
+ isClassExpression,
19
+ isComputedPropertyName,
20
+ isExportAssignment,
21
+ isExportDeclaration,
22
+ isExpressionStatement,
23
+ isFunctionDeclaration,
24
+ isFunctionExpression,
25
+ isFunctionLikeDeclaration,
26
+ isIdentifier,
27
+ isImportDeclaration,
28
+ isNamedExports,
29
+ isNamespaceImport,
30
+ isNoSubstitutionTemplateLiteral,
31
+ isNumericLiteral,
32
+ isObjectBindingPattern,
33
+ isObjectLiteralExpression,
34
+ isPropertyAccessExpression,
35
+ isPropertyAssignment,
36
+ isSpreadAssignment,
37
+ isStringLiteral,
38
+ isVariableDeclaration,
39
+ isVariableStatement,
40
+ isSourceFile,
41
+ isBlock,
42
+ isModuleBlock,
43
+ isCaseBlock,
44
+ isCatchClause,
45
+ isForStatement,
46
+ isForInStatement,
47
+ isForOfStatement,
48
+ isVariableDeclarationList,
49
+ isMetaProperty,
50
+ isAssignmentOperator,
51
+ isBinaryExpression,
52
+ isParenthesizedExpression,
53
+ isPostfixUnaryExpression,
54
+ isPrefixUnaryExpression,
55
+ isShorthandPropertyAssignment,
56
+ isSpreadElement,
57
+ NodeFlags,
58
+ SyntaxKind,
59
+ type CallExpression,
60
+ type Node,
61
+ type SourceFile,
62
+ } from "@typescript/native/ast";
63
+ import { commonJSModuleValue, type ModuleValue } from "../inventory/commonjs.ts";
64
+ import { callsPlatformBy, lexicalBinding, writtenName, type BindingOf } from "../platform-calls.ts";
65
+ import { startsRelativePath } from "../paths.ts";
66
+ import { unwrapped } from "../unwrap.ts";
67
+
68
+ /** Where a value's text starts in its file, or undefined in a document read with no positions. */
69
+ type At = number | undefined;
70
+
71
+ /** One member an object writes: its key, or undefined where a spread or a computed key hides it. */
72
+ interface Member {
73
+ readonly key: string | undefined;
74
+ readonly value: Value;
75
+ readonly at: At;
76
+ }
77
+
78
+ /** One value a module's text states, or an opaque one it does not. */
79
+ export type Value =
80
+ | { readonly kind: "text"; readonly text: string; readonly at: At }
81
+ | { readonly kind: "boolean"; readonly value: boolean; readonly at: At }
82
+ | { readonly kind: "array"; readonly elements: readonly Value[]; readonly at: At }
83
+ | { readonly kind: "object"; readonly members: readonly Member[]; readonly at: At }
84
+ | {
85
+ /** A `resolve` or `join` of the module's own directory and literal segments. */
86
+ readonly kind: "directory";
87
+ readonly segments: readonly string[];
88
+ readonly at: At;
89
+ }
90
+ | { readonly kind: "opaque"; readonly at: At };
91
+
92
+ /** A JSON or YAML document's value, which carries no positions. */
93
+ export function documentValue(value: unknown): Value {
94
+ if (typeof value === "string") {
95
+ return { kind: "text", text: value, at: undefined };
96
+ }
97
+ if (typeof value === "boolean") {
98
+ return { kind: "boolean", value, at: undefined };
99
+ }
100
+ if (Array.isArray(value)) {
101
+ return { kind: "array", elements: value.map(documentValue), at: undefined };
102
+ }
103
+ if (typeof value === "object" && value !== null) {
104
+ return {
105
+ kind: "object",
106
+ members: Object.entries(value).map(([key, held]) => ({
107
+ key,
108
+ value: documentValue(held),
109
+ at: undefined,
110
+ })),
111
+ at: undefined,
112
+ };
113
+ }
114
+ return { kind: "opaque", at: undefined };
115
+ }
116
+
117
+ /**
118
+ * The node that declares a name through one binding: the holder itself for a name, or the
119
+ * binding element of an object or array pattern that binds it.
120
+ */
121
+ function boundAt(holder: Node, name: Node | undefined, text: string): Node | undefined {
122
+ if (name === undefined) {
123
+ return undefined;
124
+ }
125
+ if (isIdentifier(name)) {
126
+ return name.text === text ? holder : undefined;
127
+ }
128
+ let found: Node | undefined;
129
+ name.forEachChild((element) => {
130
+ found ??= boundAt(element, (element as { readonly name?: Node }).name, text);
131
+ });
132
+ return found;
133
+ }
134
+
135
+ /** The declaration of a name one statement makes in the block it is written in. */
136
+ function blockDeclaration(statement: Node, name: string): Node | undefined {
137
+ if (
138
+ (isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
139
+ statement.name?.text === name
140
+ ) {
141
+ return statement;
142
+ }
143
+ if (
144
+ isVariableStatement(statement) &&
145
+ (statement.declarationList.flags & NodeFlags.BlockScoped) !== 0
146
+ ) {
147
+ return statement.declarationList.declarations
148
+ .map((one) => boundAt(one, one.name, name))
149
+ .find((one) => one !== undefined);
150
+ }
151
+ if (isImportEqualsDeclaration(statement)) {
152
+ return statement.name.text === name ? statement : undefined;
153
+ }
154
+ if (isImportDeclaration(statement)) {
155
+ const clause = statement.importClause;
156
+ if (clause?.name?.text === name) {
157
+ return clause;
158
+ }
159
+ const bindings = clause?.namedBindings;
160
+ if (bindings !== undefined && isNamespaceImport(bindings)) {
161
+ return bindings.name.text === name ? bindings : undefined;
162
+ }
163
+ return bindings?.elements.find((one) => one.name.text === name);
164
+ }
165
+ return undefined;
166
+ }
167
+
168
+ /** The `var` declaration of a name anywhere below one scope, outside every function nested in it. */
169
+ function varDeclaration(scope: Node, name: string): Node | undefined {
170
+ let found: Node | undefined;
171
+ const visit = (node: Node): void => {
172
+ if (found !== undefined || isFunctionLikeDeclaration(node) || isClassLike(node)) {
173
+ return;
174
+ }
175
+ if (isVariableDeclaration(node) && (node.parent.flags & NodeFlags.BlockScoped) === 0) {
176
+ found = boundAt(node, node.name, name);
177
+ if (found !== undefined) {
178
+ return;
179
+ }
180
+ }
181
+ node.forEachChild(visit);
182
+ };
183
+ scope.forEachChild(visit);
184
+ return found;
185
+ }
186
+
187
+ function isClassLike(node: Node): boolean {
188
+ return isClassDeclaration(node) || isClassExpression(node);
189
+ }
190
+
191
+ /** The statements a scope holds directly, for a block, a case block or a file. */
192
+ function statementsOf(scope: Node): readonly Node[] {
193
+ if (isSourceFile(scope) || isBlock(scope) || isModuleBlock(scope)) {
194
+ return scope.statements;
195
+ }
196
+ if (isCaseBlock(scope)) {
197
+ return scope.clauses.flatMap((clause) => [...clause.statements]);
198
+ }
199
+ return [];
200
+ }
201
+
202
+ /** The declaration one scope makes of a name, ECMAScript's lexical and variable environments read from the syntax. */
203
+ function scopeDeclaration(scope: Node, name: string): Node | undefined {
204
+ if (isFunctionLikeDeclaration(scope)) {
205
+ const own =
206
+ (isFunctionExpression(scope) || isFunctionDeclaration(scope)) && scope.name?.text === name
207
+ ? isFunctionExpression(scope)
208
+ ? scope
209
+ : undefined
210
+ : undefined;
211
+ const parameter = scope.parameters
212
+ .map((one) => boundAt(one, one.name, name))
213
+ .find((one) => one !== undefined);
214
+ const body = scope.body;
215
+ return (
216
+ parameter ??
217
+ (body === undefined
218
+ ? undefined
219
+ : (statementsOf(body)
220
+ .map((one) => blockDeclaration(one, name))
221
+ .find(Boolean) ?? varDeclaration(body, name))) ??
222
+ own
223
+ );
224
+ }
225
+ if (isClassExpression(scope) && scope.name?.text === name) {
226
+ return scope;
227
+ }
228
+ if (isCatchClause(scope)) {
229
+ const caught = scope.variableDeclaration;
230
+ return caught === undefined ? undefined : boundAt(caught, caught.name, name);
231
+ }
232
+ if (isForStatement(scope) || isForInStatement(scope) || isForOfStatement(scope)) {
233
+ const list = scope.initializer;
234
+ return list !== undefined &&
235
+ isVariableDeclarationList(list) &&
236
+ (list.flags & NodeFlags.BlockScoped) !== 0
237
+ ? list.declarations
238
+ .map((one) => boundAt(one, one.name, name))
239
+ .find((one) => one !== undefined)
240
+ : undefined;
241
+ }
242
+ const declared = statementsOf(scope)
243
+ .map((one) => blockDeclaration(one, name))
244
+ .find(Boolean);
245
+ return declared ?? (isSourceFile(scope) ? varDeclaration(scope, name) : undefined);
246
+ }
247
+
248
+ /**
249
+ * The declaration an identifier written in a module resolves to by the scopes that enclose
250
+ * it, nearest first, or undefined where none of them, the file included, declares it, as
251
+ * for a global. A module read outside every program has no checker to ask.
252
+ */
253
+ export function declarationOf(occurrence: Node & { readonly text: string }): Node | undefined {
254
+ for (let scope = occurrence.parent; ; scope = scope.parent) {
255
+ const found = scopeDeclaration(scope, occurrence.text);
256
+ if (found !== undefined || isSourceFile(scope)) {
257
+ return found;
258
+ }
259
+ }
260
+ }
261
+
262
+ /** The text a property name writes: a name, a literal, or a computed literal; undefined where it computes one. */
263
+ function keyOf(name: Node): string | undefined {
264
+ if (
265
+ isIdentifier(name) ||
266
+ isStringLiteral(name) ||
267
+ isNoSubstitutionTemplateLiteral(name) ||
268
+ isNumericLiteral(name)
269
+ ) {
270
+ return name.text;
271
+ }
272
+ if (isComputedPropertyName(name)) {
273
+ const key = unwrapped(name.expression);
274
+ if (isStringLiteral(key) || isNoSubstitutionTemplateLiteral(key) || isNumericLiteral(key)) {
275
+ return key.text;
276
+ }
277
+ }
278
+ return undefined;
279
+ }
280
+
281
+ /** Whether one expression is the module's own directory: a free `__dirname`, or `import.meta.dirname`. */
282
+ function isOwnDirectory(node: Node): boolean {
283
+ const held = unwrapped(node);
284
+ if (isIdentifier(held)) {
285
+ return held.text === "__dirname" && declarationOf(held) === undefined;
286
+ }
287
+ return (
288
+ isPropertyAccessExpression(held) &&
289
+ isMetaProperty(held.expression) &&
290
+ held.expression.keywordToken === SyntaxKind.ImportKeyword &&
291
+ held.name.text === "dirname"
292
+ );
293
+ }
294
+
295
+ /** The literal segments a `resolve` or `join` call of the module's own directory writes, or undefined. */
296
+ function directorySegments(node: Node): readonly string[] | undefined {
297
+ if (!isCallExpression(node)) {
298
+ return undefined;
299
+ }
300
+ const callee = unwrapped(node.expression);
301
+ const name = isPropertyAccessExpression(callee) ? callee.name : callee;
302
+ const [first, ...rest] = node.arguments;
303
+ if (
304
+ !isIdentifier(name) ||
305
+ (name.text !== "resolve" && name.text !== "join") ||
306
+ first === undefined ||
307
+ !isOwnDirectory(first)
308
+ ) {
309
+ return undefined;
310
+ }
311
+ const segments = rest.map((one) => {
312
+ const part = unwrapped(one);
313
+ return isStringLiteral(part) || isNoSubstitutionTemplateLiteral(part) ? part.text : undefined;
314
+ });
315
+ return segments.every((one) => one !== undefined) ? segments : undefined;
316
+ }
317
+
318
+ /** The value one expression writes out, parentheses, `as`, `satisfies` and `!` read through. */
319
+ export function writtenValue(node: Node): Value {
320
+ const held = unwrapped(node);
321
+ const at = held.getStart();
322
+ const segments = directorySegments(held);
323
+ if (segments !== undefined) {
324
+ return { kind: "directory", segments, at };
325
+ }
326
+ if (isStringLiteral(held) || isNoSubstitutionTemplateLiteral(held)) {
327
+ return { kind: "text", text: held.text, at };
328
+ }
329
+ if (held.kind === SyntaxKind.TrueKeyword || held.kind === SyntaxKind.FalseKeyword) {
330
+ return { kind: "boolean", value: held.kind === SyntaxKind.TrueKeyword, at };
331
+ }
332
+ if (isArrayLiteralExpression(held)) {
333
+ return {
334
+ kind: "array",
335
+ elements: held.elements.map((element) =>
336
+ element.kind === SyntaxKind.SpreadElement
337
+ ? { kind: "opaque", at: element.getStart() }
338
+ : writtenValue(element),
339
+ ),
340
+ at,
341
+ };
342
+ }
343
+ if (isObjectLiteralExpression(held)) {
344
+ return {
345
+ kind: "object",
346
+ members: held.properties.map((member): Member => {
347
+ const start = member.getStart();
348
+ if (isSpreadAssignment(member)) {
349
+ return { key: undefined, value: { kind: "opaque", at: start }, at: start };
350
+ }
351
+ const key = keyOf(member.name);
352
+ return {
353
+ key,
354
+ value: isPropertyAssignment(member)
355
+ ? writtenValue(member.initializer)
356
+ : { kind: "opaque", at: start },
357
+ at: start,
358
+ };
359
+ }),
360
+ at,
361
+ };
362
+ }
363
+ return { kind: "opaque", at };
364
+ }
365
+
366
+ /**
367
+ * The initializer of a top-level `const`, `let` or `var` declaration of the file that
368
+ * declares a name and that the file assigns nowhere else, so the initializer is the value
369
+ * the name holds.
370
+ */
371
+ function topLevelValue(declaration: Node | undefined, file: SourceFile): Node | undefined {
372
+ if (
373
+ declaration === undefined ||
374
+ !isVariableDeclaration(declaration) ||
375
+ !isIdentifier(declaration.name) ||
376
+ declaration.initializer === undefined
377
+ ) {
378
+ return undefined;
379
+ }
380
+ const statement = declaration.parent.parent;
381
+ if (!isVariableStatement(statement) || statement.parent !== file) {
382
+ return undefined;
383
+ }
384
+ const constant = (declaration.parent.flags & NodeFlags.Const) !== 0;
385
+ return constant || !assigned(file, declaration, declaration.name.text)
386
+ ? declaration.initializer
387
+ : undefined;
388
+ }
389
+
390
+ /** Whether the file writes a name that resolves to one declaration anywhere but its initializer. */
391
+ function assigned(file: SourceFile, declaration: Node, name: string): boolean {
392
+ let found = false;
393
+ const visit = (node: Node): void => {
394
+ if (found) {
395
+ return;
396
+ }
397
+ if (isIdentifier(node)) {
398
+ found = node.text === name && isWriteTarget(node) && declarationOf(node) === declaration;
399
+ return;
400
+ }
401
+ node.forEachChild(visit);
402
+ };
403
+ file.forEachChild(visit);
404
+ return found;
405
+ }
406
+
407
+ /**
408
+ * Whether one identifier is written: the target of an assignment, a destructuring
409
+ * assignment's element included, the operand of `++` or `--`, or a `for`-`in` or
410
+ * `for`-`of` target.
411
+ */
412
+ function isWriteTarget(node: Node): boolean {
413
+ let at = node;
414
+ for (let parent = at.parent; ; at = parent, parent = parent.parent) {
415
+ if (isParenthesizedExpression(parent) || isSpreadElement(parent)) {
416
+ continue;
417
+ }
418
+ if (isShorthandPropertyAssignment(parent) && parent.name === at) {
419
+ continue;
420
+ }
421
+ if (
422
+ (isPropertyAssignment(parent) && parent.initializer === at) ||
423
+ isArrayLiteralExpression(parent) ||
424
+ isObjectLiteralExpression(parent)
425
+ ) {
426
+ continue;
427
+ }
428
+ break;
429
+ }
430
+ const parent = at.parent;
431
+ if (isBinaryExpression(parent)) {
432
+ return parent.left === at && isAssignmentOperator(parent.operatorToken.kind);
433
+ }
434
+ if (isPrefixUnaryExpression(parent) || isPostfixUnaryExpression(parent)) {
435
+ return (
436
+ at === node &&
437
+ (parent.operator === SyntaxKind.PlusPlusToken ||
438
+ parent.operator === SyntaxKind.MinusMinusToken)
439
+ );
440
+ }
441
+ return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === at;
442
+ }
443
+
444
+ /**
445
+ * The values one expression stands for: a name of a top-level binding read through to its
446
+ * initializer, and, where `throughCalls`, a call read through to every argument it passes.
447
+ * A `require` call's argument names a module, which is no value the call returns.
448
+ */
449
+ function readThrough(
450
+ node: Node,
451
+ file: SourceFile,
452
+ throughCalls: boolean,
453
+ seen: ReadonlySet<Node> = new Set(),
454
+ ): readonly Value[] {
455
+ const held = unwrapped(node);
456
+ if (isIdentifier(held)) {
457
+ const value = topLevelValue(declarationOf(held), file);
458
+ if (value !== undefined && !seen.has(value)) {
459
+ return readThrough(value, file, throughCalls, new Set([...seen, value]));
460
+ }
461
+ } else if (throughCalls && isCallExpression(held) && !isRequireCall(held)) {
462
+ return held.arguments.flatMap((argument) => readThrough(argument, file, throughCalls, seen));
463
+ }
464
+ return [writtenValue(held)];
465
+ }
466
+
467
+ /** The local names one module binds to another module's exports at its top level. */
468
+ interface Bindings {
469
+ /** Each local name bound to one export, by the export's name; `default` for the module's value. */
470
+ readonly named: ReadonlyMap<Node, string>;
471
+ /** The declarations of each local name bound to the whole module. */
472
+ readonly namespaces: ReadonlySet<Node>;
473
+ }
474
+
475
+ /** A written name's binding by the scopes that enclose it, {@link declarationOf} answering. */
476
+ const scopeBinding = lexicalBinding(declarationOf);
477
+
478
+ /** Whether one call is a `require()` call, read through `bindingOf`, the file's own scopes where no checker answers. */
479
+ function isRequireCall(call: CallExpression, bindingOf: BindingOf = scopeBinding): boolean {
480
+ const callee = writtenName(call.expression);
481
+ return callee !== undefined && callsPlatformBy("require", callee, bindingOf);
482
+ }
483
+
484
+ /** The literal one call's one argument writes, where it is a string literal or a template literal with no substitution. */
485
+ function loadedSpecifier(call: CallExpression): string | undefined {
486
+ const [argument, ...rest] = call.arguments;
487
+ return argument !== undefined &&
488
+ rest.length === 0 &&
489
+ (isStringLiteral(argument) || isNoSubstitutionTemplateLiteral(argument))
490
+ ? argument.text
491
+ : undefined;
492
+ }
493
+
494
+ /** Whether one expression is a `require()` call of a module `isModule` accepts, its specifier the one literal argument. */
495
+ function requires(
496
+ node: Node,
497
+ isModule: (specifier: string) => boolean,
498
+ bindingOf: BindingOf = scopeBinding,
499
+ ): boolean {
500
+ const held = unwrapped(node);
501
+ if (!isCallExpression(held)) {
502
+ return false;
503
+ }
504
+ const specifier = loadedSpecifier(held);
505
+ return specifier !== undefined && isModule(specifier) && isRequireCall(held, bindingOf);
506
+ }
507
+
508
+ /** What {@link boundExports} lists for a name bound to the module itself. */
509
+ export const NAMESPACE = "*";
510
+
511
+ /** Whether one import or re-export names a module `isModule` accepts. */
512
+ function fromModule(declaration: Node, isModule: (specifier: string) => boolean): boolean {
513
+ return (
514
+ isImportDeclaration(declaration) &&
515
+ isStringLiteral(declaration.moduleSpecifier) &&
516
+ isModule(declaration.moduleSpecifier.text)
517
+ );
518
+ }
519
+
520
+ /**
521
+ * What one declaration binds of a module `isModule` accepts: the export each name names,
522
+ * `default` for the module's value, and {@link NAMESPACE} for the module itself. A default
523
+ * import, an import assignment and a name a `require()` call initializes bind the value and
524
+ * the module both, as interop reads them; an object binding pattern of such a call binds each
525
+ * export it lists, and a member read of one binds that member.
526
+ */
527
+ export function boundExports(
528
+ declaration: Node,
529
+ isModule: (specifier: string) => boolean,
530
+ bindingOf: BindingOf = scopeBinding,
531
+ ): readonly string[] {
532
+ if (isImportClause(declaration)) {
533
+ return declaration.name !== undefined && fromModule(declaration.parent, isModule)
534
+ ? ["default", NAMESPACE]
535
+ : [];
536
+ }
537
+ if (isNamespaceImport(declaration)) {
538
+ return fromModule(declaration.parent.parent, isModule) ? [NAMESPACE] : [];
539
+ }
540
+ if (isImportSpecifier(declaration)) {
541
+ return fromModule(declaration.parent.parent.parent, isModule)
542
+ ? [(declaration.propertyName ?? declaration.name).text]
543
+ : [];
544
+ }
545
+ if (isImportEqualsDeclaration(declaration)) {
546
+ const reference = declaration.moduleReference;
547
+ return isExternalModuleReference(reference) &&
548
+ isStringLiteralLikeNode(reference.expression) &&
549
+ isModule(reference.expression.text)
550
+ ? ["default", NAMESPACE]
551
+ : [];
552
+ }
553
+ if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) {
554
+ const holder = declaration.parent.parent;
555
+ const exported = declaration.propertyName ?? declaration.name;
556
+ const key = exported === undefined ? undefined : keyOf(exported);
557
+ return declaration.dotDotDotToken === undefined &&
558
+ declaration.name !== undefined &&
559
+ isIdentifier(declaration.name) &&
560
+ key !== undefined &&
561
+ isVariableDeclaration(holder) &&
562
+ holder.initializer !== undefined &&
563
+ requires(holder.initializer, isModule, bindingOf)
564
+ ? [key]
565
+ : [];
566
+ }
567
+ if (!isVariableDeclaration(declaration) || declaration.initializer === undefined) {
568
+ return [];
569
+ }
570
+ const value = unwrapped(declaration.initializer);
571
+ if (isIdentifier(declaration.name) && requires(value, isModule, bindingOf)) {
572
+ return ["default", NAMESPACE];
573
+ }
574
+ return isIdentifier(declaration.name) &&
575
+ isPropertyAccessExpression(value) &&
576
+ requires(value.expression, isModule, bindingOf)
577
+ ? [value.name.text]
578
+ : [];
579
+ }
580
+
581
+ /**
582
+ * The relative specifiers one module's imports and re-exports name, and those its
583
+ * `require()` calls and `import()` calls name by a literal, in the order the file writes
584
+ * them. A module read outside every program answers it so, with no checker to ask.
585
+ */
586
+ export function relativeSpecifiers(file: SourceFile): readonly string[] {
587
+ const known = specifiersOf.get(file);
588
+ if (known !== undefined) {
589
+ return known;
590
+ }
591
+ const found: string[] = [];
592
+ const named = (specifier: string | undefined): void => {
593
+ if (specifier !== undefined && startsRelativePath(specifier)) {
594
+ found.push(specifier);
595
+ }
596
+ };
597
+ const visit = (node: Node): void => {
598
+ if (
599
+ (isImportDeclaration(node) || isExportDeclaration(node)) &&
600
+ node.moduleSpecifier !== undefined &&
601
+ isStringLiteral(node.moduleSpecifier)
602
+ ) {
603
+ named(node.moduleSpecifier.text);
604
+ } else if (
605
+ isCallExpression(node) &&
606
+ (node.expression.kind === SyntaxKind.ImportKeyword || isRequireCall(node))
607
+ ) {
608
+ named(loadedSpecifier(node));
609
+ }
610
+ node.forEachChild(visit);
611
+ };
612
+ file.forEachChild(visit);
613
+ specifiersOf.set(file, found);
614
+ return found;
615
+ }
616
+
617
+ /** What {@link relativeSpecifiers} found in each file, which every project of a run reads alike. */
618
+ const specifiersOf = new WeakMap<SourceFile, readonly string[]>();
619
+
620
+ /** The declarations one top-level statement makes that may bind another module. */
621
+ function bindingDeclarations(statement: Node): readonly Node[] {
622
+ if (isImportDeclaration(statement)) {
623
+ const clause = statement.importClause;
624
+ const bindings = clause?.namedBindings;
625
+ return [
626
+ ...(clause === undefined ? [] : [clause]),
627
+ ...(bindings === undefined
628
+ ? []
629
+ : isNamespaceImport(bindings)
630
+ ? [bindings]
631
+ : bindings.elements),
632
+ ];
633
+ }
634
+ if (isImportEqualsDeclaration(statement)) {
635
+ return [statement];
636
+ }
637
+ if (!isVariableStatement(statement)) {
638
+ return [];
639
+ }
640
+ return statement.declarationList.declarations.flatMap((declaration) =>
641
+ isObjectBindingPattern(declaration.name)
642
+ ? [declaration, ...declaration.name.elements]
643
+ : [declaration],
644
+ );
645
+ }
646
+
647
+ /** The top-level bindings one file makes of a module, as {@link boundExports} reads each. */
648
+ function bindingsOf(file: SourceFile, module: string): Bindings {
649
+ const named = new Map<Node, string>();
650
+ const namespaces = new Set<Node>();
651
+ const isModule = (specifier: string): boolean => specifier === module;
652
+ for (const declaration of file.statements.flatMap(bindingDeclarations)) {
653
+ for (const exported of boundExports(declaration, isModule)) {
654
+ if (exported === NAMESPACE) {
655
+ namespaces.add(declaration);
656
+ } else {
657
+ named.set(declaration, exported);
658
+ }
659
+ }
660
+ }
661
+ return { named, namespaces };
662
+ }
663
+
664
+ /** One call of a module's export the file makes, and the export it calls. */
665
+ interface ExportCall {
666
+ readonly call: CallExpression;
667
+ readonly export: string;
668
+ }
669
+
670
+ /**
671
+ * Every call one file makes of one module's exports: through a name an import or a
672
+ * `require` binding gives it, a namespace's member, a member of a `require` of the
673
+ * module, or that `require`'s value called itself, each where no scope enclosing the
674
+ * call declares the name otherwise.
675
+ */
676
+ function callsOf(file: SourceFile, module: string): readonly ExportCall[] {
677
+ const { named, namespaces } = bindingsOf(file, module);
678
+ const found: ExportCall[] = [];
679
+ const visit = (node: Node): void => {
680
+ if (isCallExpression(node)) {
681
+ const callee = unwrapped(node.expression);
682
+ if (isIdentifier(callee)) {
683
+ const declaration = declarationOf(callee);
684
+ const exported = declaration === undefined ? undefined : named.get(declaration);
685
+ if (exported !== undefined) {
686
+ found.push({ call: node, export: exported });
687
+ }
688
+ } else if (requires(callee, (specifier) => specifier === module)) {
689
+ found.push({ call: node, export: "default" });
690
+ } else if (isPropertyAccessExpression(callee)) {
691
+ const holder = unwrapped(callee.expression);
692
+ const declaration = isIdentifier(holder) ? declarationOf(holder) : undefined;
693
+ if (
694
+ (declaration !== undefined && namespaces.has(declaration)) ||
695
+ requires(holder, (specifier) => specifier === module)
696
+ ) {
697
+ found.push({ call: node, export: callee.name.text });
698
+ }
699
+ }
700
+ }
701
+ node.forEachChild(visit);
702
+ };
703
+ file.forEachChild(visit);
704
+ return found;
705
+ }
706
+
707
+ /** What one module writes out, read without running it. */
708
+ export interface StaticModule {
709
+ /** Every value the module exports as its default, in each form, read through names and calls. */
710
+ readonly defaultExport: readonly Value[];
711
+ /** The first argument of each call of one module's export, read through a top-level name. */
712
+ options(module: string, exported: string): readonly Value[];
713
+ /** Every call of one module's exports the module makes. */
714
+ calls(module: string): readonly ExportCall[];
715
+ /** The first argument one call passes, read through a top-level name. */
716
+ argument(call: CallExpression): readonly Value[];
717
+ }
718
+
719
+ /** One parsed module, or a JSON document parsed as one, read as {@link StaticModule}. */
720
+ export function staticModule(file: SourceFile): StaticModule {
721
+ const argument = (call: CallExpression): readonly Value[] => {
722
+ const [first] = call.arguments;
723
+ return first === undefined ? [] : readThrough(first, file, false);
724
+ };
725
+ return {
726
+ defaultExport: defaultExportOf(file),
727
+ options: (module, exported) =>
728
+ callsOf(file, module).flatMap((one) => (one.export === exported ? argument(one.call) : [])),
729
+ calls: (module) => callsOf(file, module),
730
+ argument,
731
+ };
732
+ }
733
+
734
+ /** A YAML document's value read as {@link StaticModule}: its default export, and no call. */
735
+ export function documentModule(value: unknown): StaticModule {
736
+ return {
737
+ defaultExport: [documentValue(value)],
738
+ options: () => [],
739
+ calls: () => [],
740
+ argument: () => [],
741
+ };
742
+ }
743
+
744
+ /** The value a CommonJS module leaves, its later member writes and any hidden member on it. */
745
+ function moduleValueOf(written: ModuleValue, file: SourceFile): readonly Value[] {
746
+ const members: Member[] = written.members.map((member) => {
747
+ const at = member.node.getStart();
748
+ return {
749
+ key: member.key,
750
+ value: member.value === undefined ? { kind: "opaque", at } : writtenValue(member.value),
751
+ at,
752
+ };
753
+ });
754
+ if (written.open) {
755
+ members.push({ key: undefined, value: { kind: "opaque", at: undefined }, at: undefined });
756
+ }
757
+ if (written.whole === undefined) {
758
+ return members.length === 0 ? [] : [{ kind: "object", members, at: members[0]?.at }];
759
+ }
760
+ return readThrough(written.whole, file, true).map((value) =>
761
+ value.kind === "object" ? { ...value, members: [...value.members, ...members] } : value,
762
+ );
763
+ }
764
+
765
+ /**
766
+ * The values a module's default export forms write: `export default`, `export =`, a
767
+ * `default` specifier, a JSON document, and, in a file Node runs as CommonJS, the module
768
+ * value as Node leaves it. A default function or class writes no value to read.
769
+ */
770
+ function defaultExportOf(file: SourceFile): readonly Value[] {
771
+ if (file.fileName.endsWith(".json")) {
772
+ return file.statements.flatMap((statement) =>
773
+ isExpressionStatement(statement) ? [writtenValue(statement.expression)] : [],
774
+ );
775
+ }
776
+ const commonJS = commonJSModuleValue(file, declarationOf);
777
+ if (commonJS !== undefined) {
778
+ return moduleValueOf(commonJS, file);
779
+ }
780
+ const found: Value[] = [];
781
+ for (const statement of file.statements) {
782
+ if (isExportAssignment(statement)) {
783
+ found.push(...readThrough(statement.expression, file, true));
784
+ } else if (
785
+ (isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
786
+ statement.modifiers?.some((one) => one.kind === SyntaxKind.DefaultKeyword) === true
787
+ ) {
788
+ found.push({ kind: "opaque", at: statement.getStart() });
789
+ } else if (
790
+ isExportDeclaration(statement) &&
791
+ statement.moduleSpecifier === undefined &&
792
+ statement.exportClause !== undefined &&
793
+ isNamedExports(statement.exportClause)
794
+ ) {
795
+ for (const element of statement.exportClause.elements) {
796
+ if (element.name.text === "default") {
797
+ found.push(...readThrough(element.propertyName ?? element.name, file, true));
798
+ }
799
+ }
800
+ }
801
+ }
802
+ return found;
803
+ }
804
+
805
+ /**
806
+ * The values one object writes for a key after its last spread or computed key, the last
807
+ * written last, or undefined where none follows one, which may write the key unseen. A
808
+ * value that is no object writes none.
809
+ */
810
+ export function membersNamed(value: Value, key: string): readonly Value[] | undefined {
811
+ if (value.kind !== "object") {
812
+ return [];
813
+ }
814
+ const hidden = value.members.findLastIndex((member) => member.key === undefined);
815
+ const after = value.members.slice(hidden + 1).filter((member) => member.key === key);
816
+ return hidden >= 0 && after.length === 0 ? undefined : after.map((member) => member.value);
817
+ }
818
+
819
+ /**
820
+ * The texts one value writes: itself where it is text, or, where `elements`, each element
821
+ * of an array of texts. Undefined for any other value.
822
+ */
823
+ export function textsOf(value: Value, elements: boolean): readonly string[] | undefined {
824
+ if (value.kind === "text") {
825
+ return [value.text];
826
+ }
827
+ if (!elements || value.kind !== "array") {
828
+ return undefined;
829
+ }
830
+ const texts = value.elements.map((element) =>
831
+ element.kind === "text" ? element.text : undefined,
832
+ );
833
+ return texts.every((text) => text !== undefined) ? texts : undefined;
834
+ }
835
+
836
+ /**
837
+ * The values at one path of keys below each value, whatever else the path holds: a key
838
+ * reads an object's members of that name, and `*` every element of an array and every
839
+ * member of an object.
840
+ */
841
+ export function valuesAt(values: readonly Value[], path: readonly string[]): readonly Value[] {
842
+ const [key, ...rest] = path;
843
+ if (key === undefined) {
844
+ return values;
845
+ }
846
+ const next = values.flatMap((value): readonly Value[] => {
847
+ if (key === "*" && value.kind === "array") {
848
+ return value.elements;
849
+ }
850
+ if (value.kind !== "object") {
851
+ return [];
852
+ }
853
+ return value.members
854
+ .filter((member) => key === "*" || member.key === key)
855
+ .map((member) => member.value);
856
+ });
857
+ return valuesAt(next, rest);
858
+ }