@colbymchenry/codegraph-darwin-arm64 1.1.6 → 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 (220) 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 +14 -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 +59 -0
  39. package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  40. package/lib/dist/extraction/languages/c-cpp.js +499 -3
  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 +20 -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 +845 -16
  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/graph/traversal.d.ts.map +1 -1
  100. package/lib/dist/graph/traversal.js +76 -17
  101. package/lib/dist/graph/traversal.js.map +1 -1
  102. package/lib/dist/index.d.ts +63 -1
  103. package/lib/dist/index.d.ts.map +1 -1
  104. package/lib/dist/index.js +310 -6
  105. package/lib/dist/index.js.map +1 -1
  106. package/lib/dist/installer/index.d.ts.map +1 -1
  107. package/lib/dist/installer/index.js +3 -1
  108. package/lib/dist/installer/index.js.map +1 -1
  109. package/lib/dist/mcp/daemon.d.ts +25 -3
  110. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  111. package/lib/dist/mcp/daemon.js +39 -7
  112. package/lib/dist/mcp/daemon.js.map +1 -1
  113. package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  114. package/lib/dist/mcp/dynamic-boundaries.js +2 -1
  115. package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
  116. package/lib/dist/mcp/early-ppid.d.ts +26 -0
  117. package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
  118. package/lib/dist/mcp/early-ppid.js +29 -0
  119. package/lib/dist/mcp/early-ppid.js.map +1 -0
  120. package/lib/dist/mcp/index.d.ts.map +1 -1
  121. package/lib/dist/mcp/index.js +21 -4
  122. package/lib/dist/mcp/index.js.map +1 -1
  123. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  124. package/lib/dist/mcp/proxy.js +21 -3
  125. package/lib/dist/mcp/proxy.js.map +1 -1
  126. package/lib/dist/mcp/startup-handshake.d.ts +44 -0
  127. package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
  128. package/lib/dist/mcp/startup-handshake.js +73 -0
  129. package/lib/dist/mcp/startup-handshake.js.map +1 -0
  130. package/lib/dist/mcp/tools.d.ts +22 -0
  131. package/lib/dist/mcp/tools.d.ts.map +1 -1
  132. package/lib/dist/mcp/tools.js +74 -9
  133. package/lib/dist/mcp/tools.js.map +1 -1
  134. package/lib/dist/project-config.d.ts +38 -0
  135. package/lib/dist/project-config.d.ts.map +1 -1
  136. package/lib/dist/project-config.js +101 -2
  137. package/lib/dist/project-config.js.map +1 -1
  138. package/lib/dist/resolution/callback-synthesizer.d.ts +1 -1
  139. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  140. package/lib/dist/resolution/callback-synthesizer.js +743 -11
  141. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  142. package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
  143. package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
  144. package/lib/dist/resolution/cooperative-yield.js +42 -0
  145. package/lib/dist/resolution/cooperative-yield.js.map +1 -0
  146. package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
  147. package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
  148. package/lib/dist/resolution/frameworks/cics.js +90 -0
  149. package/lib/dist/resolution/frameworks/cics.js.map +1 -0
  150. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
  151. package/lib/dist/resolution/frameworks/index.js +6 -0
  152. package/lib/dist/resolution/frameworks/index.js.map +1 -1
  153. package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
  154. package/lib/dist/resolution/frameworks/java.js +14 -6
  155. package/lib/dist/resolution/frameworks/java.js.map +1 -1
  156. package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
  157. package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  158. package/lib/dist/resolution/frameworks/terraform.js +277 -0
  159. package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
  160. package/lib/dist/resolution/import-resolver.d.ts +7 -0
  161. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  162. package/lib/dist/resolution/import-resolver.js +129 -4
  163. package/lib/dist/resolution/import-resolver.js.map +1 -1
  164. package/lib/dist/resolution/index.d.ts +44 -2
  165. package/lib/dist/resolution/index.d.ts.map +1 -1
  166. package/lib/dist/resolution/index.js +335 -30
  167. package/lib/dist/resolution/index.js.map +1 -1
  168. package/lib/dist/resolution/name-matcher.d.ts +22 -3
  169. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  170. package/lib/dist/resolution/name-matcher.js +484 -39
  171. package/lib/dist/resolution/name-matcher.js.map +1 -1
  172. package/lib/dist/resolution/strip-comments.d.ts +1 -1
  173. package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
  174. package/lib/dist/resolution/strip-comments.js +49 -0
  175. package/lib/dist/resolution/strip-comments.js.map +1 -1
  176. package/lib/dist/resolution/types.d.ts +20 -0
  177. package/lib/dist/resolution/types.d.ts.map +1 -1
  178. package/lib/dist/resolution/workspace-packages.d.ts +10 -0
  179. package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
  180. package/lib/dist/resolution/workspace-packages.js +142 -4
  181. package/lib/dist/resolution/workspace-packages.js.map +1 -1
  182. package/lib/dist/search/identifier-segments.d.ts +60 -0
  183. package/lib/dist/search/identifier-segments.d.ts.map +1 -0
  184. package/lib/dist/search/identifier-segments.js +176 -0
  185. package/lib/dist/search/identifier-segments.js.map +1 -0
  186. package/lib/dist/sync/git-hooks.d.ts.map +1 -1
  187. package/lib/dist/sync/git-hooks.js +2 -0
  188. package/lib/dist/sync/git-hooks.js.map +1 -1
  189. package/lib/dist/sync/watcher.d.ts +10 -5
  190. package/lib/dist/sync/watcher.d.ts.map +1 -1
  191. package/lib/dist/sync/watcher.js +51 -14
  192. package/lib/dist/sync/watcher.js.map +1 -1
  193. package/lib/dist/sync/worktree.d.ts.map +1 -1
  194. package/lib/dist/sync/worktree.js +5 -0
  195. package/lib/dist/sync/worktree.js.map +1 -1
  196. package/lib/dist/types.d.ts +19 -1
  197. package/lib/dist/types.d.ts.map +1 -1
  198. package/lib/dist/types.js +10 -0
  199. package/lib/dist/types.js.map +1 -1
  200. package/lib/dist/upgrade/index.js +1 -1
  201. package/lib/dist/upgrade/index.js.map +1 -1
  202. package/lib/node_modules/.package-lock.json +1 -1
  203. package/lib/package.json +1 -1
  204. package/package.json +1 -1
  205. package/lib/dist/reasoning/config.d.ts +0 -45
  206. package/lib/dist/reasoning/config.d.ts.map +0 -1
  207. package/lib/dist/reasoning/config.js +0 -171
  208. package/lib/dist/reasoning/config.js.map +0 -1
  209. package/lib/dist/reasoning/credentials.d.ts +0 -5
  210. package/lib/dist/reasoning/credentials.d.ts.map +0 -1
  211. package/lib/dist/reasoning/credentials.js +0 -83
  212. package/lib/dist/reasoning/credentials.js.map +0 -1
  213. package/lib/dist/reasoning/login.d.ts +0 -21
  214. package/lib/dist/reasoning/login.d.ts.map +0 -1
  215. package/lib/dist/reasoning/login.js +0 -85
  216. package/lib/dist/reasoning/login.js.map +0 -1
  217. package/lib/dist/reasoning/reasoner.d.ts +0 -43
  218. package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
  219. package/lib/dist/reasoning/reasoner.js +0 -308
  220. 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,24 +78,68 @@ 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
  */
