@colbymchenry/codegraph-darwin-arm64 0.9.3 → 0.9.5

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 (208) hide show
  1. package/lib/dist/bin/codegraph.d.ts +3 -0
  2. package/lib/dist/bin/codegraph.d.ts.map +1 -1
  3. package/lib/dist/bin/codegraph.js +250 -0
  4. package/lib/dist/bin/codegraph.js.map +1 -1
  5. package/lib/dist/context/index.d.ts +13 -0
  6. package/lib/dist/context/index.d.ts.map +1 -1
  7. package/lib/dist/context/index.js +120 -1
  8. package/lib/dist/context/index.js.map +1 -1
  9. package/lib/dist/db/index.d.ts +18 -0
  10. package/lib/dist/db/index.d.ts.map +1 -1
  11. package/lib/dist/db/index.js +31 -0
  12. package/lib/dist/db/index.js.map +1 -1
  13. package/lib/dist/db/queries.d.ts +16 -0
  14. package/lib/dist/db/queries.d.ts.map +1 -1
  15. package/lib/dist/db/queries.js +80 -27
  16. package/lib/dist/db/queries.js.map +1 -1
  17. package/lib/dist/extraction/grammars.d.ts +6 -0
  18. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  19. package/lib/dist/extraction/grammars.js +31 -1
  20. package/lib/dist/extraction/grammars.js.map +1 -1
  21. package/lib/dist/extraction/index.d.ts +15 -2
  22. package/lib/dist/extraction/index.d.ts.map +1 -1
  23. package/lib/dist/extraction/index.js +170 -78
  24. package/lib/dist/extraction/index.js.map +1 -1
  25. package/lib/dist/extraction/languages/index.d.ts.map +1 -1
  26. package/lib/dist/extraction/languages/index.js +2 -0
  27. package/lib/dist/extraction/languages/index.js.map +1 -1
  28. package/lib/dist/extraction/languages/objc.d.ts +3 -0
  29. package/lib/dist/extraction/languages/objc.d.ts.map +1 -0
  30. package/lib/dist/extraction/languages/objc.js +133 -0
  31. package/lib/dist/extraction/languages/objc.js.map +1 -0
  32. package/lib/dist/extraction/tree-sitter-types.d.ts +4 -0
  33. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  34. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  35. package/lib/dist/extraction/tree-sitter.js +155 -9
  36. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  37. package/lib/dist/extraction/wasm-runtime-flags.d.ts +12 -0
  38. package/lib/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  39. package/lib/dist/extraction/wasm-runtime-flags.js +14 -2
  40. package/lib/dist/extraction/wasm-runtime-flags.js.map +1 -1
  41. package/lib/dist/graph/traversal.d.ts.map +1 -1
  42. package/lib/dist/graph/traversal.js +71 -36
  43. package/lib/dist/graph/traversal.js.map +1 -1
  44. package/lib/dist/index.d.ts +21 -2
  45. package/lib/dist/index.d.ts.map +1 -1
  46. package/lib/dist/index.js +42 -0
  47. package/lib/dist/index.js.map +1 -1
  48. package/lib/dist/installer/instructions-template.d.ts +2 -2
  49. package/lib/dist/installer/instructions-template.d.ts.map +1 -1
  50. package/lib/dist/installer/instructions-template.js +3 -2
  51. package/lib/dist/installer/instructions-template.js.map +1 -1
  52. package/lib/dist/mcp/daemon-paths.d.ts +46 -0
  53. package/lib/dist/mcp/daemon-paths.d.ts.map +1 -0
  54. package/lib/dist/mcp/daemon-paths.js +125 -0
  55. package/lib/dist/mcp/daemon-paths.js.map +1 -0
  56. package/lib/dist/mcp/daemon.d.ts +161 -0
  57. package/lib/dist/mcp/daemon.d.ts.map +1 -0
  58. package/lib/dist/mcp/daemon.js +403 -0
  59. package/lib/dist/mcp/daemon.js.map +1 -0
  60. package/lib/dist/mcp/engine.d.ts +100 -0
  61. package/lib/dist/mcp/engine.d.ts.map +1 -0
  62. package/lib/dist/mcp/engine.js +291 -0
  63. package/lib/dist/mcp/engine.js.map +1 -0
  64. package/lib/dist/mcp/index.d.ts +67 -52
  65. package/lib/dist/mcp/index.d.ts.map +1 -1
  66. package/lib/dist/mcp/index.js +347 -330
  67. package/lib/dist/mcp/index.js.map +1 -1
  68. package/lib/dist/mcp/proxy.d.ts +46 -0
  69. package/lib/dist/mcp/proxy.d.ts.map +1 -0
  70. package/lib/dist/mcp/proxy.js +276 -0
  71. package/lib/dist/mcp/proxy.js.map +1 -0
  72. package/lib/dist/mcp/server-instructions.d.ts +1 -1
  73. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  74. package/lib/dist/mcp/server-instructions.js +3 -1
  75. package/lib/dist/mcp/server-instructions.js.map +1 -1
  76. package/lib/dist/mcp/session.d.ts +67 -0
  77. package/lib/dist/mcp/session.d.ts.map +1 -0
  78. package/lib/dist/mcp/session.js +276 -0
  79. package/lib/dist/mcp/session.js.map +1 -0
  80. package/lib/dist/mcp/tools.d.ts +130 -2
  81. package/lib/dist/mcp/tools.d.ts.map +1 -1
  82. package/lib/dist/mcp/tools.js +902 -37
  83. package/lib/dist/mcp/tools.js.map +1 -1
  84. package/lib/dist/mcp/transport.d.ts +111 -29
  85. package/lib/dist/mcp/transport.d.ts.map +1 -1
  86. package/lib/dist/mcp/transport.js +181 -71
  87. package/lib/dist/mcp/transport.js.map +1 -1
  88. package/lib/dist/mcp/version.d.ts +19 -0
  89. package/lib/dist/mcp/version.d.ts.map +1 -0
  90. package/lib/dist/mcp/version.js +71 -0
  91. package/lib/dist/mcp/version.js.map +1 -0
  92. package/lib/dist/resolution/callback-synthesizer.d.ts +10 -0
  93. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  94. package/lib/dist/resolution/callback-synthesizer.js +847 -0
  95. package/lib/dist/resolution/callback-synthesizer.js.map +1 -0
  96. package/lib/dist/resolution/frameworks/csharp.d.ts.map +1 -1
  97. package/lib/dist/resolution/frameworks/csharp.js +36 -8
  98. package/lib/dist/resolution/frameworks/csharp.js.map +1 -1
  99. package/lib/dist/resolution/frameworks/drupal.d.ts.map +1 -1
  100. package/lib/dist/resolution/frameworks/drupal.js +44 -12
  101. package/lib/dist/resolution/frameworks/drupal.js.map +1 -1
  102. package/lib/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  103. package/lib/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  104. package/lib/dist/resolution/frameworks/expo-modules.js +143 -0
  105. package/lib/dist/resolution/frameworks/expo-modules.js.map +1 -0
  106. package/lib/dist/resolution/frameworks/express.d.ts.map +1 -1
  107. package/lib/dist/resolution/frameworks/express.js +102 -19
  108. package/lib/dist/resolution/frameworks/express.js.map +1 -1
  109. package/lib/dist/resolution/frameworks/fabric.d.ts +3 -0
  110. package/lib/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  111. package/lib/dist/resolution/frameworks/fabric.js +354 -0
  112. package/lib/dist/resolution/frameworks/fabric.js.map +1 -0
  113. package/lib/dist/resolution/frameworks/go.d.ts.map +1 -1
  114. package/lib/dist/resolution/frameworks/go.js +6 -3
  115. package/lib/dist/resolution/frameworks/go.js.map +1 -1
  116. package/lib/dist/resolution/frameworks/index.d.ts +5 -0
  117. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
  118. package/lib/dist/resolution/frameworks/index.js +25 -1
  119. package/lib/dist/resolution/frameworks/index.js.map +1 -1
  120. package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
  121. package/lib/dist/resolution/frameworks/java.js +70 -12
  122. package/lib/dist/resolution/frameworks/java.js.map +1 -1
  123. package/lib/dist/resolution/frameworks/laravel.d.ts.map +1 -1
  124. package/lib/dist/resolution/frameworks/laravel.js +17 -8
  125. package/lib/dist/resolution/frameworks/laravel.js.map +1 -1
  126. package/lib/dist/resolution/frameworks/play.d.ts +19 -0
  127. package/lib/dist/resolution/frameworks/play.d.ts.map +1 -0
  128. package/lib/dist/resolution/frameworks/play.js +111 -0
  129. package/lib/dist/resolution/frameworks/play.js.map +1 -0
  130. package/lib/dist/resolution/frameworks/python.d.ts.map +1 -1
  131. package/lib/dist/resolution/frameworks/python.js +134 -16
  132. package/lib/dist/resolution/frameworks/python.js.map +1 -1
  133. package/lib/dist/resolution/frameworks/react-native.d.ts +3 -0
  134. package/lib/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  135. package/lib/dist/resolution/frameworks/react-native.js +360 -0
  136. package/lib/dist/resolution/frameworks/react-native.js.map +1 -0
  137. package/lib/dist/resolution/frameworks/react.d.ts.map +1 -1
  138. package/lib/dist/resolution/frameworks/react.js +96 -3
  139. package/lib/dist/resolution/frameworks/react.js.map +1 -1
  140. package/lib/dist/resolution/frameworks/ruby.d.ts.map +1 -1
  141. package/lib/dist/resolution/frameworks/ruby.js +106 -2
  142. package/lib/dist/resolution/frameworks/ruby.js.map +1 -1
  143. package/lib/dist/resolution/frameworks/rust.d.ts.map +1 -1
  144. package/lib/dist/resolution/frameworks/rust.js +102 -5
  145. package/lib/dist/resolution/frameworks/rust.js.map +1 -1
  146. package/lib/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  147. package/lib/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  148. package/lib/dist/resolution/frameworks/swift-objc.js +252 -0
  149. package/lib/dist/resolution/frameworks/swift-objc.js.map +1 -0
  150. package/lib/dist/resolution/frameworks/swift.d.ts.map +1 -1
  151. package/lib/dist/resolution/frameworks/swift.js +30 -6
  152. package/lib/dist/resolution/frameworks/swift.js.map +1 -1
  153. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  154. package/lib/dist/resolution/import-resolver.js +1 -0
  155. package/lib/dist/resolution/import-resolver.js.map +1 -1
  156. package/lib/dist/resolution/index.d.ts.map +1 -1
  157. package/lib/dist/resolution/index.js +61 -9
  158. package/lib/dist/resolution/index.js.map +1 -1
  159. package/lib/dist/resolution/lru-cache.d.ts +24 -0
  160. package/lib/dist/resolution/lru-cache.d.ts.map +1 -0
  161. package/lib/dist/resolution/lru-cache.js +62 -0
  162. package/lib/dist/resolution/lru-cache.js.map +1 -0
  163. package/lib/dist/resolution/swift-objc-bridge.d.ts +134 -0
  164. package/lib/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  165. package/lib/dist/resolution/swift-objc-bridge.js +256 -0
  166. package/lib/dist/resolution/swift-objc-bridge.js.map +1 -0
  167. package/lib/dist/resolution/types.d.ts +8 -0
  168. package/lib/dist/resolution/types.d.ts.map +1 -1
  169. package/lib/dist/sync/index.d.ts +3 -1
  170. package/lib/dist/sync/index.d.ts.map +1 -1
  171. package/lib/dist/sync/index.js +7 -1
  172. package/lib/dist/sync/index.js.map +1 -1
  173. package/lib/dist/sync/watcher.d.ts +109 -7
  174. package/lib/dist/sync/watcher.d.ts.map +1 -1
  175. package/lib/dist/sync/watcher.js +215 -33
  176. package/lib/dist/sync/watcher.js.map +1 -1
  177. package/lib/dist/sync/worktree.d.ts +54 -0
  178. package/lib/dist/sync/worktree.d.ts.map +1 -0
  179. package/lib/dist/sync/worktree.js +136 -0
  180. package/lib/dist/sync/worktree.js.map +1 -0
  181. package/lib/dist/types.d.ts +1 -1
  182. package/lib/dist/types.d.ts.map +1 -1
  183. package/lib/dist/types.js +1 -0
  184. package/lib/dist/types.js.map +1 -1
  185. package/lib/dist/utils.js +1 -1
  186. package/lib/node_modules/.package-lock.json +29 -1
  187. package/lib/node_modules/chokidar/LICENSE +21 -0
  188. package/lib/node_modules/chokidar/README.md +305 -0
  189. package/lib/node_modules/chokidar/esm/handler.d.ts +90 -0
  190. package/lib/node_modules/chokidar/esm/handler.js +629 -0
  191. package/lib/node_modules/chokidar/esm/index.d.ts +215 -0
  192. package/lib/node_modules/chokidar/esm/index.js +798 -0
  193. package/lib/node_modules/chokidar/esm/package.json +1 -0
  194. package/lib/node_modules/chokidar/handler.d.ts +90 -0
  195. package/lib/node_modules/chokidar/handler.js +635 -0
  196. package/lib/node_modules/chokidar/index.d.ts +215 -0
  197. package/lib/node_modules/chokidar/index.js +804 -0
  198. package/lib/node_modules/chokidar/package.json +69 -0
  199. package/lib/node_modules/readdirp/LICENSE +21 -0
  200. package/lib/node_modules/readdirp/README.md +120 -0
  201. package/lib/node_modules/readdirp/esm/index.d.ts +108 -0
  202. package/lib/node_modules/readdirp/esm/index.js +257 -0
  203. package/lib/node_modules/readdirp/esm/package.json +1 -0
  204. package/lib/node_modules/readdirp/index.d.ts +108 -0
  205. package/lib/node_modules/readdirp/index.js +263 -0
  206. package/lib/node_modules/readdirp/package.json +70 -0
  207. package/lib/package.json +2 -1
  208. package/package.json +1 -1
