homegraph 1.2.0 → 1.4.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 (284) hide show
  1. package/dist/bin/homegraph.js +3 -0
  2. package/dist/bin/homegraph.js.map +1 -1
  3. package/dist/context/index.d.ts.map +1 -1
  4. package/dist/context/index.js +85 -0
  5. package/dist/context/index.js.map +1 -1
  6. package/dist/db/index.d.ts +64 -5
  7. package/dist/db/index.d.ts.map +1 -1
  8. package/dist/db/index.js +178 -14
  9. package/dist/db/index.js.map +1 -1
  10. package/dist/db/migrations.d.ts +1 -1
  11. package/dist/db/migrations.d.ts.map +1 -1
  12. package/dist/db/migrations.js +19 -1
  13. package/dist/db/migrations.js.map +1 -1
  14. package/dist/db/queries.d.ts +117 -2
  15. package/dist/db/queries.d.ts.map +1 -1
  16. package/dist/db/queries.js +299 -6
  17. package/dist/db/queries.js.map +1 -1
  18. package/dist/db/schema.sql +32 -1
  19. package/dist/db/wal-valve.d.ts +106 -0
  20. package/dist/db/wal-valve.d.ts.map +1 -0
  21. package/dist/db/wal-valve.js +208 -0
  22. package/dist/db/wal-valve.js.map +1 -0
  23. package/dist/directory.d.ts +9 -5
  24. package/dist/directory.d.ts.map +1 -1
  25. package/dist/directory.js +210 -19
  26. package/dist/directory.js.map +1 -1
  27. package/dist/extraction/cfml-extractor.d.ts +107 -0
  28. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  29. package/dist/extraction/cfml-extractor.js +494 -0
  30. package/dist/extraction/cfml-extractor.js.map +1 -0
  31. package/dist/extraction/grammars.d.ts +25 -1
  32. package/dist/extraction/grammars.d.ts.map +1 -1
  33. package/dist/extraction/grammars.js +177 -24
  34. package/dist/extraction/grammars.js.map +1 -1
  35. package/dist/extraction/index.d.ts +43 -4
  36. package/dist/extraction/index.d.ts.map +1 -1
  37. package/dist/extraction/index.js +308 -5
  38. package/dist/extraction/index.js.map +1 -1
  39. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  40. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  41. package/dist/extraction/languages/c-cpp.js +369 -4
  42. package/dist/extraction/languages/c-cpp.js.map +1 -1
  43. package/dist/extraction/languages/cfquery.d.ts +12 -0
  44. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  45. package/dist/extraction/languages/cfquery.js +28 -0
  46. package/dist/extraction/languages/cfquery.js.map +1 -0
  47. package/dist/extraction/languages/cfscript.d.ts +3 -0
  48. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  49. package/dist/extraction/languages/cfscript.js +73 -0
  50. package/dist/extraction/languages/cfscript.js.map +1 -0
  51. package/dist/extraction/languages/cobol.d.ts +33 -0
  52. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  53. package/dist/extraction/languages/cobol.js +499 -0
  54. package/dist/extraction/languages/cobol.js.map +1 -0
  55. package/dist/extraction/languages/erlang.d.ts +3 -0
  56. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  57. package/dist/extraction/languages/erlang.js +350 -0
  58. package/dist/extraction/languages/erlang.js.map +1 -0
  59. package/dist/extraction/languages/index.d.ts.map +1 -1
  60. package/dist/extraction/languages/index.js +16 -0
  61. package/dist/extraction/languages/index.js.map +1 -1
  62. package/dist/extraction/languages/nix.d.ts +3 -0
  63. package/dist/extraction/languages/nix.d.ts.map +1 -0
  64. package/dist/extraction/languages/nix.js +294 -0
  65. package/dist/extraction/languages/nix.js.map +1 -0
  66. package/dist/extraction/languages/solidity.d.ts +3 -0
  67. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  68. package/dist/extraction/languages/solidity.js +293 -0
  69. package/dist/extraction/languages/solidity.js.map +1 -0
  70. package/dist/extraction/languages/terraform.d.ts +3 -0
  71. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  72. package/dist/extraction/languages/terraform.js +641 -0
  73. package/dist/extraction/languages/terraform.js.map +1 -0
  74. package/dist/extraction/languages/vbnet.d.ts +11 -0
  75. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  76. package/dist/extraction/languages/vbnet.js +141 -0
  77. package/dist/extraction/languages/vbnet.js.map +1 -0
  78. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  79. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  80. package/dist/extraction/mybatis-extractor.js +140 -38
  81. package/dist/extraction/mybatis-extractor.js.map +1 -1
  82. package/dist/extraction/parse-pool.d.ts +27 -1
  83. package/dist/extraction/parse-pool.d.ts.map +1 -1
  84. package/dist/extraction/parse-pool.js +75 -7
  85. package/dist/extraction/parse-pool.js.map +1 -1
  86. package/dist/extraction/parse-worker.js +9 -2
  87. package/dist/extraction/parse-worker.js.map +1 -1
  88. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  89. package/dist/extraction/tree-sitter-helpers.js +1 -0
  90. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  91. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  92. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  93. package/dist/extraction/tree-sitter.d.ts +38 -0
  94. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  95. package/dist/extraction/tree-sitter.js +754 -10
  96. package/dist/extraction/tree-sitter.js.map +1 -1
  97. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  98. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  99. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  100. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  101. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  102. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  103. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  104. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  105. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  106. package/dist/graph/traversal.d.ts.map +1 -1
  107. package/dist/graph/traversal.js +1 -1
  108. package/dist/graph/traversal.js.map +1 -1
  109. package/dist/index.d.ts +60 -2
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +348 -5
  112. package/dist/index.js.map +1 -1
  113. package/dist/installer/index.d.ts +42 -0
  114. package/dist/installer/index.d.ts.map +1 -1
  115. package/dist/installer/index.js +97 -2
  116. package/dist/installer/index.js.map +1 -1
  117. package/dist/mcp/daemon.d.ts +25 -3
  118. package/dist/mcp/daemon.d.ts.map +1 -1
  119. package/dist/mcp/daemon.js +62 -8
  120. package/dist/mcp/daemon.js.map +1 -1
  121. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  122. package/dist/mcp/dynamic-boundaries.js +2 -1
  123. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  124. package/dist/mcp/early-ppid.d.ts +26 -0
  125. package/dist/mcp/early-ppid.d.ts.map +1 -0
  126. package/dist/mcp/early-ppid.js +29 -0
  127. package/dist/mcp/early-ppid.js.map +1 -0
  128. package/dist/mcp/engine.d.ts +4 -5
  129. package/dist/mcp/engine.d.ts.map +1 -1
  130. package/dist/mcp/engine.js +27 -1
  131. package/dist/mcp/engine.js.map +1 -1
  132. package/dist/mcp/index.d.ts.map +1 -1
  133. package/dist/mcp/index.js +41 -6
  134. package/dist/mcp/index.js.map +1 -1
  135. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  136. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  137. package/dist/mcp/liveness-watchdog.js +73 -13
  138. package/dist/mcp/liveness-watchdog.js.map +1 -1
  139. package/dist/mcp/memory-budget.d.ts +30 -0
  140. package/dist/mcp/memory-budget.d.ts.map +1 -0
  141. package/dist/mcp/memory-budget.js +123 -0
  142. package/dist/mcp/memory-budget.js.map +1 -0
  143. package/dist/mcp/proxy.d.ts.map +1 -1
  144. package/dist/mcp/proxy.js +88 -4
  145. package/dist/mcp/proxy.js.map +1 -1
  146. package/dist/mcp/query-cache.d.ts +35 -4
  147. package/dist/mcp/query-cache.d.ts.map +1 -1
  148. package/dist/mcp/query-cache.js +89 -19
  149. package/dist/mcp/query-cache.js.map +1 -1
  150. package/dist/mcp/query-pool.d.ts +46 -12
  151. package/dist/mcp/query-pool.d.ts.map +1 -1
  152. package/dist/mcp/query-pool.js +100 -23
  153. package/dist/mcp/query-pool.js.map +1 -1
  154. package/dist/mcp/server-instructions.d.ts +4 -27
  155. package/dist/mcp/server-instructions.d.ts.map +1 -1
  156. package/dist/mcp/server-instructions.js +39 -84
  157. package/dist/mcp/server-instructions.js.map +1 -1
  158. package/dist/mcp/session.d.ts +14 -0
  159. package/dist/mcp/session.d.ts.map +1 -1
  160. package/dist/mcp/session.js +28 -1
  161. package/dist/mcp/session.js.map +1 -1
  162. package/dist/mcp/startup-handshake.d.ts +44 -0
  163. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  164. package/dist/mcp/startup-handshake.js +73 -0
  165. package/dist/mcp/startup-handshake.js.map +1 -0
  166. package/dist/mcp/tools.d.ts +104 -4
  167. package/dist/mcp/tools.d.ts.map +1 -1
  168. package/dist/mcp/tools.js +2390 -191
  169. package/dist/mcp/tools.js.map +1 -1
  170. package/dist/mcp/transport.d.ts.map +1 -1
  171. package/dist/mcp/transport.js +18 -0
  172. package/dist/mcp/transport.js.map +1 -1
  173. package/dist/project-config.d.ts +38 -0
  174. package/dist/project-config.d.ts.map +1 -1
  175. package/dist/project-config.js +101 -2
  176. package/dist/project-config.js.map +1 -1
  177. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  178. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  179. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  180. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  181. package/dist/resolution/callback-synthesizer.d.ts +9 -1
  182. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  183. package/dist/resolution/callback-synthesizer.js +1164 -176
  184. package/dist/resolution/callback-synthesizer.js.map +1 -1
  185. package/dist/resolution/frameworks/cics.d.ts +20 -0
  186. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  187. package/dist/resolution/frameworks/cics.js +90 -0
  188. package/dist/resolution/frameworks/cics.js.map +1 -0
  189. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  190. package/dist/resolution/frameworks/index.js +3 -0
  191. package/dist/resolution/frameworks/index.js.map +1 -1
  192. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  193. package/dist/resolution/frameworks/java.js +14 -6
  194. package/dist/resolution/frameworks/java.js.map +1 -1
  195. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  196. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  197. package/dist/resolution/frameworks/terraform.js +277 -0
  198. package/dist/resolution/frameworks/terraform.js.map +1 -0
  199. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  200. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  201. package/dist/resolution/goframe-synthesizer.js +8 -3
  202. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  203. package/dist/resolution/import-resolver.d.ts +7 -0
  204. package/dist/resolution/import-resolver.d.ts.map +1 -1
  205. package/dist/resolution/import-resolver.js +129 -4
  206. package/dist/resolution/import-resolver.js.map +1 -1
  207. package/dist/resolution/index.d.ts +60 -7
  208. package/dist/resolution/index.d.ts.map +1 -1
  209. package/dist/resolution/index.js +430 -80
  210. package/dist/resolution/index.js.map +1 -1
  211. package/dist/resolution/name-matcher.d.ts +0 -3
  212. package/dist/resolution/name-matcher.d.ts.map +1 -1
  213. package/dist/resolution/name-matcher.js +354 -33
  214. package/dist/resolution/name-matcher.js.map +1 -1
  215. package/dist/resolution/strip-comments.d.ts +1 -1
  216. package/dist/resolution/strip-comments.d.ts.map +1 -1
  217. package/dist/resolution/strip-comments.js +49 -0
  218. package/dist/resolution/strip-comments.js.map +1 -1
  219. package/dist/resolution/types.d.ts +29 -0
  220. package/dist/resolution/types.d.ts.map +1 -1
  221. package/dist/resolution/workspace-packages.d.ts +10 -0
  222. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  223. package/dist/resolution/workspace-packages.js +142 -4
  224. package/dist/resolution/workspace-packages.js.map +1 -1
  225. package/dist/search/identifier-segments.d.ts +60 -0
  226. package/dist/search/identifier-segments.d.ts.map +1 -0
  227. package/dist/search/identifier-segments.js +176 -0
  228. package/dist/search/identifier-segments.js.map +1 -0
  229. package/dist/search/query-utils.d.ts +201 -0
  230. package/dist/search/query-utils.d.ts.map +1 -1
  231. package/dist/search/query-utils.js +919 -2
  232. package/dist/search/query-utils.js.map +1 -1
  233. package/dist/sync/git-hooks.d.ts.map +1 -1
  234. package/dist/sync/git-hooks.js +2 -0
  235. package/dist/sync/git-hooks.js.map +1 -1
  236. package/dist/sync/watcher.d.ts +10 -5
  237. package/dist/sync/watcher.d.ts.map +1 -1
  238. package/dist/sync/watcher.js +51 -14
  239. package/dist/sync/watcher.js.map +1 -1
  240. package/dist/sync/worktree.d.ts.map +1 -1
  241. package/dist/sync/worktree.js +5 -0
  242. package/dist/sync/worktree.js.map +1 -1
  243. package/dist/types.d.ts +19 -1
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/types.js +10 -0
  246. package/dist/types.js.map +1 -1
  247. package/dist/upgrade/index.d.ts +32 -0
  248. package/dist/upgrade/index.d.ts.map +1 -1
  249. package/dist/upgrade/index.js +162 -14
  250. package/dist/upgrade/index.js.map +1 -1
  251. package/dist/upgrade/remove-binary.d.ts +87 -0
  252. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  253. package/dist/upgrade/remove-binary.js +289 -0
  254. package/dist/upgrade/remove-binary.js.map +1 -0
  255. package/dist/upgrade/update-check.d.ts +92 -0
  256. package/dist/upgrade/update-check.d.ts.map +1 -0
  257. package/dist/upgrade/update-check.js +258 -0
  258. package/dist/upgrade/update-check.js.map +1 -0
  259. package/package.json +2 -2
  260. package/scripts/agent-eval/run-all.sh +6 -0
  261. package/scripts/build-bundle.sh +5 -0
  262. package/scripts/npm-shim.js +8 -1
  263. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  264. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  265. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  266. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  267. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  268. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  269. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  270. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  271. package/scripts/qa_eval/README.md +0 -407
  272. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  273. package/scripts/qa_eval/agent_runner.py +0 -526
  274. package/scripts/qa_eval/data/.gitignore +0 -4
  275. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  276. package/scripts/qa_eval/eval_metrics.py +0 -274
  277. package/scripts/qa_eval/external_agent.py +0 -976
  278. package/scripts/qa_eval/llm_config.py +0 -92
  279. package/scripts/qa_eval/memory_monitor.py +0 -132
  280. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  281. package/scripts/qa_eval/requirements.txt +0 -2
  282. package/scripts/qa_eval/run_pipeline.py +0 -804
  283. package/scripts/qa_eval/stats_efficiency.py +0 -279
  284. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -54,6 +54,7 @@ const astro_extractor_1 = require("./astro-extractor");