83
105
  function extractName(node, source, extractor) {
106
+ const name = extractNameRaw(node, source, extractor);
107
+ // Universal fallback: recover a real identifier from a name still mangled by a
108
+ // macro the pre-parse didn't blank (C/C++ only — see recoverMangledName). A
109
+ // no-op on well-formed names, so a clean name is never altered.
110
+ return extractor.recoverMangledName ? extractor.recoverMangledName(name) : name;
111
+ }
112
+ function extractNameRaw(node, source, extractor) {
84
113
  const hookName = extractor.resolveName?.(node, source);
85
114
  if (hookName)
86
115
  return hookName;
87
116
  // Try field name first
88
117
  const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, extractor.nameField);
89
118
  if (nameNode) {
90
- // Unwrap pointer_declarator(s) for C/C++ pointer return types
119
+ // Unwrap pointer_declarator / reference_declarator for C/C++ pointer and
120
+ // reference return types (`int* f()`, `int& f()`, `int&& f()`). Without
121
+ // unwrapping the reference wrapper an inline reference-returning method is
122
+ // named "& f() const" instead of "f" — common in Unreal Engine gameplay
123
+ // headers (`const FGameplayTagContainer& GetActiveTags() const`). Out-of-line
124
+ // defs (`T& C::f()`) already resolve via the qualified-name hook. A
125
+ // pointer_declarator exposes its inner through a `declarator` field; a
126
+ // reference_declarator has none, so it's reached via namedChild(0).
91
127
  let resolved = nameNode;
92
- while (resolved.type === 'pointer_declarator') {
128
+ while (resolved.type === 'pointer_declarator' || resolved.type === 'reference_declarator') {
93
129
  const inner = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
94
130
  if (!inner)
95
131
  break;
96
132
  resolved = inner;
97
133
  }
134
+ // C++ user-defined conversion operator: the declarator is an `operator_cast`
135
+ // whose first child is the target type and second is the `() const` tail. Name
136
+ // it `operator <type>` (the conventional spelling) rather than the whole
137
+ // `operator EALSMovementState() const` declarator, so it matches symbolic
138
+ // overloads (`operator+`) and is findable by the type name.
139
+ if (resolved.type === 'operator_cast') {
140
+ const typeNode = resolved.namedChild(0);
141
+ return typeNode ? `operator ${(0, tree_sitter_helpers_1.getNodeText)(typeNode, source).trim()}` : (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
142
+ }
98
143
  // Handle complex declarators (C/C++)
99
144
  if (resolved.type === 'function_declarator' || resolved.type === 'declarator') {
100
145
  const innerName = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
@@ -324,7 +369,7 @@ class TreeSitterExtractor {
324
369
  // Value-reference edges (default ON; set CODEGRAPH_VALUE_REFS=0 to disable; see flushValueRefs).
325
370
  // Same-file reads of file-scope const/var symbols → `references` edges so impact analysis catches
326
371
  // value consumers ("change this constant/table, affect its readers").
327
- 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']);
328
373
  static MAX_VALUE_REF_NODES = 20_000;
329
374
  valueRefsEnabled = process.env.CODEGRAPH_VALUE_REFS !== '0';
330
375
  fileScopeValues = new Map();
@@ -333,6 +378,20 @@ class TreeSitterExtractor {
333
378
  errors = [];
334
379
  extractor = null;
335
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();
336
395
  methodIndex = null; // lookup key → node ID for Pascal defProc lookup
337
396
  // Function-as-value capture (#756): per-language spec + candidates collected
338
397
  // during the walk, gated & flushed into unresolvedReferences at end-of-file
@@ -393,7 +452,7 @@ class TreeSitterExtractor {
393
452
  // this.source so downstream getNodeText reads the same bytes the parser
394
453
  // saw (identical outside the blanked directive lines).
395
454
  if (this.extractor?.preParse) {
396
- this.source = this.extractor.preParse(this.source);
455
+ this.source = this.extractor.preParse(this.source, this.filePath);
397
456
  }
398
457
  this.tree = parser.parse(this.source) ?? null;
399
458
  if (!this.tree) {
@@ -851,6 +910,31 @@ class TreeSitterExtractor {
851
910
  if (skipChildren)
852
911
  return;
853
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
+ }
854
938
  // Function-as-value capture (#756) — independent of the dispatch ladder
855
939
  // below (the captured container types have no other handler there), so it
856
940
  // can never shadow or be shadowed by an extraction branch.
@@ -1084,7 +1168,8 @@ class TreeSitterExtractor {
1084
1168
  // falls through to its normal declaration extraction).
1085
1169
  else if (nodeType === 'export_statement' &&
1086
1170
  (this.language === 'typescript' || this.language === 'tsx' ||
1087
- this.language === 'javascript' || this.language === 'jsx') &&
1171
+ this.language === 'javascript' || this.language === 'jsx' ||
1172
+ this.language === 'arkts') &&
1088
1173
  (0, tree_sitter_helpers_1.getChildByField)(node, 'source')) {
1089
1174
  const parentId = this.nodeStack[this.nodeStack.length - 1];
1090
1175
  if (parentId)
@@ -1114,7 +1199,7 @@ class TreeSitterExtractor {
1114
1199
  // produce an `instantiates` reference. Children still walked so
1115
1200
  // nested calls inside the constructor args (`new Foo(bar())`) get
1116
1201
  // their own `calls` refs.
1117
- else if (INSTANTIATION_KINDS.has(nodeType)) {
1202
+ else if (INSTANTIATION_KINDS.has(nodeType) || this.isVbnetConstructorShapedArrayCreation(node)) {
1118
1203
  this.extractInstantiation(node);
1119
1204
  // Java/C# `new T(...) { ... }` — anonymous class with body. Without
1120
1205
  // extracting it as a class node + its methods, the interface→impl
@@ -1265,7 +1350,8 @@ class TreeSitterExtractor {
1265
1350
  buildQualifiedName(name) {
1266
1351
  // Build a qualified name from the semantic hierarchy only (no file path).
1267
1352
  // The file path is stored separately in filePath and pollutes FTS if included here.
1268
- const parts = [];
1353
+ // C/C++ enclosing namespaces prefix first (empty for every other language).
1354
+ const parts = [...this.namespacePrefix];
1269
1355
  for (const nodeId of this.nodeStack) {
1270
1356
  const node = this.nodes.find((n) => n.id === nodeId);
1271
1357
  if (node && node.kind !== 'file') {
@@ -1484,6 +1570,15 @@ class TreeSitterExtractor {
1484
1570
  extractClass(node, kind = 'class') {
1485
1571
  if (!this.extractor)
1486
1572
  return;
1573
+ // Skip forward declarations / elaborated type references (`class Foo;`) in
1574
+ // languages that opt in — bodiless there means "not a definition", so it
1575
+ // would otherwise mint a phantom node competing with the real definition
1576
+ // (#1093). Languages where a bodiless class is complete (Kotlin, Scala)
1577
+ // leave the flag unset. Resolved once here and reused for the body walk.
1578
+ const resolvedBody = this.extractor.resolveBody?.(node, this.extractor.bodyField)
1579
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
1580
+ if (this.extractor.skipBodilessClass && !resolvedBody)
1581
+ return;
1487
1582
  const name = extractName(node, this.source, this.extractor);
1488
1583
  const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1489
1584
  const visibility = this.extractor.getVisibility?.(node);
@@ -1503,10 +1598,7 @@ class TreeSitterExtractor {
1503
1598
  this.extractDecoratorsFor(node, classNode.id);
1504
1599
  // Push to stack and visit body
1505
1600
  this.nodeStack.push(classNode.id);
1506
- let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
1507
- ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
1508
- if (!body)
1509
- body = node;
1601
+ const body = resolvedBody ?? node;
1510
1602
  // Visit all children for methods and properties
1511
1603
  for (let i = 0; i < body.namedChildCount; i++) {
1512
1604
  const child = body.namedChild(i);
@@ -2340,7 +2432,8 @@ class TreeSitterExtractor {
2340
2432
  const isExported = this.extractor.isExported?.(node, this.source) ?? false;
2341
2433
  // Extract variable declarators based on language
2342
2434
  if (this.language === 'typescript' || this.language === 'javascript' ||
2343
- this.language === 'tsx' || this.language === 'jsx') {
2435
+ this.language === 'tsx' || this.language === 'jsx' || this.language === 'cfscript' ||
2436
+ this.language === 'arkts') {
2344
2437
  // Handle lexical_declaration and variable_declaration
2345
2438
  // These contain one or more variable_declarator children
2346
2439
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2754,7 +2847,7 @@ class TreeSitterExtractor {
2754
2847
  // property/method nodes under the type alias so `recorder.stop()`
2755
2848
  // can attach the call edge to `RecorderHandle.stop` instead of
2756
2849
  // an unrelated class method picked by path-proximity (#359).
2757
- if (this.language === 'typescript' || this.language === 'tsx') {
2850
+ if (this.language === 'typescript' || this.language === 'tsx' || this.language === 'arkts') {
2758
2851
  this.extractTsTypeAliasMembers(value, typeAliasNode);
2759
2852
  // `type List = [ Service<'name', Req, Resp>, … ]` — surface each
2760
2853
  // entry's string-literal name as a searchable member (issue #634).
@@ -2975,7 +3068,8 @@ class TreeSitterExtractor {
2975
3068
  // Link each imported binding to its definition so imported-but-not-
2976
3069
  // called/typed symbols still record a cross-file dependency (TS/JS only).
2977
3070
  if (this.language === 'typescript' || this.language === 'tsx' ||
2978
- this.language === 'javascript' || this.language === 'jsx') {
3071
+ this.language === 'javascript' || this.language === 'jsx' ||
3072
+ this.language === 'arkts') {
2979
3073
  const parentId = this.nodeStack[this.nodeStack.length - 1];
2980
3074
  if (parentId)
2981
3075
  this.emitImportBindingRefs(node, parentId);
@@ -3414,12 +3508,522 @@ class TreeSitterExtractor {
3414
3508
  /**
3415
3509
  * Extract a function call
3416
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
+ }
3417
3573
  extractCall(node) {
3418
3574
  if (this.nodeStack.length === 0)
3419
3575
  return;
3420
3576
  const callerId = this.nodeStack[this.nodeStack.length - 1];
3421
3577
  if (!callerId)
3422
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
+ }
3800
+ // Ruby `call` nodes use `receiver` + `method` fields (tree-sitter-ruby), not
3801
+ // the `object`/`name`/`function` fields the branches below expect — so
3802
+ // without this they fell through to the generic path, which took the
3803
+ // receiver as the callee and DROPPED the method name: `lg.log()` produced a
3804
+ // `calls` ref to `lg` (unresolvable) and no method edge was ever recorded,
3805
+ // so a Ruby method's callers/impact were invisible (#1108 follow-up). Build
3806
+ // `receiver.method` so the resolver — and local-variable type inference —
3807
+ // can link it; `Foo.new` stays an instantiation.
3808
+ if (this.language === 'ruby' && (node.type === 'call' || node.type === 'method_call')) {
3809
+ const methodNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'method');
3810
+ const methodName = methodNode ? (0, tree_sitter_helpers_1.getNodeText)(methodNode, this.source) : '';
3811
+ if (!methodName)
3812
+ return; // operator/element-reference call with no method name
3813
+ const receiverNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'receiver');
3814
+ const line = node.startPosition.row + 1;
3815
+ const column = node.startPosition.column;
3816
+ if (!receiverNode) {
3817
+ // Bare `foo(...)` — just the method name (unchanged behavior).
3818
+ this.unresolvedReferences.push({ fromNodeId: callerId, referenceName: methodName, referenceKind: 'calls', line, column });
3819
+ return;
3820
+ }
3821
+ const receiverName = (0, tree_sitter_helpers_1.getNodeText)(receiverNode, this.source);
3822
+ // `Foo.new` / `Foo::Bar.new` is construction — emit an `instantiates` ref to
3823
+ // the class (last `::` segment), preserving the "what creates X" edge.
3824
+ if (methodName === 'new') {
3825
+ const className = receiverName.includes('::')
3826
+ ? receiverName.slice(receiverName.lastIndexOf('::') + 2)
3827
+ : receiverName;
3828
+ if (/^[A-Z]/.test(className)) {
3829
+ this.unresolvedReferences.push({ fromNodeId: callerId, referenceName: className, referenceKind: 'instantiates', line, column });
3830
+ return;
3831
+ }
3832
+ }
3833
+ const SKIP_RECEIVERS = new Set(['self', 'super']);
3834
+ const skip = SKIP_RECEIVERS.has(receiverName);
3835
+ this.unresolvedReferences.push({
3836
+ fromNodeId: callerId,
3837
+ referenceName: skip ? methodName : `${receiverName}.${methodName}`,
3838
+ referenceKind: 'calls',
3839
+ line,
3840
+ column,
3841
+ });
3842
+ // A capitalized (constant) receiver — `Foo.bar`, a class/module method call
3843
+ // — is itself a dependency on that constant; emit a `references` ref so a
3844
+ // class used only via its class methods still records a dependent (the edge
3845
+ // the old receiver-only callee happened to provide, now made explicit).
3846
+ if (!skip && receiverNode.type === 'constant') {
3847
+ this.unresolvedReferences.push({
3848
+ fromNodeId: callerId,
3849
+ referenceName: receiverName,
3850
+ referenceKind: 'references',
3851
+ line: receiverNode.startPosition.row + 1,
3852
+ column: receiverNode.startPosition.column,
3853
+ });
3854
+ }
3855
+ return;
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
+ }
3423
4027
  // Get the function/method being called
3424
4028
  let calleeName = '';
3425
4029
  // Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
@@ -3711,6 +4315,20 @@ class TreeSitterExtractor {
3711
4315
  }
3712
4316
  calleeName = reencode ? `${innerCallee}().${methodName}` : methodName;
3713
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
+ }
3714
4332
  else {
3715
4333
  calleeName = methodName;
3716
4334
  }
@@ -3753,6 +4371,40 @@ class TreeSitterExtractor {
3753
4371
  if (conv && conv[1])
3754
4372
  calleeName = conv[1];
3755
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
+ }
3756
4408
  if (calleeName) {
3757
4409
  this.unresolvedReferences.push({
3758
4410
  fromNodeId: callerId,
@@ -3772,6 +4424,26 @@ class TreeSitterExtractor {
3772
4424
  * Children are still walked so nested calls inside the constructor
3773
4425
  * arguments (`new Foo(bar())`) get their own `calls` references.
3774
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
+ }
3775
4447
  extractInstantiation(node) {
3776
4448
  if (this.nodeStack.length === 0)
3777
4449
  return;
@@ -3834,6 +4506,14 @@ class TreeSitterExtractor {
3834
4506
  const ltIdx = className.indexOf('<');
3835
4507
  if (ltIdx > 0)
3836
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
+ }
3837
4517
  // For namespaced/qualified constructors (`new ns.Foo()`,
3838
4518
  // `new ns::Foo()`) keep the trailing identifier — that's what
3839
4519
  // matches a class node in the index.
@@ -4048,6 +4728,28 @@ class TreeSitterExtractor {
4048
4728
  const consider = (n) => {
4049
4729
  if (!n)
4050
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
+ }
4051
4753
  // `marker_annotation` is Java's grammar for arg-less annotations
4052
4754
  // (`@Override`, `@Deprecated`); `attribute` is Swift's grammar for
4053
4755
  // attributes and PROPERTY WRAPPERS (`@objc`, `@Argument`, `@Published`,
@@ -4225,6 +4927,43 @@ class TreeSitterExtractor {
4225
4927
  }
4226
4928
  flush();
4227
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
+ }
4228
4967
  visitFunctionBody(body, _functionId) {
4229
4968
  if (!this.extractor)
4230
4969
  return;
@@ -4240,7 +4979,7 @@ class TreeSitterExtractor {
4240
4979
  if (this.extractor.callTypes.includes(nodeType)) {
4241
4980
  this.extractCall(node);
4242
4981
  }
4243
- else if (INSTANTIATION_KINDS.has(nodeType)) {
4982
+ else if (INSTANTIATION_KINDS.has(nodeType) || this.isVbnetConstructorShapedArrayCreation(node)) {
4244
4983
  // `new Foo()` inside a function body — emit an `instantiates`
4245
4984
  // reference. Without this branch the body walker only knew
4246
4985
  // about `call_expression`, so constructor invocations
@@ -4282,6 +5021,31 @@ class TreeSitterExtractor {
4282
5021
  if (nodeType === 'declaration' && this.language === 'cpp' && this.isCppStackConstruction(node)) {
4283
5022
  this.extractInstantiation(node);
4284
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
+ }
4285
5049
  // Static-member / value-read: `Enum.value`, `Type.CONST`, `Foo::BAR`.
4286
5050
  this.extractStaticMemberRef(node);
4287
5051
  // Local variable type annotations inside a body — `const items: Foo[] = []`,
@@ -4606,6 +5370,33 @@ class TreeSitterExtractor {
4606
5370
  }
4607
5371
  }
4608
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
+ }
4609
5400
  // C#: `class Movie : BaseItem, IPlugin` → base_list with identifier children
4610
5401
  // base_list combines both base class and interfaces in a single colon-separated list.
4611
5402
  // We emit all as 'extends' since the syntax doesn't distinguish them.
@@ -4681,6 +5472,33 @@ class TreeSitterExtractor {
4681
5472
  if (child.type === 'field_declaration_list' || child.type === 'class_heritage') {
4682
5473
  this.extractInheritance(child, classId);
4683
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
+ }
4684
5502
  }
4685
5503
  }
4686
5504
  /**
@@ -4740,7 +5558,7 @@ class TreeSitterExtractor {
4740
5558
  * Languages that support type annotations (TypeScript, etc.)
4741
5559
  */
4742
5560
  TYPE_ANNOTATION_LANGUAGES = new Set([
4743
- 'typescript', 'tsx', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
5561
+ 'typescript', 'tsx', 'arkts', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp', 'scala', 'php',
4744
5562
  ]);
4745
5563
  /**
4746
5564
  * PHP pseudo-types and `self`/`static`/`parent` that aren't project symbols.
@@ -5634,6 +6452,17 @@ function extractFromSource(filePath, source, language, frameworkNames) {
5634
6452
  const extractor = new mybatis_extractor_1.MyBatisExtractor(filePath, source);
5635
6453
  result = extractor.extract();
5636
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
+ }
5637
6466
  else if ((0, grammars_1.isFileLevelOnlyLanguage)(detectedLanguage)) {
5638
6467
  // No symbol extraction at this stage — files are tracked at the file-record
5639
6468
  // level only. Framework extractors (Drupal routing yml, Spring `@Value`