@@ -0,0 +1,847 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
4
+ const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
5
+ const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
6
+ const MAX_CALLBACKS_PER_CHANNEL = 40;
7
+ const EVENT_FANOUT_CAP = 6; // skip events with more handlers/dispatchers than this (too generic without type info)
8
+ const ON_RE = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*(?:function\s+(\w+)|(?:this\.)?(\w+))/g;
9
+ const EMIT_RE = /\.(?:emit|fire|dispatchEvent)\(\s*['"]([^'"]+)['"]/g;
10
+ const SETSTATE_RE = /this\.setState\s*\(/;
11
+ const FLUTTER_SETSTATE_RE = /\bsetState\s*\(/; // Flutter: setState((){…}) / this.setState
12
+ const JSX_TAG_RE = /<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
13
+ const MAX_JSX_CHILDREN = 30;
14
+ // Vue SFC templates: kebab-case child components (<el-button> → ElButton) and
15
+ // event bindings (@click="fn" / v-on:click="fn"). PascalCase children (<VPNav/>)
16
+ // are already caught by JSX_TAG_RE via the SFC component node.
17
+ const VUE_KEBAB_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)[\s/>]/g;
18
+ const VUE_HANDLER_RE = /(?:@|v-on:)([a-zA-Z][\w-]*)(?:\.[\w]+)*\s*=\s*"([^"]+)"/g;
19
+ // Composable/hook destructure: `const { close: closeSidebar } = useSidebarControl()`.
20
+ // Captures the destructure body + the called composable; only `use*` calls qualify.
21
+ const VUE_DESTRUCTURE_RE = /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*(\w+)\s*\(/g;
22
+ function kebabToPascal(s) {
23
+ return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
24
+ }
25
+ function sliceLines(content, startLine, endLine) {
26
+ if (!startLine || !endLine)
27
+ return null;
28
+ return content.split('\n').slice(startLine - 1, endLine).join('\n');
29
+ }
30
+ function registrarField(src) {
31
+ const m = src.match(/this\.(\w+)\.(?:add|push|set)\(/);
32
+ return m ? m[1] : null;
33
+ }
34
+ function dispatcherField(src) {
35
+ const forOf = src.match(/\bof\s+(?:Array\.from\(\s*)?this\.(\w+)/);
36
+ if (forOf && /\b\w+\s*\(/.test(src))
37
+ return forOf[1];
38
+ const forEach = src.match(/this\.(\w+)\.forEach\(/);
39
+ if (forEach)
40
+ return forEach[1];
41
+ return null;
42
+ }
43
+ const FN_KINDS = new Set(['method', 'function', 'component']);
44
+ /** Innermost function/method node whose line range contains `line`. */
45
+ function enclosingFn(nodesInFile, line) {
46
+ let best = null;
47
+ for (const n of nodesInFile) {
48
+ if (!FN_KINDS.has(n.kind))
49
+ continue;
50
+ const end = n.endLine ?? n.startLine;
51
+ if (n.startLine <= line && end >= line) {
52
+ if (!best || n.startLine >= best.startLine)
53
+ best = n; // prefer the tightest (latest-starting) encloser
54
+ }
55
+ }
56
+ return best;
57
+ }
58
+ /** Phase 1: field-backed observer channels (registrar/dispatcher share a store). */
59
+ function fieldChannelEdges(queries, ctx) {
60
+ const candidates = [...queries.getNodesByKind('method'), ...queries.getNodesByKind('function')];
61
+ const registrars = [];
62
+ const dispatchers = [];
63
+ for (const m of candidates) {
64
+ const isReg = REGISTRAR_NAME.test(m.name);
65
+ const isDisp = DISPATCHER_NAME.test(m.name);
66
+ if (!isReg && !isDisp)
67
+ continue;
68
+ const content = ctx.readFile(m.filePath);
69
+ const src = content && sliceLines(content, m.startLine, m.endLine);
70
+ if (!src)
71
+ continue;
72
+ if (isReg) {
73
+ const f = registrarField(src);
74
+ if (f)
75
+ registrars.push({ node: m, field: f });
76
+ }
77
+ if (isDisp) {
78
+ const f = dispatcherField(src);
79
+ if (f)
80
+ dispatchers.push({ node: m, field: f });
81
+ }
82
+ }
83
+ const edges = [];
84
+ const seen = new Set();
85
+ for (const reg of registrars) {
86
+ const chDispatchers = dispatchers.filter((d) => d.node.filePath === reg.node.filePath && d.field === reg.field);
87
+ if (chDispatchers.length === 0)
88
+ continue;
89
+ const argRe = new RegExp(`${reg.node.name}\\s*\\(\\s*(?:this\\.)?(\\w+)`);
90
+ let added = 0;
91
+ for (const e of queries.getIncomingEdges(reg.node.id, ['calls'])) {
92
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
93
+ break;
94
+ if (!e.line)
95
+ continue;
96
+ const caller = queries.getNodeById(e.source);
97
+ if (!caller)
98
+ continue;
99
+ const line = ctx.readFile(caller.filePath)?.split('\n')[e.line - 1];
100
+ const am = line?.match(argRe);
101
+ if (!am)
102
+ continue;
103
+ const fn = ctx.getNodesByName(am[1]).find((n) => n.kind === 'method' || n.kind === 'function');
104
+ if (!fn)
105
+ continue;
106
+ for (const disp of chDispatchers) {
107
+ if (disp.node.id === fn.id)
108
+ continue;
109
+ const key = `${disp.node.id}>${fn.id}`;
110
+ if (seen.has(key))
111
+ continue;
112
+ seen.add(key);
113
+ edges.push({
114
+ source: disp.node.id, target: fn.id, kind: 'calls', line: disp.node.startLine,
115
+ provenance: 'heuristic',
116
+ metadata: {
117
+ synthesizedBy: 'callback', via: reg.node.name, field: reg.field,
118
+ // Where the callback was wired up (`scene.onUpdate(this.triggerRender)`).
119
+ // This is the #1 thing an agent reads/greps to explain the flow — surface
120
+ // it so node/trace/context can show it without a callers() + Read round-trip.
121
+ registeredAt: `${caller.filePath}:${e.line}`,
122
+ },
123
+ });
124
+ added++;
125
+ }
126
+ }
127
+ }
128
+ return edges;
129
+ }
130
+ /** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
131
+ function eventEmitterEdges(ctx) {
132
+ const emitsByEvent = new Map(); // event → dispatcher node ids
133
+ const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
134
+ for (const file of ctx.getAllFiles()) {
135
+ const content = ctx.readFile(file);
136
+ if (!content)
137
+ continue;
138
+ const hasEmit = content.includes('.emit(') || content.includes('.fire(') || content.includes('.dispatchEvent(');
139
+ const hasOn = content.includes('.on(') || content.includes('.once(') || content.includes('.addListener(');
140
+ if (!hasEmit && !hasOn)
141
+ continue;
142
+ const nodesInFile = ctx.getNodesInFile(file);
143
+ const lineOf = (idx) => content.slice(0, idx).split('\n').length;
144
+ if (hasEmit) {
145
+ EMIT_RE.lastIndex = 0;
146
+ let m;
147
+ while ((m = EMIT_RE.exec(content))) {
148
+ const disp = enclosingFn(nodesInFile, lineOf(m.index));
149
+ if (!disp)
150
+ continue;
151
+ const set = emitsByEvent.get(m[1]) ?? new Set();
152
+ set.add(disp.id);
153
+ emitsByEvent.set(m[1], set);
154
+ }
155
+ }
156
+ if (hasOn) {
157
+ ON_RE.lastIndex = 0;
158
+ let m;
159
+ while ((m = ON_RE.exec(content))) {
160
+ const handlerName = m[2] || m[3];
161
+ if (!handlerName)
162
+ continue;
163
+ const handler = ctx.getNodesByName(handlerName).find((n) => n.kind === 'function' || n.kind === 'method');
164
+ if (!handler)
165
+ continue;
166
+ const map = handlersByEvent.get(m[1]) ?? new Map();
167
+ map.set(handler.id, `${file}:${lineOf(m.index)}`);
168
+ handlersByEvent.set(m[1], map);
169
+ }
170
+ }
171
+ }
172
+ const edges = [];
173
+ const seen = new Set();
174
+ for (const [event, dispatchers] of emitsByEvent) {
175
+ const handlers = handlersByEvent.get(event);
176
+ if (!handlers)
177
+ continue;
178
+ // Precision guard: a generic event name with many handlers/dispatchers can't
179
+ // be matched without receiver-type info (Phase 3) — skip rather than over-link.
180
+ if (dispatchers.size > EVENT_FANOUT_CAP || handlers.size > EVENT_FANOUT_CAP)
181
+ continue;
182
+ for (const d of dispatchers)
183
+ for (const [h, registeredAt] of handlers) {
184
+ if (d === h)
185
+ continue;
186
+ const key = `${d}>${h}`;
187
+ if (seen.has(key))
188
+ continue;
189
+ seen.add(key);
190
+ edges.push({ source: d, target: h, kind: 'calls', provenance: 'heuristic', metadata: { synthesizedBy: 'event-emitter', event, registeredAt } });
191
+ }
192
+ }
193
+ return edges;
194
+ }
195
+ /**
196
+ * Phase 4: React class-component re-render. `this.setState(...)` re-runs the
197
+ * component's `render()`, but that hop is React-internal — no static edge — so a
198
+ * flow like "mutation → setState → canvas repaint" dead-ends at setState even
199
+ * though `render → getRenderableElements → …` is fully call-connected after it.
200
+ * Bridge it: for each class that has a `render` method, link every sibling method
201
+ * whose body calls `this.setState(` → `render`. The setState gate keeps this to
202
+ * React class components (a non-React class with a `render` method won't call
203
+ * `this.setState`). Over-approximation (all setState methods reach render) is
204
+ * accepted — it's reachability-correct, like the callback channels.
205
+ */
206
+ function reactRenderEdges(queries, ctx) {
207
+ const edges = [];
208
+ const seen = new Set();
209
+ for (const cls of queries.getNodesByKind('class')) {
210
+ const children = queries.getOutgoingEdges(cls.id, ['contains'])
211
+ .map((e) => queries.getNodeById(e.target))
212
+ .filter((n) => !!n && n.kind === 'method');
213
+ const render = children.find((n) => n.name === 'render');
214
+ if (!render)
215
+ continue;
216
+ let added = 0;
217
+ for (const m of children) {
218
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
219
+ break;
220
+ if (m.id === render.id)
221
+ continue;
222
+ const content = ctx.readFile(m.filePath);
223
+ const src = content && sliceLines(content, m.startLine, m.endLine);
224
+ if (!src || !SETSTATE_RE.test(src))
225
+ continue;
226
+ const key = `${m.id}>${render.id}`;
227
+ if (seen.has(key))
228
+ continue;
229
+ seen.add(key);
230
+ edges.push({
231
+ source: m.id, target: render.id, kind: 'calls', line: m.startLine,
232
+ provenance: 'heuristic',
233
+ metadata: { synthesizedBy: 'react-render', via: 'setState', registeredAt: `${render.filePath}:${render.startLine}` },
234
+ });
235
+ added++;
236
+ }
237
+ }
238
+ return edges;
239
+ }
240
+ /**
241
+ * Phase 4b: Flutter setState → build (the Dart analog of react-render). In a
242
+ * StatefulWidget's State class, `setState(() {…})` re-runs `build(context)`, but
243
+ * that hop is framework-internal (Flutter calls build), so a flow like
244
+ * "onPressed → _increment → setState → rebuilt UI" dead-ends at setState. Bridge
245
+ * it: for each Dart class with a `build` method, link every sibling method whose
246
+ * body calls `setState(` → `build`. The setState gate + `.dart` file keep this to
247
+ * Flutter State classes. Over-approximation accepted (reachability-correct).
248
+ */
249
+ function flutterBuildEdges(queries, ctx) {
250
+ const edges = [];
251
+ const seen = new Set();
252
+ for (const cls of queries.getNodesByKind('class')) {
253
+ const children = queries.getOutgoingEdges(cls.id, ['contains'])
254
+ .map((e) => queries.getNodeById(e.target))
255
+ .filter((n) => !!n && n.kind === 'method');
256
+ const build = children.find((n) => n.name === 'build');
257
+ if (!build || !build.filePath.endsWith('.dart'))
258
+ continue;
259
+ let added = 0;
260
+ for (const m of children) {
261
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
262
+ break;
263
+ if (m.id === build.id)
264
+ continue;
265
+ const content = ctx.readFile(m.filePath);
266
+ const src = content && sliceLines(content, m.startLine, m.endLine);
267
+ if (!src || !FLUTTER_SETSTATE_RE.test(src))
268
+ continue;
269
+ const key = `${m.id}>${build.id}`;
270
+ if (seen.has(key))
271
+ continue;
272
+ seen.add(key);
273
+ edges.push({
274
+ source: m.id, target: build.id, kind: 'calls', line: m.startLine,
275
+ provenance: 'heuristic',
276
+ metadata: { synthesizedBy: 'flutter-build', via: 'setState', registeredAt: `${build.filePath}:${build.startLine}` },
277
+ });
278
+ added++;
279
+ }
280
+ }
281
+ return edges;
282
+ }
283
+ /**
284
+ * Phase 4c: C++ virtual override. A call through a base/interface pointer
285
+ * (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
286
+ * but that hop is a vtable indirection — no static call edge — so a flow stops at
287
+ * the abstract base method. Bridge it like react-render: for each C++ class that
288
+ * `extends` a base, link each base method → the subclass method of the same name
289
+ * (the override), so trace/callees from the interface method reach the
290
+ * implementation(s). Over-approximation accepted (reachability-correct); capped
291
+ * per class and gated to C++ to avoid touching other languages' dispatch.
292
+ */
293
+ function cppOverrideEdges(queries) {
294
+ const edges = [];
295
+ const seen = new Set();
296
+ const methodsOf = (classId) => queries
297
+ .getOutgoingEdges(classId, ['contains'])
298
+ .map((e) => queries.getNodeById(e.target))
299
+ .filter((n) => !!n && n.kind === 'method');
300
+ for (const cls of queries.getNodesByKind('class')) {
301
+ const subMethods = methodsOf(cls.id).filter((n) => n.language === 'cpp');
302
+ if (subMethods.length === 0)
303
+ continue;
304
+ for (const ext of queries.getOutgoingEdges(cls.id, ['extends'])) {
305
+ const base = queries.getNodeById(ext.target);
306
+ if (!base || base.language !== 'cpp' || base.id === cls.id)
307
+ continue;
308
+ const baseMethods = new Map(methodsOf(base.id).map((m) => [m.name, m]));
309
+ let added = 0;
310
+ for (const m of subMethods) {
311
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
312
+ break;
313
+ const bm = baseMethods.get(m.name);
314
+ if (!bm || bm.id === m.id)
315
+ continue;
316
+ const key = `${bm.id}>${m.id}`;
317
+ if (seen.has(key))
318
+ continue;
319
+ seen.add(key);
320
+ edges.push({
321
+ source: bm.id,
322
+ target: m.id,
323
+ kind: 'calls',
324
+ line: bm.startLine,
325
+ provenance: 'heuristic',
326
+ metadata: { synthesizedBy: 'cpp-override', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
327
+ });
328
+ added++;
329
+ }
330
+ }
331
+ }
332
+ return edges;
333
+ }
334
+ /**
335
+ * Phase 5.5: interface / abstract dispatch (Java, Kotlin). A call through an
336
+ * injected interface (`@Autowired FooService svc; svc.list()`) or an abstract
337
+ * base dispatches at runtime to the implementing class's override — a vtable
338
+ * indirection with no static call edge — so a request→service flow stops at the
339
+ * interface method. Bridge it like cpp-override: for each class that
340
+ * `implements` an interface (or `extends` an abstract base), link each
341
+ * base/interface method → the class's same-name method (the override) so
342
+ * trace/callees reach the implementation. Over-approximation accepted
343
+ * (reachability-correct); capped per class, gated to JVM languages.
344
+ */
345
+ const IFACE_OVERRIDE_LANGS = new Set(['java', 'kotlin']);
346
+ function interfaceOverrideEdges(queries) {
347
+ const edges = [];
348
+ const seen = new Set();
349
+ const methodsOf = (classId) => queries
350
+ .getOutgoingEdges(classId, ['contains'])
351
+ .map((e) => queries.getNodeById(e.target))
352
+ .filter((n) => !!n && n.kind === 'method');
353
+ for (const cls of queries.getNodesByKind('class')) {
354
+ const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
355
+ if (implMethods.length === 0)
356
+ continue;
357
+ for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) {
358
+ const base = queries.getNodeById(sup.target);
359
+ if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
360
+ continue;
361
+ // Group impl methods by name to handle OVERLOADS: an interface `list()` and
362
+ // `list(params)` are distinct nodes and a call may resolve to either, so
363
+ // link every base overload → every same-name impl overload (keying by name
364
+ // alone would drop all but one and miss the resolved overload).
365
+ const implByName = new Map();
366
+ for (const m of implMethods) {
367
+ const arr = implByName.get(m.name);
368
+ if (arr)
369
+ arr.push(m);
370
+ else
371
+ implByName.set(m.name, [m]);
372
+ }
373
+ let added = 0;
374
+ for (const bm of methodsOf(base.id)) {
375
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
376
+ break;
377
+ for (const m of implByName.get(bm.name) ?? []) {
378
+ if (added >= MAX_CALLBACKS_PER_CHANNEL)
379
+ break;
380
+ if (bm.id === m.id)
381
+ continue;
382
+ const key = `${bm.id}>${m.id}`;
383
+ if (seen.has(key))
384
+ continue;
385
+ seen.add(key);
386
+ edges.push({
387
+ source: bm.id,
388
+ target: m.id,
389
+ kind: 'calls',
390
+ line: bm.startLine,
391
+ provenance: 'heuristic',
392
+ metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
393
+ });
394
+ added++;
395
+ }
396
+ }
397
+ }
398
+ }
399
+ return edges;
400
+ }
401
+ /**
402
+ * Phase 5: React JSX child rendering. A component that returns `<Child .../>`
403
+ * mounts Child — React calls it — but JSX instantiation isn't a static call edge,
404
+ * so a render tree (App.render → StaticCanvas → renderStaticScene) breaks at the
405
+ * JSX hop. Link parent → each capitalized JSX child it renders. File-oriented
406
+ * (read each JSX file once). Precision gate: the child name must resolve to a
407
+ * component/function/class node — TS generics like `Array<Foo>` resolve to a type
408
+ * (or nothing) and are dropped.
409
+ */
410
+ function reactJsxChildEdges(ctx) {
411
+ const edges = [];
412
+ const seen = new Set();
413
+ const PARENT_KINDS = new Set(['method', 'function', 'component']);
414
+ for (const file of ctx.getAllFiles()) {
415
+ const content = ctx.readFile(file);
416
+ if (!content || (!content.includes('</') && !content.includes('/>')))
417
+ continue; // JSX-file gate
418
+ const parents = ctx.getNodesInFile(file).filter((n) => PARENT_KINDS.has(n.kind));
419
+ for (const parent of parents) {
420
+ const src = sliceLines(content, parent.startLine, parent.endLine);
421
+ if (!src || (!src.includes('</') && !src.includes('/>')))
422
+ continue;
423
+ const names = new Set();
424
+ JSX_TAG_RE.lastIndex = 0;
425
+ let m;
426
+ while ((m = JSX_TAG_RE.exec(src)))
427
+ names.add(m[1]);
428
+ let added = 0;
429
+ for (const name of names) {
430
+ if (added >= MAX_JSX_CHILDREN)
431
+ break;
432
+ const child = ctx.getNodesByName(name).find((n) => n.kind === 'component' || n.kind === 'function' || n.kind === 'class');
433
+ if (!child || child.id === parent.id)
434
+ continue;
435
+ const key = `${parent.id}>${child.id}`;
436
+ if (seen.has(key))
437
+ continue;
438
+ seen.add(key);
439
+ edges.push({
440
+ source: parent.id, target: child.id, kind: 'calls', line: parent.startLine,
441
+ provenance: 'heuristic',
442
+ metadata: { synthesizedBy: 'jsx-render', via: name },
443
+ });
444
+ added++;
445
+ }
446
+ }
447
+ }
448
+ return edges;
449
+ }
450
+ /**
451
+ * Phase 6: Vue SFC templates. The `.vue` extractor only parses `<script>`, so
452
+ * template usage is invisible — child components and event handlers used ONLY in
453
+ * the template have no edge to them. PascalCase children (`<VPNav/>`) are already
454
+ * caught by reactJsxChildEdges (which scans the SFC component node), so this adds
455
+ * the two Vue-specific shapes:
456
+ * - kebab-case children: `<el-button>` → `ElButton` component (renders).
457
+ * - event bindings: `@click="onClick"` / `v-on:submit="save"` → handler method.
458
+ * Scoped to the `<template>` block of `.vue` files; resolution gate (kebab→
459
+ * component, handler→function/method) keeps precision; inline arrows / `$emit`
460
+ * skipped.
461
+ */
462
+ function vueTemplateEdges(ctx) {
463
+ const edges = [];
464
+ const seen = new Set();
465
+ const COMPONENT_KINDS = new Set(['component', 'function', 'class']);
466
+ const HANDLER_KINDS = new Set(['method', 'function']);
467
+ // A composable's returned member may be a fn (`function close(){}`) or an
468
+ // arrow assigned to a const (`const close = () => {}`).
469
+ const RETURN_KINDS = new Set(['method', 'function', 'variable', 'constant']);
470
+ for (const file of ctx.getAllFiles()) {
471
+ if (!file.endsWith('.vue'))
472
+ continue;
473
+ const content = ctx.readFile(file);
474
+ const tpl = content && content.match(/<template[^>]*>([\s\S]*)<\/template>/i)?.[1];
475
+ if (!tpl)
476
+ continue;
477
+ const comp = ctx.getNodesInFile(file).find((n) => n.kind === 'component');
478
+ if (!comp)
479
+ continue;
480
+ // Composable-destructure map: alias → { composable, key }. Lets us resolve a
481
+ // template handler that isn't a local function but a destructured composable
482
+ // return (`@click="closeSidebar"` ← `const { close: closeSidebar } = useSidebarControl()`).
483
+ const script = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? '';
484
+ const destructured = new Map();
485
+ VUE_DESTRUCTURE_RE.lastIndex = 0;
486
+ let dm;
487
+ while ((dm = VUE_DESTRUCTURE_RE.exec(script))) {
488
+ if (!/^use[A-Z]/.test(dm[2]))
489
+ continue; // composables / hooks only
490
+ for (const part of dm[1].split(',')) {
491
+ const pm = part.trim().match(/^(\w+)\s*(?::\s*(\w+))?$/); // key | key: alias
492
+ if (pm)
493
+ destructured.set(pm[2] || pm[1], { composable: dm[2], key: pm[1] });
494
+ }
495
+ }
496
+ let added = 0;
497
+ const addEdge = (target, meta) => {
498
+ if (added >= MAX_JSX_CHILDREN || !target || target.id === comp.id)
499
+ return;
500
+ const k = `${comp.id}>${target.id}>${meta.synthesizedBy}`;
501
+ if (seen.has(k))
502
+ return;
503
+ seen.add(k);
504
+ edges.push({ source: comp.id, target: target.id, kind: 'calls', line: comp.startLine, provenance: 'heuristic', metadata: meta });
505
+ added++;
506
+ };
507
+ // Prefer a target in THIS SFC (handlers live in the same file's script) —
508
+ // avoids cross-file mis-match when a name repeats across a monorepo.
509
+ const resolve = (name, kinds) => {
510
+ const matches = ctx.getNodesByName(name).filter((n) => kinds.has(n.kind));
511
+ return matches.find((n) => n.filePath === file) ?? matches[0];
512
+ };
513
+ let m;
514
+ VUE_KEBAB_RE.lastIndex = 0;
515
+ while ((m = VUE_KEBAB_RE.exec(tpl)))
516
+ addEdge(resolve(kebabToPascal(m[1]), COMPONENT_KINDS), { synthesizedBy: 'jsx-render', via: m[1] });
517
+ VUE_HANDLER_RE.lastIndex = 0;
518
+ while ((m = VUE_HANDLER_RE.exec(tpl))) {
519
+ const event = m[1];
520
+ const expr = m[2].trim();
521
+ if (expr.includes('=>') || expr.startsWith('$'))
522
+ continue; // inline arrow / $emit
523
+ const name = expr.match(/^([A-Za-z_]\w*)/)?.[1];
524
+ if (!name)
525
+ continue;
526
+ const direct = resolve(name, HANDLER_KINDS);
527
+ if (direct) {
528
+ addEdge(direct, { synthesizedBy: 'vue-handler', event });
529
+ continue;
530
+ }
531
+ // Composable-destructure handler → resolve to the composable's returned fn.
532
+ const d = destructured.get(name);
533
+ if (!d)
534
+ continue;
535
+ const composable = resolve(d.composable, HANDLER_KINDS);
536
+ // Resolve to the SPECIFIC returned member (e.g. `close`) defined in the
537
+ // composable's file. No fallback to the composable itself — the component
538
+ // already has a static `useX()` call edge, so that would just be redundant
539
+ // and less precise.
540
+ const keyFn = composable
541
+ ? ctx.getNodesByName(d.key).find((n) => RETURN_KINDS.has(n.kind) && n.filePath === composable.filePath)
542
+ : undefined;
543
+ if (keyFn)
544
+ addEdge(keyFn, { synthesizedBy: 'vue-handler', event, via: d.composable });
545
+ }
546
+ }
547
+ return edges;
548
+ }
549
+ /**
550
+ * React Native cross-language event channel (Phase 3 of the mixed-iOS/RN
551
+ * bridging effort). Same shape as `eventEmitterEdges` but cross-language:
552
+ *
553
+ * Native (ObjC, on RCTEventEmitter subclass):
554
+ * [self sendEventWithName:@"locationUpdate" body:@{...}];
555
+ *
556
+ * Native (Java/Kotlin, via the JS module dispatcher):
557
+ * emitter.emit("locationUpdate", body);
558
+ * reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("locationUpdate", body);
559
+ *
560
+ * JS (subscriber):
561
+ * new NativeEventEmitter(NativeModules.Geo).addListener("locationUpdate", handler);
562
+ * DeviceEventEmitter.addListener("locationUpdate", handler);
563
+ *
564
+ * Synthesize: native dispatch site → JS handler, keyed by the literal
565
+ * event name. Only matches NAMED handlers (the existing `ON_RE` named-
566
+ * capture form). Inline arrow handlers like `addListener('x', d => …)`
567
+ * aren't named at extraction time and would need link-through-body
568
+ * support; matches the deliberate scope of the in-language synthesizer.
569
+ *
570
+ * Provenance `'heuristic'`, synthesizedBy `'rn-event-channel'`.
571
+ */
572
+ // ObjC's `[self sendEventWithName:@"X" body:...]` shape (bracket syntax,
573
+ // `@` string literals).
574
+ const RN_OBJC_SEND_RE = /\bsendEventWithName\s*:\s*@"([^"]+)"/g;
575
+ // Swift's `sendEvent(withName: "X", body: ...)` shape — same RCTEventEmitter
576
+ // method, different call syntax. Both Objective-C and Swift subclass
577
+ // RCTEventEmitter so this catches the Swift-side equivalent emission sites
578
+ // (e.g. RNFusedLocation.swift's `sendEvent(withName: "geolocationDidChange",
579
+ // body: locationData)`).
580
+ const RN_SWIFT_SEND_RE = /\bsendEvent\s*\(\s*withName\s*:\s*"([^"]+)"/g;
581
+ // JVM-side emitter calls: `emitter.emit("X", body)`. Matches both Java
582
+ // and Kotlin syntax because the call form is identical. Restricted to
583
+ // JVM source files in the consumer so we don't re-process JS emits
584
+ // (which `eventEmitterEdges` already handles).
585
+ const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
586
+ function rnEventEdges(ctx) {
587
+ // Native dispatchers (source = the native method whose body sends the
588
+ // event) and JS handlers (target = the function/method registered as
589
+ // the listener) keyed by event name.
590
+ const nativeDispatchersByEvent = new Map();
591
+ const jsHandlersByEvent = new Map();
592
+ for (const file of ctx.getAllFiles()) {
593
+ const content = ctx.readFile(file);
594
+ if (!content)
595
+ continue;
596
+ const nodesInFile = ctx.getNodesInFile(file);
597
+ const lineOf = (idx) => content.slice(0, idx).split('\n').length;
598
+ const addDispatcher = (event, line) => {
599
+ const disp = enclosingFn(nodesInFile, line);
600
+ if (!disp)
601
+ return;
602
+ const set = nativeDispatchersByEvent.get(event) ?? new Set();
603
+ set.add(disp.id);
604
+ nativeDispatchersByEvent.set(event, set);
605
+ };
606
+ // ObjC side: `sendEventWithName:@"X"` only fires inside `.m`/`.mm`
607
+ // files (RCTEventEmitter subclasses).
608
+ if (file.endsWith('.m') || file.endsWith('.mm')) {
609
+ RN_OBJC_SEND_RE.lastIndex = 0;
610
+ let m;
611
+ while ((m = RN_OBJC_SEND_RE.exec(content))) {
612
+ if (m[1])
613
+ addDispatcher(m[1], lineOf(m.index));
614
+ }
615
+ }
616
+ // Swift side: same RCTEventEmitter method, parens/named-args syntax.
617
+ if (file.endsWith('.swift')) {
618
+ RN_SWIFT_SEND_RE.lastIndex = 0;
619
+ let m;
620
+ while ((m = RN_SWIFT_SEND_RE.exec(content))) {
621
+ if (m[1])
622
+ addDispatcher(m[1], lineOf(m.index));
623
+ }
624
+ }
625
+ // JVM side: `.emit("X", …)` in Java/Kotlin. (We pattern-match
626
+ // anywhere in the file; the JS in-language path uses a separate
627
+ // emitter object pattern and is already handled by eventEmitterEdges.)
628
+ if (file.endsWith('.java') || file.endsWith('.kt')) {
629
+ RN_JVM_EMIT_RE.lastIndex = 0;
630
+ let m;
631
+ while ((m = RN_JVM_EMIT_RE.exec(content))) {
632
+ if (m[1])
633
+ addDispatcher(m[1], lineOf(m.index));
634
+ }
635
+ }
636
+ // JS subscribers (.addListener("X", handler)). Restrict to JS-family
637
+ // files so a native file's `addListener:` (the ObjC method) doesn't
638
+ // get mistaken for a JS subscription — they're entirely different
639
+ // things despite sharing a name.
640
+ if (file.endsWith('.js') ||
641
+ file.endsWith('.jsx') ||
642
+ file.endsWith('.ts') ||
643
+ file.endsWith('.tsx') ||
644
+ file.endsWith('.mjs') ||
645
+ file.endsWith('.cjs')) {
646
+ // Match BOTH the named-handler form (`.addListener('x', fn)`) and
647
+ // an unnamed-handler form (`.addListener('x', listener)` where
648
+ // `listener` is a parameter — common in RN wrapper APIs like
649
+ // RNFirebase's `messaging().onMessageReceived(listener)`). For the
650
+ // unnamed case we attribute the subscription to the ENCLOSING JS
651
+ // function (the abstraction layer), giving a reachability-correct
652
+ // hop even when the actual user-side handler lives one call up.
653
+ const ADDLISTENER_ANY = /\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s*([A-Za-z_][\w.]*)/g;
654
+ ADDLISTENER_ANY.lastIndex = 0;
655
+ let m;
656
+ while ((m = ADDLISTENER_ANY.exec(content))) {
657
+ const event = m[1];
658
+ const arg = m[2];
659
+ if (!event || !arg)
660
+ continue;
661
+ const bareName = arg.includes('.') ? arg.slice(arg.lastIndexOf('.') + 1) : arg;
662
+ // Try a named-symbol match first (matches the in-language semantic).
663
+ const namedHandler = ctx
664
+ .getNodesByName(bareName)
665
+ .find((n) => n.kind === 'function' || n.kind === 'method');
666
+ let targetId = namedHandler?.id ?? null;
667
+ if (!targetId) {
668
+ // Fall back to the enclosing function — the subscribe-wrapper
669
+ // pattern means the event fires THROUGH this function on its
670
+ // way to user code. Reachability-correct attribution.
671
+ const enclosing = enclosingFn(nodesInFile, lineOf(m.index));
672
+ targetId = enclosing?.id ?? null;
673
+ }
674
+ if (!targetId) {
675
+ // Broader fallback for JS object-literal API shape
676
+ // (`const Foo = { watchX(...) { … addListener(...) … } }`):
677
+ // method shorthand inside an object literal isn't extracted
678
+ // as a method node, so enclosingFn returns null. Attribute to
679
+ // the smallest enclosing `constant` / `variable` node — that's
680
+ // the API surface a downstream caller would `import` and
681
+ // invoke. Reachability-correct.
682
+ const line = lineOf(m.index);
683
+ let smallest = null;
684
+ for (const n of nodesInFile) {
685
+ if (n.kind !== 'constant' && n.kind !== 'variable')
686
+ continue;
687
+ const end = n.endLine ?? n.startLine;
688
+ if (n.startLine <= line && end >= line) {
689
+ if (!smallest || n.startLine >= smallest.startLine)
690
+ smallest = n;
691
+ }
692
+ }
693
+ targetId = smallest?.id ?? null;
694
+ }
695
+ if (!targetId)
696
+ continue;
697
+ const map = jsHandlersByEvent.get(event) ?? new Map();
698
+ map.set(targetId, `${file}:${lineOf(m.index)}`);
699
+ jsHandlersByEvent.set(event, map);
700
+ }
701
+ }
702
+ }
703
+ const edges = [];
704
+ const seen = new Set();
705
+ for (const [event, dispatchers] of nativeDispatchersByEvent) {
706
+ const handlers = jsHandlersByEvent.get(event);
707
+ if (!handlers)
708
+ continue;
709
+ // Same fan-out guard as the in-language channel: generic event names
710
+ // (e.g. 'change', 'error', 'data') with many handlers/dispatchers
711
+ // can't be matched precisely without receiver-type info.
712
+ if (dispatchers.size > EVENT_FANOUT_CAP || handlers.size > EVENT_FANOUT_CAP)
713
+ continue;
714
+ for (const d of dispatchers) {
715
+ for (const [h, registeredAt] of handlers) {
716
+ if (d === h)
717
+ continue;
718
+ const key = `${d}>${h}`;
719
+ if (seen.has(key))
720
+ continue;
721
+ seen.add(key);
722
+ edges.push({
723
+ source: d,
724
+ target: h,
725
+ kind: 'calls',
726
+ provenance: 'heuristic',
727
+ metadata: { synthesizedBy: 'rn-event-channel', event, registeredAt },
728
+ });
729
+ }
730
+ }
731
+ }
732
+ return edges;
733
+ }
734
+ /**
735
+ * Phase 6 — React Native Fabric/Codegen view component bridge.
736
+ *
737
+ * The Fabric framework extractor (`frameworks/fabric.ts`) emits
738
+ * `component` nodes named after the JS-visible component (e.g.
739
+ * `RNSScreenStack`) from each `codegenNativeComponent<Props>('Name')`
740
+ * spec declaration. The native implementation lives in an ObjC++/.mm or
741
+ * Kotlin/Java class whose name follows one of RN's conventions:
742
+ *
743
+ * - Exact: `RNSScreenStack`
744
+ * - With suffix: `RNSScreenStackView`, `RNSScreenStackViewManager`,
745
+ * `RNSScreenStackComponentView`, `RNSScreenStackManager`
746
+ *
747
+ * This synthesizer walks every Fabric component node and looks for a
748
+ * native class matching one of those names; when found, emits a
749
+ * `calls` edge `component → native class` (provenance `'heuristic'`,
750
+ * `synthesizedBy:'fabric-native-impl'`) so trace from JSX usage of the
751
+ * component continues into native.
752
+ *
753
+ * The convention-based suffix lookup is precise: there's no name
754
+ * collision in RN view-manager codebases by design (Codegen output would
755
+ * conflict otherwise).
756
+ */
757
+ const FABRIC_NATIVE_SUFFIXES = ['', 'View', 'ViewManager', 'ComponentView', 'Manager'];
758
+ function fabricNativeImplEdges(ctx) {
759
+ const edges = [];
760
+ const seen = new Set();
761
+ // The Fabric extractor IDs are prefixed `fabric-component:` so we can
762
+ // filter to just those without iterating all `component` nodes.
763
+ const components = ctx.getNodesByKind('component').filter((n) => n.id.startsWith('fabric-component:'));
764
+ if (components.length === 0)
765
+ return edges;
766
+ // Pre-index native classes by name for O(1) lookup.
767
+ const nativeClassesByName = new Map();
768
+ for (const n of ctx.getNodesByKind('class')) {
769
+ if (n.language !== 'objc' && n.language !== 'kotlin' && n.language !== 'java' && n.language !== 'cpp')
770
+ continue;
771
+ const arr = nativeClassesByName.get(n.name);
772
+ if (arr)
773
+ arr.push(n);
774
+ else
775
+ nativeClassesByName.set(n.name, [n]);
776
+ }
777
+ for (const component of components) {
778
+ for (const suffix of FABRIC_NATIVE_SUFFIXES) {
779
+ const candidate = component.name + suffix;
780
+ const matches = nativeClassesByName.get(candidate);
781
+ if (!matches || matches.length === 0)
782
+ continue;
783
+ // Link the component node to every matching native class (iOS +
784
+ // Android each have one).
785
+ for (const native of matches) {
786
+ const key = `${component.id}>${native.id}`;
787
+ if (seen.has(key))
788
+ continue;
789
+ seen.add(key);
790
+ edges.push({
791
+ source: component.id,
792
+ target: native.id,
793
+ kind: 'calls',
794
+ provenance: 'heuristic',
795
+ metadata: {
796
+ synthesizedBy: 'fabric-native-impl',
797
+ viaSuffix: suffix || '(exact)',
798
+ componentName: component.name,
799
+ },
800
+ });
801
+ }
802
+ }
803
+ }
804
+ return edges;
805
+ }
806
+ /**
807
+ * Synthesize dispatcher→callback edges (field observers + EventEmitters +
808
+ * React re-render + JSX children + Vue templates + RN event channel +
809
+ * Fabric native-impl). Returns the count added. Never throws into
810
+ * indexing — callers wrap in try/catch.
811
+ */
812
+ function synthesizeCallbackEdges(queries, ctx) {
813
+ const fieldEdges = fieldChannelEdges(queries, ctx);
814
+ const emitterEdges = eventEmitterEdges(ctx);
815
+ const renderEdges = reactRenderEdges(queries, ctx);
816
+ const jsxEdges = reactJsxChildEdges(ctx);
817
+ const vueEdges = vueTemplateEdges(ctx);
818
+ const flutterEdges = flutterBuildEdges(queries, ctx);
819
+ const cppEdges = cppOverrideEdges(queries);
820
+ const ifaceEdges = interfaceOverrideEdges(queries);
821
+ const rnEventEdgesList = rnEventEdges(ctx);
822
+ const fabricNativeEdges = fabricNativeImplEdges(ctx);
823
+ const merged = [];
824
+ const seen = new Set();
825
+ for (const e of [
826
+ ...fieldEdges,
827
+ ...emitterEdges,
828
+ ...renderEdges,
829
+ ...jsxEdges,
830
+ ...vueEdges,
831
+ ...flutterEdges,
832
+ ...cppEdges,
833
+ ...ifaceEdges,
834
+ ...rnEventEdgesList,
835
+ ...fabricNativeEdges,
836
+ ]) {
837
+ const key = `${e.source}>${e.target}`;
838
+ if (seen.has(key))
839
+ continue;
840
+ seen.add(key);
841
+ merged.push(e);
842
+ }
843
+ if (merged.length > 0)
844
+ queries.insertEdges(merged);
845
+ return merged.length;
846
+ }
847
+ //# sourceMappingURL=callback-synthesizer.js.map