@shortlink-org/portolan 0.1.0 → 0.2.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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -1,320 +0,0 @@
1
- package gohttp
2
-
3
- import (
4
- "go/ast"
5
- "sort"
6
- "strings"
7
- )
8
-
9
- // rootFlows discovers execution roots that do not need the provider-factory
10
- // join used by endpointFlows. Every root still needs an outbound FlowGroup;
11
- // registering a handler alone is not enough to manufacture a flow.
12
- func (s *scanner) rootFlows(groups []FlowGroup, providerEndpoints []EndpointFlow) []RootFlow {
13
- byFunction := map[string]FlowGroup{}
14
- for _, group := range groups {
15
- byFunction[group.Function] = group
16
- }
17
- providerRoutes := map[string]bool{}
18
- for _, endpoint := range providerEndpoints {
19
- providerRoutes[routeKey(endpoint.Method, endpoint.Path)] = true
20
- }
21
-
22
- var out []RootFlow
23
- rootedHandlers := map[string]bool{}
24
- rootedRoutes := map[string]bool{}
25
- registrations := s.handlerRegistrations()
26
- for _, owner := range s.sortedFunctions() {
27
- ast.Inspect(owner.fn.Body, func(node ast.Node) bool {
28
- call, ok := node.(*ast.CallExpr)
29
- if !ok {
30
- return true
31
- }
32
- method, path, handler, ok := s.routeCall(owner, call)
33
- if !ok || providerRoutes[routeKey(method, path)] || rootedRoutes[routeKey(method, path)] {
34
- return true
35
- }
36
- root := RootFlow{
37
- Kind: RootHTTP, Method: method, Path: path,
38
- Label: strings.TrimSpace(method + " " + path), Confidence: "high",
39
- Source: s.source(owner.file, call.Pos()),
40
- }
41
- for _, candidate := range s.routeHandlers(owner, handler) {
42
- group, found := byFunction[candidate]
43
- if !found || len(group.Calls) == 0 {
44
- continue
45
- }
46
- if root.Handler == "" {
47
- root.Handler = candidate
48
- }
49
- root.Calls = append(root.Calls, group.Calls...)
50
- root.Covered = append(root.Covered, candidate)
51
- rootedHandlers[candidate] = true
52
- }
53
- root.Calls = uniqueFlowCalls(root.Calls)
54
- root.Covered = uniqueStrings(root.Covered)
55
- if len(root.Calls) == 0 {
56
- return true
57
- }
58
- rootedRoutes[routeKey(method, path)] = true
59
- out = append(out, root)
60
- return true
61
- })
62
- }
63
-
64
- for _, owner := range s.sortedFunctions() {
65
- ast.Inspect(owner.fn.Body, func(node ast.Node) bool {
66
- call, ok := node.(*ast.CallExpr)
67
- if !ok {
68
- return true
69
- }
70
- schedule, handler, ok := s.scheduledRegistration(owner, call)
71
- if !ok {
72
- return true
73
- }
74
- for _, candidate := range s.routeHandlers(owner, handler) {
75
- group, found := byFunction[candidate]
76
- if !found || len(group.Calls) == 0 {
77
- continue
78
- }
79
- label := "Schedule " + schedule + " → " + displayFunction(candidate)
80
- out = append(out, RootFlow{
81
- Kind: RootScheduled, Handler: candidate, Label: label, Confidence: "high",
82
- Source: s.source(owner.file, call.Pos()), Calls: group.Calls,
83
- Covered: []string{candidate},
84
- })
85
- rootedHandlers[candidate] = true
86
- }
87
- return true
88
- })
89
- }
90
-
91
- // Swagger-style route annotations are useful for handler factories and
92
- // custom registries whose final path is assembled by framework code. They
93
- // are medium-confidence evidence: a concrete registration above wins when
94
- // both point to the same source function.
95
- for _, declaration := range s.sortedFunctions() {
96
- if rootedHandlers[declaration.key] {
97
- continue
98
- }
99
- group, found := byFunction[declaration.key]
100
- if !found || len(group.Calls) == 0 {
101
- continue
102
- }
103
- method, path, ok := annotatedRoute(declaration.fn)
104
- if !ok || providerRoutes[routeKey(method, path)] || rootedRoutes[routeKey(method, path)] {
105
- continue
106
- }
107
- kind := RootHTTP
108
- if looksLikeCallback(path, declaration.key) {
109
- kind = RootCallback
110
- }
111
- confidence := "medium"
112
- source := group.Source
113
- if registeredAt, registered := registrations[declaration.key]; registered {
114
- confidence = "high"
115
- source = registeredAt
116
- }
117
- out = append(out, RootFlow{
118
- Kind: kind, Method: method, Path: path, Handler: declaration.key,
119
- Label: strings.TrimSpace(method + " " + path), Confidence: confidence,
120
- Source: source, Calls: group.Calls, Covered: []string{declaration.key},
121
- })
122
- rootedHandlers[declaration.key] = true
123
- rootedRoutes[routeKey(method, path)] = true
124
- }
125
-
126
- // main and package-main init are lifecycle roots. Calls inside registered
127
- // closures are deliberately skipped: those execute when their own HTTP or
128
- // async root fires, not while the process is starting.
129
- for _, owner := range s.sortedFunctions() {
130
- if !lifecycleFunction(owner) {
131
- continue
132
- }
133
- out = append(out, s.lifecycleRoots(owner, byFunction, map[string]bool{}, 0)...)
134
- }
135
-
136
- out = uniqueRootFlows(out)
137
- sort.Slice(out, func(i, j int) bool {
138
- if out[i].Kind != out[j].Kind {
139
- return out[i].Kind < out[j].Kind
140
- }
141
- if out[i].Label != out[j].Label {
142
- return out[i].Label < out[j].Label
143
- }
144
- return out[i].Handler < out[j].Handler
145
- })
146
- return out
147
- }
148
-
149
- // handlerRegistrations upgrades a route annotation from documentation-only
150
- // evidence when source also passes that handler to a conventional registry.
151
- // The annotation still supplies the path; the registration proves it is live.
152
- func (s *scanner) handlerRegistrations() map[string]Source {
153
- out := map[string]Source{}
154
- for _, owner := range s.sortedFunctions() {
155
- ast.Inspect(owner.fn.Body, func(node ast.Node) bool {
156
- call, ok := node.(*ast.CallExpr)
157
- if !ok {
158
- return true
159
- }
160
- selector, ok := call.Fun.(*ast.SelectorExpr)
161
- if !ok || (selector.Sel.Name != "Register" && selector.Sel.Name != "Handle") {
162
- return true
163
- }
164
- for _, argument := range call.Args {
165
- target := s.handlerExpressionTarget(owner, argument)
166
- if target != "" {
167
- out[target] = s.source(owner.file, call.Pos())
168
- }
169
- }
170
- return true
171
- })
172
- }
173
- return out
174
- }
175
-
176
- func (s *scanner) handlerExpressionTarget(owner *functionDecl, expr ast.Expr) string {
177
- if call, ok := expr.(*ast.CallExpr); ok {
178
- return s.localTarget(owner, call.Fun)
179
- }
180
- if target := s.localTarget(owner, expr); target != "" {
181
- return target
182
- }
183
- return s.localVariableMethod(owner, expr)
184
- }
185
-
186
- func (s *scanner) scheduledRegistration(owner *functionDecl, call *ast.CallExpr) (string, ast.Expr, bool) {
187
- selector, ok := call.Fun.(*ast.SelectorExpr)
188
- if !ok || len(call.Args) < 2 {
189
- return "", nil, false
190
- }
191
- switch selector.Sel.Name {
192
- case "AddFunc", "AfterFunc", "Schedule":
193
- default:
194
- return "", nil, false
195
- }
196
- locals := s.localStrings(owner.file, owner.fn)
197
- schedule := s.value(owner.file, call.Args[0], locals, map[string]bool{})
198
- if schedule == "" {
199
- schedule = expression(call.Args[0])
200
- }
201
- if schedule == "" {
202
- schedule = "registered"
203
- }
204
- return schedule, call.Args[len(call.Args)-1], true
205
- }
206
-
207
- func (s *scanner) lifecycleRoots(owner *functionDecl, groups map[string]FlowGroup, visiting map[string]bool, depth int) []RootFlow {
208
- if owner == nil || depth > 4 || visiting[owner.key] {
209
- return nil
210
- }
211
- visiting[owner.key] = true
212
- defer delete(visiting, owner.key)
213
- var out []RootFlow
214
- ast.Inspect(owner.fn.Body, func(node ast.Node) bool {
215
- if _, nested := node.(*ast.FuncLit); nested {
216
- return false
217
- }
218
- call, ok := node.(*ast.CallExpr)
219
- if !ok {
220
- return true
221
- }
222
- target := s.localTarget(owner, call.Fun)
223
- if target == "" {
224
- target = s.localVariableMethod(owner, call.Fun)
225
- }
226
- if group, found := groups[target]; found && len(group.Calls) > 0 {
227
- out = append(out, RootFlow{
228
- Kind: RootStartup, Handler: target,
229
- Label: "Startup → " + displayFunction(target), Confidence: "high",
230
- Source: s.source(owner.file, call.Pos()), Calls: group.Calls,
231
- Covered: []string{target},
232
- })
233
- return true
234
- }
235
- if next := s.functions[target]; assemblyEntrypoint(next) {
236
- out = append(out, s.lifecycleRoots(next, groups, visiting, depth+1)...)
237
- }
238
- return true
239
- })
240
- return out
241
- }
242
-
243
- func (s *scanner) sortedFunctions() []*functionDecl {
244
- keys := make([]string, 0, len(s.functions))
245
- for key := range s.functions {
246
- keys = append(keys, key)
247
- }
248
- sort.Strings(keys)
249
- out := make([]*functionDecl, 0, len(keys))
250
- for _, key := range keys {
251
- out = append(out, s.functions[key])
252
- }
253
- return out
254
- }
255
-
256
- func annotatedRoute(fn *ast.FuncDecl) (string, string, bool) {
257
- if fn.Doc == nil {
258
- return "", "", false
259
- }
260
- for _, line := range strings.Split(fn.Doc.Text(), "\n") {
261
- at := strings.Index(line, "@Router")
262
- if at < 0 {
263
- continue
264
- }
265
- fields := strings.Fields(line[at+len("@Router"):])
266
- if len(fields) < 2 || !strings.HasPrefix(fields[0], "/") {
267
- continue
268
- }
269
- method := strings.ToUpper(strings.Trim(fields[1], "[]"))
270
- if method == "" {
271
- method = "HTTP"
272
- }
273
- return method, fields[0], true
274
- }
275
- return "", "", false
276
- }
277
-
278
- func lifecycleFunction(declaration *functionDecl) bool {
279
- name := displayFunction(declaration.key)
280
- return declaration.file.pkg == "main" && (name == "init" || name == "main")
281
- }
282
-
283
- func assemblyEntrypoint(declaration *functionDecl) bool {
284
- if declaration == nil {
285
- return false
286
- }
287
- name := displayFunction(declaration.key)
288
- if at := strings.LastIndex(name, "."); at >= 0 {
289
- return false
290
- }
291
- switch name {
292
- case "Run", "Start", "Bootstrap":
293
- dir := "/" + strings.Trim(declaration.file.dir, "/") + "/"
294
- return strings.Contains(dir, "/app/") || strings.Contains(dir, "/cmd/")
295
- }
296
- return false
297
- }
298
-
299
- func looksLikeCallback(path, function string) bool {
300
- value := strings.ToLower(path + " " + function)
301
- return strings.Contains(value, "callback") || strings.Contains(value, "webhook")
302
- }
303
-
304
- func routeKey(method, path string) string {
305
- return strings.ToUpper(method) + "\x00" + path
306
- }
307
-
308
- func uniqueRootFlows(in []RootFlow) []RootFlow {
309
- seen := map[string]bool{}
310
- out := make([]RootFlow, 0, len(in))
311
- for _, root := range in {
312
- key := string(root.Kind) + "\x00" + root.Label + "\x00" + root.Handler
313
- if seen[key] {
314
- continue
315
- }
316
- seen[key] = true
317
- out = append(out, root)
318
- }
319
- return out
320
- }
@@ -1,143 +0,0 @@
1
- package gohttp
2
-
3
- import (
4
- "context"
5
- "fmt"
6
- "path/filepath"
7
- "sort"
8
- "strconv"
9
- "strings"
10
- "time"
11
-
12
- "golang.org/x/tools/go/callgraph"
13
- "golang.org/x/tools/go/callgraph/vta"
14
- "golang.org/x/tools/go/packages"
15
- "golang.org/x/tools/go/ssa"
16
- "golang.org/x/tools/go/ssa/ssautil"
17
- )
18
-
19
- const typedAnalysisTimeout = 45 * time.Second
20
-
21
- // indexTypedCallEdges augments the syntax call graph with dynamic calls proven
22
- // by Go type information. Failure is deliberately non-fatal: repositories with
23
- // unavailable private modules, incomplete build tags, or type errors continue
24
- // to use the deterministic syntax analyzer.
25
- func (s *scanner) indexTypedCallEdges() bool {
26
- ctx, cancel := context.WithTimeout(context.Background(), typedAnalysisTimeout)
27
- defer cancel()
28
-
29
- loaded, err := packages.Load(&packages.Config{
30
- Context: ctx,
31
- Dir: s.root,
32
- Mode: packages.LoadSyntax,
33
- Tests: false,
34
- BuildFlags: []string{"-mod=readonly"},
35
- }, "./...")
36
- if err != nil {
37
- s.typedCallGraphError = err.Error()
38
- return false
39
- }
40
- if err := typedPackageError(loaded); err != nil {
41
- s.typedCallGraphError = err.Error()
42
- return false
43
- }
44
-
45
- program, _ := ssautil.Packages(loaded, ssa.InstantiateGenerics)
46
- program.Build()
47
- functions := ssautil.AllFunctions(program)
48
- if len(functions) == 0 {
49
- s.typedCallGraphError = "SSA program contains no source functions"
50
- return false
51
- }
52
-
53
- positions := s.functionKeysByPosition()
54
- graph := vta.CallGraph(functions, nil)
55
- edges := map[string][]localEdge{}
56
- err = callgraph.GraphVisitEdges(graph, func(edge *callgraph.Edge) error {
57
- caller := s.typedFunctionKey(program, edge.Caller.Func, positions)
58
- callee := s.typedFunctionKey(program, edge.Callee.Func, positions)
59
- if caller == "" || callee == "" || caller == callee {
60
- return nil
61
- }
62
- position := program.Fset.Position(edge.Pos())
63
- edges[caller] = append(edges[caller], localEdge{target: callee, line: position.Line})
64
- return nil
65
- })
66
- if err != nil {
67
- s.typedCallGraphError = err.Error()
68
- return false
69
- }
70
-
71
- for caller, outgoing := range edges {
72
- sort.Slice(outgoing, func(i, j int) bool {
73
- if outgoing[i].line != outgoing[j].line {
74
- return outgoing[i].line < outgoing[j].line
75
- }
76
- return outgoing[i].target < outgoing[j].target
77
- })
78
- seen := map[string]bool{}
79
- for _, edge := range outgoing {
80
- key := strconv.Itoa(edge.line) + "\x00" + edge.target
81
- if !seen[key] {
82
- s.typedEdges[caller] = append(s.typedEdges[caller], edge)
83
- seen[key] = true
84
- }
85
- }
86
- }
87
- return len(s.typedEdges) > 0
88
- }
89
-
90
- func typedPackageError(loaded []*packages.Package) error {
91
- for _, pkg := range loaded {
92
- if len(pkg.Errors) > 0 {
93
- return fmt.Errorf("%s: %s", pkg.PkgPath, pkg.Errors[0].Msg)
94
- }
95
- if pkg.IllTyped {
96
- return fmt.Errorf("%s: package is ill-typed", pkg.PkgPath)
97
- }
98
- }
99
- return nil
100
- }
101
-
102
- func (s *scanner) functionKeysByPosition() map[string][]string {
103
- out := map[string][]string{}
104
- for key, declaration := range s.functions {
105
- position := s.fset.Position(declaration.fn.Name.Pos())
106
- location := s.relativePosition(position.Filename, position.Line)
107
- if location != "" {
108
- out[location] = append(out[location], key)
109
- }
110
- }
111
- for location := range out {
112
- sort.Strings(out[location])
113
- }
114
- return out
115
- }
116
-
117
- func (s *scanner) typedFunctionKey(program *ssa.Program, function *ssa.Function, positions map[string][]string) string {
118
- for candidate := function; candidate != nil; candidate = candidate.Parent() {
119
- position := program.Fset.Position(candidate.Pos())
120
- keys := positions[s.relativePosition(position.Filename, position.Line)]
121
- if len(keys) == 1 {
122
- return keys[0]
123
- }
124
- for _, key := range keys {
125
- display := displayFunction(key)
126
- if display == candidate.Name() || strings.HasSuffix(display, "."+candidate.Name()) {
127
- return key
128
- }
129
- }
130
- }
131
- return ""
132
- }
133
-
134
- func (s *scanner) relativePosition(filename string, line int) string {
135
- if filename == "" || line == 0 {
136
- return ""
137
- }
138
- relative, err := filepath.Rel(s.root, filename)
139
- if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
140
- return ""
141
- }
142
- return filepath.ToSlash(relative) + ":" + strconv.Itoa(line)
143
- }
@@ -1,85 +0,0 @@
1
- package goscan
2
-
3
- import (
4
- "go/ast"
5
- "go/token"
6
- "strconv"
7
- )
8
-
9
- // indexConstants records every constant in the tree. A name in a const group
10
- // with no value of its own takes the values of the line before it, the way
11
- // the language does.
12
- func (t *Tree) indexConstants() {
13
- for _, file := range t.Files {
14
- for _, decl := range file.Node.Decls {
15
- gen, ok := decl.(*ast.GenDecl)
16
- if !ok || gen.Tok != token.CONST {
17
- continue
18
- }
19
- var inherited []ast.Expr
20
- for _, raw := range gen.Specs {
21
- spec := raw.(*ast.ValueSpec)
22
- values := spec.Values
23
- if len(values) == 0 {
24
- values = inherited
25
- } else {
26
- inherited = values
27
- }
28
- for i, name := range spec.Names {
29
- if len(values) > 0 {
30
- t.Constants[file.Pkg+"."+name.Name] = ConstExpr{Expr: values[min(i, len(values)-1)], File: file}
31
- }
32
- }
33
- }
34
- }
35
- }
36
- }
37
-
38
- // StringOf is the string an expression is worth: a literal; a constant of the
39
- // tree, by its own name or through an import; or what Foreign says about a
40
- // name from outside. Empty when it is none of those. visiting is the chain of
41
- // constants being followed, so one defined in terms of itself ends rather
42
- // than recurses; nil to start.
43
- func (t *Tree) StringOf(expr ast.Expr, file *File, visiting map[string]bool) string {
44
- switch value := Unwrap(expr).(type) {
45
- case *ast.BasicLit:
46
- if value.Kind == token.STRING {
47
- text, _ := strconv.Unquote(value.Value)
48
- return text
49
- }
50
- case *ast.Ident:
51
- return t.ConstantString(file.Pkg+"."+value.Name, visiting)
52
- case *ast.SelectorExpr:
53
- pkg, ok := value.X.(*ast.Ident)
54
- if !ok {
55
- return ""
56
- }
57
- imported := file.Imports[pkg.Name]
58
- if t.Foreign != nil {
59
- if known, ok := t.Foreign(imported, value.Sel.Name); ok {
60
- return known
61
- }
62
- }
63
- return t.ConstantString(imported+"."+value.Sel.Name, visiting)
64
- }
65
- return ""
66
- }
67
-
68
- // ConstantString is the string behind a constant, by "<import path>.<Name>",
69
- // followed through whatever it was declared as.
70
- func (t *Tree) ConstantString(key string, visiting map[string]bool) string {
71
- if visiting == nil {
72
- visiting = map[string]bool{}
73
- }
74
- if visiting[key] {
75
- return ""
76
- }
77
- found, ok := t.Constants[key]
78
- if !ok {
79
- return ""
80
- }
81
- visiting[key] = true
82
- value := t.StringOf(found.Expr, found.File, visiting)
83
- delete(visiting, key)
84
- return value
85
- }