@colbymchenry/codegraph-darwin-arm64 1.2.0 → 1.3.0

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