@oddessentials/odd-ai-reviewers 1.0.0

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 (370) hide show
  1. package/README.md +190 -0
  2. package/dist/__tests__/hermetic-setup.d.ts +55 -0
  3. package/dist/__tests__/hermetic-setup.d.ts.map +1 -0
  4. package/dist/__tests__/hermetic-setup.js +62 -0
  5. package/dist/__tests__/hermetic-setup.js.map +1 -0
  6. package/dist/__tests__/test-utils/hermetic.d.ts +84 -0
  7. package/dist/__tests__/test-utils/hermetic.d.ts.map +1 -0
  8. package/dist/__tests__/test-utils/hermetic.js +147 -0
  9. package/dist/__tests__/test-utils/hermetic.js.map +1 -0
  10. package/dist/agents/ai_semantic_review.d.ts +12 -0
  11. package/dist/agents/ai_semantic_review.d.ts.map +1 -0
  12. package/dist/agents/ai_semantic_review.js +317 -0
  13. package/dist/agents/ai_semantic_review.js.map +1 -0
  14. package/dist/agents/control_flow/budget.d.ts +162 -0
  15. package/dist/agents/control_flow/budget.d.ts.map +1 -0
  16. package/dist/agents/control_flow/budget.js +331 -0
  17. package/dist/agents/control_flow/budget.js.map +1 -0
  18. package/dist/agents/control_flow/cfg-builder.d.ts +26 -0
  19. package/dist/agents/control_flow/cfg-builder.d.ts.map +1 -0
  20. package/dist/agents/control_flow/cfg-builder.js +776 -0
  21. package/dist/agents/control_flow/cfg-builder.js.map +1 -0
  22. package/dist/agents/control_flow/cfg-types.d.ts +186 -0
  23. package/dist/agents/control_flow/cfg-types.d.ts.map +1 -0
  24. package/dist/agents/control_flow/cfg-types.js +114 -0
  25. package/dist/agents/control_flow/cfg-types.js.map +1 -0
  26. package/dist/agents/control_flow/finding-generator.d.ts +118 -0
  27. package/dist/agents/control_flow/finding-generator.d.ts.map +1 -0
  28. package/dist/agents/control_flow/finding-generator.js +354 -0
  29. package/dist/agents/control_flow/finding-generator.js.map +1 -0
  30. package/dist/agents/control_flow/index.d.ts +39 -0
  31. package/dist/agents/control_flow/index.d.ts.map +1 -0
  32. package/dist/agents/control_flow/index.js +270 -0
  33. package/dist/agents/control_flow/index.js.map +1 -0
  34. package/dist/agents/control_flow/logger.d.ts +333 -0
  35. package/dist/agents/control_flow/logger.d.ts.map +1 -0
  36. package/dist/agents/control_flow/logger.js +607 -0
  37. package/dist/agents/control_flow/logger.js.map +1 -0
  38. package/dist/agents/control_flow/mitigation-detector.d.ts +207 -0
  39. package/dist/agents/control_flow/mitigation-detector.d.ts.map +1 -0
  40. package/dist/agents/control_flow/mitigation-detector.js +625 -0
  41. package/dist/agents/control_flow/mitigation-detector.js.map +1 -0
  42. package/dist/agents/control_flow/mitigation-patterns.d.ts +53 -0
  43. package/dist/agents/control_flow/mitigation-patterns.d.ts.map +1 -0
  44. package/dist/agents/control_flow/mitigation-patterns.js +620 -0
  45. package/dist/agents/control_flow/mitigation-patterns.js.map +1 -0
  46. package/dist/agents/control_flow/path-analyzer.d.ts +287 -0
  47. package/dist/agents/control_flow/path-analyzer.d.ts.map +1 -0
  48. package/dist/agents/control_flow/path-analyzer.js +695 -0
  49. package/dist/agents/control_flow/path-analyzer.js.map +1 -0
  50. package/dist/agents/control_flow/pattern-validator.d.ts +132 -0
  51. package/dist/agents/control_flow/pattern-validator.d.ts.map +1 -0
  52. package/dist/agents/control_flow/pattern-validator.js +420 -0
  53. package/dist/agents/control_flow/pattern-validator.js.map +1 -0
  54. package/dist/agents/control_flow/timeout-regex.d.ts +144 -0
  55. package/dist/agents/control_flow/timeout-regex.d.ts.map +1 -0
  56. package/dist/agents/control_flow/timeout-regex.js +339 -0
  57. package/dist/agents/control_flow/timeout-regex.js.map +1 -0
  58. package/dist/agents/control_flow/types.d.ts +782 -0
  59. package/dist/agents/control_flow/types.d.ts.map +1 -0
  60. package/dist/agents/control_flow/types.js +428 -0
  61. package/dist/agents/control_flow/types.js.map +1 -0
  62. package/dist/agents/control_flow/vulnerability-detector.d.ts +85 -0
  63. package/dist/agents/control_flow/vulnerability-detector.d.ts.map +1 -0
  64. package/dist/agents/control_flow/vulnerability-detector.js +493 -0
  65. package/dist/agents/control_flow/vulnerability-detector.js.map +1 -0
  66. package/dist/agents/date-utils.d.ts +19 -0
  67. package/dist/agents/date-utils.d.ts.map +1 -0
  68. package/dist/agents/date-utils.js +29 -0
  69. package/dist/agents/date-utils.js.map +1 -0
  70. package/dist/agents/index.d.ts +25 -0
  71. package/dist/agents/index.d.ts.map +1 -0
  72. package/dist/agents/index.js +50 -0
  73. package/dist/agents/index.js.map +1 -0
  74. package/dist/agents/json-utils.d.ts +34 -0
  75. package/dist/agents/json-utils.d.ts.map +1 -0
  76. package/dist/agents/json-utils.js +62 -0
  77. package/dist/agents/json-utils.js.map +1 -0
  78. package/dist/agents/local_llm.d.ts +24 -0
  79. package/dist/agents/local_llm.d.ts.map +1 -0
  80. package/dist/agents/local_llm.js +566 -0
  81. package/dist/agents/local_llm.js.map +1 -0
  82. package/dist/agents/metadata.d.ts +57 -0
  83. package/dist/agents/metadata.d.ts.map +1 -0
  84. package/dist/agents/metadata.js +45 -0
  85. package/dist/agents/metadata.js.map +1 -0
  86. package/dist/agents/opencode.d.ts +18 -0
  87. package/dist/agents/opencode.d.ts.map +1 -0
  88. package/dist/agents/opencode.js +364 -0
  89. package/dist/agents/opencode.js.map +1 -0
  90. package/dist/agents/path-filter.d.ts +25 -0
  91. package/dist/agents/path-filter.d.ts.map +1 -0
  92. package/dist/agents/path-filter.js +43 -0
  93. package/dist/agents/path-filter.js.map +1 -0
  94. package/dist/agents/pr_agent.d.ts +3 -0
  95. package/dist/agents/pr_agent.d.ts.map +1 -0
  96. package/dist/agents/pr_agent.js +312 -0
  97. package/dist/agents/pr_agent.js.map +1 -0
  98. package/dist/agents/retry.d.ts +12 -0
  99. package/dist/agents/retry.d.ts.map +1 -0
  100. package/dist/agents/retry.js +65 -0
  101. package/dist/agents/retry.js.map +1 -0
  102. package/dist/agents/reviewdog.d.ts +24 -0
  103. package/dist/agents/reviewdog.d.ts.map +1 -0
  104. package/dist/agents/reviewdog.js +259 -0
  105. package/dist/agents/reviewdog.js.map +1 -0
  106. package/dist/agents/security.d.ts +49 -0
  107. package/dist/agents/security.d.ts.map +1 -0
  108. package/dist/agents/security.js +302 -0
  109. package/dist/agents/security.js.map +1 -0
  110. package/dist/agents/semgrep.d.ts +8 -0
  111. package/dist/agents/semgrep.d.ts.map +1 -0
  112. package/dist/agents/semgrep.js +157 -0
  113. package/dist/agents/semgrep.js.map +1 -0
  114. package/dist/agents/types.d.ts +450 -0
  115. package/dist/agents/types.d.ts.map +1 -0
  116. package/dist/agents/types.js +127 -0
  117. package/dist/agents/types.js.map +1 -0
  118. package/dist/budget.d.ts +59 -0
  119. package/dist/budget.d.ts.map +1 -0
  120. package/dist/budget.js +82 -0
  121. package/dist/budget.js.map +1 -0
  122. package/dist/cache/key.d.ts +49 -0
  123. package/dist/cache/key.d.ts.map +1 -0
  124. package/dist/cache/key.js +71 -0
  125. package/dist/cache/key.js.map +1 -0
  126. package/dist/cache/store.d.ts +47 -0
  127. package/dist/cache/store.d.ts.map +1 -0
  128. package/dist/cache/store.js +328 -0
  129. package/dist/cache/store.js.map +1 -0
  130. package/dist/cli/commands/check.d.ts +60 -0
  131. package/dist/cli/commands/check.d.ts.map +1 -0
  132. package/dist/cli/commands/check.js +163 -0
  133. package/dist/cli/commands/check.js.map +1 -0
  134. package/dist/cli/commands/index.d.ts +12 -0
  135. package/dist/cli/commands/index.d.ts.map +1 -0
  136. package/dist/cli/commands/index.js +12 -0
  137. package/dist/cli/commands/index.js.map +1 -0
  138. package/dist/cli/commands/local-review.d.ts +149 -0
  139. package/dist/cli/commands/local-review.d.ts.map +1 -0
  140. package/dist/cli/commands/local-review.js +755 -0
  141. package/dist/cli/commands/local-review.js.map +1 -0
  142. package/dist/cli/config-wizard.d.ts +87 -0
  143. package/dist/cli/config-wizard.d.ts.map +1 -0
  144. package/dist/cli/config-wizard.js +240 -0
  145. package/dist/cli/config-wizard.js.map +1 -0
  146. package/dist/cli/dependencies/catalog.d.ts +44 -0
  147. package/dist/cli/dependencies/catalog.d.ts.map +1 -0
  148. package/dist/cli/dependencies/catalog.js +89 -0
  149. package/dist/cli/dependencies/catalog.js.map +1 -0
  150. package/dist/cli/dependencies/checker.d.ts +42 -0
  151. package/dist/cli/dependencies/checker.d.ts.map +1 -0
  152. package/dist/cli/dependencies/checker.js +240 -0
  153. package/dist/cli/dependencies/checker.js.map +1 -0
  154. package/dist/cli/dependencies/index.d.ts +16 -0
  155. package/dist/cli/dependencies/index.d.ts.map +1 -0
  156. package/dist/cli/dependencies/index.js +16 -0
  157. package/dist/cli/dependencies/index.js.map +1 -0
  158. package/dist/cli/dependencies/messages.d.ts +58 -0
  159. package/dist/cli/dependencies/messages.d.ts.map +1 -0
  160. package/dist/cli/dependencies/messages.js +183 -0
  161. package/dist/cli/dependencies/messages.js.map +1 -0
  162. package/dist/cli/dependencies/platform.d.ts +25 -0
  163. package/dist/cli/dependencies/platform.d.ts.map +1 -0
  164. package/dist/cli/dependencies/platform.js +42 -0
  165. package/dist/cli/dependencies/platform.js.map +1 -0
  166. package/dist/cli/dependencies/schemas.d.ts +65 -0
  167. package/dist/cli/dependencies/schemas.d.ts.map +1 -0
  168. package/dist/cli/dependencies/schemas.js +42 -0
  169. package/dist/cli/dependencies/schemas.js.map +1 -0
  170. package/dist/cli/dependencies/types.d.ts +112 -0
  171. package/dist/cli/dependencies/types.d.ts.map +1 -0
  172. package/dist/cli/dependencies/types.js +6 -0
  173. package/dist/cli/dependencies/types.js.map +1 -0
  174. package/dist/cli/dependencies/version.d.ts +67 -0
  175. package/dist/cli/dependencies/version.d.ts.map +1 -0
  176. package/dist/cli/dependencies/version.js +125 -0
  177. package/dist/cli/dependencies/version.js.map +1 -0
  178. package/dist/cli/git-context.d.ts +105 -0
  179. package/dist/cli/git-context.d.ts.map +1 -0
  180. package/dist/cli/git-context.js +313 -0
  181. package/dist/cli/git-context.js.map +1 -0
  182. package/dist/cli/interactive-prompts.d.ts +126 -0
  183. package/dist/cli/interactive-prompts.d.ts.map +1 -0
  184. package/dist/cli/interactive-prompts.js +128 -0
  185. package/dist/cli/interactive-prompts.js.map +1 -0
  186. package/dist/cli/options/index.d.ts +7 -0
  187. package/dist/cli/options/index.d.ts.map +1 -0
  188. package/dist/cli/options/index.js +11 -0
  189. package/dist/cli/options/index.js.map +1 -0
  190. package/dist/cli/options/local-review-options.d.ts +221 -0
  191. package/dist/cli/options/local-review-options.d.ts.map +1 -0
  192. package/dist/cli/options/local-review-options.js +332 -0
  193. package/dist/cli/options/local-review-options.js.map +1 -0
  194. package/dist/cli/output/colors.d.ts +154 -0
  195. package/dist/cli/output/colors.d.ts.map +1 -0
  196. package/dist/cli/output/colors.js +255 -0
  197. package/dist/cli/output/colors.js.map +1 -0
  198. package/dist/cli/output/errors.d.ts +157 -0
  199. package/dist/cli/output/errors.d.ts.map +1 -0
  200. package/dist/cli/output/errors.js +266 -0
  201. package/dist/cli/output/errors.js.map +1 -0
  202. package/dist/cli/output/index.d.ts +12 -0
  203. package/dist/cli/output/index.d.ts.map +1 -0
  204. package/dist/cli/output/index.js +15 -0
  205. package/dist/cli/output/index.js.map +1 -0
  206. package/dist/cli/output/progress.d.ts +237 -0
  207. package/dist/cli/output/progress.d.ts.map +1 -0
  208. package/dist/cli/output/progress.js +405 -0
  209. package/dist/cli/output/progress.js.map +1 -0
  210. package/dist/cli/signals.d.ts +145 -0
  211. package/dist/cli/signals.d.ts.map +1 -0
  212. package/dist/cli/signals.js +223 -0
  213. package/dist/cli/signals.js.map +1 -0
  214. package/dist/cli/validation-report.d.ts +106 -0
  215. package/dist/cli/validation-report.d.ts.map +1 -0
  216. package/dist/cli/validation-report.js +108 -0
  217. package/dist/cli/validation-report.js.map +1 -0
  218. package/dist/config/index.d.ts +9 -0
  219. package/dist/config/index.d.ts.map +1 -0
  220. package/dist/config/index.js +12 -0
  221. package/dist/config/index.js.map +1 -0
  222. package/dist/config/mitigation-config.d.ts +94 -0
  223. package/dist/config/mitigation-config.d.ts.map +1 -0
  224. package/dist/config/mitigation-config.js +430 -0
  225. package/dist/config/mitigation-config.js.map +1 -0
  226. package/dist/config/providers.d.ts +118 -0
  227. package/dist/config/providers.d.ts.map +1 -0
  228. package/dist/config/providers.js +229 -0
  229. package/dist/config/providers.js.map +1 -0
  230. package/dist/config/schemas.d.ts +278 -0
  231. package/dist/config/schemas.d.ts.map +1 -0
  232. package/dist/config/schemas.js +111 -0
  233. package/dist/config/schemas.js.map +1 -0
  234. package/dist/config/zero-config.d.ts +126 -0
  235. package/dist/config/zero-config.d.ts.map +1 -0
  236. package/dist/config/zero-config.js +243 -0
  237. package/dist/config/zero-config.js.map +1 -0
  238. package/dist/config.d.ts +110 -0
  239. package/dist/config.d.ts.map +1 -0
  240. package/dist/config.js +302 -0
  241. package/dist/config.js.map +1 -0
  242. package/dist/diff.d.ts +224 -0
  243. package/dist/diff.d.ts.map +1 -0
  244. package/dist/diff.js +832 -0
  245. package/dist/diff.js.map +1 -0
  246. package/dist/git-validators.d.ts +106 -0
  247. package/dist/git-validators.d.ts.map +1 -0
  248. package/dist/git-validators.js +224 -0
  249. package/dist/git-validators.js.map +1 -0
  250. package/dist/main.d.ts +61 -0
  251. package/dist/main.d.ts.map +1 -0
  252. package/dist/main.js +704 -0
  253. package/dist/main.js.map +1 -0
  254. package/dist/phases/execute.d.ts +60 -0
  255. package/dist/phases/execute.d.ts.map +1 -0
  256. package/dist/phases/execute.js +168 -0
  257. package/dist/phases/execute.js.map +1 -0
  258. package/dist/phases/index.d.ts +9 -0
  259. package/dist/phases/index.d.ts.map +1 -0
  260. package/dist/phases/index.js +9 -0
  261. package/dist/phases/index.js.map +1 -0
  262. package/dist/phases/preflight.d.ts +40 -0
  263. package/dist/phases/preflight.d.ts.map +1 -0
  264. package/dist/phases/preflight.js +122 -0
  265. package/dist/phases/preflight.js.map +1 -0
  266. package/dist/phases/report.d.ts +51 -0
  267. package/dist/phases/report.d.ts.map +1 -0
  268. package/dist/phases/report.js +152 -0
  269. package/dist/phases/report.js.map +1 -0
  270. package/dist/policy.d.ts +33 -0
  271. package/dist/policy.d.ts.map +1 -0
  272. package/dist/policy.js +34 -0
  273. package/dist/policy.js.map +1 -0
  274. package/dist/preflight.d.ts +181 -0
  275. package/dist/preflight.d.ts.map +1 -0
  276. package/dist/preflight.js +627 -0
  277. package/dist/preflight.js.map +1 -0
  278. package/dist/report/ado.d.ts +53 -0
  279. package/dist/report/ado.d.ts.map +1 -0
  280. package/dist/report/ado.js +411 -0
  281. package/dist/report/ado.js.map +1 -0
  282. package/dist/report/agent-icons.d.ts +36 -0
  283. package/dist/report/agent-icons.d.ts.map +1 -0
  284. package/dist/report/agent-icons.js +46 -0
  285. package/dist/report/agent-icons.js.map +1 -0
  286. package/dist/report/base.d.ts +30 -0
  287. package/dist/report/base.d.ts.map +1 -0
  288. package/dist/report/base.js +64 -0
  289. package/dist/report/base.js.map +1 -0
  290. package/dist/report/formats.d.ts +206 -0
  291. package/dist/report/formats.d.ts.map +1 -0
  292. package/dist/report/formats.js +481 -0
  293. package/dist/report/formats.js.map +1 -0
  294. package/dist/report/github.d.ts +44 -0
  295. package/dist/report/github.d.ts.map +1 -0
  296. package/dist/report/github.js +409 -0
  297. package/dist/report/github.js.map +1 -0
  298. package/dist/report/line-resolver.d.ts +208 -0
  299. package/dist/report/line-resolver.d.ts.map +1 -0
  300. package/dist/report/line-resolver.js +578 -0
  301. package/dist/report/line-resolver.js.map +1 -0
  302. package/dist/report/resolution.d.ts +158 -0
  303. package/dist/report/resolution.d.ts.map +1 -0
  304. package/dist/report/resolution.js +272 -0
  305. package/dist/report/resolution.js.map +1 -0
  306. package/dist/report/sanitize.d.ts +32 -0
  307. package/dist/report/sanitize.d.ts.map +1 -0
  308. package/dist/report/sanitize.js +84 -0
  309. package/dist/report/sanitize.js.map +1 -0
  310. package/dist/report/terminal.d.ts +440 -0
  311. package/dist/report/terminal.d.ts.map +1 -0
  312. package/dist/report/terminal.js +840 -0
  313. package/dist/report/terminal.js.map +1 -0
  314. package/dist/reviewignore.d.ts +125 -0
  315. package/dist/reviewignore.d.ts.map +1 -0
  316. package/dist/reviewignore.js +335 -0
  317. package/dist/reviewignore.js.map +1 -0
  318. package/dist/security-logger.d.ts +178 -0
  319. package/dist/security-logger.d.ts.map +1 -0
  320. package/dist/security-logger.js +256 -0
  321. package/dist/security-logger.js.map +1 -0
  322. package/dist/telemetry/backends/console.d.ts +24 -0
  323. package/dist/telemetry/backends/console.d.ts.map +1 -0
  324. package/dist/telemetry/backends/console.js +54 -0
  325. package/dist/telemetry/backends/console.js.map +1 -0
  326. package/dist/telemetry/backends/jsonl.d.ts +31 -0
  327. package/dist/telemetry/backends/jsonl.d.ts.map +1 -0
  328. package/dist/telemetry/backends/jsonl.js +121 -0
  329. package/dist/telemetry/backends/jsonl.js.map +1 -0
  330. package/dist/telemetry/emitter.d.ts +43 -0
  331. package/dist/telemetry/emitter.d.ts.map +1 -0
  332. package/dist/telemetry/emitter.js +83 -0
  333. package/dist/telemetry/emitter.js.map +1 -0
  334. package/dist/telemetry/hook.d.ts +53 -0
  335. package/dist/telemetry/hook.d.ts.map +1 -0
  336. package/dist/telemetry/hook.js +118 -0
  337. package/dist/telemetry/hook.js.map +1 -0
  338. package/dist/telemetry/index.d.ts +58 -0
  339. package/dist/telemetry/index.d.ts.map +1 -0
  340. package/dist/telemetry/index.js +143 -0
  341. package/dist/telemetry/index.js.map +1 -0
  342. package/dist/telemetry/types.d.ts +139 -0
  343. package/dist/telemetry/types.d.ts.map +1 -0
  344. package/dist/telemetry/types.js +133 -0
  345. package/dist/telemetry/types.js.map +1 -0
  346. package/dist/trust.d.ts +65 -0
  347. package/dist/trust.d.ts.map +1 -0
  348. package/dist/trust.js +78 -0
  349. package/dist/trust.js.map +1 -0
  350. package/dist/types/assert-never.d.ts +30 -0
  351. package/dist/types/assert-never.d.ts.map +1 -0
  352. package/dist/types/assert-never.js +32 -0
  353. package/dist/types/assert-never.js.map +1 -0
  354. package/dist/types/branded.d.ts +172 -0
  355. package/dist/types/branded.d.ts.map +1 -0
  356. package/dist/types/branded.js +262 -0
  357. package/dist/types/branded.js.map +1 -0
  358. package/dist/types/errors.d.ts +320 -0
  359. package/dist/types/errors.d.ts.map +1 -0
  360. package/dist/types/errors.js +551 -0
  361. package/dist/types/errors.js.map +1 -0
  362. package/dist/types/index.d.ts +37 -0
  363. package/dist/types/index.d.ts.map +1 -0
  364. package/dist/types/index.js +77 -0
  365. package/dist/types/index.js.map +1 -0
  366. package/dist/types/result.d.ts +323 -0
  367. package/dist/types/result.d.ts.map +1 -0
  368. package/dist/types/result.js +423 -0
  369. package/dist/types/result.js.map +1 -0
  370. package/package.json +63 -0
