circle-ir 4.17.0 → 4.18.1

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 (280) hide show
  1. package/dist/analysis/advisories.d.ts +186 -0
  2. package/dist/analysis/advisories.d.ts.map +1 -0
  3. package/dist/analysis/advisories.js +391 -0
  4. package/dist/analysis/advisories.js.map +1 -0
  5. package/dist/analysis/comment-lines.d.ts +14 -0
  6. package/dist/analysis/comment-lines.d.ts.map +1 -0
  7. package/dist/analysis/comment-lines.js +34 -0
  8. package/dist/analysis/comment-lines.js.map +1 -0
  9. package/dist/analysis/config-loader.d.ts.map +1 -1
  10. package/dist/analysis/config-loader.js +59 -5
  11. package/dist/analysis/config-loader.js.map +1 -1
  12. package/dist/analysis/constant-caller-gate.d.ts +2 -2
  13. package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
  14. package/dist/analysis/constant-caller-gate.js +205 -29
  15. package/dist/analysis/constant-caller-gate.js.map +1 -1
  16. package/dist/analysis/constant-propagation/propagator.d.ts +4 -0
  17. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  18. package/dist/analysis/constant-propagation/propagator.js +99 -0
  19. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  20. package/dist/analysis/entry-point-detection.d.ts.map +1 -1
  21. package/dist/analysis/entry-point-detection.js +72 -0
  22. package/dist/analysis/entry-point-detection.js.map +1 -1
  23. package/dist/analysis/interprocedural.d.ts.map +1 -1
  24. package/dist/analysis/interprocedural.js +75 -8
  25. package/dist/analysis/interprocedural.js.map +1 -1
  26. package/dist/analysis/maven-declared.d.ts +72 -0
  27. package/dist/analysis/maven-declared.d.ts.map +1 -0
  28. package/dist/analysis/maven-declared.js +216 -0
  29. package/dist/analysis/maven-declared.js.map +1 -0
  30. package/dist/analysis/maven-version.d.ts +28 -0
  31. package/dist/analysis/maven-version.d.ts.map +1 -0
  32. package/dist/analysis/maven-version.js +172 -0
  33. package/dist/analysis/maven-version.js.map +1 -0
  34. package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
  35. package/dist/analysis/passes/constant-propagation-pass.js +29 -1
  36. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
  37. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  38. package/dist/analysis/passes/cross-file-pass.js +81 -1
  39. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  40. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
  41. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  42. package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
  43. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  44. package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
  45. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  46. package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
  47. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  48. package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
  49. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  50. package/dist/analysis/passes/interprocedural-pass.js +562 -0
  51. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  52. package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
  53. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  54. package/dist/analysis/passes/language-sources-pass.js +1057 -14
  55. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  56. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  57. package/dist/analysis/passes/param-source-flow-gate-pass.js +66 -3
  58. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  59. package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
  60. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  61. package/dist/analysis/passes/sink-filter-pass.js +690 -12
  62. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  63. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  64. package/dist/analysis/passes/taint-propagation-pass.js +133 -10
  65. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  66. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
  67. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
  68. package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
  69. package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
  70. package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
  71. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  72. package/dist/analysis/passes/weak-random-pass.js +29 -0
  73. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  74. package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
  75. package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
  76. package/dist/analysis/promote-cross-file-flows.js +32 -0
  77. package/dist/analysis/promote-cross-file-flows.js.map +1 -0
  78. package/dist/analysis/sbom.d.ts +100 -0
  79. package/dist/analysis/sbom.d.ts.map +1 -1
  80. package/dist/analysis/sbom.js +514 -3
  81. package/dist/analysis/sbom.js.map +1 -1
  82. package/dist/analysis/semver-version.d.ts +30 -0
  83. package/dist/analysis/semver-version.d.ts.map +1 -0
  84. package/dist/analysis/semver-version.js +76 -0
  85. package/dist/analysis/semver-version.js.map +1 -0
  86. package/dist/analysis/taint-matcher.d.ts +5 -0
  87. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  88. package/dist/analysis/taint-matcher.js +225 -4
  89. package/dist/analysis/taint-matcher.js.map +1 -1
  90. package/dist/analyzer.d.ts +57 -0
  91. package/dist/analyzer.d.ts.map +1 -1
  92. package/dist/analyzer.js +69 -6
  93. package/dist/analyzer.js.map +1 -1
  94. package/dist/browser/circle-ir.js +2936 -291
  95. package/dist/core/circle-ir-core.cjs +634 -203
  96. package/dist/core/circle-ir-core.js +634 -203
  97. package/dist/core/extractors/dfg.js +205 -2
  98. package/dist/core/extractors/dfg.js.map +1 -1
  99. package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
  100. package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
  101. package/dist/core/extractors/ts-declaration-detail.js +266 -0
  102. package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
  103. package/dist/graph/analysis-pass.d.ts +4 -1
  104. package/dist/graph/analysis-pass.d.ts.map +1 -1
  105. package/dist/graph/analysis-pass.js +4 -1
  106. package/dist/graph/analysis-pass.js.map +1 -1
  107. package/dist/graph-facts/dead-code.d.ts +62 -0
  108. package/dist/graph-facts/dead-code.d.ts.map +1 -0
  109. package/dist/graph-facts/dead-code.js +90 -0
  110. package/dist/graph-facts/dead-code.js.map +1 -0
  111. package/dist/graph-facts/dependency-calls.d.ts +86 -0
  112. package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
  113. package/dist/graph-facts/dependency-calls.js +93 -0
  114. package/dist/graph-facts/dependency-calls.js.map +1 -0
  115. package/dist/graph-facts/index.d.ts +10 -0
  116. package/dist/graph-facts/index.d.ts.map +1 -0
  117. package/dist/graph-facts/index.js +10 -0
  118. package/dist/graph-facts/index.js.map +1 -0
  119. package/dist/graph-facts/maven-packages.d.ts +42 -0
  120. package/dist/graph-facts/maven-packages.d.ts.map +1 -0
  121. package/dist/graph-facts/maven-packages.js +133 -0
  122. package/dist/graph-facts/maven-packages.js.map +1 -0
  123. package/dist/graph-facts/reach.d.ts +203 -0
  124. package/dist/graph-facts/reach.d.ts.map +1 -0
  125. package/dist/graph-facts/reach.js +219 -0
  126. package/dist/graph-facts/reach.js.map +1 -0
  127. package/dist/graph-facts/removal-reach.d.ts +73 -0
  128. package/dist/graph-facts/removal-reach.d.ts.map +1 -0
  129. package/dist/graph-facts/removal-reach.js +64 -0
  130. package/dist/graph-facts/removal-reach.js.map +1 -0
  131. package/dist/index.d.ts +21 -6
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +20 -3
  134. package/dist/index.js.map +1 -1
  135. package/dist/navigation/go-graph.d.ts +68 -0
  136. package/dist/navigation/go-graph.d.ts.map +1 -0
  137. package/dist/navigation/go-graph.js +753 -0
  138. package/dist/navigation/go-graph.js.map +1 -0
  139. package/dist/navigation/index.d.ts +7 -1
  140. package/dist/navigation/index.d.ts.map +1 -1
  141. package/dist/navigation/index.js +6 -0
  142. package/dist/navigation/index.js.map +1 -1
  143. package/dist/navigation/java-entry-tables.d.ts +43 -0
  144. package/dist/navigation/java-entry-tables.d.ts.map +1 -0
  145. package/dist/navigation/java-entry-tables.js +56 -0
  146. package/dist/navigation/java-entry-tables.js.map +1 -0
  147. package/dist/navigation/java-graph.d.ts +64 -0
  148. package/dist/navigation/java-graph.d.ts.map +1 -0
  149. package/dist/navigation/java-graph.js +466 -0
  150. package/dist/navigation/java-graph.js.map +1 -0
  151. package/dist/navigation/navigation-index.d.ts +51 -1
  152. package/dist/navigation/navigation-index.d.ts.map +1 -1
  153. package/dist/navigation/navigation-index.js +72 -0
  154. package/dist/navigation/navigation-index.js.map +1 -1
  155. package/dist/navigation/python-graph.d.ts +73 -0
  156. package/dist/navigation/python-graph.d.ts.map +1 -0
  157. package/dist/navigation/python-graph.js +867 -0
  158. package/dist/navigation/python-graph.js.map +1 -0
  159. package/dist/navigation/ts-call-edges.d.ts +193 -0
  160. package/dist/navigation/ts-call-edges.d.ts.map +1 -0
  161. package/dist/navigation/ts-call-edges.js +2089 -0
  162. package/dist/navigation/ts-call-edges.js.map +1 -0
  163. package/dist/navigation/ts-entry-tables.d.ts +88 -0
  164. package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
  165. package/dist/navigation/ts-entry-tables.js +50 -0
  166. package/dist/navigation/ts-entry-tables.js.map +1 -0
  167. package/dist/path-set/branches.d.ts +52 -0
  168. package/dist/path-set/branches.d.ts.map +1 -0
  169. package/dist/path-set/branches.js +124 -0
  170. package/dist/path-set/branches.js.map +1 -0
  171. package/dist/path-set/finding-diff.d.ts +63 -0
  172. package/dist/path-set/finding-diff.d.ts.map +1 -0
  173. package/dist/path-set/finding-diff.js +150 -0
  174. package/dist/path-set/finding-diff.js.map +1 -0
  175. package/dist/path-set/index.d.ts +18 -0
  176. package/dist/path-set/index.d.ts.map +1 -0
  177. package/dist/path-set/index.js +12 -0
  178. package/dist/path-set/index.js.map +1 -0
  179. package/dist/path-set/line-diff.d.ts +19 -0
  180. package/dist/path-set/line-diff.d.ts.map +1 -0
  181. package/dist/path-set/line-diff.js +98 -0
  182. package/dist/path-set/line-diff.js.map +1 -0
  183. package/dist/path-set/path-set.d.ts +88 -0
  184. package/dist/path-set/path-set.d.ts.map +1 -0
  185. package/dist/path-set/path-set.js +716 -0
  186. package/dist/path-set/path-set.js.map +1 -0
  187. package/dist/path-set/templates.d.ts +42 -0
  188. package/dist/path-set/templates.d.ts.map +1 -0
  189. package/dist/path-set/templates.js +119 -0
  190. package/dist/path-set/templates.js.map +1 -0
  191. package/dist/path-set/types.d.ts +195 -0
  192. package/dist/path-set/types.d.ts.map +1 -0
  193. package/dist/path-set/types.js +8 -0
  194. package/dist/path-set/types.js.map +1 -0
  195. package/dist/remedy-class/index.d.ts +12 -0
  196. package/dist/remedy-class/index.d.ts.map +1 -0
  197. package/dist/remedy-class/index.js +9 -0
  198. package/dist/remedy-class/index.js.map +1 -0
  199. package/dist/remedy-class/match.d.ts +165 -0
  200. package/dist/remedy-class/match.d.ts.map +1 -0
  201. package/dist/remedy-class/match.js +705 -0
  202. package/dist/remedy-class/match.js.map +1 -0
  203. package/dist/remedy-class/output-context.d.ts +25 -0
  204. package/dist/remedy-class/output-context.d.ts.map +1 -0
  205. package/dist/remedy-class/output-context.js +150 -0
  206. package/dist/remedy-class/output-context.js.map +1 -0
  207. package/dist/remedy-class/sha256.d.ts +18 -0
  208. package/dist/remedy-class/sha256.d.ts.map +1 -0
  209. package/dist/remedy-class/sha256.js +89 -0
  210. package/dist/remedy-class/sha256.js.map +1 -0
  211. package/dist/remedy-class/table.d.ts +130 -0
  212. package/dist/remedy-class/table.d.ts.map +1 -0
  213. package/dist/remedy-class/table.js +457 -0
  214. package/dist/remedy-class/table.js.map +1 -0
  215. package/dist/resolution/cross-file.d.ts +9 -0
  216. package/dist/resolution/cross-file.d.ts.map +1 -1
  217. package/dist/resolution/cross-file.js +60 -5
  218. package/dist/resolution/cross-file.js.map +1 -1
  219. package/dist/stable-ids/builder.d.ts +20 -0
  220. package/dist/stable-ids/builder.d.ts.map +1 -0
  221. package/dist/stable-ids/builder.js +65 -0
  222. package/dist/stable-ids/builder.js.map +1 -0
  223. package/dist/stable-ids/diff.d.ts +18 -0
  224. package/dist/stable-ids/diff.d.ts.map +1 -0
  225. package/dist/stable-ids/diff.js +37 -0
  226. package/dist/stable-ids/diff.js.map +1 -0
  227. package/dist/stable-ids/go.d.ts +28 -0
  228. package/dist/stable-ids/go.d.ts.map +1 -0
  229. package/dist/stable-ids/go.js +160 -0
  230. package/dist/stable-ids/go.js.map +1 -0
  231. package/dist/stable-ids/hash.d.ts +22 -0
  232. package/dist/stable-ids/hash.d.ts.map +1 -0
  233. package/dist/stable-ids/hash.js +167 -0
  234. package/dist/stable-ids/hash.js.map +1 -0
  235. package/dist/stable-ids/index.d.ts +30 -0
  236. package/dist/stable-ids/index.d.ts.map +1 -0
  237. package/dist/stable-ids/index.js +59 -0
  238. package/dist/stable-ids/index.js.map +1 -0
  239. package/dist/stable-ids/java.d.ts +28 -0
  240. package/dist/stable-ids/java.d.ts.map +1 -0
  241. package/dist/stable-ids/java.js +224 -0
  242. package/dist/stable-ids/java.js.map +1 -0
  243. package/dist/stable-ids/moves.d.ts +87 -0
  244. package/dist/stable-ids/moves.d.ts.map +1 -0
  245. package/dist/stable-ids/moves.js +222 -0
  246. package/dist/stable-ids/moves.js.map +1 -0
  247. package/dist/stable-ids/nodes.d.ts +32 -0
  248. package/dist/stable-ids/nodes.d.ts.map +1 -0
  249. package/dist/stable-ids/nodes.js +92 -0
  250. package/dist/stable-ids/nodes.js.map +1 -0
  251. package/dist/stable-ids/normalize.d.ts +35 -0
  252. package/dist/stable-ids/normalize.d.ts.map +1 -0
  253. package/dist/stable-ids/normalize.js +174 -0
  254. package/dist/stable-ids/normalize.js.map +1 -0
  255. package/dist/stable-ids/parse-errors.d.ts +24 -0
  256. package/dist/stable-ids/parse-errors.d.ts.map +1 -0
  257. package/dist/stable-ids/parse-errors.js +46 -0
  258. package/dist/stable-ids/parse-errors.js.map +1 -0
  259. package/dist/stable-ids/python.d.ts +27 -0
  260. package/dist/stable-ids/python.d.ts.map +1 -0
  261. package/dist/stable-ids/python.js +212 -0
  262. package/dist/stable-ids/python.js.map +1 -0
  263. package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
  264. package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
  265. package/dist/stable-ids/ts-parse-errors.js +75 -0
  266. package/dist/stable-ids/ts-parse-errors.js.map +1 -0
  267. package/dist/stable-ids/types.d.ts +106 -0
  268. package/dist/stable-ids/types.d.ts.map +1 -0
  269. package/dist/stable-ids/types.js +16 -0
  270. package/dist/stable-ids/types.js.map +1 -0
  271. package/dist/stable-ids/typescript.d.ts +30 -0
  272. package/dist/stable-ids/typescript.d.ts.map +1 -0
  273. package/dist/stable-ids/typescript.js +362 -0
  274. package/dist/stable-ids/typescript.js.map +1 -0
  275. package/dist/types/config.d.ts +9 -0
  276. package/dist/types/config.d.ts.map +1 -1
  277. package/dist/types/index.d.ts +35 -0
  278. package/dist/types/index.d.ts.map +1 -1
  279. package/dist/types/index.js.map +1 -1
  280. package/package.json +3 -3