54
54
  const dfm_extractor_1 = require("./dfm-extractor");
55
55
  const vue_extractor_1 = require("./vue-extractor");
56
56
  const mybatis_extractor_1 = require("./mybatis-extractor");
57
+ const cfml_extractor_1 = require("./cfml-extractor");
57
58
  const arkts_1 = require("./languages/arkts");
58
59
  const frameworks_1 = require("../resolution/frameworks");
59
60
  // Re-export for backward compatibility
@@ -78,6 +79,27 @@ const VUE_STORE_FACTORY_CALLEES = new Set(['defineStore', 'createStore']);
78
79
  /** Distinct signals that a file is a Vuex/Pinia store (≥2 ⇒ treat a bare
79
80
  * `const actions = {…}` as a store collection — see looksLikeVueStoreFile). */
80
81
  const VUE_STORE_FILE_SIGNAL = /\bdefineStore\b|\bcreateStore\b|\bVuex\b|\bmutations\b|\bactions\b|\bgetters\b|\bnamespaced\b/g;
82
+ /**
83
+ * Erlang calls that take their real callee as (Module, Function, Args)
84
+ * ARGUMENTS — the spawn/apply family. Keys are the callee as the call site
85
+ * spells it: bare for auto-imported BIFs, `module:function` for remote calls.
86
+ * Used by the erlang branch of extractCall to lift a static MFA pair into a
87
+ * call edge (the spawned/applied function is otherwise invisible to the graph).
88
+ */
89
+ /** Compiler-predefined Erlang macros — no `-define` exists to link a use to. */
90
+ const ERLANG_PREDEFINED_MACROS = new Set([
91
+ 'MODULE', 'MODULE_STRING', 'FILE', 'LINE', 'MACHINE',
92
+ 'FUNCTION_NAME', 'FUNCTION_ARITY', 'OTP_RELEASE',
93
+ 'FEATURE_AVAILABLE', 'FEATURE_ENABLED',
94
+ ]);
95
+ const ERLANG_MFA_CALLS = new Set([
96
+ 'spawn', 'spawn_link', 'spawn_monitor', 'spawn_opt', 'apply',
97
+ 'erlang:spawn', 'erlang:spawn_link', 'erlang:spawn_monitor', 'erlang:spawn_opt', 'erlang:apply',
98
+ 'proc_lib:spawn', 'proc_lib:spawn_link', 'proc_lib:spawn_opt', 'proc_lib:start', 'proc_lib:start_link',
99
+ 'timer:apply_after', 'timer:apply_interval',
100
+ 'rpc:call', 'rpc:cast', 'rpc:async_call',
101
+ 'erpc:call', 'erpc:cast',
102
+ ]);
81
103
  /**
82
104
  * Extract the name from a node based on language
83
105
  */