@@ -0,0 +1,776 @@
1
+ /**
2
+ * Control Flow Graph Builder
3
+ *
4
+ * Constructs control flow graphs from TypeScript/JavaScript AST.
5
+ * Implements FR-001 (track data flow through control structures) and
6
+ * FR-002 (language-specific CFG construction).
7
+ */
8
+ import ts from 'typescript';
9
+ import { createBuilderContext, generateNodeId, createNode, addEdge, getLineNumber, getEndLineNumber, } from './cfg-types.js';
10
+ /**
11
+ * Build a control flow graph for a function
12
+ */
13
+ export function buildCFG(functionNode, sourceFile, filePath) {
14
+ const ctx = createBuilderContext(sourceFile, filePath);
15
+ // Get function name and check if async
16
+ const functionName = getFunctionName(functionNode);
17
+ const startLine = getLineNumber(functionNode, sourceFile);
18
+ const endLine = getEndLineNumber(functionNode, sourceFile);
19
+ const functionId = `${filePath}:${startLine}:${functionName}`;
20
+ const isAsync = isAsyncFunction(functionNode);
21
+ // Track await boundaries
22
+ const awaitBoundaries = [];
23
+ // Create entry node
24
+ const entryId = generateNodeId(ctx, 'entry');
25
+ const entryNode = createNode(entryId, 'entry', startLine, startLine);
26
+ ctx.nodes.set(entryId, entryNode);
27
+ ctx.entryNode = entryId;
28
+ // Process function body
29
+ const body = functionNode.body;
30
+ if (body) {
31
+ if (ts.isBlock(body)) {
32
+ const exitId = processBlock(ctx, body, entryId);
33
+ if (exitId) {
34
+ // Create implicit exit node if function doesn't explicitly return
35
+ const implicitExitId = generateNodeId(ctx, 'exit');
36
+ const implicitExit = createNode(implicitExitId, 'exit', endLine, endLine);
37
+ ctx.nodes.set(implicitExitId, implicitExit);
38
+ addEdge(ctx, exitId, implicitExitId, 'sequential');
39
+ ctx.exitNodes.push(implicitExitId);
40
+ }
41
+ }
42
+ else {
43
+ // Arrow function with expression body - check for await
44
+ const awaits = findAwaitExpressions(body, sourceFile);
45
+ if (awaits.length > 0 && isAsync && awaits[0]) {
46
+ // Create await node for the expression
47
+ const awaitExpr = awaits[0];
48
+ const awaitNodeId = generateNodeId(ctx, 'await');
49
+ const awaitNode = createNode(awaitNodeId, 'await', getLineNumber(awaitExpr, sourceFile), getEndLineNumber(awaitExpr, sourceFile));
50
+ awaitNode.awaitExpression = awaitExpr;
51
+ awaitNode.isAsyncBoundary = true;
52
+ ctx.nodes.set(awaitNodeId, awaitNode);
53
+ addEdge(ctx, entryId, awaitNodeId, 'await');
54
+ awaitBoundaries.push(awaitNodeId);
55
+ // Check for call expressions in the await
56
+ if (ts.isCallExpression(awaitExpr.expression)) {
57
+ findCallExpressions(ctx, awaitExpr.expression, awaitNodeId);
58
+ }
59
+ // Create exit node
60
+ const exitId = generateNodeId(ctx, 'exit');
61
+ const exitNode = createNode(exitId, 'exit', endLine, endLine);
62
+ ctx.nodes.set(exitId, exitNode);
63
+ addEdge(ctx, awaitNodeId, exitId, 'return');
64
+ ctx.exitNodes.push(exitId);
65
+ }
66
+ else {
67
+ // No await - regular expression body
68
+ const bodyNodeId = generateNodeId(ctx, 'body');
69
+ const bodyNode = createNode(bodyNodeId, 'basic', getLineNumber(body, sourceFile), getEndLineNumber(body, sourceFile));
70
+ bodyNode.statements.push(ts.factory.createReturnStatement(body));
71
+ ctx.nodes.set(bodyNodeId, bodyNode);
72
+ addEdge(ctx, entryId, bodyNodeId, 'sequential');
73
+ // Check for call expressions
74
+ findCallExpressions(ctx, body, bodyNodeId);
75
+ // Create exit node
76
+ const exitId = generateNodeId(ctx, 'exit');
77
+ const exitNode = createNode(exitId, 'exit', endLine, endLine);
78
+ ctx.nodes.set(exitId, exitNode);
79
+ addEdge(ctx, bodyNodeId, exitId, 'return');
80
+ ctx.exitNodes.push(exitId);
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ // No body (declaration only)
86
+ const exitId = generateNodeId(ctx, 'exit');
87
+ const exitNode = createNode(exitId, 'exit', endLine, endLine);
88
+ ctx.nodes.set(exitId, exitNode);
89
+ addEdge(ctx, entryId, exitId, 'sequential');
90
+ ctx.exitNodes.push(exitId);
91
+ }
92
+ // Collect all await boundaries from nodes
93
+ for (const [nodeId, node] of ctx.nodes) {
94
+ if (node.type === 'await' && !awaitBoundaries.includes(nodeId)) {
95
+ awaitBoundaries.push(nodeId);
96
+ }
97
+ }
98
+ return {
99
+ functionId,
100
+ functionName,
101
+ filePath,
102
+ startLine,
103
+ endLine,
104
+ nodes: ctx.nodes,
105
+ edges: ctx.edges,
106
+ entryNode: ctx.entryNode ?? entryId,
107
+ exitNodes: ctx.exitNodes,
108
+ callSites: ctx.callSites,
109
+ functionNode,
110
+ isAsync,
111
+ awaitBoundaries,
112
+ };
113
+ }
114
+ /**
115
+ * Process a block of statements, returning the last node ID or null if all paths exit
116
+ */
117
+ function processBlock(ctx, block, entryPoint) {
118
+ let currentNode = entryPoint;
119
+ for (const statement of block.statements) {
120
+ const result = processStatement(ctx, statement, currentNode);
121
+ if (result === null) {
122
+ // All paths from this statement exit (return/throw)
123
+ return null;
124
+ }
125
+ currentNode = result;
126
+ }
127
+ return currentNode;
128
+ }
129
+ /**
130
+ * Process a single statement, returning the exit node ID or null if it exits
131
+ */
132
+ function processStatement(ctx, statement, entryPoint) {
133
+ const sourceFile = ctx.sourceFile;
134
+ const lineStart = getLineNumber(statement, sourceFile);
135
+ const lineEnd = getEndLineNumber(statement, sourceFile);
136
+ // Handle different statement types
137
+ if (ts.isIfStatement(statement)) {
138
+ return processIfStatement(ctx, statement, entryPoint);
139
+ }
140
+ if (ts.isSwitchStatement(statement)) {
141
+ return processSwitchStatement(ctx, statement, entryPoint);
142
+ }
143
+ if (ts.isForStatement(statement) ||
144
+ ts.isForInStatement(statement) ||
145
+ ts.isForOfStatement(statement)) {
146
+ return processForLoop(ctx, statement, entryPoint);
147
+ }
148
+ if (ts.isWhileStatement(statement)) {
149
+ return processWhileLoop(ctx, statement, entryPoint);
150
+ }
151
+ if (ts.isDoStatement(statement)) {
152
+ return processDoWhileLoop(ctx, statement, entryPoint);
153
+ }
154
+ if (ts.isTryStatement(statement)) {
155
+ return processTryStatement(ctx, statement, entryPoint);
156
+ }
157
+ if (ts.isReturnStatement(statement)) {
158
+ return processReturnStatement(ctx, statement, entryPoint);
159
+ }
160
+ if (ts.isThrowStatement(statement)) {
161
+ return processThrowStatement(ctx, statement, entryPoint);
162
+ }
163
+ if (ts.isBreakStatement(statement)) {
164
+ return processBreakStatement(ctx, statement, entryPoint);
165
+ }
166
+ if (ts.isContinueStatement(statement)) {
167
+ return processContinueStatement(ctx, statement, entryPoint);
168
+ }
169
+ if (ts.isBlock(statement)) {
170
+ return processBlock(ctx, statement, entryPoint);
171
+ }
172
+ // Check for await expressions in the statement
173
+ const awaits = findAwaitExpressions(statement, sourceFile);
174
+ if (awaits.length > 0) {
175
+ // Statement contains await - create await node(s)
176
+ return processAwaitStatement(ctx, statement, entryPoint, awaits);
177
+ }
178
+ // Default: basic block statement
179
+ const nodeId = generateNodeId(ctx, 'basic');
180
+ const node = createNode(nodeId, 'basic', lineStart, lineEnd);
181
+ node.statements.push(statement);
182
+ ctx.nodes.set(nodeId, node);
183
+ addEdge(ctx, entryPoint, nodeId, 'sequential');
184
+ // Find call expressions in this statement
185
+ findCallExpressions(ctx, statement, nodeId);
186
+ return nodeId;
187
+ }
188
+ /**
189
+ * Process a statement containing await expressions.
190
+ * Creates await nodes to represent async boundaries.
191
+ */
192
+ function processAwaitStatement(ctx, statement, entryPoint, awaits) {
193
+ const sourceFile = ctx.sourceFile;
194
+ const firstAwait = awaits[0];
195
+ // Guard against empty awaits array (should not happen based on call sites)
196
+ if (!firstAwait) {
197
+ // Fall back to basic node
198
+ const nodeId = generateNodeId(ctx, 'basic');
199
+ const node = createNode(nodeId, 'basic', getLineNumber(statement, sourceFile), getEndLineNumber(statement, sourceFile));
200
+ node.statements.push(statement);
201
+ ctx.nodes.set(nodeId, node);
202
+ addEdge(ctx, entryPoint, nodeId, 'sequential');
203
+ return nodeId;
204
+ }
205
+ // For simplicity, we create a single await node for the statement
206
+ // In a more sophisticated implementation, we could split around each await
207
+ const lineStart = getLineNumber(statement, sourceFile);
208
+ const lineEnd = getEndLineNumber(statement, sourceFile);
209
+ // Create the await node
210
+ const awaitNodeId = generateNodeId(ctx, 'await');
211
+ const awaitNode = createNode(awaitNodeId, 'await', lineStart, lineEnd);
212
+ awaitNode.statements.push(statement);
213
+ awaitNode.awaitExpression = firstAwait;
214
+ awaitNode.isAsyncBoundary = true;
215
+ ctx.nodes.set(awaitNodeId, awaitNode);
216
+ addEdge(ctx, entryPoint, awaitNodeId, 'await');
217
+ // Find call expressions in the await (the awaited expression)
218
+ for (const awaitExpr of awaits) {
219
+ if (ts.isCallExpression(awaitExpr.expression)) {
220
+ findCallExpressions(ctx, awaitExpr.expression, awaitNodeId);
221
+ }
222
+ }
223
+ // Also find other call expressions in the statement
224
+ findCallExpressions(ctx, statement, awaitNodeId);
225
+ return awaitNodeId;
226
+ }
227
+ /**
228
+ * Process an if statement
229
+ */
230
+ function processIfStatement(ctx, stmt, entryPoint) {
231
+ const sourceFile = ctx.sourceFile;
232
+ const lineStart = getLineNumber(stmt, sourceFile);
233
+ // Create branch node for the condition
234
+ const branchId = generateNodeId(ctx, 'branch');
235
+ const branchNode = createNode(branchId, 'branch', lineStart, lineStart);
236
+ ctx.nodes.set(branchId, branchNode);
237
+ addEdge(ctx, entryPoint, branchId, 'sequential');
238
+ // Find call expressions in condition
239
+ findCallExpressions(ctx, stmt.expression, branchId);
240
+ // Create merge node for after the if
241
+ const mergeId = generateNodeId(ctx, 'merge');
242
+ const mergeNode = createNode(mergeId, 'merge', getEndLineNumber(stmt, sourceFile), getEndLineNumber(stmt, sourceFile));
243
+ ctx.nodes.set(mergeId, mergeNode);
244
+ // Process then branch
245
+ const thenEntry = generateNodeId(ctx, 'then');
246
+ const thenNode = createNode(thenEntry, 'basic', getLineNumber(stmt.thenStatement, sourceFile), getLineNumber(stmt.thenStatement, sourceFile));
247
+ ctx.nodes.set(thenEntry, thenNode);
248
+ addEdge(ctx, branchId, thenEntry, 'branch_true', stmt.expression, true);
249
+ let thenExit;
250
+ if (ts.isBlock(stmt.thenStatement)) {
251
+ thenExit = processBlock(ctx, stmt.thenStatement, thenEntry);
252
+ }
253
+ else {
254
+ thenExit = processStatement(ctx, stmt.thenStatement, thenEntry);
255
+ }
256
+ if (thenExit !== null) {
257
+ addEdge(ctx, thenExit, mergeId, 'sequential');
258
+ }
259
+ // Process else branch
260
+ let elseExit = null;
261
+ if (stmt.elseStatement) {
262
+ const elseEntry = generateNodeId(ctx, 'else');
263
+ const elseNode = createNode(elseEntry, 'basic', getLineNumber(stmt.elseStatement, sourceFile), getLineNumber(stmt.elseStatement, sourceFile));
264
+ ctx.nodes.set(elseEntry, elseNode);
265
+ addEdge(ctx, branchId, elseEntry, 'branch_false', stmt.expression, false);
266
+ if (ts.isBlock(stmt.elseStatement)) {
267
+ elseExit = processBlock(ctx, stmt.elseStatement, elseEntry);
268
+ }
269
+ else {
270
+ elseExit = processStatement(ctx, stmt.elseStatement, elseEntry);
271
+ }
272
+ if (elseExit !== null) {
273
+ addEdge(ctx, elseExit, mergeId, 'sequential');
274
+ }
275
+ }
276
+ else {
277
+ // No else branch - false condition goes directly to merge
278
+ addEdge(ctx, branchId, mergeId, 'branch_false', stmt.expression, false);
279
+ elseExit = mergeId;
280
+ }
281
+ // If both branches exit, the merge is unreachable
282
+ if (thenExit === null && (stmt.elseStatement ? elseExit === null : false)) {
283
+ return null;
284
+ }
285
+ return mergeId;
286
+ }
287
+ /**
288
+ * Process a switch statement
289
+ */
290
+ function processSwitchStatement(ctx, stmt, entryPoint) {
291
+ const sourceFile = ctx.sourceFile;
292
+ const lineStart = getLineNumber(stmt, sourceFile);
293
+ // Create branch node for the switch expression
294
+ const branchId = generateNodeId(ctx, 'switch');
295
+ const branchNode = createNode(branchId, 'branch', lineStart, lineStart);
296
+ ctx.nodes.set(branchId, branchNode);
297
+ addEdge(ctx, entryPoint, branchId, 'sequential');
298
+ findCallExpressions(ctx, stmt.expression, branchId);
299
+ // Create merge node for after switch
300
+ const mergeId = generateNodeId(ctx, 'merge');
301
+ const mergeNode = createNode(mergeId, 'merge', getEndLineNumber(stmt, sourceFile), getEndLineNumber(stmt, sourceFile));
302
+ ctx.nodes.set(mergeId, mergeNode);
303
+ // Push break target
304
+ ctx.breakTargets.push(mergeId);
305
+ let hasDefault = false;
306
+ let allCasesExit = true;
307
+ let fallthrough = null;
308
+ for (const clause of stmt.caseBlock.clauses) {
309
+ const caseId = generateNodeId(ctx, ts.isDefaultClause(clause) ? 'default' : 'case');
310
+ const caseNode = createNode(caseId, 'basic', getLineNumber(clause, sourceFile), getLineNumber(clause, sourceFile));
311
+ ctx.nodes.set(caseId, caseNode);
312
+ if (ts.isDefaultClause(clause)) {
313
+ hasDefault = true;
314
+ }
315
+ // Edge from branch to case (or fallthrough from previous case)
316
+ if (fallthrough) {
317
+ addEdge(ctx, fallthrough, caseId, 'sequential');
318
+ }
319
+ addEdge(ctx, branchId, caseId, ts.isDefaultClause(clause) ? 'branch_false' : 'branch_true');
320
+ // Process case statements
321
+ let caseExit = caseId;
322
+ for (const statement of clause.statements) {
323
+ if (caseExit === null)
324
+ break;
325
+ caseExit = processStatement(ctx, statement, caseExit);
326
+ }
327
+ if (caseExit !== null) {
328
+ fallthrough = caseExit;
329
+ allCasesExit = false;
330
+ }
331
+ else {
332
+ fallthrough = null;
333
+ }
334
+ }
335
+ // Connect last fallthrough to merge
336
+ if (fallthrough) {
337
+ addEdge(ctx, fallthrough, mergeId, 'sequential');
338
+ }
339
+ // If no default case, switch can fall through without matching
340
+ if (!hasDefault) {
341
+ addEdge(ctx, branchId, mergeId, 'branch_false');
342
+ allCasesExit = false;
343
+ }
344
+ ctx.breakTargets.pop();
345
+ return allCasesExit ? null : mergeId;
346
+ }
347
+ /**
348
+ * Process a for loop (for, for-in, for-of)
349
+ */
350
+ function processForLoop(ctx, stmt, entryPoint) {
351
+ const sourceFile = ctx.sourceFile;
352
+ const lineStart = getLineNumber(stmt, sourceFile);
353
+ // Create loop header node
354
+ const headerId = generateNodeId(ctx, 'loop_header');
355
+ const headerNode = createNode(headerId, 'loop_header', lineStart, lineStart);
356
+ ctx.nodes.set(headerId, headerNode);
357
+ // For standard for loop, process initializer first
358
+ if (ts.isForStatement(stmt) && stmt.initializer) {
359
+ const initId = generateNodeId(ctx, 'for_init');
360
+ const initNode = createNode(initId, 'basic', lineStart, lineStart);
361
+ if (ts.isVariableDeclarationList(stmt.initializer)) {
362
+ // Convert to statement for storage
363
+ initNode.statements.push(ts.factory.createVariableStatement(undefined, stmt.initializer));
364
+ }
365
+ ctx.nodes.set(initId, initNode);
366
+ addEdge(ctx, entryPoint, initId, 'sequential');
367
+ addEdge(ctx, initId, headerId, 'sequential');
368
+ }
369
+ else {
370
+ addEdge(ctx, entryPoint, headerId, 'sequential');
371
+ }
372
+ // Create exit node for after loop
373
+ const exitId = generateNodeId(ctx, 'merge');
374
+ const exitNode = createNode(exitId, 'merge', getEndLineNumber(stmt, sourceFile), getEndLineNumber(stmt, sourceFile));
375
+ ctx.nodes.set(exitId, exitNode);
376
+ // Edge from header to exit (loop condition false)
377
+ addEdge(ctx, headerId, exitId, 'loop_exit');
378
+ // Push break/continue targets
379
+ ctx.breakTargets.push(exitId);
380
+ ctx.continueTargets.push(headerId);
381
+ // Create loop body node
382
+ const bodyId = generateNodeId(ctx, 'loop_body');
383
+ const bodyNode = createNode(bodyId, 'loop_body', getLineNumber(stmt.statement, sourceFile), getLineNumber(stmt.statement, sourceFile));
384
+ ctx.nodes.set(bodyId, bodyNode);
385
+ addEdge(ctx, headerId, bodyId, 'branch_true');
386
+ // Process body
387
+ let bodyExit;
388
+ if (ts.isBlock(stmt.statement)) {
389
+ bodyExit = processBlock(ctx, stmt.statement, bodyId);
390
+ }
391
+ else {
392
+ bodyExit = processStatement(ctx, stmt.statement, bodyId);
393
+ }
394
+ // For standard for loop, process incrementor
395
+ if (ts.isForStatement(stmt) && stmt.incrementor && bodyExit !== null) {
396
+ const incId = generateNodeId(ctx, 'for_inc');
397
+ const incNode = createNode(incId, 'basic', getLineNumber(stmt.incrementor, sourceFile), getLineNumber(stmt.incrementor, sourceFile));
398
+ ctx.nodes.set(incId, incNode);
399
+ addEdge(ctx, bodyExit, incId, 'sequential');
400
+ addEdge(ctx, incId, headerId, 'loop_back');
401
+ }
402
+ else if (bodyExit !== null) {
403
+ addEdge(ctx, bodyExit, headerId, 'loop_back');
404
+ }
405
+ ctx.breakTargets.pop();
406
+ ctx.continueTargets.pop();
407
+ return exitId;
408
+ }
409
+ /**
410
+ * Process a while loop
411
+ */
412
+ function processWhileLoop(ctx, stmt, entryPoint) {
413
+ const sourceFile = ctx.sourceFile;
414
+ const lineStart = getLineNumber(stmt, sourceFile);
415
+ // Create loop header
416
+ const headerId = generateNodeId(ctx, 'loop_header');
417
+ const headerNode = createNode(headerId, 'loop_header', lineStart, lineStart);
418
+ ctx.nodes.set(headerId, headerNode);
419
+ addEdge(ctx, entryPoint, headerId, 'sequential');
420
+ findCallExpressions(ctx, stmt.expression, headerId);
421
+ // Create exit node
422
+ const exitId = generateNodeId(ctx, 'merge');
423
+ const exitNode = createNode(exitId, 'merge', getEndLineNumber(stmt, sourceFile), getEndLineNumber(stmt, sourceFile));
424
+ ctx.nodes.set(exitId, exitNode);
425
+ addEdge(ctx, headerId, exitId, 'loop_exit', stmt.expression, false);
426
+ // Push targets
427
+ ctx.breakTargets.push(exitId);
428
+ ctx.continueTargets.push(headerId);
429
+ // Create body entry
430
+ const bodyId = generateNodeId(ctx, 'loop_body');
431
+ const bodyNode = createNode(bodyId, 'loop_body', getLineNumber(stmt.statement, sourceFile), getLineNumber(stmt.statement, sourceFile));
432
+ ctx.nodes.set(bodyId, bodyNode);
433
+ addEdge(ctx, headerId, bodyId, 'branch_true', stmt.expression, true);
434
+ // Process body
435
+ let bodyExit;
436
+ if (ts.isBlock(stmt.statement)) {
437
+ bodyExit = processBlock(ctx, stmt.statement, bodyId);
438
+ }
439
+ else {
440
+ bodyExit = processStatement(ctx, stmt.statement, bodyId);
441
+ }
442
+ if (bodyExit !== null) {
443
+ addEdge(ctx, bodyExit, headerId, 'loop_back');
444
+ }
445
+ ctx.breakTargets.pop();
446
+ ctx.continueTargets.pop();
447
+ return exitId;
448
+ }
449
+ /**
450
+ * Process a do-while loop
451
+ */
452
+ function processDoWhileLoop(ctx, stmt, entryPoint) {
453
+ const sourceFile = ctx.sourceFile;
454
+ const lineStart = getLineNumber(stmt, sourceFile);
455
+ // Create body entry (executed first in do-while)
456
+ const bodyId = generateNodeId(ctx, 'loop_body');
457
+ const bodyNode = createNode(bodyId, 'loop_body', lineStart, lineStart);
458
+ ctx.nodes.set(bodyId, bodyNode);
459
+ addEdge(ctx, entryPoint, bodyId, 'sequential');
460
+ // Create loop header (condition check at end)
461
+ const headerId = generateNodeId(ctx, 'loop_header');
462
+ const headerNode = createNode(headerId, 'loop_header', getLineNumber(stmt.expression, sourceFile), getEndLineNumber(stmt.expression, sourceFile));
463
+ ctx.nodes.set(headerId, headerNode);
464
+ findCallExpressions(ctx, stmt.expression, headerId);
465
+ // Create exit node
466
+ const exitId = generateNodeId(ctx, 'merge');
467
+ const exitNode = createNode(exitId, 'merge', getEndLineNumber(stmt, sourceFile), getEndLineNumber(stmt, sourceFile));
468
+ ctx.nodes.set(exitId, exitNode);
469
+ // Push targets
470
+ ctx.breakTargets.push(exitId);
471
+ ctx.continueTargets.push(headerId);
472
+ // Process body
473
+ let bodyExit;
474
+ if (ts.isBlock(stmt.statement)) {
475
+ bodyExit = processBlock(ctx, stmt.statement, bodyId);
476
+ }
477
+ else {
478
+ bodyExit = processStatement(ctx, stmt.statement, bodyId);
479
+ }
480
+ if (bodyExit !== null) {
481
+ addEdge(ctx, bodyExit, headerId, 'sequential');
482
+ }
483
+ // Loop back or exit from header
484
+ addEdge(ctx, headerId, bodyId, 'loop_back', stmt.expression, true);
485
+ addEdge(ctx, headerId, exitId, 'loop_exit', stmt.expression, false);
486
+ ctx.breakTargets.pop();
487
+ ctx.continueTargets.pop();
488
+ return exitId;
489
+ }
490
+ /**
491
+ * Process a try statement
492
+ */
493
+ function processTryStatement(ctx, stmt, entryPoint) {
494
+ const sourceFile = ctx.sourceFile;
495
+ const lineEnd = getEndLineNumber(stmt, sourceFile);
496
+ // Create merge node for after try/catch/finally
497
+ const mergeId = generateNodeId(ctx, 'merge');
498
+ const mergeNode = createNode(mergeId, 'merge', lineEnd, lineEnd);
499
+ ctx.nodes.set(mergeId, mergeNode);
500
+ // Process try block
501
+ const tryExit = processBlock(ctx, stmt.tryBlock, entryPoint);
502
+ // Process catch clause
503
+ let catchExit = null;
504
+ if (stmt.catchClause) {
505
+ const catchId = generateNodeId(ctx, 'catch');
506
+ const catchNode = createNode(catchId, 'basic', getLineNumber(stmt.catchClause, sourceFile), getLineNumber(stmt.catchClause, sourceFile));
507
+ ctx.nodes.set(catchId, catchNode);
508
+ // Exception edge from try entry to catch
509
+ addEdge(ctx, entryPoint, catchId, 'exception');
510
+ catchExit = processBlock(ctx, stmt.catchClause.block, catchId);
511
+ }
512
+ // Process finally clause
513
+ if (stmt.finallyBlock) {
514
+ const finallyId = generateNodeId(ctx, 'finally');
515
+ const finallyNode = createNode(finallyId, 'basic', getLineNumber(stmt.finallyBlock, sourceFile), getLineNumber(stmt.finallyBlock, sourceFile));
516
+ ctx.nodes.set(finallyId, finallyNode);
517
+ // Connect try exit to finally
518
+ if (tryExit !== null) {
519
+ addEdge(ctx, tryExit, finallyId, 'sequential');
520
+ }
521
+ // Connect catch exit to finally
522
+ if (catchExit !== null) {
523
+ addEdge(ctx, catchExit, finallyId, 'sequential');
524
+ }
525
+ const finallyExit = processBlock(ctx, stmt.finallyBlock, finallyId);
526
+ if (finallyExit !== null) {
527
+ addEdge(ctx, finallyExit, mergeId, 'sequential');
528
+ }
529
+ return finallyExit !== null ? mergeId : null;
530
+ }
531
+ else {
532
+ // No finally - connect try/catch exits to merge
533
+ if (tryExit !== null) {
534
+ addEdge(ctx, tryExit, mergeId, 'sequential');
535
+ }
536
+ if (catchExit !== null) {
537
+ addEdge(ctx, catchExit, mergeId, 'sequential');
538
+ }
539
+ return tryExit !== null || catchExit !== null ? mergeId : null;
540
+ }
541
+ }
542
+ /**
543
+ * Process a return statement
544
+ */
545
+ function processReturnStatement(ctx, stmt, entryPoint) {
546
+ const sourceFile = ctx.sourceFile;
547
+ const lineStart = getLineNumber(stmt, sourceFile);
548
+ const lineEnd = getEndLineNumber(stmt, sourceFile);
549
+ // Check for await in the return expression (e.g., return await foo())
550
+ if (stmt.expression) {
551
+ const awaits = findAwaitExpressions(stmt.expression, sourceFile);
552
+ if (awaits.length > 0) {
553
+ // Create await node before exit
554
+ const awaitNodeId = generateNodeId(ctx, 'await');
555
+ const awaitNode = createNode(awaitNodeId, 'await', lineStart, lineEnd);
556
+ awaitNode.statements.push(stmt);
557
+ awaitNode.awaitExpression = awaits[0];
558
+ awaitNode.isAsyncBoundary = true;
559
+ ctx.nodes.set(awaitNodeId, awaitNode);
560
+ addEdge(ctx, entryPoint, awaitNodeId, 'await');
561
+ // Find call expressions in the await
562
+ for (const awaitExpr of awaits) {
563
+ if (ts.isCallExpression(awaitExpr.expression)) {
564
+ findCallExpressions(ctx, awaitExpr.expression, awaitNodeId);
565
+ }
566
+ }
567
+ // Create exit node connected from await
568
+ const exitId = generateNodeId(ctx, 'exit');
569
+ const exitNode = createNode(exitId, 'exit', lineStart, lineEnd);
570
+ ctx.nodes.set(exitId, exitNode);
571
+ addEdge(ctx, awaitNodeId, exitId, 'return');
572
+ ctx.exitNodes.push(exitId);
573
+ return null;
574
+ }
575
+ }
576
+ // No await - normal return processing
577
+ const exitId = generateNodeId(ctx, 'exit');
578
+ const exitNode = createNode(exitId, 'exit', lineStart, lineEnd);
579
+ exitNode.statements.push(stmt);
580
+ ctx.nodes.set(exitId, exitNode);
581
+ addEdge(ctx, entryPoint, exitId, 'return');
582
+ ctx.exitNodes.push(exitId);
583
+ if (stmt.expression) {
584
+ findCallExpressions(ctx, stmt.expression, exitId);
585
+ }
586
+ return null; // Return always exits
587
+ }
588
+ /**
589
+ * Process a throw statement
590
+ */
591
+ function processThrowStatement(ctx, stmt, entryPoint) {
592
+ const sourceFile = ctx.sourceFile;
593
+ const lineStart = getLineNumber(stmt, sourceFile);
594
+ const lineEnd = getEndLineNumber(stmt, sourceFile);
595
+ // Create throw node
596
+ const throwId = generateNodeId(ctx, 'throw');
597
+ const throwNode = createNode(throwId, 'throw', lineStart, lineEnd);
598
+ throwNode.statements.push(stmt);
599
+ ctx.nodes.set(throwId, throwNode);
600
+ addEdge(ctx, entryPoint, throwId, 'exception');
601
+ ctx.exitNodes.push(throwId);
602
+ findCallExpressions(ctx, stmt.expression, throwId);
603
+ return null; // Throw always exits
604
+ }
605
+ /**
606
+ * Process a break statement
607
+ */
608
+ function processBreakStatement(ctx, stmt, entryPoint) {
609
+ const target = ctx.breakTargets[ctx.breakTargets.length - 1];
610
+ if (target) {
611
+ addEdge(ctx, entryPoint, target, 'sequential');
612
+ }
613
+ return null;
614
+ }
615
+ /**
616
+ * Process a continue statement
617
+ */
618
+ function processContinueStatement(ctx, stmt, entryPoint) {
619
+ const target = ctx.continueTargets[ctx.continueTargets.length - 1];
620
+ if (target) {
621
+ addEdge(ctx, entryPoint, target, 'loop_back');
622
+ }
623
+ return null;
624
+ }
625
+ /**
626
+ * Find and record call expressions in a node
627
+ */
628
+ function findCallExpressions(ctx, node, cfgNodeId) {
629
+ const sourceFile = ctx.sourceFile;
630
+ function visit(n) {
631
+ if (ts.isCallExpression(n)) {
632
+ const callSite = createCallSite(n, cfgNodeId, sourceFile, ctx.filePath);
633
+ ctx.callSites.push(callSite);
634
+ }
635
+ ts.forEachChild(n, visit);
636
+ }
637
+ visit(node);
638
+ }
639
+ /**
640
+ * Find await expressions in a node and record them.
641
+ * Returns array of await expression locations for creating await nodes.
642
+ */
643
+ function findAwaitExpressions(node, _sourceFile) {
644
+ const awaits = [];
645
+ function visit(n) {
646
+ if (ts.isAwaitExpression(n)) {
647
+ awaits.push(n);
648
+ }
649
+ ts.forEachChild(n, visit);
650
+ }
651
+ visit(node);
652
+ return awaits;
653
+ }
654
+ /**
655
+ * Check if a function is async
656
+ */
657
+ function isAsyncFunction(node) {
658
+ if (!node.modifiers)
659
+ return false;
660
+ return node.modifiers.some((mod) => mod.kind === ts.SyntaxKind.AsyncKeyword);
661
+ }
662
+ /**
663
+ * Create a call site record
664
+ */
665
+ function createCallSite(call, nodeId, sourceFile, filePath) {
666
+ const location = {
667
+ file: filePath,
668
+ line: getLineNumber(call, sourceFile),
669
+ column: call.getStart(sourceFile) -
670
+ sourceFile.getLineAndCharacterOfPosition(call.getStart(sourceFile)).character,
671
+ endLine: getEndLineNumber(call, sourceFile),
672
+ };
673
+ let calleeName = '<unknown>';
674
+ let isDynamic = false;
675
+ if (ts.isIdentifier(call.expression)) {
676
+ calleeName = call.expression.text;
677
+ }
678
+ else if (ts.isPropertyAccessExpression(call.expression)) {
679
+ calleeName = call.expression.name.text;
680
+ }
681
+ else if (ts.isElementAccessExpression(call.expression)) {
682
+ isDynamic = true;
683
+ calleeName = '<computed>';
684
+ }
685
+ else {
686
+ isDynamic = true;
687
+ }
688
+ return {
689
+ nodeId,
690
+ calleeName,
691
+ calleeFile: undefined, // Resolved later during inter-procedural analysis
692
+ isResolved: false,
693
+ isDynamic,
694
+ location,
695
+ callExpression: call,
696
+ };
697
+ }
698
+ /**
699
+ * Get the name of a function
700
+ */
701
+ function getFunctionName(node) {
702
+ if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
703
+ return node.name?.getText() ?? '<anonymous>';
704
+ }
705
+ if (ts.isFunctionExpression(node)) {
706
+ return node.name?.text ?? '<anonymous>';
707
+ }
708
+ if (ts.isArrowFunction(node)) {
709
+ // Try to get name from parent variable declaration
710
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
711
+ return node.parent.name.text;
712
+ }
713
+ return '<arrow>';
714
+ }
715
+ if (ts.isConstructorDeclaration(node)) {
716
+ return 'constructor';
717
+ }
718
+ if (ts.isGetAccessorDeclaration(node)) {
719
+ return `get ${node.name.getText()}`;
720
+ }
721
+ if (ts.isSetAccessorDeclaration(node)) {
722
+ return `set ${node.name.getText()}`;
723
+ }
724
+ return '<anonymous>';
725
+ }
726
+ /**
727
+ * Find all functions in a source file
728
+ */
729
+ export function findFunctions(sourceFile) {
730
+ const functions = [];
731
+ function visit(node) {
732
+ if (ts.isFunctionDeclaration(node) ||
733
+ ts.isFunctionExpression(node) ||
734
+ ts.isArrowFunction(node) ||
735
+ ts.isMethodDeclaration(node) ||
736
+ ts.isConstructorDeclaration(node) ||
737
+ ts.isGetAccessorDeclaration(node) ||
738
+ ts.isSetAccessorDeclaration(node)) {
739
+ functions.push(node);
740
+ }
741
+ ts.forEachChild(node, visit);
742
+ }
743
+ visit(sourceFile);
744
+ return functions;
745
+ }
746
+ /**
747
+ * Parse a source file
748
+ */
749
+ export function parseSourceFile(content, filePath, scriptKind) {
750
+ // Determine script kind from file extension if not provided
751
+ if (scriptKind === undefined) {
752
+ if (filePath.endsWith('.tsx')) {
753
+ scriptKind = ts.ScriptKind.TSX;
754
+ }
755
+ else if (filePath.endsWith('.ts')) {
756
+ scriptKind = ts.ScriptKind.TS;
757
+ }
758
+ else if (filePath.endsWith('.jsx')) {
759
+ scriptKind = ts.ScriptKind.JSX;
760
+ }
761
+ else {
762
+ scriptKind = ts.ScriptKind.JS;
763
+ }
764
+ }
765
+ return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, // setParentNodes
766
+ scriptKind);
767
+ }
768
+ /**
769
+ * Build CFGs for all functions in a source file
770
+ */
771
+ export function buildAllCFGs(content, filePath) {
772
+ const sourceFile = parseSourceFile(content, filePath);
773
+ const functions = findFunctions(sourceFile);
774
+ return functions.map((fn) => buildCFG(fn, sourceFile, filePath));
775
+ }
776
+ //# sourceMappingURL=cfg-builder.js.map