@@ -0,0 +1,753 @@
1
+ /**
2
+ * Go graph facts keyed by stable ids (`go-graph@0.1`): the reach core's
3
+ * input for Go.
4
+ *
5
+ * - **Declarations** from `deriveGoIds` (`ids@0.1`); a package is a directory.
6
+ * - **Call edges**, resolved from the syntax tree:
7
+ * - `exact`: a function of the same package, `pkg.F` through an import of
8
+ * a project package, `recv.M` on the method's own receiver, `T.M` method
9
+ * expressions, and `T{}` / `&T{}` composite literals (a reference to `T`).
10
+ * - `inferred`: `x.M` on a value of unknown type reaches every project
11
+ * method named `M`, up to `maxInferred`; beyond it the site is unresolved.
12
+ * A target outside the project (the standard library, modules) is an
13
+ * unresolved `external` site, never dropped.
14
+ * - **References**: a project function or method used as a value (a handler
15
+ * passed to a router, a callback).
16
+ * - **Imports**: a file loads the project packages it imports; a package's
17
+ * files load each other.
18
+ * - **Entries** (`entry-index@0.1`): `main` in package `main`, `init`,
19
+ * net/http (`HandleFunc`, `Handle`, Go 1.22 method patterns), gin, echo and
20
+ * chi routes with group and route prefixes composed, `_test.go` tests,
21
+ * benchmarks, examples and fuzz targets, and the exported API of packages
22
+ * the caller names as libraries.
23
+ * - **typeOnly**: interface types.
24
+ *
25
+ * Library code: no file access; the caller passes the sources.
26
+ */
27
+ import { disposeTree, parse } from '../core/parser.js';
28
+ import { deriveGoIds, goPackageDir, goReceiverType } from '../stable-ids/go.js';
29
+ import { declLocator } from '../stable-ids/nodes.js';
30
+ import { namedChildren } from '../stable-ids/normalize.js';
31
+ import { joinRoutePath } from './ts-call-edges.js';
32
+ export const GO_GRAPH_SCHEME = 'go-graph@0.1';
33
+ // ---------------------------------------------------------------------------
34
+ // Tables
35
+ /** Router packages: import path prefix → framework. */
36
+ const ROUTERS = {
37
+ 'net/http': 'net/http',
38
+ 'github.com/gin-gonic/gin': 'gin',
39
+ 'github.com/labstack/echo': 'echo',
40
+ 'github.com/go-chi/chi': 'chi',
41
+ 'github.com/gorilla/mux': 'gorilla',
42
+ };
43
+ const UPPER_VERBS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'Any', 'Match'];
44
+ const CHI_VERBS = ['Get', 'Post', 'Put', 'Patch', 'Delete', 'Head', 'Options', 'Connect', 'Trace', 'Method', 'MethodFunc', 'Handle', 'HandleFunc'];
45
+ const HTTP_FUNCS = ['HandleFunc', 'Handle'];
46
+ const BUILTINS = new Set(['append', 'cap', 'clear', 'close', 'complex', 'copy', 'delete', 'imag', 'len', 'make', 'max', 'min', 'new', 'panic', 'print', 'println', 'real', 'recover']);
47
+ const TEST_FUNC = /^(Test|Benchmark|Example|Fuzz)([A-Z_]|$)/;
48
+ const stringValue = (n) => {
49
+ if (!n)
50
+ return null;
51
+ if (n.type === 'interpreted_string_literal')
52
+ return namedChildren(n).map(c => c.text).join('');
53
+ if (n.type === 'raw_string_literal')
54
+ return n.text.slice(1, -1);
55
+ return null;
56
+ };
57
+ function walk(node, visit) {
58
+ if (visit(node) === false)
59
+ return;
60
+ for (const c of namedChildren(node))
61
+ walk(c, visit);
62
+ }
63
+ function importsOf(root) {
64
+ const out = new Map();
65
+ for (const decl of namedChildren(root).filter(c => c.type === 'import_declaration')) {
66
+ const specs = namedChildren(decl).flatMap(c => (c.type === 'import_spec_list' ? namedChildren(c) : [c])).filter(c => c.type === 'import_spec');
67
+ for (const spec of specs) {
68
+ const path = stringValue(spec.childForFieldName('path'));
69
+ if (!path)
70
+ continue;
71
+ const alias = spec.childForFieldName('name')?.text;
72
+ if (alias === '_')
73
+ continue;
74
+ // The package name is conventionally the last element (ignoring a `/vN` major suffix).
75
+ const parts = path.split('/');
76
+ let name = parts[parts.length - 1];
77
+ if (/^v\d+$/.test(name) && parts.length > 1)
78
+ name = parts[parts.length - 2];
79
+ name = name.replace(/^go-/, '').replace(/[.-].*$/, '');
80
+ out.set(alias && alias !== '.' ? alias : name, path);
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+ class GoGraphBuilder {
86
+ options;
87
+ ctxs = [];
88
+ pkgs = new Map();
89
+ methodsByName = new Map();
90
+ edges = [];
91
+ unresolved = [];
92
+ references = [];
93
+ imports = [];
94
+ loads = [];
95
+ entries = [];
96
+ unresolvedRegistrations = [];
97
+ typeOnly = new Set();
98
+ siteKeys = new Set();
99
+ edgeKeys = new Set();
100
+ constructor(options) {
101
+ this.options = options;
102
+ }
103
+ add(ctx) {
104
+ this.ctxs.push(ctx);
105
+ let p = this.pkgs.get(ctx.pkg);
106
+ if (!p) {
107
+ p = { dir: ctx.pkg, files: [], top: new Map(), methods: new Map(), interfaces: new Map(), fields: new Map(), results: new Map() };
108
+ this.pkgs.set(ctx.pkg, p);
109
+ }
110
+ p.files.push(ctx);
111
+ const module = ctx.ids.decls[0];
112
+ for (const d of ctx.ids.decls) {
113
+ if (d.flags.includes('unstable:anonymous') || d === module)
114
+ continue;
115
+ if (d.kind === 'method') {
116
+ const recv = d.qualified_name.slice(d.qualified_name.indexOf(':') + 1).split('.')[0];
117
+ const m = p.methods.get(recv) ?? new Map();
118
+ m.set(d.name, d);
119
+ p.methods.set(recv, m);
120
+ const list = this.methodsByName.get(d.name) ?? [];
121
+ list.push(d);
122
+ this.methodsByName.set(d.name, list);
123
+ }
124
+ else if (d.parent === module.id && d.name !== 'init')
125
+ p.top.set(d.name, d);
126
+ }
127
+ for (const td of namedChildren(ctx.root).filter(c => c.type === 'type_declaration')) {
128
+ for (const spec of namedChildren(td)) {
129
+ const name = spec.childForFieldName('name')?.text;
130
+ if (!name || spec.childForFieldName('type')?.type !== 'interface_type')
131
+ continue;
132
+ const iface = spec.childForFieldName('type');
133
+ p.interfaces.set(name, new Set(namedChildren(iface).filter(c => c.type === 'method_elem').map(c => c.childForFieldName('name')?.text ?? '')));
134
+ const d = p.top.get(name);
135
+ if (d)
136
+ this.typeOnly.add(d.id);
137
+ }
138
+ }
139
+ }
140
+ /** The project package directory an import path names, if any. */
141
+ pkgFor(importPath) {
142
+ const mod = this.options.modulePath;
143
+ if (mod) {
144
+ if (importPath === mod)
145
+ return this.pkgs.get('.');
146
+ if (importPath.startsWith(mod + '/'))
147
+ return this.pkgs.get(importPath.slice(mod.length + 1));
148
+ return undefined;
149
+ }
150
+ // By directory suffix: the longest project directory the import path ends with.
151
+ let best;
152
+ for (const p of this.pkgs.values()) {
153
+ if (p.dir === '.')
154
+ continue;
155
+ if ((importPath === p.dir || importPath.endsWith('/' + p.dir)) && (!best || p.dir.length > best.dir.length))
156
+ best = p;
157
+ }
158
+ return best;
159
+ }
160
+ // -- recording -----------------------------------------------------------
161
+ site(ctx, n) {
162
+ return { file: ctx.path, line: n.startPosition.row + 1, col: n.startPosition.column };
163
+ }
164
+ edge(from, to, tier, methodName, site, evidence) {
165
+ const key = `${from.id}>${to.id}@${site.file}:${site.line}:${site.col}`;
166
+ if (this.edgeKeys.has(key))
167
+ return;
168
+ this.edgeKeys.add(key);
169
+ this.siteKeys.add(`${site.file}:${site.line}:${site.col}:${methodName}`);
170
+ this.edges.push({ from: from.id, to: to.id, tier, methodName, site, evidence });
171
+ }
172
+ unresolvedSite(from, methodName, site, reason, evidence) {
173
+ this.siteKeys.add(`${site.file}:${site.line}:${site.col}:${methodName}`);
174
+ this.unresolved.push({ from: from.id, methodName, site, reason, evidence });
175
+ }
176
+ get sites() {
177
+ return this.siteKeys.size;
178
+ }
179
+ entry(e) {
180
+ if (this.entries.some(x => x.id === e.id && x.kind === e.kind && x.method === e.method && x.path === e.path))
181
+ return;
182
+ this.entries.push(e);
183
+ }
184
+ enclosing(ctx, n) {
185
+ return ctx.locate(n.startPosition.row + 1, n.startPosition.column) ?? ctx.ids.decls[0];
186
+ }
187
+ /** The receiver name and type of the method enclosing a node. */
188
+ receiverOf(ctx, n) {
189
+ let p = n;
190
+ while (p && p.type !== 'method_declaration')
191
+ p = p.parent;
192
+ if (!p)
193
+ return null;
194
+ const recv = p.childForFieldName('receiver');
195
+ const decl = recv ? namedChildren(recv).find(c => c.type === 'parameter_declaration') : undefined;
196
+ const name = decl?.childForFieldName('name')?.text;
197
+ return name ? { name, type: goReceiverType(p) } : null;
198
+ }
199
+ // -- resolution ----------------------------------------------------------
200
+ // -- types ---------------------------------------------------------------
201
+ /** Index struct fields and function results; called after every file is added. */
202
+ indexTypes() {
203
+ for (const p of this.pkgs.values()) {
204
+ for (const ctx of p.files) {
205
+ for (const td of namedChildren(ctx.root).filter(c => c.type === 'type_declaration')) {
206
+ for (const spec of namedChildren(td)) {
207
+ const name = spec.childForFieldName('name')?.text;
208
+ const st = spec.childForFieldName('type');
209
+ if (!name || st?.type !== 'struct_type')
210
+ continue;
211
+ const fields = new Map();
212
+ for (const list of namedChildren(st)) {
213
+ for (const fd of namedChildren(list).filter(c => c.type === 'field_declaration')) {
214
+ const ref = this.typeRef(ctx, fd.childForFieldName('type'));
215
+ const names = namedChildren(fd).filter(c => c.type === 'field_identifier');
216
+ if (names.length === 0 && ref)
217
+ fields.set(ref.name, ref); // embedded
218
+ for (const n of names)
219
+ fields.set(n.text, ref);
220
+ }
221
+ }
222
+ p.fields.set(name, fields);
223
+ }
224
+ }
225
+ for (const fn of namedChildren(ctx.root)) {
226
+ if (fn.type !== 'function_declaration' && fn.type !== 'method_declaration')
227
+ continue;
228
+ const name = fn.childForFieldName('name')?.text;
229
+ if (!name)
230
+ continue;
231
+ const key = fn.type === 'method_declaration' ? `${goReceiverType(fn)}.${name}` : name;
232
+ const result = fn.childForFieldName('result');
233
+ const first = result?.type === 'parameter_list' ? namedChildren(result)[0]?.childForFieldName('type') : result;
234
+ p.results.set(key, this.typeRef(ctx, first ?? null));
235
+ }
236
+ }
237
+ }
238
+ }
239
+ /** The project type a type node names (`T`, `*T`, `pkg.T`), or null. */
240
+ typeRef(ctx, t) {
241
+ if (!t)
242
+ return null;
243
+ if (t.type === 'pointer_type' || t.type === 'parenthesized_type')
244
+ return this.typeRef(ctx, namedChildren(t)[0]);
245
+ if (t.type === 'generic_type')
246
+ return this.typeRef(ctx, t.childForFieldName('type'));
247
+ if (t.type === 'type_identifier')
248
+ return this.pkgs.get(ctx.pkg).top.get(t.text)?.kind === 'type' ? { pkg: ctx.pkg, name: t.text } : null;
249
+ if (t.type === 'qualified_type') {
250
+ const path = ctx.imports.get(t.childForFieldName('package')?.text ?? '');
251
+ const p = path !== undefined ? this.pkgFor(path) : undefined;
252
+ const name = t.childForFieldName('name')?.text ?? '';
253
+ return p && p.top.get(name)?.kind === 'type' ? { pkg: p.dir, name } : null;
254
+ }
255
+ return null;
256
+ }
257
+ envs = new Map();
258
+ /** Local names with a known project type in the function enclosing `n`: receiver, parameters, `x := T{}` / `x := NewT()`. */
259
+ env(ctx, n) {
260
+ let fn = n;
261
+ while (fn && fn.type !== 'function_declaration' && fn.type !== 'method_declaration' && fn.type !== 'func_literal')
262
+ fn = fn.parent;
263
+ if (!fn)
264
+ return new Map();
265
+ const cached = this.envs.get(fn.id);
266
+ if (cached)
267
+ return cached;
268
+ const env = new Map(fn.type === 'func_literal' ? this.env(ctx, fn.parent ?? fn) : []);
269
+ this.envs.set(fn.id, env);
270
+ for (const listName of ['receiver', 'parameters']) {
271
+ const list = fn.childForFieldName(listName);
272
+ for (const p of list ? namedChildren(list) : []) {
273
+ const ref = this.typeRef(ctx, p.childForFieldName('type'));
274
+ if (ref)
275
+ for (const id of namedChildren(p).filter(c => c.type === 'identifier'))
276
+ env.set(id.text, ref);
277
+ }
278
+ }
279
+ walk(fn.childForFieldName('body') ?? fn, m => {
280
+ if (m.type === 'func_literal')
281
+ return false;
282
+ if (m.type === 'short_var_declaration' || m.type === 'var_spec') {
283
+ const left = m.type === 'var_spec' ? namedChildren(m).filter(c => c.type === 'identifier') : namedChildren(m.childForFieldName('left') ?? m);
284
+ const declared = m.type === 'var_spec' ? this.typeRef(ctx, m.childForFieldName('type')) : null;
285
+ const right = namedChildren(m.type === 'var_spec' ? m.childForFieldName('value') ?? m : m.childForFieldName('right') ?? m);
286
+ left.forEach((l, i) => {
287
+ const ref = declared ?? (right[i] ? this.typeOf(ctx, right[i], env) : null) ?? (i === 0 && right.length === 1 ? this.typeOf(ctx, right[0], env) : null);
288
+ if (ref && l.type === 'identifier')
289
+ env.set(l.text, ref);
290
+ });
291
+ }
292
+ return undefined;
293
+ });
294
+ return env;
295
+ }
296
+ /** The project type of an expression, when the tree says it. */
297
+ typeOf(ctx, e, env) {
298
+ const scope = env ?? this.env(ctx, e);
299
+ switch (e.type) {
300
+ case 'identifier':
301
+ return scope.get(e.text) ?? null;
302
+ case 'unary_expression':
303
+ case 'parenthesized_expression':
304
+ return this.typeOf(ctx, namedChildren(e)[0], scope);
305
+ case 'composite_literal':
306
+ return this.typeRef(ctx, e.childForFieldName('type'));
307
+ case 'selector_expression': {
308
+ const op = e.childForFieldName('operand');
309
+ const field = e.childForFieldName('field')?.text ?? '';
310
+ const t = op ? this.typeOf(ctx, op, scope) : null;
311
+ return t ? this.pkgs.get(t.pkg)?.fields.get(t.name)?.get(field) ?? null : null;
312
+ }
313
+ case 'call_expression': {
314
+ const fn = e.childForFieldName('function');
315
+ if (!fn)
316
+ return null;
317
+ if (fn.type === 'identifier')
318
+ return this.pkgs.get(ctx.pkg).results.get(fn.text) ?? null;
319
+ if (fn.type === 'selector_expression') {
320
+ const op = fn.childForFieldName('operand');
321
+ const name = fn.childForFieldName('field')?.text ?? '';
322
+ const path = op?.type === 'identifier' ? ctx.imports.get(op.text) : undefined;
323
+ if (path !== undefined)
324
+ return this.pkgFor(path)?.results.get(name) ?? null;
325
+ const t = op ? this.typeOf(ctx, op, scope) : null;
326
+ return t ? this.pkgs.get(t.pkg)?.results.get(`${t.name}.${name}`) ?? null : null;
327
+ }
328
+ return null;
329
+ }
330
+ default:
331
+ return null;
332
+ }
333
+ }
334
+ /** A method on a project type, through embedded fields. */
335
+ methodOn(t, name, depth = 0) {
336
+ const p = this.pkgs.get(t.pkg);
337
+ const m = p?.methods.get(t.name)?.get(name);
338
+ if (m || depth > 3)
339
+ return m ?? null;
340
+ for (const [fname, ref] of p?.fields.get(t.name) ?? []) {
341
+ if (ref && fname === ref.name) {
342
+ const em = this.methodOn(ref, name, depth + 1);
343
+ if (em)
344
+ return em;
345
+ }
346
+ }
347
+ return null;
348
+ }
349
+ /** Project types whose method set covers an interface's. */
350
+ implementers(t, name) {
351
+ const need = this.pkgs.get(t.pkg)?.interfaces.get(t.name);
352
+ if (!need)
353
+ return [];
354
+ const out = [];
355
+ for (const p of this.pkgs.values()) {
356
+ for (const [type, ms] of p.methods) {
357
+ if ([...need].every(x => ms.has(x)) && ms.has(name))
358
+ out.push(ms.get(name));
359
+ void type;
360
+ }
361
+ }
362
+ return out;
363
+ }
364
+ /** What an operand names: a project declaration, an external package, or nothing known. */
365
+ resolve(ctx, n) {
366
+ const pkg = this.pkgs.get(ctx.pkg);
367
+ if (n.type === 'identifier') {
368
+ if (BUILTINS.has(n.text))
369
+ return null;
370
+ const d = pkg.top.get(n.text);
371
+ return d ? { decl: d, name: n.text } : null;
372
+ }
373
+ if (n.type === 'selector_expression') {
374
+ const operand = n.childForFieldName('operand');
375
+ const field = n.childForFieldName('field')?.text ?? '';
376
+ if (!operand)
377
+ return null;
378
+ if (operand.type === 'identifier') {
379
+ // pkg.F
380
+ const path = ctx.imports.get(operand.text);
381
+ if (path !== undefined) {
382
+ const target = this.pkgFor(path);
383
+ if (!target)
384
+ return { external: path, name: field };
385
+ const d = target.top.get(field);
386
+ return d ? { decl: d, name: field } : { name: field };
387
+ }
388
+ // recv.M on the enclosing method's receiver
389
+ const recv = this.receiverOf(ctx, n);
390
+ if (recv && recv.name === operand.text) {
391
+ const m = pkg.methods.get(recv.type)?.get(field);
392
+ if (m)
393
+ return { decl: m, name: field };
394
+ }
395
+ // T.M method expression
396
+ const typeDecl = pkg.top.get(operand.text);
397
+ if (typeDecl?.kind === 'type') {
398
+ const m = pkg.methods.get(operand.text)?.get(field);
399
+ if (m)
400
+ return { decl: m, name: field };
401
+ }
402
+ }
403
+ // x.M on a value of known project type: the type's method, or an interface's implementers.
404
+ const t = this.typeOf(ctx, operand);
405
+ if (t) {
406
+ if (this.pkgs.get(t.pkg)?.interfaces.has(t.name)) {
407
+ const impls = this.implementers(t, field);
408
+ if (impls.length > 0)
409
+ return { polymorphic: impls, name: field };
410
+ }
411
+ else {
412
+ const m = this.methodOn(t, field);
413
+ if (m)
414
+ return { decl: m, name: field };
415
+ if (this.pkgs.get(t.pkg)?.fields.get(t.name)?.has(field))
416
+ return { name: field }; // a func-typed field
417
+ }
418
+ }
419
+ // x.M on a value of unknown type
420
+ const candidates = this.methodsByName.get(field) ?? [];
421
+ return { candidates, name: field };
422
+ }
423
+ return null;
424
+ }
425
+ calls(ctx) {
426
+ walk(ctx.root, n => {
427
+ if (n.type === 'call_expression') {
428
+ this.callOne(ctx, n);
429
+ return false;
430
+ }
431
+ return this.visit(ctx, n);
432
+ });
433
+ }
434
+ /** Values: function and method references, composite literals. */
435
+ visit(ctx, n) {
436
+ if (n.type === 'call_expression') {
437
+ this.callOne(ctx, n);
438
+ return false;
439
+ }
440
+ if (n.type === 'composite_literal') {
441
+ const t = n.childForFieldName('type');
442
+ const name = t?.type === 'type_identifier' ? t.text : t?.type === 'qualified_type' ? t.text : null;
443
+ if (name) {
444
+ const pkg = this.pkgs.get(ctx.pkg);
445
+ let d;
446
+ if (name.includes('.')) {
447
+ const [p, tn] = name.split('.');
448
+ const path = ctx.imports.get(p);
449
+ d = path !== undefined ? this.pkgFor(path)?.top.get(tn) : undefined;
450
+ }
451
+ else
452
+ d = pkg.top.get(name);
453
+ if (d)
454
+ this.loads.push({ from: this.enclosing(ctx, n).id, to: d.id }); // constructs a value of the type
455
+ }
456
+ return undefined;
457
+ }
458
+ if (n.type === 'identifier' || n.type === 'selector_expression') {
459
+ const parent = n.parent;
460
+ if (!parent)
461
+ return undefined;
462
+ if (['function_declaration', 'method_declaration', 'parameter_declaration', 'var_spec', 'const_spec', 'import_spec', 'field_declaration', 'type_spec', 'labeled_statement'].includes(parent.type))
463
+ return false;
464
+ if (parent.type === 'selector_expression' && parent.childForFieldName('field') === n)
465
+ return undefined;
466
+ const r = this.resolve(ctx, n);
467
+ const enc = this.enclosing(ctx, n);
468
+ if (r?.decl && (r.decl.kind === 'function' || r.decl.kind === 'method') && r.decl.id !== enc.id) {
469
+ this.references.push({ from: enc.id, to: r.decl.id, site: this.site(ctx, n) });
470
+ }
471
+ else if (n.type === 'selector_expression' && r?.candidates && r.candidates.length > 0 && r.candidates.length <= (this.options.maxInferred ?? 16)) {
472
+ // A method value on a value of unknown type (`s.handle`): a reference to each method of that name.
473
+ for (const c of r.candidates)
474
+ if (c.id !== enc.id)
475
+ this.references.push({ from: enc.id, to: c.id, site: this.site(ctx, n) });
476
+ }
477
+ return n.type === 'selector_expression' ? false : undefined;
478
+ }
479
+ return undefined;
480
+ }
481
+ callOne(ctx, n) {
482
+ const fn = n.childForFieldName('function');
483
+ if (!fn)
484
+ return;
485
+ const from = this.enclosing(ctx, n);
486
+ const site = this.site(ctx, fn);
487
+ const r = this.resolve(ctx, fn);
488
+ if (r?.decl) {
489
+ if (r.decl.kind === 'type')
490
+ this.loads.push({ from: from.id, to: r.decl.id }); // a conversion `T(x)`
491
+ else if (r.decl.kind !== 'field')
492
+ this.edge(from, r.decl, 'exact', r.name, site, 'resolved by package scope, import or receiver');
493
+ }
494
+ else if (r?.polymorphic) {
495
+ for (const m of r.polymorphic)
496
+ this.edge(from, m, 'polymorphic', r.name, site, `.${r.name}() on an interface; an implementing type's method`);
497
+ }
498
+ else if (r?.external !== undefined)
499
+ this.unresolvedSite(from, r.name, site, 'external', `${r.external}.${r.name}`);
500
+ else if (r?.candidates) {
501
+ if (r.candidates.length === 0)
502
+ this.unresolvedSite(from, r.name, site, 'external', `.${r.name}(): no project method of that name`);
503
+ else if (r.candidates.length > (this.options.maxInferred ?? 16))
504
+ this.unresolvedSite(from, r.name, site, 'unknown', `.${r.name}(): ${r.candidates.length} project methods of that name`);
505
+ else
506
+ for (const m of r.candidates)
507
+ this.edge(from, m, 'inferred', r.name, site, `.${r.name}() on a value of unknown type`);
508
+ }
509
+ else if (fn.type === 'parenthesized_expression' || fn.type === 'index_expression' || fn.type === 'call_expression') {
510
+ this.unresolvedSite(from, '<computed>', site, 'dynamic', 'computed callee');
511
+ }
512
+ if (fn.type === 'selector_expression') {
513
+ const op = fn.childForFieldName('operand');
514
+ if (op && op.type !== 'identifier')
515
+ walk(op, m => this.visit(ctx, m));
516
+ }
517
+ else if (fn.type !== 'identifier')
518
+ walk(fn, m => this.visit(ctx, m));
519
+ const args = n.childForFieldName('arguments');
520
+ if (args)
521
+ for (const a of namedChildren(args))
522
+ walk(a, m => this.visit(ctx, m));
523
+ }
524
+ // -- imports -------------------------------------------------------------
525
+ importsOf(ctx) {
526
+ const self = ctx.ids.decls[0];
527
+ const seen = new Set();
528
+ for (const path of ctx.imports.values()) {
529
+ const p = this.pkgFor(path);
530
+ if (!p || p.dir === ctx.pkg)
531
+ continue;
532
+ for (const f of p.files) {
533
+ if (seen.has(f.path))
534
+ continue;
535
+ seen.add(f.path);
536
+ this.imports.push({ from: self.id, to: f.ids.decls[0].id });
537
+ }
538
+ }
539
+ // A package is compiled whole: its files load each other.
540
+ for (const f of this.pkgs.get(ctx.pkg).files) {
541
+ if (f !== ctx && !f.path.endsWith('_test.go'))
542
+ this.loads.push({ from: self.id, to: f.ids.decls[0].id });
543
+ }
544
+ }
545
+ // -- entries -------------------------------------------------------------
546
+ entriesOf(ctx) {
547
+ const module = ctx.ids.decls[0];
548
+ const isTest = ctx.path.endsWith('_test.go');
549
+ const frameworkOf = (alias) => {
550
+ const path = ctx.imports.get(alias);
551
+ if (path === undefined)
552
+ return null;
553
+ for (const [prefix, fw] of Object.entries(ROUTERS))
554
+ if (path === prefix || path.startsWith(prefix + '/'))
555
+ return fw;
556
+ return null;
557
+ };
558
+ const routerImports = [...ctx.imports.keys()].map(frameworkOf).filter((x) => x !== null);
559
+ for (const d of ctx.ids.decls) {
560
+ if (d.flags.includes('unstable:anonymous') || d === module)
561
+ continue;
562
+ if (d.kind === 'function' && d.name === 'main' && ctx.pkgName === 'main') {
563
+ this.entry({ id: d.id, kind: 'main', certainty: 'declared', file: ctx.path, line: d.start_line, evidence: 'func main in package main' });
564
+ }
565
+ if (d.kind === 'function' && d.name === 'init') {
566
+ this.entry({ id: d.id, kind: 'lifecycle', certainty: 'declared', file: ctx.path, line: d.start_line, evidence: 'func init runs when the package loads' });
567
+ }
568
+ if (isTest && d.kind === 'function' && TEST_FUNC.test(d.name)) {
569
+ this.entry({ id: d.id, kind: 'test', certainty: 'declared', framework: 'testing', file: ctx.path, line: d.start_line, evidence: `go test: ${d.name}` });
570
+ }
571
+ if (isTest && d.kind === 'function' && d.name === 'TestMain') {
572
+ this.entry({ id: d.id, kind: 'test', certainty: 'declared', framework: 'testing', file: ctx.path, line: d.start_line, evidence: 'TestMain' });
573
+ }
574
+ if ((this.options.libraryPackages ?? []).includes(ctx.pkg) && /^[A-Z]/.test(d.name) && !isTest && d.kind !== 'field') {
575
+ this.entry({ id: d.id, kind: 'export', certainty: 'conventional', file: ctx.path, line: d.start_line, evidence: 'exported from a library package' });
576
+ }
577
+ }
578
+ if (isTest)
579
+ this.entry({ id: module.id, kind: 'test', certainty: 'conventional', framework: 'testing', file: ctx.path, line: 1, evidence: 'test file' });
580
+ if (routerImports.length > 0)
581
+ this.routes(ctx, frameworkOf);
582
+ }
583
+ /** Router registrations, with group and route prefixes composed per function body. */
584
+ routes(ctx, frameworkOf) {
585
+ const prefixes = new Map(); // variable → prefix (function-local, flow-insensitive)
586
+ const register = (call, framework, method, path, handler, prefix, certainty) => {
587
+ const site = this.site(ctx, call);
588
+ const full = joinRoutePath([prefix, path]);
589
+ if (!handler)
590
+ return;
591
+ const targets = [];
592
+ const h = handler.type === 'unary_expression' ? namedChildren(handler)[0] ?? handler : handler;
593
+ if (h.type === 'func_literal') {
594
+ const d = ctx.locate(h.startPosition.row + 1, h.startPosition.column);
595
+ if (d)
596
+ targets.push(d);
597
+ }
598
+ else if (h.type === 'call_expression') {
599
+ // http.HandlerFunc(f), handlerFactory(dep): the wrapped function, or the factory itself.
600
+ const inner = namedChildren(h.childForFieldName('arguments') ?? h)[0];
601
+ const r = inner ? this.resolve(ctx, inner) : null;
602
+ if (r?.decl)
603
+ targets.push(r.decl);
604
+ else {
605
+ const f = this.resolve(ctx, h.childForFieldName('function') ?? h);
606
+ if (f?.decl)
607
+ targets.push(f.decl);
608
+ }
609
+ }
610
+ else {
611
+ const r = this.resolve(ctx, h);
612
+ if (r?.decl)
613
+ targets.push(r.decl);
614
+ else if (r?.polymorphic)
615
+ targets.push(...r.polymorphic);
616
+ else if (r?.candidates && r.candidates.length > 0 && r.candidates.length <= (this.options.maxInferred ?? 16)) {
617
+ targets.push(...r.candidates);
618
+ certainty = 'inferred';
619
+ }
620
+ }
621
+ if (targets.length === 0) {
622
+ this.unresolvedRegistrations.push({ site, reason: 'handler-unresolved', evidence: `${framework} ${method} ${full}: ${handler.text}` });
623
+ return;
624
+ }
625
+ for (const t of targets) {
626
+ const e = { id: t.id, kind: 'route', certainty, framework, method, path: full, surface: 'api', file: ctx.path, line: site.line, evidence: `${framework} ${method} ${path}` };
627
+ if (prefix) {
628
+ e.registeredPath = path;
629
+ e.mounts = [prefix];
630
+ }
631
+ this.entry(e);
632
+ }
633
+ };
634
+ walk(ctx.root, n => {
635
+ if (n.type === 'function_declaration' || n.type === 'method_declaration')
636
+ prefixes.clear();
637
+ // g := r.Group("/api") / e.Group("/api")
638
+ if (n.type === 'short_var_declaration' || n.type === 'assignment_statement') {
639
+ const left = namedChildren(n.childForFieldName('left') ?? n)[0];
640
+ const right = namedChildren(n.childForFieldName('right') ?? n)[0];
641
+ if (left?.type === 'identifier' && right?.type === 'call_expression') {
642
+ const fn = right.childForFieldName('function');
643
+ if (fn?.type === 'selector_expression' && ['Group', 'PathPrefix', 'Subrouter'].includes(fn.childForFieldName('field')?.text ?? '')) {
644
+ const base = fn.childForFieldName('operand');
645
+ const arg = stringValue(namedChildren(right.childForFieldName('arguments') ?? right)[0]);
646
+ let p = base?.type === 'identifier' ? prefixes.get(base.text) ?? '' : '';
647
+ // r.PathPrefix("/x").Subrouter()
648
+ if (base?.type === 'call_expression') {
649
+ const inner = base.childForFieldName('function');
650
+ if (inner?.type === 'selector_expression' && inner.childForFieldName('field')?.text === 'PathPrefix') {
651
+ const ib = inner.childForFieldName('operand');
652
+ p = (ib?.type === 'identifier' ? prefixes.get(ib.text) ?? '' : '') + (stringValue(namedChildren(base.childForFieldName('arguments') ?? base)[0]) ?? '');
653
+ }
654
+ }
655
+ prefixes.set(left.text, joinRoutePath([p, arg ?? '']).replace(/^\/$/, ''));
656
+ }
657
+ }
658
+ }
659
+ if (n.type !== 'call_expression')
660
+ return undefined;
661
+ const fn = n.childForFieldName('function');
662
+ if (fn?.type !== 'selector_expression')
663
+ return undefined;
664
+ const recv = fn.childForFieldName('operand');
665
+ const verb = fn.childForFieldName('field')?.text ?? '';
666
+ const args = namedChildren(n.childForFieldName('arguments') ?? n);
667
+ const recvName = recv?.type === 'identifier' ? recv.text : '';
668
+ const prefix = prefixes.get(recvName) ?? '';
669
+ const pkgFw = recv?.type === 'identifier' ? frameworkOf(recv.text) : null;
670
+ // net/http: http.HandleFunc("/x", h), mux.HandleFunc("GET /x", h)
671
+ if (HTTP_FUNCS.includes(verb)) {
672
+ const pattern = stringValue(args[0]);
673
+ if (pattern === null)
674
+ return undefined;
675
+ const m = /^([A-Z]+)\s+(.*)$/.exec(pattern);
676
+ const fw = pkgFw ?? 'net/http';
677
+ register(n, fw === 'chi' || fw === 'gorilla' ? fw : 'net/http', m ? m[1] : 'ANY', m ? m[2] : pattern, args[1], prefix, pkgFw || [...ctx.imports.values()].includes('net/http') ? 'declared' : 'inferred');
678
+ return undefined;
679
+ }
680
+ // chi: r.Route("/x", func(r chi.Router) { ... }) — prefix the inner router.
681
+ if (verb === 'Route' && args.length >= 2 && args[1].type === 'func_literal') {
682
+ const p = stringValue(args[0]) ?? '';
683
+ const params = args[1].childForFieldName('parameters');
684
+ const inner = params ? namedChildren(params)[0]?.childForFieldName('name')?.text : undefined;
685
+ if (inner)
686
+ prefixes.set(inner, joinRoutePath([prefix, p]));
687
+ return undefined;
688
+ }
689
+ if (pkgFw)
690
+ return undefined; // gin.Default(), echo.New(): not a registration
691
+ const path = stringValue(args[0]);
692
+ if (path === null)
693
+ return undefined;
694
+ if (UPPER_VERBS.includes(verb)) {
695
+ const handlers = args.slice(1);
696
+ register(n, 'gin/echo', verb === 'Any' || verb === 'Match' ? 'ANY' : verb, path, handlers[handlers.length - 1], prefix, 'declared');
697
+ }
698
+ else if (CHI_VERBS.includes(verb)) {
699
+ register(n, 'chi', ['Handle', 'HandleFunc', 'Method', 'MethodFunc'].includes(verb) ? 'ANY' : verb.toUpperCase(), path, args[args.length - 1], prefix, 'declared');
700
+ }
701
+ return undefined;
702
+ });
703
+ }
704
+ }
705
+ /**
706
+ * Build the Go graph facts for a set of files. Each file is parsed once;
707
+ * nothing is read from disk.
708
+ */
709
+ export async function buildGoGraph(files, options = {}) {
710
+ const b = new GoGraphBuilder(options);
711
+ const trees = [];
712
+ const partial = [];
713
+ const sorted = [...files].sort((x, y) => (x.path < y.path ? -1 : x.path > y.path ? 1 : 0));
714
+ try {
715
+ for (const f of sorted) {
716
+ const tree = await parse(f.code, 'go');
717
+ const ids = deriveGoIds(tree, f.path);
718
+ if (ids.partial)
719
+ partial.push(f.path);
720
+ const pkgName = namedChildren(namedChildren(tree.rootNode).find(c => c.type === 'package_clause') ?? tree.rootNode)[0]?.text ?? '';
721
+ const ctx = { path: f.path, pkg: goPackageDir(f.path), pkgName, ids, root: tree.rootNode, imports: importsOf(tree.rootNode), locate: declLocator(ids) };
722
+ b.add(ctx);
723
+ trees.push({ tree, ctx });
724
+ }
725
+ b.indexTypes();
726
+ for (const t of trees) {
727
+ b.calls(t.ctx);
728
+ b.importsOf(t.ctx);
729
+ b.entriesOf(t.ctx);
730
+ }
731
+ }
732
+ finally {
733
+ for (const t of trees)
734
+ disposeTree(t.tree);
735
+ }
736
+ return {
737
+ scheme: GO_GRAPH_SCHEME,
738
+ ids: 'ids@0.1',
739
+ files: trees.map(t => t.ctx.ids),
740
+ edges: b.edges,
741
+ unresolved: b.unresolved,
742
+ references: b.references,
743
+ imports: b.imports,
744
+ loads: b.loads,
745
+ entryScheme: 'entry-index@0.1',
746
+ entries: b.entries,
747
+ unresolvedRegistrations: b.unresolvedRegistrations,
748
+ partial,
749
+ typeOnly: [...b.typeOnly],
750
+ sites: b.sites,
751
+ };
752
+ }
753
+ //# sourceMappingURL=go-graph.js.map