@@ -348,7 +370,7 @@ class TreeSitterExtractor {
348
370
  // Value-reference edges (default ON; set HOMEGRAPH_VALUE_REFS=0 to disable; see flushValueRefs).
349
371
  // Same-file reads of file-scope const/var symbols → `references` edges so impact analysis catches
350
372
  // value consumers ("change this constant/table, affect its readers").
351
- static VALUE_REF_LANGS = new Set(['typescript', 'javascript', 'tsx', 'go', 'python', 'rust', 'ruby', 'c', 'java', 'csharp', 'php', 'scala', 'kotlin', 'swift', 'dart', 'pascal']);
373
+ static VALUE_REF_LANGS = new Set(['typescript', 'javascript', 'tsx', 'arkts', 'go', 'python', 'rust', 'ruby', 'c', 'java', 'csharp', 'php', 'scala', 'kotlin', 'swift', 'dart', 'pascal']);
352
374
  static MAX_VALUE_REF_NODES = 20_000;
353
375
  valueRefsEnabled = process.env.HOMEGRAPH_VALUE_REFS !== '0';
354
376
  fileScopeValues = new Map();
@@ -357,6 +379,20 @@ class TreeSitterExtractor {
357
379
  errors = [];
358
380
  extractor = null;
359
381
  nodeStack = []; // Stack of parent node IDs
382
+ // C/C++ enclosing `namespace ns { … }` names, prepended to every contained
383
+ // symbol's qualifiedName (see visitNode). Prefix-only by design — no
384
+ // namespace NODE is created: `namespace cutlass {` opens in thousands of
385
+ // files, and a node per block would flood search with same-named symbols
386
+ // (the #1093 crowd-out failure mode). Always empty outside C/C++.
387
+ namespacePrefix = [];
388
+ // C++ local function-pointer bindings, per enclosing symbol:
389
+ // `auto kernel = &flash_fwd_kernel<…>;` recorded as callerId → kernel →
390
+ // {flash_fwd_kernel}, so a later `kernel<<<grid, block>>>(params)` (or plain
391
+ // `kernel(args)`) in the same body emits calls refs to the real target(s)
392
+ // instead of an unresolvable local name. Branch reassignments accumulate —
393
+ // each assigned target is a genuine possible callee. Same-body locality is
394
+ // the precision guard (the #932 table-dispatch philosophy scoped to locals).
395
+ cppLocalFnPtrs = new Map();
360
396
  methodIndex = null; // lookup key → node ID for Pascal defProc lookup
361
397
  // Function-as-value capture (#756): per-language spec + candidates collected
362
398
  // during the walk, gated & flushed into unresolvedReferences at end-of-file
@@ -417,7 +453,7 @@ class TreeSitterExtractor {
417
453
  // this.source so downstream getNodeText reads the same bytes the parser
418
454
  // saw (identical outside the blanked directive lines).
419
455
  if (this.extractor?.preParse) {
420
- this.source = this.extractor.preParse(this.source);
456
+ this.source = this.extractor.preParse(this.source, this.filePath);
421
457
  }
422
458
  this.tree = parser.parse(this.source) ?? null;
423
459
  if (!this.tree) {
@@ -875,6 +911,31 @@ class TreeSitterExtractor {
875
911
  if (skipChildren)
876
912
  return;
877
913
  }
914
+ // C++ namespace blocks: carry the namespace name as a qualifiedName prefix
915
+ // while walking the body, so `namespace flash { void compute_attn(); }`
916
+ // indexes compute_attn with qualifiedName `flash::compute_attn` and a
917
+ // namespace-qualified call (`flash::compute_attn(...)`) resolves by exact
918
+ // qualified match instead of never resolving — C++ namespaces previously
919
+ // left no trace in qualifiedNames at all, so every `ns::fn()` call site
920
+ // was a permanently dead edge (surfaced by #387 flow validation on
921
+ // flash-attention/cutlass, whose kernel dispatch is namespace-qualified).
922
+ // C++17 nested forms (`namespace a::b {`) prefix as written. An anonymous
923
+ // namespace falls through to the generic walk — its contents stay bare,
924
+ // matching how call sites spell them.
925
+ if (this.language === 'cpp' && nodeType === 'namespace_definition') {
926
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
927
+ const nsName = nameNode ? (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source) : '';
928
+ if (nsName) {
929
+ this.namespacePrefix.push(nsName);
930
+ for (let i = 0; i < node.namedChildCount; i++) {
931
+ const child = node.namedChild(i);
932
+ if (child)
933
+ this.visitNode(child);
934
+ }
935
+ this.namespacePrefix.pop();
936
+ return;
937
+ }
938
+ }
878
939
  // Function-as-value capture (#756) — independent of the dispatch ladder
879
940
  // below (the captured container types have no other handler there), so it
880
941
  // can never shadow or be shadowed by an extraction branch.
@@ -1108,7 +1169,8 @@ class TreeSitterExtractor {
1108
1169
  // falls through to its normal declaration extraction).
1109
1170
  else if (nodeType === 'export_statement' &&
1110
1171
  (this.language === 'typescript' || this.language === 'tsx' ||
1111
- this.language === 'javascript' || this.language === 'jsx') &&
1172
+ this.language === 'javascript' || this.language === 'jsx' ||
1173
+ this.language === 'arkts') &&
1112
1174
  (0, tree_sitter_helpers_1.getChildByField)(node, 'source')) {
1113
1175
  const parentId = this.nodeStack[this.nodeStack.length - 1];
1114
1176
  if (parentId)
@@ -1138,7 +1200,7 @@ class TreeSitterExtractor {
1138
1200
  // produce an `instantiates` reference. Children still walked so
1139
1201
  // nested calls inside the constructor args (`new Foo(bar())`) get
1140
1202
  // their own `calls` refs.
1141
- else if (INSTANTIATION_KINDS.has(nodeType)) {
1203
+ else if (INSTANTIATION_KINDS.has(nodeType) || this.isVbnetConstructorShapedArrayCreation(node)) {
1142
1204
  this.extractInstantiation(node);
1143
1205
  // Java/C# `new T(...) { ... }` — anonymous class with body. Without
1144
1206
  // extracting it as a class node + its methods, the interface→impl
@@ -1289,7 +1351,8 @@ class TreeSitterExtractor {
1289
1351
  buildQualifiedName(name) {
1290
1352
  // Build a qualified name from the semantic hierarchy only (no file path).
1291
1353
  // The file path is stored separately in filePath and pollutes FTS if included here.
1292
- const parts = [];
1354
+ // C/C++ enclosing namespaces prefix first (empty for every other language).
1355
+ const parts = [...this.namespacePrefix];
1293
1356
  for (const nodeId of this.nodeStack) {
1294
1357
  const node = this.nodes.find((n) => n.id === nodeId);
1295
1358
  if (node && node.kind !== 'file') {
@@ -2370,7 +2433,8 @@ class TreeSitterExtractor {
2370
2433
  const isExported = this.extractor.isExported?.(node, this.source) ?? false;
2371
2434
  // Extract variable declarators based on language
2372
2435
  if (this.language === 'typescript' || this.language === 'javascript' ||
2373
- this.language === 'tsx' || this.language === 'jsx') {
2436
+ this.language === 'tsx' || this.language === 'jsx' || this.language === 'cfscript' ||
2437
+ this.language === 'arkts') {
2374
2438
  // Handle lexical_declaration and variable_declaration
2375
2439
  // These contain one or more variable_declarator children
2376
2440
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2784,7 +2848,7 @@ class TreeSitterExtractor {
2784
2848
  // property/method nodes under the type alias so `recorder.stop()`
2785
2849
  // can attach the call edge to `RecorderHandle.stop` instead of
2786
2850
  // an unrelated class method picked by path-proximity (#359).
2787
- if (this.language === 'typescript' || this.language === 'tsx') {
2851
+ if (this.language === 'typescript' || this.language === 'tsx' || this.language === 'arkts') {
2788
2852
  this.extractTsTypeAliasMembers(value, typeAliasNode);
2789
2853
  // `type List = [ Service<'name', Req, Resp>, … ]` — surface each
2790
2854
  // entry's string-literal name as a searchable member (issue #634).
@@ -3005,7 +3069,8 @@ class TreeSitterExtractor {
3005
3069
  // Link each imported binding to its definition so imported-but-not-
3006
3070
  // called/typed symbols still record a cross-file dependency (TS/JS only).
3007
3071
  if (this.language === 'typescript' || this.language === 'tsx' ||
3008
- this.language === 'javascript' || this.language === 'jsx') {
3072
+ this.language === 'javascript' || this.language === 'jsx' ||
3073
+ this.language === 'arkts') {
3009
3074
  const parentId = this.nodeStack[this.nodeStack.length - 1];
3010
3075
  if (parentId)
3011
3076
  this.emitImportBindingRefs(node, parentId);
@@ -3444,12 +3509,295 @@ class TreeSitterExtractor {
3444
3509
  /**
3445
3510
  * Extract a function call
3446
3511
  */
3512
+ /**
3513
+ * The module an Erlang gen_server target expression statically names, or
3514
+ * null when it's dynamic (pid/var/tuple form). Static shapes:
3515
+ * - a bare atom — either this module or another one; OTP's dominant
3516
+ * registration convention (`{local, ?MODULE}`) names a server process
3517
+ * after its module, so `gen_server:call(other_mod, …)` reaches
3518
+ * `other_mod`'s handlers. A registered name that matches no module
3519
+ * resolves to nothing downstream (the qualified ref just drops).
3520
+ * - `?MODULE`, or a macro the file defines as `?MODULE`
3521
+ * (`-define(SERVER, ?MODULE)` — the standard self idiom)
3522
+ * - a macro the file defines as a bare atom
3523
+ * (`-define(STORE, kv_store)` — the cross-module variant)
3524
+ * The macro tables are memoized per file (single entry — extraction is
3525
+ * file-sequential).
3526
+ */
3527
+ erlangServerMacroFile = '';
3528
+ erlangSelfMacros = new Set();
3529
+ erlangAtomMacros = new Map();
3530
+ resolveErlangGenServerTarget(target) {
3531
+ const ownModule = (this.filePath.split('/').pop() ?? '').replace(/\.erl$/, '');
3532
+ if (target.type === 'atom') {
3533
+ const name = (0, tree_sitter_helpers_1.getNodeText)(target, this.source).replace(/^'([\s\S]*)'$/, '$1');
3534
+ return name || null;
3535
+ }
3536
+ if (target.type !== 'macro_call_expr')
3537
+ return null;
3538
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(target, 'name');
3539
+ if (!nameNode)
3540
+ return null;
3541
+ const macroName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
3542
+ if (macroName === 'MODULE')
3543
+ return ownModule || null;
3544
+ if (this.erlangServerMacroFile !== this.filePath) {
3545
+ this.erlangServerMacroFile = this.filePath;
3546
+ this.erlangSelfMacros = new Set();
3547
+ this.erlangAtomMacros = new Map();
3548
+ let root = target;
3549
+ while (root.parent)
3550
+ root = root.parent;
3551
+ for (let i = 0; i < root.namedChildCount; i++) {
3552
+ const form = root.namedChild(i);
3553
+ if (form?.type !== 'pp_define')
3554
+ continue;
3555
+ const lhs = (0, tree_sitter_helpers_1.getChildByField)(form, 'lhs');
3556
+ const defName = lhs ? (0, tree_sitter_helpers_1.getChildByField)(lhs, 'name') : null;
3557
+ const replacement = (0, tree_sitter_helpers_1.getChildByField)(form, 'replacement');
3558
+ if (!defName || !replacement)
3559
+ continue;
3560
+ if (replacement.type === 'macro_call_expr' &&
3561
+ (0, tree_sitter_helpers_1.getChildByField)(replacement, 'name') &&
3562
+ (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(replacement, 'name'), this.source) === 'MODULE') {
3563
+ this.erlangSelfMacros.add((0, tree_sitter_helpers_1.getNodeText)(defName, this.source));
3564
+ }
3565
+ else if (replacement.type === 'atom') {
3566
+ this.erlangAtomMacros.set((0, tree_sitter_helpers_1.getNodeText)(defName, this.source), (0, tree_sitter_helpers_1.getNodeText)(replacement, this.source).replace(/^'([\s\S]*)'$/, '$1'));
3567
+ }
3568
+ }
3569
+ }
3570
+ if (this.erlangSelfMacros.has(macroName))
3571
+ return ownModule || null;
3572
+ return this.erlangAtomMacros.get(macroName) ?? null;
3573
+ }
3447
3574
  extractCall(node) {
3448
3575
  if (this.nodeStack.length === 0)
3449
3576
  return;
3450
3577
  const callerId = this.nodeStack[this.nodeStack.length - 1];
3451
3578
  if (!callerId)
3452
3579
  return;
3580
+ // VB.NET: `foo(args)` is syntactically ambiguous between a call and an
3581
+ // index read, so the grammar parses non-empty parens as
3582
+ // array_access_expression (field `array`, not `function`) — even Roslyn
3583
+ // parses both as InvocationExpression and resolves during binding. Treat
3584
+ // all three shapes as call sites: the callee is the member/identifier
3585
+ // under the array/function field, qualified with a simple-identifier
3586
+ // receiver for resolution. Index reads on collections simply never
3587
+ // resolve to a callable, so they cost nothing.
3588
+ if (this.language === 'vbnet' &&
3589
+ (node.type === 'array_access_expression' ||
3590
+ node.type === 'invocation_expression' ||
3591
+ node.type === 'generic_invocation_expression')) {
3592
+ const fn = (0, tree_sitter_helpers_1.getChildByField)(node, 'function') || (0, tree_sitter_helpers_1.getChildByField)(node, 'array');
3593
+ if (!fn)
3594
+ return;
3595
+ let calleeName = '';
3596
+ if (fn.type === 'member_access_expression') {
3597
+ const member = (0, tree_sitter_helpers_1.getChildByField)(fn, 'member');
3598
+ const memberName = member ? (0, tree_sitter_helpers_1.getNodeText)(member, this.source) : '';
3599
+ if (!memberName)
3600
+ return;
3601
+ const receiver = (0, tree_sitter_helpers_1.getChildByField)(fn, 'object');
3602
+ const SKIP = new Set(['me', 'mybase', 'myclass']);
3603
+ if (receiver && receiver.type === 'identifier' && !SKIP.has((0, tree_sitter_helpers_1.getNodeText)(receiver, this.source).toLowerCase())) {
3604
+ calleeName = `${(0, tree_sitter_helpers_1.getNodeText)(receiver, this.source)}.${memberName}`;
3605
+ }
3606
+ else {
3607
+ calleeName = memberName;
3608
+ }
3609
+ }
3610
+ else if (fn.type === 'identifier') {
3611
+ calleeName = (0, tree_sitter_helpers_1.getNodeText)(fn, this.source);
3612
+ }
3613
+ else {
3614
+ return; // parenthesized/chained receivers: no static name to link
3615
+ }
3616
+ if (calleeName) {
3617
+ this.unresolvedReferences.push({
3618
+ fromNodeId: callerId,
3619
+ referenceName: calleeName,
3620
+ referenceKind: 'calls',
3621
+ line: node.startPosition.row + 1,
3622
+ column: node.startPosition.column,
3623
+ });
3624
+ }
3625
+ return;
3626
+ }
3627
+ // Erlang: a local call is `call(expr: atom, args)`; a remote call nests it
3628
+ // under `remote(module: remote_module, fun: call)` — the module qualifier
3629
+ // lives on the PARENT. Remote calls are emitted as `mod::fn`, which is
3630
+ // byte-identical to the qualifiedName the module namespace gives every
3631
+ // function (see packageTypes in languages/erlang.ts), so they resolve via
3632
+ // matchByQualifiedName. A var/macro callee or module (`F(X)`, `?M(X)`,
3633
+ // `Mod:handle(X)`) has no static target — except `?MODULE:fn(X)`, which the
3634
+ // bare name + same-file preference resolves correctly. `fun name/1` /
3635
+ // `fun mod:name/1` values are function REFERENCES (callback registration),
3636
+ // and record construction/update/index/field-access are `references` to the
3637
+ // record's struct node.
3638
+ if (this.language === 'erlang') {
3639
+ const line = node.startPosition.row + 1;
3640
+ const column = node.startPosition.column;
3641
+ const erlAtom = (n) => (0, tree_sitter_helpers_1.getNodeText)(n, this.source).replace(/^'([\s\S]*)'$/, '$1');
3642
+ if (node.type === 'call') {
3643
+ let callee = (0, tree_sitter_helpers_1.getChildByField)(node, 'expr');
3644
+ let moduleNode = null;
3645
+ // remote(module, fun: call) — the shape the grammar produces today; the
3646
+ // node-types also permit call(expr: remote), so handle both nestings.
3647
+ if (node.parent?.type === 'remote') {
3648
+ moduleNode = (0, tree_sitter_helpers_1.getChildByField)(node.parent, 'module');
3649
+ }
3650
+ else if (callee?.type === 'remote') {
3651
+ moduleNode = (0, tree_sitter_helpers_1.getChildByField)(callee, 'module');
3652
+ callee = (0, tree_sitter_helpers_1.getChildByField)(callee, 'fun');
3653
+ }
3654
+ if (callee?.type === 'atom') {
3655
+ const fnBare = erlAtom(callee);
3656
+ let calleeName = fnBare;
3657
+ const moduleExpr = moduleNode ? (0, tree_sitter_helpers_1.getChildByField)(moduleNode, 'module') : null;
3658
+ if (moduleExpr?.type === 'atom') {
3659
+ calleeName = `${erlAtom(moduleExpr)}::${calleeName}`;
3660
+ }
3661
+ else if (moduleExpr) {
3662
+ // Non-atom module qualifier. `?MODULE:f(X)` targets THIS module —
3663
+ // keep the bare name so same-file preference resolves it. Anything
3664
+ // else (`Mod:f(X)`) is behaviour-style dynamic dispatch with no
3665
+ // static target: emitting the bare name would link an arbitrary
3666
+ // same-named function, so stay silent instead.
3667
+ const macroName = moduleExpr.type === 'macro_call_expr' ? (0, tree_sitter_helpers_1.getChildByField)(moduleExpr, 'name') : null;
3668
+ if (!macroName || (0, tree_sitter_helpers_1.getNodeText)(macroName, this.source) !== 'MODULE')
3669
+ return;
3670
+ }
3671
+ this.unresolvedReferences.push({
3672
+ fromNodeId: callerId,
3673
+ referenceName: calleeName,
3674
+ referenceKind: 'calls',
3675
+ line,
3676
+ column,
3677
+ });
3678
+ // gen_server dispatch: `gen_server:call(?SERVER, Msg)` /
3679
+ // `gen_server:cast(other_mod, Msg)` — a request routes to the TARGET
3680
+ // module's handle_call/handle_cast. The target is static when the
3681
+ // first argument names a module: ?MODULE or a ?MODULE-defined macro
3682
+ // (the self API-wrapper idiom), a bare atom (OTP's `{local, ?MODULE}`
3683
+ // convention names a server after its module, so a cross-module
3684
+ // registered name reaches that module's handlers — and a registered
3685
+ // name matching no module resolves to nothing), or a macro defined
3686
+ // as a bare atom. Pid/var/tuple targets stay silent.
3687
+ if (moduleExpr?.type === 'atom' &&
3688
+ erlAtom(moduleExpr) === 'gen_server' &&
3689
+ (fnBare === 'call' || fnBare === 'cast' || fnBare === 'send_request')) {
3690
+ const argsNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'args');
3691
+ const target = argsNode?.namedChild(0) ?? null;
3692
+ const targetModule = target ? this.resolveErlangGenServerTarget(target) : null;
3693
+ if (targetModule) {
3694
+ this.unresolvedReferences.push({
3695
+ fromNodeId: callerId,
3696
+ referenceName: `${targetModule}::${fnBare === 'cast' ? 'handle_cast' : 'handle_call'}`,
3697
+ referenceKind: 'calls',
3698
+ line,
3699
+ column,
3700
+ });
3701
+ }
3702
+ }
3703
+ // MFA-in-argument dispatch: the spawn/apply family names its real
3704
+ // callee in ARGUMENT position — `proc_lib:spawn_link(?MODULE,
3705
+ // request_process, [Req, Env, Middlewares])` — so the walker above
3706
+ // sees only the spawn itself and the spawned function ends up with
3707
+ // zero callers (measured on cowboy: request_process had no incoming
3708
+ // edges and the agent Read the file to find it). When the (Module,
3709
+ // Function) pair is static, lift it as a call edge. The pair is
3710
+ // found positionally-agnostically (first adjacent module-atom/
3711
+ // ?MODULE + atom pair) so every arity variant works: spawn/3,
3712
+ // spawn(Node,M,F,A)/4, timer:apply_after(Time,M,F,A),
3713
+ // rpc:call(Node,M,F,A). A var module or fun stays silent.
3714
+ const familyKey = moduleExpr?.type === 'atom' ? `${erlAtom(moduleExpr)}:${fnBare}` : fnBare;
3715
+ if (ERLANG_MFA_CALLS.has(familyKey)) {
3716
+ const argsNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'args');
3717
+ const argExprs = argsNode ? argsNode.namedChildren : [];
3718
+ for (let i = 0; i + 1 < argExprs.length; i++) {
3719
+ const m = argExprs[i];
3720
+ const f = argExprs[i + 1];
3721
+ if (f.type !== 'atom')
3722
+ continue;
3723
+ const isLocalModule = m.type === 'macro_call_expr' &&
3724
+ (0, tree_sitter_helpers_1.getChildByField)(m, 'name') !== null &&
3725
+ (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(m, 'name'), this.source) === 'MODULE';
3726
+ if (m.type !== 'atom' && !isLocalModule)
3727
+ continue;
3728
+ this.unresolvedReferences.push({
3729
+ fromNodeId: callerId,
3730
+ referenceName: isLocalModule ? erlAtom(f) : `${erlAtom(m)}::${erlAtom(f)}`,
3731
+ referenceKind: 'calls',
3732
+ line: f.startPosition.row + 1,
3733
+ column: f.startPosition.column,
3734
+ });
3735
+ break;
3736
+ }
3737
+ }
3738
+ }
3739
+ return;
3740
+ }
3741
+ if (node.type === 'internal_fun' || node.type === 'external_fun') {
3742
+ const funNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'fun');
3743
+ if (funNode?.type !== 'atom')
3744
+ return; // fun Mod:F/A with var parts — dynamic
3745
+ let refName = erlAtom(funNode);
3746
+ if (node.type === 'external_fun') {
3747
+ const moduleWrapper = (0, tree_sitter_helpers_1.getChildByField)(node, 'module');
3748
+ const moduleAtom = moduleWrapper ? (0, tree_sitter_helpers_1.getChildByField)(moduleWrapper, 'name') : null;
3749
+ if (moduleAtom?.type !== 'atom')
3750
+ return;
3751
+ refName = `${erlAtom(moduleAtom)}::${refName}`;
3752
+ }
3753
+ this.unresolvedReferences.push({
3754
+ fromNodeId: callerId,
3755
+ referenceName: refName,
3756
+ referenceKind: 'references',
3757
+ line,
3758
+ column,
3759
+ });
3760
+ return;
3761
+ }
3762
+ if (node.type === 'macro_call_expr') {
3763
+ // Macro use site → the `-define` constant node. Function-like uses
3764
+ // (`?LOG_AUDIT(X)` — args present) are inlined code, so they join the
3765
+ // call chain and connect through the macro node to the body's calls
3766
+ // (attributed there by handlePpDefine); bare reads (`?TIMEOUT`) are
3767
+ // `references`, answering "where is this macro used" without
3768
+ // polluting call paths. Compiler-predefined macros carry no
3769
+ // definition to link. The use site's ARGUMENTS are children and keep
3770
+ // walking, so a call nested in `?assertEqual(ok, do_thing())` still
3771
+ // attributes to the enclosing function.
3772
+ const macroName = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
3773
+ if (!macroName)
3774
+ return;
3775
+ const name = (0, tree_sitter_helpers_1.getNodeText)(macroName, this.source);
3776
+ if (ERLANG_PREDEFINED_MACROS.has(name))
3777
+ return;
3778
+ this.unresolvedReferences.push({
3779
+ fromNodeId: callerId,
3780
+ referenceName: name,
3781
+ referenceKind: (0, tree_sitter_helpers_1.getChildByField)(node, 'args') ? 'calls' : 'references',
3782
+ line,
3783
+ column,
3784
+ });
3785
+ return;
3786
+ }
3787
+ // record_expr / record_update_expr / record_index_expr / record_field_expr
3788
+ const recordName = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
3789
+ const recordAtom = recordName?.type === 'record_name' ? (0, tree_sitter_helpers_1.getChildByField)(recordName, 'name') : null;
3790
+ if (recordAtom?.type === 'atom') {
3791
+ this.unresolvedReferences.push({
3792
+ fromNodeId: callerId,
3793
+ referenceName: erlAtom(recordAtom),
3794
+ referenceKind: 'references',
3795
+ line,
3796
+ column,
3797
+ });
3798
+ }
3799
+ return;
3800
+ }
3453
3801
  // Ruby `call` nodes use `receiver` + `method` fields (tree-sitter-ruby), not
3454
3802
  // the `object`/`name`/`function` fields the branches below expect — so
3455
3803
  // without this they fell through to the generic path, which took the
@@ -3507,6 +3855,176 @@ class TreeSitterExtractor {
3507
3855
  }
3508
3856
  return;
3509
3857
  }
3858
+ // ArkTS build()-DSL handling. Three shapes carry UI-attribute chains, and
3859
+ // all of their attribute names are emitted with a LEADING DOT
3860
+ // (`.titleStyle`, `.width`) — an impossible identifier that routes them to
3861
+ // a dedicated matcher strategy resolving ONLY to decorator-marked
3862
+ // attribute helpers (`@Extend`/`@Styles`/`@AnimatableExtend`/`@Builder`
3863
+ // functions). Bare names would go through global name matching, where
3864
+ // framework attributes (`.width`, `.fontSize`, appearing on nearly every
3865
+ // UI line) hit arbitrary same-named symbols — measured on the OpenHarmony
3866
+ // samples monorepo, that produced 36k wrong edges (17% of all calls),
3867
+ // including single properties with 3,400+ false callers.
3868
+ //
3869
+ // 1. `Column({space:8}) { … }.height('100%')` — ONE
3870
+ // arkui_component_expression: `function:` = the component, chained
3871
+ // attributes as repeated `property:`/`arguments:` field pairs.
3872
+ // The component ref (`Column`, `TodoRow`) stays a PLAIN name — it
3873
+ // resolves to the child `@Component struct`, giving the parent→child
3874
+ // component-tree edge the way JSX children do for React.
3875
+ // 2. `Image(x).width(10).onClick(this.f)` — ordinary nested
3876
+ // call_expressions whose `function:` is a member_expression chained
3877
+ // on a CALL RESULT (never a named receiver, so `svc.save()` /
3878
+ // `this.vm.load()` are untouched and fall through to the generic
3879
+ // paths below).
3880
+ // 3. A nested component whose chain starts on the line AFTER its
3881
+ // closing `}` inside arkui_children — the grammar detaches the chain
3882
+ // into sibling `leading_dot_expression(identifier)` +
3883
+ // `parenthesized_expression(args)` statement pairs; reassemble from
3884
+ // the siblings.
3885
+ //
3886
+ // `.onXxx(this.handler)` METHOD-REFERENCE bindings (no call parens, so
3887
+ // nothing else records them) additionally emit a call ref to the bare
3888
+ // handler name — same-class resolution links the tap→handler hop.
3889
+ // Arrow-function handlers need nothing: their bodies' calls already
3890
+ // attribute to the enclosing build(). Children/argument subtrees are
3891
+ // still walked by the caller, so nested components extract normally.
3892
+ if (this.language === 'arkts') {
3893
+ const emitAttr = (nameNode) => {
3894
+ const attrName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
3895
+ if (!attrName)
3896
+ return;
3897
+ this.unresolvedReferences.push({
3898
+ fromNodeId: callerId,
3899
+ referenceName: '.' + attrName,
3900
+ referenceKind: 'calls',
3901
+ line: nameNode.startPosition.row + 1,
3902
+ column: nameNode.startPosition.column,
3903
+ });
3904
+ };
3905
+ // Emit `handler` for each bare `this.handler` among an on-attribute's
3906
+ // arguments.
3907
+ const emitThisHandlers = (args) => {
3908
+ if (!args)
3909
+ return;
3910
+ for (let j = 0; j < args.namedChildCount; j++) {
3911
+ const arg = args.namedChild(j);
3912
+ if (arg?.type !== 'member_expression')
3913
+ continue;
3914
+ const obj = (0, tree_sitter_helpers_1.getChildByField)(arg, 'object');
3915
+ const prop = (0, tree_sitter_helpers_1.getChildByField)(arg, 'property');
3916
+ if (obj?.type === 'this' && prop) {
3917
+ this.unresolvedReferences.push({
3918
+ fromNodeId: callerId,
3919
+ referenceName: (0, tree_sitter_helpers_1.getNodeText)(prop, this.source),
3920
+ referenceKind: 'calls',
3921
+ line: arg.startPosition.row + 1,
3922
+ column: arg.startPosition.column,
3923
+ });
3924
+ }
3925
+ }
3926
+ };
3927
+ // Shape 1: arkui_component_expression with property/arguments pairs.
3928
+ if (node.type === 'arkui_component_expression') {
3929
+ const componentField = (0, tree_sitter_helpers_1.getChildByField)(node, 'function');
3930
+ if (componentField && componentField.type === 'identifier') {
3931
+ this.unresolvedReferences.push({
3932
+ fromNodeId: callerId,
3933
+ referenceName: (0, tree_sitter_helpers_1.getNodeText)(componentField, this.source),
3934
+ referenceKind: 'calls',
3935
+ line: node.startPosition.row + 1,
3936
+ column: node.startPosition.column,
3937
+ });
3938
+ }
3939
+ for (let i = 0; i < node.childCount; i++) {
3940
+ const child = node.child(i);
3941
+ if (!child || child.type !== 'property_identifier')
3942
+ continue;
3943
+ emitAttr(child);
3944
+ if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(child, this.source))) {
3945
+ // The attribute's arguments node is the next `arguments`-typed
3946
+ // child before the following attribute name.
3947
+ let args = null;
3948
+ for (let k = i + 1; k < node.childCount; k++) {
3949
+ const next = node.child(k);
3950
+ if (!next)
3951
+ continue;
3952
+ if (next.type === 'property_identifier')
3953
+ break;
3954
+ if (next.type === 'arguments') {
3955
+ args = next;
3956
+ break;
3957
+ }
3958
+ }
3959
+ emitThisHandlers(args);
3960
+ }
3961
+ }
3962
+ return;
3963
+ }
3964
+ // Shape 2: fluent chain on a call result —
3965
+ // call_expression(function: member_expression(object: <call>)), or the
3966
+ // grammar's DSL-specific arkui_dsl_decorator_member_expression (same
3967
+ // object/property fields; produced e.g. by `Column() { … }.alignItems(x)`
3968
+ // in some chain positions — it ONLY occurs in attribute chains).
3969
+ if (node.type === 'call_expression') {
3970
+ const fn = (0, tree_sitter_helpers_1.getChildByField)(node, 'function');
3971
+ if (fn?.type === 'member_expression' || fn?.type === 'arkui_dsl_decorator_member_expression') {
3972
+ const obj = (0, tree_sitter_helpers_1.getChildByField)(fn, 'object');
3973
+ const prop = (0, tree_sitter_helpers_1.getChildByField)(fn, 'property');
3974
+ if (prop &&
3975
+ (fn.type === 'arkui_dsl_decorator_member_expression' ||
3976
+ obj?.type === 'call_expression' ||
3977
+ obj?.type === 'arkui_component_expression')) {
3978
+ emitAttr(prop);
3979
+ if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(prop, this.source))) {
3980
+ emitThisHandlers((0, tree_sitter_helpers_1.getChildByField)(node, 'arguments'));
3981
+ }
3982
+ return;
3983
+ }
3984
+ }
3985
+ // The INNERMOST call of a proper-form detached chain
3986
+ // (`.alignItems(x).layoutWeight(1)…` under a leading_dot_expression)
3987
+ // has a BARE IDENTIFIER function — the leading dot was consumed by
3988
+ // the wrapper, so it masquerades as a plain `alignItems(...)` call.
3989
+ // Walk up the member/call alternation; topping out at
3990
+ // leading_dot_expression means the dot belongs to this chain.
3991
+ if (fn?.type === 'identifier') {
3992
+ let p = node.parent;
3993
+ while (p && (p.type === 'member_expression' || p.type === 'call_expression')) {
3994
+ p = p.parent;
3995
+ }
3996
+ if (p?.type === 'leading_dot_expression') {
3997
+ emitAttr(fn);
3998
+ if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(fn, this.source))) {
3999
+ emitThisHandlers((0, tree_sitter_helpers_1.getChildByField)(node, 'arguments'));
4000
+ }
4001
+ return;
4002
+ }
4003
+ }
4004
+ // Not a chained attribute — fall through to the generic call paths.
4005
+ }
4006
+ // Shape 3: detached chain segment — leading_dot_expression whose only
4007
+ // named child is a bare identifier; its arguments sit in the NEXT
4008
+ // sibling statement as a parenthesized_expression.
4009
+ if (node.type === 'leading_dot_expression') {
4010
+ const only = node.namedChildCount === 1 ? node.namedChild(0) : null;
4011
+ if (only && only.type === 'identifier') {
4012
+ emitAttr(only);
4013
+ if (/^on[A-Z]/.test((0, tree_sitter_helpers_1.getNodeText)(only, this.source))) {
4014
+ const stmt = node.parent; // expression_statement
4015
+ const nextStmt = stmt?.nextNamedSibling;
4016
+ const paren = nextStmt?.namedChild(0);
4017
+ if (paren?.type === 'parenthesized_expression') {
4018
+ emitThisHandlers(paren);
4019
+ }
4020
+ }
4021
+ }
4022
+ // The proper form (child is a call_expression chain, as inside
4023
+ // `@Extend` bodies) needs nothing here — the walker descends into it
4024
+ // and the inner call_expressions take the paths above.
4025
+ return;
4026
+ }
4027
+ }
3510
4028
  // Get the function/method being called
3511
4029
  let calleeName = '';
3512
4030
  // Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
@@ -3798,6 +4316,20 @@ class TreeSitterExtractor {
3798
4316
  }
3799
4317
  calleeName = reencode ? `${innerCallee}().${methodName}` : methodName;
3800
4318
  }
4319
+ else if (this.language === 'cfscript' &&
4320
+ receiver &&
4321
+ receiver.type === 'member_expression' &&
4322
+ /^(variables|this|local|arguments)\.[A-Za-z_][\w]*$/i.test((0, tree_sitter_helpers_1.getNodeText)(receiver, this.source))) {
4323
+ // CFML scope-prefixed member call — `variables.svc.save()` /
4324
+ // `arguments.svc.save()`: the receiver is a component field,
4325
+ // injected property, or typed argument reached through one of
4326
+ // CFML's file-local scopes. Keep the full receiver chain so
4327
+ // resolution can strip the scope prefix and infer the field's
4328
+ // component type from its declaration (#1108). Gated to these
4329
+ // scope keywords: such calls previously emitted a bare method
4330
+ // name, which either failed to resolve or resolved ambiguously.
4331
+ calleeName = `${(0, tree_sitter_helpers_1.getNodeText)(receiver, this.source)}.${methodName}`;
4332
+ }
3801
4333
  else {
3802
4334
  calleeName = methodName;
3803
4335
  }
@@ -3840,6 +4372,40 @@ class TreeSitterExtractor {
3840
4372
  if (conv && conv[1])
3841
4373
  calleeName = conv[1];
3842
4374
  }
4375
+ // C/C++ templated callees — a direct templated call (`fn<T, 256>(args)`,
4376
+ // the shape every CUDA kernel-launch site takes once its `<<<…>>>` config
4377
+ // is blanked) or a qualified one (`ns::fn<T>(args)`) — carry template
4378
+ // arguments in the callee text, which can never match the bare name the
4379
+ // function was DEFINED as, so the call edge silently never resolves. Strip
4380
+ // them: the same normalization base-class `extends` refs already get
4381
+ // (#1043). `operator<`/`operator<<` callees are excluded — their `<` is the
4382
+ // operator itself, not a template-argument list.
4383
+ if (calleeName &&
4384
+ calleeName.includes('<') &&
4385
+ (this.language === 'cpp' || this.language === 'c') &&
4386
+ !calleeName.includes('operator')) {
4387
+ calleeName = (0, c_cpp_1.stripCppTemplateArgs)(calleeName);
4388
+ }
4389
+ // C++ call/launch through a local function pointer: `auto kernel =
4390
+ // &flash_fwd_kernel<…>; … kernel<<<grid, block>>>(params);` — the callee
4391
+ // is an unresolvable local name. When the same enclosing symbol bound the
4392
+ // local from `&fn` (each branch assignment counts), emit the call against
4393
+ // every recorded target instead of the local.
4394
+ if (calleeName && this.language === 'cpp' && /^[A-Za-z_]\w*$/.test(calleeName)) {
4395
+ const targets = this.cppLocalFnPtrs.get(callerId)?.get(calleeName);
4396
+ if (targets && targets.size > 0) {
4397
+ for (const target of targets) {
4398
+ this.unresolvedReferences.push({
4399
+ fromNodeId: callerId,
4400
+ referenceName: target,
4401
+ referenceKind: 'calls',
4402
+ line: node.startPosition.row + 1,
4403
+ column: node.startPosition.column,
4404
+ });
4405
+ }
4406
+ return;
4407
+ }
4408
+ }
3843
4409
  if (calleeName) {
3844
4410
  this.unresolvedReferences.push({
3845
4411
  fromNodeId: callerId,
@@ -3859,6 +4425,26 @@ class TreeSitterExtractor {
3859
4425
  * Children are still walked so nested calls inside the constructor
3860
4426
  * arguments (`new Foo(bar())`) get their own `calls` references.
3861
4427
  */
4428
+ /**
4429
+ * VB.NET `New Invoice(1)` is syntactically ambiguous between constructing
4430
+ * Invoice with an argument and allocating an Invoice array of bound 1; the
4431
+ * grammar parses the parenthesized form as array_creation_expression. A
4432
+ * user-defined type with no `{...}` array initializer is overwhelmingly a
4433
+ * constructor call, so treat it as an instantiation. Predefined element
4434
+ * types (`New Byte(1023)`) and brace-initialized forms stay arrays.
4435
+ */
4436
+ isVbnetConstructorShapedArrayCreation(node) {
4437
+ if (this.language !== 'vbnet' || node.type !== 'array_creation_expression')
4438
+ return false;
4439
+ const typeNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'type');
4440
+ if (!typeNode || typeNode.type === 'predefined_type' || typeNode.type === 'array_type')
4441
+ return false;
4442
+ for (const child of node.namedChildren) {
4443
+ if (child?.type === 'array_initializer')
4444
+ return false;
4445
+ }
4446
+ return true;
4447
+ }
3862
4448
  extractInstantiation(node) {
3863
4449
  if (this.nodeStack.length === 0)
3864
4450
  return;
@@ -3921,6 +4507,14 @@ class TreeSitterExtractor {
3921
4507
  const ltIdx = className.indexOf('<');
3922
4508
  if (ltIdx > 0)
3923
4509
  className = className.slice(0, ltIdx);
4510
+ // VB.NET spells generics with parentheses: `New List(Of String)` /
4511
+ // `New Dictionary(Of K, V)(cap)` — strip from the `(` so the bare
4512
+ // type name is what resolution matches.
4513
+ if (this.language === 'vbnet') {
4514
+ const parenIdx = className.indexOf('(');
4515
+ if (parenIdx > 0)
4516
+ className = className.slice(0, parenIdx);
4517
+ }
3924
4518
  // For namespaced/qualified constructors (`new ns.Foo()`,
3925
4519
  // `new ns::Foo()`) keep the trailing identifier — that's what
3926
4520
  // matches a class node in the index.
@@ -4135,6 +4729,28 @@ class TreeSitterExtractor {
4135
4729
  const consider = (n) => {
4136
4730
  if (!n)
4137
4731
  return;
4732
+ // Solidity `modifier_invocation` (unique to that grammar) sits
4733
+ // decorator-position in the function header — OUTSIDE the `body:` field
4734
+ // the call walker descends — but its body executes around the function
4735
+ // via `_;`, so it is a real call-flow hop (`withdraw → onlyOwner →
4736
+ // _checkRole` is the canonical audit trace). The same node type carries
4737
+ // base-constructor invocations (`constructor() ERC20("T","TOK")`), the
4738
+ // constructor-chain hop. Emit `calls`, not `decorates`, so flow
4739
+ // traversal rides it.
4740
+ if (n.type === 'modifier_invocation') {
4741
+ const target = n.namedChild(0);
4742
+ const name = target?.type === 'identifier' ? (0, tree_sitter_helpers_1.getNodeText)(target, this.source) : undefined;
4743
+ if (name) {
4744
+ this.unresolvedReferences.push({
4745
+ fromNodeId: decoratedId,
4746
+ referenceName: name,
4747
+ referenceKind: 'calls',
4748
+ line: n.startPosition.row + 1,
4749
+ column: n.startPosition.column,
4750
+ });
4751
+ }
4752
+ return;
4753
+ }
4138
4754
  // `marker_annotation` is Java's grammar for arg-less annotations
4139
4755
  // (`@Override`, `@Deprecated`); `attribute` is Swift's grammar for
4140
4756
  // attributes and PROPERTY WRAPPERS (`@objc`, `@Argument`, `@Published`,
@@ -4312,6 +4928,43 @@ class TreeSitterExtractor {
4312
4928
  }
4313
4929
  flush();
4314
4930
  }
4931
+ /**
4932
+ * Record a C++ local function-pointer binding (`local = &fn` / `&fn<…>` /
4933
+ * `&ns::fn<…>`) for the CURRENT enclosing symbol, so calls through the local
4934
+ * resolve to the real target (see cppLocalFnPtrs). Only the address-of shape
4935
+ * is accepted — a bare-identifier RHS (`auto x = y;`) is any value copy, and
4936
+ * linking through it would guess.
4937
+ */
4938
+ recordCppFnPtrBinding(localName, value) {
4939
+ if (!value || value.type !== 'pointer_expression')
4940
+ return;
4941
+ if (value.child(0)?.type !== '&')
4942
+ return; // `*p` dereference, not address-of
4943
+ const arg = (0, tree_sitter_helpers_1.getChildByField)(value, 'argument') ?? value.namedChild(0);
4944
+ if (!arg ||
4945
+ (arg.type !== 'identifier' &&
4946
+ arg.type !== 'template_function' &&
4947
+ arg.type !== 'qualified_identifier')) {
4948
+ return;
4949
+ }
4950
+ const callerId = this.nodeStack[this.nodeStack.length - 1];
4951
+ if (!callerId)
4952
+ return;
4953
+ const target = (0, c_cpp_1.stripCppTemplateArgs)((0, tree_sitter_helpers_1.getNodeText)(arg, this.source));
4954
+ if (!target || target === localName)
4955
+ return;
4956
+ let locals = this.cppLocalFnPtrs.get(callerId);
4957
+ if (!locals) {
4958
+ locals = new Map();
4959
+ this.cppLocalFnPtrs.set(callerId, locals);
4960
+ }
4961
+ let targets = locals.get(localName);
4962
+ if (!targets) {
4963
+ targets = new Set();
4964
+ locals.set(localName, targets);
4965
+ }
4966
+ targets.add(target);
4967
+ }
4315
4968
  visitFunctionBody(body, _functionId) {
4316
4969
  if (!this.extractor)
4317
4970
  return;
@@ -4327,7 +4980,7 @@ class TreeSitterExtractor {
4327
4980
  if (this.extractor.callTypes.includes(nodeType)) {
4328
4981
  this.extractCall(node);
4329
4982
  }
4330
- else if (INSTANTIATION_KINDS.has(nodeType)) {
4983
+ else if (INSTANTIATION_KINDS.has(nodeType) || this.isVbnetConstructorShapedArrayCreation(node)) {
4331
4984
  // `new Foo()` inside a function body — emit an `instantiates`
4332
4985
  // reference. Without this branch the body walker only knew
4333
4986
  // about `call_expression`, so constructor invocations
@@ -4369,6 +5022,31 @@ class TreeSitterExtractor {
4369
5022
  if (nodeType === 'declaration' && this.language === 'cpp' && this.isCppStackConstruction(node)) {
4370
5023
  this.extractInstantiation(node);
4371
5024
  }
5025
+ // C++ local function-pointer bindings (see cppLocalFnPtrs): record
5026
+ // `auto kernel = &fn<…>;` declarations and `kernel = &other_fn<…>;`
5027
+ // branch reassignments so a call/launch through the local links to the
5028
+ // real target(s). The body walker sees these in source order, and C++
5029
+ // requires declaration-before-use, so the map is always populated before
5030
+ // the call that consumes it.
5031
+ if (this.language === 'cpp' && this.nodeStack.length > 0) {
5032
+ if (nodeType === 'declaration') {
5033
+ for (let i = 0; i < node.namedChildCount; i++) {
5034
+ const child = node.namedChild(i);
5035
+ if (child?.type !== 'init_declarator')
5036
+ continue;
5037
+ const decl = (0, tree_sitter_helpers_1.getChildByField)(child, 'declarator');
5038
+ if (decl?.type !== 'identifier')
5039
+ continue;
5040
+ this.recordCppFnPtrBinding((0, tree_sitter_helpers_1.getNodeText)(decl, this.source), (0, tree_sitter_helpers_1.getChildByField)(child, 'value'));
5041
+ }
5042
+ }
5043
+ else if (nodeType === 'assignment_expression') {
5044
+ const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left');
5045
+ if (left?.type === 'identifier') {
5046
+ this.recordCppFnPtrBinding((0, tree_sitter_helpers_1.getNodeText)(left, this.source), (0, tree_sitter_helpers_1.getChildByField)(node, 'right'));
5047
+ }
5048
+ }
5049
+ }
4372
5050
  // Static-member / value-read: `Enum.value`, `Type.CONST`, `Foo::BAR`.
4373
5051
  this.extractStaticMemberRef(node);
4374
5052
  // Local variable type annotations inside a body — `const items: Foo[] = []`,
@@ -4693,6 +5371,33 @@ class TreeSitterExtractor {
4693
5371
  }
4694
5372
  }
4695
5373
  }
5374
+ // VB.NET: `Inherits Base` / `Implements IFoo, IBar(Of T)` are STATEMENTS
5375
+ // inside the class body (children of the class node), not header clauses.
5376
+ // Each name is a simple/qualified/generic reference; generics unwrap to
5377
+ // the base identifier and dotted paths keep the trailing segment.
5378
+ if (this.language === 'vbnet' &&
5379
+ (child.type === 'inherits_statement' || child.type === 'implements_statement')) {
5380
+ const kind = child.type === 'inherits_statement' ? 'extends' : 'implements';
5381
+ for (const ref of child.namedChildren) {
5382
+ if (!ref || (ref.type !== 'simple_name' && ref.type !== 'qualified_name' && ref.type !== 'generic_name' && ref.type !== 'global_qualified_name'))
5383
+ continue;
5384
+ let name = (0, tree_sitter_helpers_1.getNodeText)(ref, this.source);
5385
+ name = name.replace(/\(\s*Of\b[^)]*\)/gi, '');
5386
+ const lastDot = name.lastIndexOf('.');
5387
+ if (lastDot >= 0)
5388
+ name = name.slice(lastDot + 1);
5389
+ name = name.trim();
5390
+ if (!name)
5391
+ continue;
5392
+ this.unresolvedReferences.push({
5393
+ fromNodeId: classId,
5394
+ referenceName: name,
5395
+ referenceKind: kind,
5396
+ line: ref.startPosition.row + 1,
5397
+ column: ref.startPosition.column,
5398
+ });
5399
+ }
5400
+ }
4696
5401
  // C#: `class Movie : BaseItem, IPlugin` → base_list with identifier children
4697
5402
  // base_list combines both base class and interfaces in a single colon-separated list.
4698
5403
  // We emit all as 'extends' since the syntax doesn't distinguish them.
@@ -4768,6 +5473,33 @@ class TreeSitterExtractor {
4768
5473
  if (child.type === 'field_declaration_list' || child.type === 'class_heritage') {
4769
5474
  this.extractInheritance(child, classId);
4770
5475
  }
5476
+ // CFML cfscript `component extends="Base" implements="IFoo,IBar" { ... }`
5477
+ // (also covers `interface extends="IBase" { ... }`, which reuses the same
5478
+ // component_attribute shape). Attributes are generic name=value pairs —
5479
+ // (identifier label, expression value) — not a dedicated extends_clause,
5480
+ // so filter by the label text. `implements` is a comma-separated list.
5481
+ if (child.type === 'component_attribute' && node.type === 'component') {
5482
+ const label = child.namedChildren.find((c) => c.type === 'identifier');
5483
+ const value = child.namedChildren.find((c) => c.type !== 'identifier');
5484
+ if (label && value) {
5485
+ const labelText = (0, tree_sitter_helpers_1.getNodeText)(label, this.source).toLowerCase();
5486
+ if (labelText === 'extends' || labelText === 'implements') {
5487
+ const valueText = (0, tree_sitter_helpers_1.getNodeText)(value, this.source).replace(/^["']|["']$/g, '');
5488
+ const names = labelText === 'implements'
5489
+ ? valueText.split(',').map((s) => s.trim()).filter(Boolean)
5490
+ : [valueText.trim()].filter(Boolean);
5491
+ for (const name of names) {
5492
+ this.unresolvedReferences.push({
5493
+ fromNodeId: classId,
5494
+ referenceName: name,
5495
+ referenceKind: labelText === 'implements' ? 'implements' : 'extends',
5496
+ line: value.startPosition.row + 1,
5497
+ column: value.startPosition.column,
5498
+ });
5499
+ }
5500
+ }
5501
+ }
5502
+ }
4771
5503
  }
4772
5504
  }
4773
5505
  /**
@@ -4827,7 +5559,7 @@ class TreeSitterExtractor {
4827
5559
  * Languages that support type annotations (TypeScript, etc.)
4828
5560
  */
4829
5561
  TYPE_ANNOTATION_LANGUAGES = new Set([
4830
- 'typescript', 'tsx', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
5562
+ 'typescript', 'tsx', 'arkts', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
4831
5563
  ]);
4832
5564
  /**
4833
5565
  * PHP pseudo-types and `self`/`static`/`parent` that aren't project symbols.
@@ -5721,7 +6453,19 @@ function extractFromSource(filePath, source, language, frameworkNames) {
5721
6453
  const extractor = new mybatis_extractor_1.MyBatisExtractor(filePath, source);
5722
6454
  result = extractor.extract();
5723
6455
  }
6456
+ else if (detectedLanguage === 'cfml' || detectedLanguage === 'cfscript') {
6457
+ // Custom extractor for CFML (.cfc/.cfm) — dialect-switches between the
6458
+ // tag-based cfml grammar and the bare-script cfscript grammar. Standalone
6459
+ // `.cfs` files (language 'cfscript') are always pure script (never `<`-led),
6460
+ // so routing them through here too gets them the same anonymous-component
6461
+ // filename fallback as a bare-script `.cfc` — without it a `.cfs` whose
6462
+ // `component { ... }` declares no name (the grammar has no `name` field;
6463
+ // CFML never spells one in source) stays `<anonymous>`.
6464
+ const extractor = new cfml_extractor_1.CfmlExtractor(filePath, source, detectedLanguage);
6465
+ result = extractor.extract();
6466
+ }
5724
6467
  else if (detectedLanguage === 'arkts') {
6468
+ // HomeGraph uses the arkanalyzer-backed ArkTS extractor (not tree-sitter).
5725
6469
  const extractor = new arkts_1.ArkTSExtractor(filePath, source);
5726
6470
  result = extractor.extract();
5727
6471
  }