@colbymchenry/codegraph-darwin-x64 1.0.0 → 1.1.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 (175) hide show
  1. package/lib/dist/bin/codegraph.js +258 -17
  2. package/lib/dist/bin/codegraph.js.map +1 -1
  3. package/lib/dist/bin/fatal-handler.d.ts +20 -0
  4. package/lib/dist/bin/fatal-handler.d.ts.map +1 -0
  5. package/lib/dist/bin/fatal-handler.js +118 -0
  6. package/lib/dist/bin/fatal-handler.js.map +1 -0
  7. package/lib/dist/db/index.d.ts +22 -1
  8. package/lib/dist/db/index.d.ts.map +1 -1
  9. package/lib/dist/db/index.js +46 -1
  10. package/lib/dist/db/index.js.map +1 -1
  11. package/lib/dist/db/queries.d.ts +14 -0
  12. package/lib/dist/db/queries.d.ts.map +1 -1
  13. package/lib/dist/db/queries.js +25 -0
  14. package/lib/dist/db/queries.js.map +1 -1
  15. package/lib/dist/directory.d.ts +58 -0
  16. package/lib/dist/directory.d.ts.map +1 -1
  17. package/lib/dist/directory.js +165 -0
  18. package/lib/dist/directory.js.map +1 -1
  19. package/lib/dist/extraction/grammars.d.ts +11 -3
  20. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  21. package/lib/dist/extraction/grammars.js +14 -5
  22. package/lib/dist/extraction/grammars.js.map +1 -1
  23. package/lib/dist/extraction/index.d.ts.map +1 -1
  24. package/lib/dist/extraction/index.js +202 -32
  25. package/lib/dist/extraction/index.js.map +1 -1
  26. package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  27. package/lib/dist/extraction/languages/c-cpp.js +47 -2
  28. package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
  29. package/lib/dist/extraction/languages/csharp.d.ts.map +1 -1
  30. package/lib/dist/extraction/languages/csharp.js +20 -0
  31. package/lib/dist/extraction/languages/csharp.js.map +1 -1
  32. package/lib/dist/extraction/languages/dart.d.ts.map +1 -1
  33. package/lib/dist/extraction/languages/dart.js +22 -0
  34. package/lib/dist/extraction/languages/dart.js.map +1 -1
  35. package/lib/dist/extraction/languages/java.d.ts.map +1 -1
  36. package/lib/dist/extraction/languages/java.js +213 -9
  37. package/lib/dist/extraction/languages/java.js.map +1 -1
  38. package/lib/dist/extraction/languages/kotlin.d.ts.map +1 -1
  39. package/lib/dist/extraction/languages/kotlin.js +51 -0
  40. package/lib/dist/extraction/languages/kotlin.js.map +1 -1
  41. package/lib/dist/extraction/languages/scala.d.ts.map +1 -1
  42. package/lib/dist/extraction/languages/scala.js +19 -9
  43. package/lib/dist/extraction/languages/scala.js.map +1 -1
  44. package/lib/dist/extraction/parse-worker.js +4 -1
  45. package/lib/dist/extraction/parse-worker.js.map +1 -1
  46. package/lib/dist/extraction/tree-sitter-types.d.ts +13 -0
  47. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  48. package/lib/dist/extraction/tree-sitter.d.ts +119 -0
  49. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  50. package/lib/dist/extraction/tree-sitter.js +890 -11
  51. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  52. package/lib/dist/index.d.ts +33 -0
  53. package/lib/dist/index.d.ts.map +1 -1
  54. package/lib/dist/index.js +68 -7
  55. package/lib/dist/index.js.map +1 -1
  56. package/lib/dist/installer/index.d.ts.map +1 -1
  57. package/lib/dist/installer/index.js +33 -56
  58. package/lib/dist/installer/index.js.map +1 -1
  59. package/lib/dist/installer/instructions-template.d.ts +3 -3
  60. package/lib/dist/installer/instructions-template.d.ts.map +1 -1
  61. package/lib/dist/installer/instructions-template.js +4 -4
  62. package/lib/dist/installer/targets/claude.d.ts +18 -12
  63. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  64. package/lib/dist/installer/targets/claude.js +78 -6
  65. package/lib/dist/installer/targets/claude.js.map +1 -1
  66. package/lib/dist/installer/targets/shared.d.ts +12 -2
  67. package/lib/dist/installer/targets/shared.d.ts.map +1 -1
  68. package/lib/dist/installer/targets/shared.js +13 -12
  69. package/lib/dist/installer/targets/shared.js.map +1 -1
  70. package/lib/dist/installer/targets/types.d.ts +7 -0
  71. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  72. package/lib/dist/mcp/daemon-manager.d.ts +42 -0
  73. package/lib/dist/mcp/daemon-manager.d.ts.map +1 -0
  74. package/lib/dist/mcp/daemon-manager.js +129 -0
  75. package/lib/dist/mcp/daemon-manager.js.map +1 -0
  76. package/lib/dist/mcp/daemon-registry.d.ts +47 -0
  77. package/lib/dist/mcp/daemon-registry.d.ts.map +1 -0
  78. package/lib/dist/mcp/daemon-registry.js +229 -0
  79. package/lib/dist/mcp/daemon-registry.js.map +1 -0
  80. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  81. package/lib/dist/mcp/daemon.js +5 -0
  82. package/lib/dist/mcp/daemon.js.map +1 -1
  83. package/lib/dist/mcp/engine.d.ts.map +1 -1
  84. package/lib/dist/mcp/engine.js +8 -0
  85. package/lib/dist/mcp/engine.js.map +1 -1
  86. package/lib/dist/mcp/index.d.ts +1 -0
  87. package/lib/dist/mcp/index.d.ts.map +1 -1
  88. package/lib/dist/mcp/index.js +13 -0
  89. package/lib/dist/mcp/index.js.map +1 -1
  90. package/lib/dist/mcp/liveness-watchdog.d.ts +18 -0
  91. package/lib/dist/mcp/liveness-watchdog.d.ts.map +1 -0
  92. package/lib/dist/mcp/liveness-watchdog.js +207 -0
  93. package/lib/dist/mcp/liveness-watchdog.js.map +1 -0
  94. package/lib/dist/mcp/server-instructions.d.ts +18 -14
  95. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  96. package/lib/dist/mcp/server-instructions.js +57 -52
  97. package/lib/dist/mcp/server-instructions.js.map +1 -1
  98. package/lib/dist/mcp/session.d.ts.map +1 -1
  99. package/lib/dist/mcp/session.js +23 -18
  100. package/lib/dist/mcp/session.js.map +1 -1
  101. package/lib/dist/mcp/tools.d.ts +51 -1
  102. package/lib/dist/mcp/tools.d.ts.map +1 -1
  103. package/lib/dist/mcp/tools.js +585 -151
  104. package/lib/dist/mcp/tools.js.map +1 -1
  105. package/lib/dist/project-config.d.ts +19 -0
  106. package/lib/dist/project-config.d.ts.map +1 -0
  107. package/lib/dist/project-config.js +180 -0
  108. package/lib/dist/project-config.js.map +1 -0
  109. package/lib/dist/reasoning/config.d.ts +45 -0
  110. package/lib/dist/reasoning/config.d.ts.map +1 -0
  111. package/lib/dist/reasoning/config.js +171 -0
  112. package/lib/dist/reasoning/config.js.map +1 -0
  113. package/lib/dist/reasoning/credentials.d.ts +5 -0
  114. package/lib/dist/reasoning/credentials.d.ts.map +1 -0
  115. package/lib/dist/reasoning/credentials.js +83 -0
  116. package/lib/dist/reasoning/credentials.js.map +1 -0
  117. package/lib/dist/reasoning/login.d.ts +21 -0
  118. package/lib/dist/reasoning/login.d.ts.map +1 -0
  119. package/lib/dist/reasoning/login.js +85 -0
  120. package/lib/dist/reasoning/login.js.map +1 -0
  121. package/lib/dist/reasoning/reasoner.d.ts +43 -0
  122. package/lib/dist/reasoning/reasoner.d.ts.map +1 -0
  123. package/lib/dist/reasoning/reasoner.js +308 -0
  124. package/lib/dist/reasoning/reasoner.js.map +1 -0
  125. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +33 -0
  126. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -0
  127. package/lib/dist/resolution/c-fnptr-synthesizer.js +352 -0
  128. package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -0
  129. package/lib/dist/resolution/callback-synthesizer.d.ts +6 -1
  130. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  131. package/lib/dist/resolution/callback-synthesizer.js +1109 -1
  132. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  133. package/lib/dist/resolution/frameworks/goframe.d.ts +41 -0
  134. package/lib/dist/resolution/frameworks/goframe.d.ts.map +1 -0
  135. package/lib/dist/resolution/frameworks/goframe.js +112 -0
  136. package/lib/dist/resolution/frameworks/goframe.js.map +1 -0
  137. package/lib/dist/resolution/frameworks/index.d.ts +1 -0
  138. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
  139. package/lib/dist/resolution/frameworks/index.js +5 -1
  140. package/lib/dist/resolution/frameworks/index.js.map +1 -1
  141. package/lib/dist/resolution/frameworks/react.d.ts.map +1 -1
  142. package/lib/dist/resolution/frameworks/react.js +17 -60
  143. package/lib/dist/resolution/frameworks/react.js.map +1 -1
  144. package/lib/dist/resolution/goframe-synthesizer.d.ts +28 -0
  145. package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -0
  146. package/lib/dist/resolution/goframe-synthesizer.js +158 -0
  147. package/lib/dist/resolution/goframe-synthesizer.js.map +1 -0
  148. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  149. package/lib/dist/resolution/import-resolver.js +56 -0
  150. package/lib/dist/resolution/import-resolver.js.map +1 -1
  151. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  152. package/lib/dist/resolution/name-matcher.js +48 -8
  153. package/lib/dist/resolution/name-matcher.js.map +1 -1
  154. package/lib/dist/resolution/strip-comments.d.ts +1 -1
  155. package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
  156. package/lib/dist/resolution/strip-comments.js +2 -0
  157. package/lib/dist/resolution/strip-comments.js.map +1 -1
  158. package/lib/dist/sync/watcher.d.ts +68 -1
  159. package/lib/dist/sync/watcher.d.ts.map +1 -1
  160. package/lib/dist/sync/watcher.js +212 -14
  161. package/lib/dist/sync/watcher.js.map +1 -1
  162. package/lib/dist/telemetry/index.d.ts +0 -3
  163. package/lib/dist/telemetry/index.d.ts.map +1 -1
  164. package/lib/dist/telemetry/index.js +4 -7
  165. package/lib/dist/telemetry/index.js.map +1 -1
  166. package/lib/dist/upgrade/index.d.ts.map +1 -1
  167. package/lib/dist/upgrade/index.js +40 -4
  168. package/lib/dist/upgrade/index.js.map +1 -1
  169. package/lib/dist/utils.d.ts +14 -1
  170. package/lib/dist/utils.d.ts.map +1 -1
  171. package/lib/dist/utils.js +20 -2
  172. package/lib/dist/utils.js.map +1 -1
  173. package/lib/node_modules/.package-lock.json +1 -1
  174. package/lib/package.json +2 -2
  175. package/package.json +1 -1
@@ -57,6 +57,25 @@ const frameworks_1 = require("../resolution/frameworks");
57
57
  // Re-export for backward compatibility
58
58
  var tree_sitter_helpers_2 = require("./tree-sitter-helpers");
59
59
  Object.defineProperty(exports, "generateNodeId", { enumerable: true, get: function () { return tree_sitter_helpers_2.generateNodeId; } });
60
+ /**
61
+ * RTK Query generated-hook naming convention: `use` + PascalCase endpoint (with
62
+ * an optional `Lazy` variant prefix) + `Query`/`Mutation`. Matches the hook
63
+ * bindings to extract from an `export const {...} = api` destructuring. Kept in
64
+ * sync with the same convention in `callback-synthesizer.ts` (the synth side).
65
+ */
66
+ const RTK_HOOK_NAME_RE = /^use[A-Z][A-Za-z0-9]*(?:Query|Mutation)$/;
67
+ /** React HOC callees whose result is itself a component — a PascalCase const
68
+ * initialized with one of these is a component, not a constant (#841). */
69
+ const REACT_COMPONENT_HOCS = new Set(['forwardRef', 'memo', 'React.forwardRef', 'React.memo']);
70
+ /** Vue store collections whose object-literal members are the symbols an agent
71
+ * looks for. Extracted as function nodes so `actions`/`mutations`/`getters` are
72
+ * findable + readable (the foundation under any later dispatch-bridge synth). */
73
+ const VUE_STORE_COLLECTION_NAMES = new Set(['actions', 'mutations', 'getters']);
74
+ /** Store-definition callees whose config object carries those collections. */
75
+ const VUE_STORE_FACTORY_CALLEES = new Set(['defineStore', 'createStore']);
76
+ /** Distinct signals that a file is a Vuex/Pinia store (≥2 ⇒ treat a bare
77
+ * `const actions = {…}` as a store collection — see looksLikeVueStoreFile). */
78
+ const VUE_STORE_FILE_SIGNAL = /\bdefineStore\b|\bcreateStore\b|\bVuex\b|\bmutations\b|\bactions\b|\bgetters\b|\bnamespaced\b/g;
60
79
  /**
61
80
  * Extract the name from a node based on language
62
81
  */
@@ -164,6 +183,77 @@ function scalaBaseTypeName(node, source) {
164
183
  }
165
184
  }
166
185
  }
186
+ /**
187
+ * Resolve the declared identifier inside a C declarator. A `declaration`'s
188
+ * `declarator` field nests the name through `init_declarator` (with value),
189
+ * `pointer_declarator`/`array_declarator`/`parenthesized_declarator`
190
+ * wrappers (each via their own `declarator` field) down to an `identifier`.
191
+ * A `function_declarator` means the declaration is a function prototype (or a
192
+ * function-pointer var) — return null so it isn't extracted as a variable.
193
+ */
194
+ function cDeclaratorIdentifier(node) {
195
+ let cur = node;
196
+ let guard = 0;
197
+ while (cur && guard++ < 12) {
198
+ switch (cur.type) {
199
+ case 'identifier':
200
+ return cur;
201
+ case 'function_declarator':
202
+ return null;
203
+ case 'init_declarator':
204
+ case 'pointer_declarator':
205
+ case 'array_declarator':
206
+ case 'parenthesized_declarator':
207
+ cur = (0, tree_sitter_helpers_1.getChildByField)(cur, 'declarator');
208
+ break;
209
+ default:
210
+ return null;
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ /** First `simple_identifier` in `node`'s subtree (breadth-ish, first-found).
216
+ * Swift's property name nests as `property_declaration → <name> pattern →
217
+ * bound_identifier → simple_identifier`; this resolves it (and the bound name of
218
+ * a Kotlin/Swift property declarator for the shadow prune). For a tuple pattern
219
+ * (`let (a, b)`) it returns the first — acceptable, those are rare for consts. */
220
+ function firstSimpleIdentifier(node) {
221
+ const stack = node ? [node] : [];
222
+ let guard = 0;
223
+ while (stack.length > 0 && guard++ < 40) {
224
+ const n = stack.shift();
225
+ if (n.type === 'simple_identifier')
226
+ return n;
227
+ for (let i = 0; i < n.namedChildCount; i++) {
228
+ const c = n.namedChild(i);
229
+ if (c)
230
+ stack.push(c);
231
+ }
232
+ }
233
+ return null;
234
+ }
235
+ /** Swift property facts: the bound name, whether it's a `let`, and whether it's
236
+ * a *computed* property (a getter block, no stored value — never a constant). */
237
+ function swiftPropertyInfo(node, source) {
238
+ const pattern = (0, tree_sitter_helpers_1.getChildByField)(node, 'name') ??
239
+ node.namedChildren.find((c) => c.type === 'value_binding_pattern' || c.type === 'pattern') ??
240
+ null;
241
+ const binding = node.namedChildren.find((c) => c.type === 'value_binding_pattern');
242
+ const isLet = binding != null && (0, tree_sitter_helpers_1.getNodeText)(binding, source).trimStart().startsWith('let');
243
+ const isComputed = node.namedChildren.some((c) => c.type === 'computed_property' || c.type === 'protocol_property_requirements');
244
+ return { nameNode: firstSimpleIdentifier(pattern), isLet, isComputed };
245
+ }
246
+ /** True when `node` is (transitively) inside a C function body — i.e. a local,
247
+ * not a file/namespace-scope declaration. Walks the parent chain to the root. */
248
+ function hasFunctionAncestor(node) {
249
+ let p = node.parent;
250
+ while (p) {
251
+ if (p.type === 'function_definition')
252
+ return true;
253
+ p = p.parent;
254
+ }
255
+ return false;
256
+ }
167
257
  /**
168
258
  * PHP type-position wrapper node kinds (a type-hint is `named_type`,
169
259
  * `?Foo` is `optional_type`, `A|B` is `union_type`, `A&B` is
@@ -230,6 +320,15 @@ class TreeSitterExtractor {
230
320
  nodes = [];
231
321
  edges = [];
232
322
  unresolvedReferences = [];
323
+ // Value-reference edges (default ON; set CODEGRAPH_VALUE_REFS=0 to disable; see flushValueRefs).
324
+ // Same-file reads of file-scope const/var symbols → `references` edges so impact analysis catches
325
+ // value consumers ("change this constant/table, affect its readers").
326
+ static VALUE_REF_LANGS = new Set(['typescript', 'javascript', 'tsx', 'go', 'python', 'rust', 'ruby', 'c', 'java', 'csharp', 'php', 'scala', 'kotlin', 'swift', 'dart', 'pascal']);
327
+ static MAX_VALUE_REF_NODES = 20_000;
328
+ valueRefsEnabled = process.env.CODEGRAPH_VALUE_REFS !== '0';
329
+ fileScopeValues = new Map();
330
+ fileScopeValueCounts = new Map(); // file-scope nodes per name (conditional-def detection)
331
+ valueRefScopes = [];
233
332
  errors = [];
234
333
  extractor = null;
235
334
  nodeStack = []; // Stack of parent node IDs
@@ -239,6 +338,8 @@ class TreeSitterExtractor {
239
338
  // (see flushFnRefCandidates).
240
339
  fnRefSpec;
241
340
  fnRefCandidates = [];
341
+ // Memoized "is this a Vue store file" verdict (per-extractor = per-file).
342
+ vueStoreFile = null;
242
343
  constructor(filePath, source, language) {
243
344
  this.filePath = filePath;
244
345
  this.source = source;
@@ -326,6 +427,7 @@ class TreeSitterExtractor {
326
427
  // Gate + flush function-as-value candidates (#756) while the file's
327
428
  // nodes and import refs are complete and the file node is still pushed.
328
429
  this.flushFnRefCandidates();
430
+ this.flushValueRefs();
329
431
  if (packageNodeId)
330
432
  this.nodeStack.pop();
331
433
  this.nodeStack.pop();
@@ -514,6 +616,213 @@ class TreeSitterExtractor {
514
616
  });
515
617
  }
516
618
  }
619
+ /**
620
+ * Record value-reference bookkeeping as nodes are created: file-scope const/var symbols with
621
+ * distinctive names become reference targets; function/method/const/var symbols become reader
622
+ * scopes whose bodies flushValueRefs scans.
623
+ */
624
+ captureValueRefScope(kind, name, id, node) {
625
+ // Pascal targets `constant` only: its extractor emits function PARAMETERS
626
+ // (`Dest: TBufferWriter`) and class fields (`declField`) as `variable` at the
627
+ // enclosing scope, which would otherwise become noisy targets (a param name
628
+ // shared across many procs collapses to one file-wide target). Genuine
629
+ // Pascal shared values are `const` (`constant`), so restrict to that. (Unit
630
+ // `var` globals are the rare cost; the parameter/field noise dominates.)
631
+ const targetKindOk = this.language === 'pascal' ? kind === 'constant' : kind === 'constant' || kind === 'variable';
632
+ if (targetKindOk && name.length >= 3 && /[A-Z_]/.test(name)) {
633
+ const parentId = this.nodeStack[this.nodeStack.length - 1];
634
+ // file-scope OR class/module/struct/enum-scope constants are targets.
635
+ // Class/module scope matters for languages (Ruby) that keep nearly all
636
+ // constants inside a class or module; struct/enum scope matters for Swift,
637
+ // which namespaces shared constants in `struct`/`enum` (`enum Constants {
638
+ // static let X }`). Readers are same-file methods of that type.
639
+ if (parentId &&
640
+ (parentId.startsWith('file:') || parentId.startsWith('class:') ||
641
+ parentId.startsWith('module:') || parentId.startsWith('struct:') ||
642
+ parentId.startsWith('enum:'))) {
643
+ this.fileScopeValues.set(name, id);
644
+ // How many target nodes carry this name. A conditional def
645
+ // (`try: X = a; except: X = b`) makes >1 — distinct from a local shadow,
646
+ // which adds a binding the prune must catch (see flushValueRefs).
647
+ this.fileScopeValueCounts.set(name, (this.fileScopeValueCounts.get(name) ?? 0) + 1);
648
+ }
649
+ }
650
+ if (kind === 'function' || kind === 'method' || kind === 'constant' || kind === 'variable') {
651
+ this.valueRefScopes.push({ id, node, name });
652
+ }
653
+ }
654
+ /**
655
+ * Emit same-file `references` edges from a symbol to the file-scope const/var it reads (TS/JS).
656
+ * The engine doesn't edge const→consumer, so impact analysis misses "change this table, affect
657
+ * its readers" (the ReScript-PR false positive). Same-file only (resolution is unambiguous),
658
+ * distinctive target names only (dodges the local-shadowing precision trap documented on
659
+ * function_ref), deduped per (reader, target). Default on (CODEGRAPH_VALUE_REFS=0 disables) +
660
+ * additive. Shadowed targets are pruned — see below.
661
+ */
662
+ flushValueRefs() {
663
+ const scopes = this.valueRefScopes;
664
+ const targets = this.fileScopeValues;
665
+ const fileScopeCounts = this.fileScopeValueCounts;
666
+ this.valueRefScopes = [];
667
+ this.fileScopeValues = new Map();
668
+ this.fileScopeValueCounts = new Map();
669
+ if (!this.valueRefsEnabled || !TreeSitterExtractor.VALUE_REF_LANGS.has(this.language))
670
+ return;
671
+ if (targets.size === 0 || scopes.length === 0 || (0, generated_detection_1.isGeneratedFile)(this.filePath))
672
+ return;
673
+ // Prune SHADOWED targets. A target re-bound in an INNER scope (a
674
+ // bundled/Emscripten `const Module` re-declared as a nested `var Module`; a
675
+ // Go package `const Timeout` shadowed by a local `Timeout := …`; a Python
676
+ // module `CONFIG` shadowed by a local `CONFIG = …`) resolves to the inner
677
+ // binding for nested readers, so a file-scope edge is a false positive.
678
+ // Inner re-bindings aren't graph nodes, so detect them at the syntax level:
679
+ // count every declarator of the name across the tree and compare against how
680
+ // many FILE-SCOPE nodes carry it. A real shadow makes (declarators >
681
+ // file-scope nodes) — the excess is the local binding. A conditional
682
+ // module-level def (`try: X = a; except: X = b`) makes them EQUAL (both
683
+ // declarators are file-scope nodes), so it's correctly kept. Complements the
684
+ // path-based isGeneratedFile() check, which can't catch content-minified
685
+ // bundles.
686
+ //
687
+ // Declarator node types are per-grammar; a file only contains its own
688
+ // language's nodes, so matching all of them in one switch is safe.
689
+ if (this.tree) {
690
+ const declCounts = new Map();
691
+ const bump = (nameNode) => {
692
+ // `simple_identifier` is Kotlin's name node (a property declarator's name).
693
+ if (nameNode && (nameNode.type === 'identifier' || nameNode.type === 'simple_identifier')) {
694
+ const nm = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
695
+ if (targets.has(nm))
696
+ declCounts.set(nm, (declCounts.get(nm) ?? 0) + 1);
697
+ }
698
+ };
699
+ const dstack = [this.tree.rootNode];
700
+ let dvisited = 0;
701
+ while (dstack.length > 0 && dvisited < TreeSitterExtractor.MAX_VALUE_REF_NODES) {
702
+ const n = dstack.pop();
703
+ dvisited++;
704
+ switch (n.type) {
705
+ case 'variable_declarator': // TS/JS/tsx
706
+ case 'const_spec': // Go `const X = …`
707
+ case 'var_spec': // Go `var X = …`
708
+ bump(n.namedChild(0));
709
+ break;
710
+ case 'const_item': // Rust `const X: T = …`
711
+ case 'static_item': // Rust `static X: T = …`
712
+ bump((0, tree_sitter_helpers_1.getChildByField)(n, 'name'));
713
+ break;
714
+ case 'let_declaration': // Rust `let x = …` (locals — the shadow source)
715
+ case 'short_var_declaration': // Go `x, Y := …`
716
+ case 'assignment': { // Python `X = …` / `X: T = …` / `A, B = …`
717
+ const left = (0, tree_sitter_helpers_1.getChildByField)(n, 'left') ?? (0, tree_sitter_helpers_1.getChildByField)(n, 'pattern') ?? n.namedChild(0);
718
+ if (left?.type === 'identifier')
719
+ bump(left);
720
+ else if (left)
721
+ for (const c of left.namedChildren)
722
+ bump(c);
723
+ break;
724
+ }
725
+ case 'init_declarator': // C `T X = …` (file-scope const AND the local that shadows it)
726
+ bump(cDeclaratorIdentifier(n));
727
+ break;
728
+ case 'val_definition': // Scala `val X = …` (object/top-level const AND a method-local that shadows it)
729
+ case 'var_definition': { // Scala `var X = …`
730
+ const pat = (0, tree_sitter_helpers_1.getChildByField)(n, 'pattern');
731
+ if (pat?.type === 'identifier')
732
+ bump(pat);
733
+ break;
734
+ }
735
+ case 'static_final_declaration': // Dart top-level/`static` `const`/`final` (the target itself)
736
+ case 'initialized_identifier': // Dart instance field / `var`
737
+ case 'initialized_variable_definition': { // Dart a method-local `const`/`final`/`var` that shadows a const
738
+ const id = n.namedChildren.find((c) => c.type === 'identifier');
739
+ if (id)
740
+ bump(id);
741
+ break;
742
+ }
743
+ case 'declConst': // Pascal unit/class `const` (the target itself) AND a function-local `const` that shadows it
744
+ case 'declVar': { // Pascal a function-local `var` that shadows a const
745
+ bump((0, tree_sitter_helpers_1.getChildByField)(n, 'name'));
746
+ break;
747
+ }
748
+ case 'property_declaration': { // Kotlin / Swift `val`/`let X = …` (object/static const AND a method-local that shadows it)
749
+ // Kotlin: variable_declaration → simple_identifier; Swift: a `pattern`
750
+ // (`<name>` field) → simple_identifier. Resolve either shape.
751
+ const vd = n.namedChildren.find((c) => c.type === 'variable_declaration');
752
+ const id = vd
753
+ ? vd.namedChildren.find((c) => c.type === 'simple_identifier')
754
+ : firstSimpleIdentifier((0, tree_sitter_helpers_1.getChildByField)(n, 'name') ??
755
+ n.namedChildren.find((c) => c.type === 'value_binding_pattern' || c.type === 'pattern') ??
756
+ null);
757
+ if (id)
758
+ bump(id);
759
+ break;
760
+ }
761
+ }
762
+ for (let i = 0; i < n.namedChildCount; i++) {
763
+ const c = n.namedChild(i);
764
+ if (c)
765
+ dstack.push(c);
766
+ }
767
+ }
768
+ for (const [nm, c] of declCounts)
769
+ if (c > (fileScopeCounts.get(nm) ?? 1))
770
+ targets.delete(nm);
771
+ if (targets.size === 0)
772
+ return;
773
+ }
774
+ for (const scope of scopes) {
775
+ const seen = new Set();
776
+ const stack = [scope.node];
777
+ // Dart and Pascal attach a function/method BODY as a *next sibling* of the
778
+ // signature node that is stored as the reader scope (Dart `method_signature`
779
+ // ← `function_body`; Pascal `declProc` ← `block`, both under a `defProc`),
780
+ // not as a child — so the scope subtree is just the signature and the reads
781
+ // live in the sibling. Pull it in. (A body as a next sibling of the scope
782
+ // node is unique to Dart/Pascal among the value-ref languages — every other
783
+ // grammar nests the body inside the function node — so this is inert
784
+ // elsewhere.)
785
+ const sib = scope.node.nextNamedSibling;
786
+ if (sib && (sib.type === 'function_body' || sib.type === 'block'))
787
+ stack.push(sib);
788
+ let visited = 0;
789
+ while (stack.length > 0 && visited < TreeSitterExtractor.MAX_VALUE_REF_NODES) {
790
+ const n = stack.pop();
791
+ visited++;
792
+ // `constant` covers Ruby, where both a constant's definition and its
793
+ // references are `constant`-typed nodes, not `identifier`. `name` covers
794
+ // PHP, where a constant reference — bare `MAX_ITEMS` or the const half of
795
+ // `self::MAX_ITEMS` / `Foo::MAX_ITEMS` — is a `name` node (a `$var` local
796
+ // is a `variable_name`, a different namespace, so it can never shadow a
797
+ // bare constant — no prune wiring needed). `simple_identifier` covers
798
+ // Kotlin, whose every name reference (a const read included) is that
799
+ // node type. Safe across languages: a file only holds its own grammar's
800
+ // nodes; `name` is PHP-only and `simple_identifier` is Kotlin-only here.
801
+ if (n.type === 'identifier' || n.type === 'constant' ||
802
+ n.type === 'name' || n.type === 'simple_identifier') {
803
+ const refName = (0, tree_sitter_helpers_1.getNodeText)(n, this.source);
804
+ const targetId = targets.get(refName);
805
+ // Skip self and same-name targets: a symbol referencing a file-scope
806
+ // sibling of its own name (the two halves of a conditional `try: X=…;
807
+ // except: X=…`) is never a meaningful value read.
808
+ if (targetId && targetId !== scope.id && refName !== scope.name && !seen.has(targetId)) {
809
+ seen.add(targetId);
810
+ this.edges.push({
811
+ source: scope.id,
812
+ target: targetId,
813
+ kind: 'references',
814
+ metadata: { valueRef: true },
815
+ });
816
+ }
817
+ }
818
+ for (let i = 0; i < n.namedChildCount; i++) {
819
+ const c = n.namedChild(i);
820
+ if (c)
821
+ stack.push(c);
822
+ }
823
+ }
824
+ }
825
+ }
517
826
  /**
518
827
  * Visit a node and extract information
519
828
  */
@@ -652,8 +961,13 @@ class TreeSitterExtractor {
652
961
  skipChildren = true;
653
962
  }
654
963
  // Check for variable declarations (const, let, var, etc.)
655
- // Only extract top-level variables (not inside functions/methods)
656
- else if (this.extractor.variableTypes.includes(nodeType) && !this.isInsideClassLikeNode()) {
964
+ // Only extract top-level variables (not inside functions/methods) — plus
965
+ // class/module-scope CONSTANTS, which Ruby (and other const-in-class
966
+ // languages) keep almost exclusively inside a class/module. A Ruby `CONST =
967
+ // …` has a `constant`-typed LHS; other languages don't put one here, so this
968
+ // is effectively Ruby-only and doesn't disturb their class-internal locals.
969
+ else if (this.extractor.variableTypes.includes(nodeType) &&
970
+ (!this.isInsideClassLikeNode() || this.isClassScopeConstantAssignment(node))) {
657
971
  this.extractVariable(node);
658
972
  // extractVariable doesn't walk every initializer shape (object literals
659
973
  // are deliberately skipped; Python/Ruby don't walk at all), so scan the
@@ -675,6 +989,20 @@ class TreeSitterExtractor {
675
989
  nodeType === 'property_declaration' &&
676
990
  this.isInsideClassLikeNode()) {
677
991
  const ownerId = this.nodeStack[this.nodeStack.length - 1];
992
+ // A `static let`/`static var` member is a SHARED constant of the type
993
+ // (Swift's `static`-namespacing idiom, esp. in `enum`/`struct`) — extract
994
+ // it as `constant`/`variable` so value-reference edges can target it. An
995
+ // instance stored property stays a `field` (per-instance; Swift instance
996
+ // properties otherwise aren't own nodes — that's unchanged). A *computed*
997
+ // property (getter, no stored value) is never a constant — skip the node.
998
+ const { nameNode, isLet, isComputed } = swiftPropertyInfo(node, this.source);
999
+ if (nameNode && !isComputed) {
1000
+ const isStatic = this.extractor.isStatic?.(node) ?? false;
1001
+ this.createNode(isStatic ? (isLet ? 'constant' : 'variable') : 'field', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node, {
1002
+ visibility: this.extractor.getVisibility?.(node),
1003
+ isStatic,
1004
+ });
1005
+ }
678
1006
  if (ownerId) {
679
1007
  this.extractDecoratorsFor(node, ownerId);
680
1008
  this.extractVariableTypeAnnotation(node, ownerId);
@@ -732,6 +1060,22 @@ class TreeSitterExtractor {
732
1060
  if (parentId)
733
1061
  this.emitReExportRefs(node, parentId);
734
1062
  }
1063
+ // Vuex MODULE default export — `export default { namespaced, actions: {…},
1064
+ // mutations: {…} }` (the canonical Vuex module shape). Object-literal methods
1065
+ // aren't otherwise extracted, so scan the config's actions/mutations/getters
1066
+ // collections and extract their methods as nodes. Store-file gated (the
1067
+ // ≥2-signal heuristic) so a plain default-exported object is untouched; skip
1068
+ // the subtree afterward (the collection methods are now handled).
1069
+ else if (nodeType === 'export_statement' &&
1070
+ (this.language === 'typescript' || this.language === 'tsx' ||
1071
+ this.language === 'javascript' || this.language === 'jsx') &&
1072
+ this.looksLikeVueStoreFile()) {
1073
+ const exported = (0, tree_sitter_helpers_1.getChildByField)(node, 'value');
1074
+ if (exported && (exported.type === 'object' || exported.type === 'object_expression')) {
1075
+ this.extractStoreCollectionMethods(exported);
1076
+ skipChildren = true;
1077
+ }
1078
+ }
735
1079
  // Check for function calls
736
1080
  else if (this.extractor.callTypes.includes(nodeType)) {
737
1081
  this.extractCall(node);
@@ -843,6 +1187,8 @@ class TreeSitterExtractor {
843
1187
  });
844
1188
  }
845
1189
  }
1190
+ if (this.valueRefsEnabled)
1191
+ this.captureValueRefScope(kind, name, id, node);
846
1192
  return newNode;
847
1193
  }
848
1194
  /**
@@ -938,6 +1284,18 @@ class TreeSitterExtractor {
938
1284
  parentNode.kind === 'enum' ||
939
1285
  parentNode.kind === 'module');
940
1286
  }
1287
+ /**
1288
+ * Ruby `CONST = …` assignment whose LHS is a `constant` node — a class/module
1289
+ * (or top-level) constant worth extracting as a symbol even inside a class.
1290
+ * Other languages don't give an assignment a `constant`-typed LHS, so this
1291
+ * gate is effectively Ruby-only.
1292
+ */
1293
+ isClassScopeConstantAssignment(node) {
1294
+ if (node.type !== 'assignment')
1295
+ return false;
1296
+ const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') ?? node.namedChild(0);
1297
+ return left?.type === 'constant';
1298
+ }
941
1299
  /**
942
1300
  * Extract a function
943
1301
  */
@@ -1026,6 +1384,70 @@ class TreeSitterExtractor {
1026
1384
  }
1027
1385
  this.nodeStack.pop();
1028
1386
  }
1387
+ /**
1388
+ * Detect a React component declared via an HOC wrapper whose result is itself a
1389
+ * component: `forwardRef(...)`, `memo(...)`, `React.forwardRef/memo(...)`, and
1390
+ * styled-components / emotion `styled.tag\`…\`` / `styled(Base)\`…\``. These
1391
+ * initializers are a call / tagged-template (not a bare arrow), so the const is
1392
+ * otherwise classified `constant` — and a constant is skipped by both the
1393
+ * JSX-render edge synthesizer and component resolution, so `<Button/>` usages
1394
+ * get no edge and callers/impact silently return empty (#841).
1395
+ *
1396
+ * Returns `{ inner }` — the inline render function to extract as the component
1397
+ * body, or `null` when the wrapper has no inline function (`memo(Imported)`,
1398
+ * `styled.button\`…\``) and only a bodyless component node is minted — or
1399
+ * `undefined` when this initializer is not a recognized component wrapper.
1400
+ */
1401
+ reactComponentHoc(valueNode) {
1402
+ if (valueNode.type !== 'call_expression')
1403
+ return undefined;
1404
+ const callee = (0, tree_sitter_helpers_1.getChildByField)(valueNode, 'function');
1405
+ if (!callee)
1406
+ return undefined;
1407
+ const calleeText = (0, tree_sitter_helpers_1.getNodeText)(callee, this.source);
1408
+ // styled-components / emotion: `styled.button\`…\`` / `styled(Base)\`…\``.
1409
+ // tree-sitter models these tagged templates as a call_expression whose callee
1410
+ // is the `styled.x` / `styled(Base)` tag (\b avoids matching `styledFoo`).
1411
+ // No inline render fn — the argument is the CSS template.
1412
+ if (/^styled\b/.test(calleeText))
1413
+ return { inner: null };
1414
+ // React HOCs: `forwardRef`/`memo`/`React.forwardRef`/`React.memo`.
1415
+ if (!REACT_COMPONENT_HOCS.has(calleeText))
1416
+ return undefined;
1417
+ // The first arrow / function-expression argument is the render fn (if inline;
1418
+ // `memo(Imported)` passes a bare identifier and has none).
1419
+ const args = (0, tree_sitter_helpers_1.getChildByField)(valueNode, 'arguments');
1420
+ let inner = null;
1421
+ if (args) {
1422
+ for (let i = 0; i < args.namedChildCount; i++) {
1423
+ const a = args.namedChild(i);
1424
+ if (a && (a.type === 'arrow_function' || a.type === 'function_expression')) {
1425
+ inner = a;
1426
+ break;
1427
+ }
1428
+ }
1429
+ }
1430
+ return { inner };
1431
+ }
1432
+ /**
1433
+ * Emit a `component` node for an HOC-wrapped React component declaration (see
1434
+ * reactComponentHoc). Named by the declarator (`Button`) and located at it so
1435
+ * the node range spans the body. When the wrapper has an inline render
1436
+ * function, its body is walked so the component's callees (hooks, helpers) are
1437
+ * captured under the component node — matching how a plain
1438
+ * `const Foo = () => …` arrow component already behaves.
1439
+ */
1440
+ extractReactComponentNode(name, declarator, innerFn, extra) {
1441
+ const compNode = this.createNode('component', name, declarator, extra);
1442
+ if (!compNode || !innerFn || !this.extractor)
1443
+ return;
1444
+ this.nodeStack.push(compNode.id);
1445
+ const body = this.extractor.resolveBody?.(innerFn, this.extractor.bodyField)
1446
+ ?? (0, tree_sitter_helpers_1.getChildByField)(innerFn, this.extractor.bodyField);
1447
+ if (body)
1448
+ this.visitFunctionBody(body, compNode.id);
1449
+ this.nodeStack.pop();
1450
+ }
1029
1451
  /**
1030
1452
  * Extract a class
1031
1453
  */
@@ -1062,6 +1484,12 @@ class TreeSitterExtractor {
1062
1484
  this.visitNode(child);
1063
1485
  }
1064
1486
  }
1487
+ // Synthesize compile-time-generated members (Lombok accessors, #912). Runs
1488
+ // after the body so the hook can dedup against hand-written members, and
1489
+ // while the class is still on the stack so containment/QNs attach.
1490
+ if (this.extractor.synthesizeMembers) {
1491
+ this.extractor.synthesizeMembers(node, this.makeExtractorContext());
1492
+ }
1065
1493
  this.nodeStack.pop();
1066
1494
  }
1067
1495
  /**
@@ -1350,6 +1778,15 @@ class TreeSitterExtractor {
1350
1778
  const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1351
1779
  const visibility = this.extractor.getVisibility?.(node);
1352
1780
  const isStatic = this.extractor.isStatic?.(node) ?? false;
1781
+ // A class field that is actually a CONSTANT (Java `static final`, C# `const`
1782
+ // / `static readonly`) is extracted as `constant` kind, not `field`, so
1783
+ // value-reference edges treat it as a target (the gate accepts
1784
+ // constant/variable, not field). Scoped to languages whose `isConst`
1785
+ // predicate is field-shaped — other languages' fields stay `field`.
1786
+ const fieldKind = (this.language === 'java' || this.language === 'csharp') &&
1787
+ (this.extractor.isConst?.(node) ?? false)
1788
+ ? 'constant'
1789
+ : 'field';
1353
1790
  // Java field_declaration: "private final String name = value;" → variable_declarator(s) are direct children
1354
1791
  // C# field_declaration: wraps in variable_declaration → variable_declarator(s)
1355
1792
  let declarators = node.namedChildren.filter(c => c.type === 'variable_declarator');
@@ -1402,7 +1839,7 @@ class TreeSitterExtractor {
1402
1839
  continue;
1403
1840
  const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
1404
1841
  const signature = typeText ? `${typeText} ${name}` : name;
1405
- const fieldNode = this.createNode('field', name, decl, {
1842
+ const fieldNode = this.createNode(fieldKind, name, decl, {
1406
1843
  docstring,
1407
1844
  signature,
1408
1845
  visibility,
@@ -1427,7 +1864,7 @@ class TreeSitterExtractor {
1427
1864
  || node.namedChildren.find(c => c.type === 'identifier');
1428
1865
  if (nameNode) {
1429
1866
  const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
1430
- this.createNode('field', name, node, {
1867
+ this.createNode(fieldKind, name, node, {
1431
1868
  docstring,
1432
1869
  visibility,
1433
1870
  isStatic,
@@ -1543,6 +1980,317 @@ class TreeSitterExtractor {
1543
1980
  }
1544
1981
  return null;
1545
1982
  }
1983
+ /**
1984
+ * RTK Query: from a `createApi({ ..., endpoints: build => ({...}) })` or a
1985
+ * `baseApi.injectEndpoints({ endpoints: build => ({...}) })` call initializer,
1986
+ * return the object literal of endpoint definitions (the object the `endpoints`
1987
+ * arrow returns). Returns null for any other call — the common case — so this
1988
+ * stays cheap and silent. Keyed on the RTK entry-point names (`createApi` /
1989
+ * `injectEndpoints`) like the framework extractors key on their library APIs.
1990
+ */
1991
+ findRtkEndpointsObject(callNode) {
1992
+ const callee = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'function');
1993
+ if (!callee)
1994
+ return null;
1995
+ const calleeName = callee.type === 'identifier'
1996
+ ? (0, tree_sitter_helpers_1.getNodeText)(callee, this.source)
1997
+ : callee.type === 'member_expression'
1998
+ ? (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(callee, 'property') ?? callee, this.source)
1999
+ : '';
2000
+ if (calleeName !== 'createApi' && calleeName !== 'injectEndpoints')
2001
+ return null;
2002
+ const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
2003
+ if (!args)
2004
+ return null;
2005
+ for (let i = 0; i < args.namedChildCount; i++) {
2006
+ const arg = args.namedChild(i);
2007
+ if (arg?.type !== 'object' && arg?.type !== 'object_expression')
2008
+ continue;
2009
+ for (let j = 0; j < arg.namedChildCount; j++) {
2010
+ const member = arg.namedChild(j);
2011
+ // Two equally-common spellings: `endpoints: build => ({...})` (pair with an
2012
+ // arrow value) and `endpoints(build) { return {...} }` (method shorthand).
2013
+ if (member?.type === 'pair') {
2014
+ const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
2015
+ if (!key || (0, tree_sitter_helpers_1.getNodeText)(key, this.source) !== 'endpoints')
2016
+ continue;
2017
+ const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
2018
+ if (value && (value.type === 'arrow_function' || value.type === 'function_expression')) {
2019
+ return this.functionReturnedObject(value);
2020
+ }
2021
+ }
2022
+ else if (member?.type === 'method_definition') {
2023
+ const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'name');
2024
+ if (!key || (0, tree_sitter_helpers_1.getNodeText)(key, this.source) !== 'endpoints')
2025
+ continue;
2026
+ return this.functionReturnedObject(member);
2027
+ }
2028
+ }
2029
+ }
2030
+ return null;
2031
+ }
2032
+ /**
2033
+ * Extract each RTK Query endpoint (`getX: build.query({...})` / `build.mutation`)
2034
+ * as a function node named by the endpoint key, spanning its primary handler
2035
+ * (the `queryFn`/`query` arrow) so the fetch logic's calls attribute to the
2036
+ * endpoint. Without this an endpoint exists only as an object-literal property —
2037
+ * never a node — so the generated `useXQuery` hook can't be bridged to it.
2038
+ */
2039
+ extractRtkEndpoints(obj) {
2040
+ for (let i = 0; i < obj.namedChildCount; i++) {
2041
+ const member = obj.namedChild(i);
2042
+ if (member?.type !== 'pair')
2043
+ continue;
2044
+ const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
2045
+ const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
2046
+ if (!key || value?.type !== 'call_expression')
2047
+ continue;
2048
+ // The value must be a builder dispatch `<builder>.query|mutation(...)`.
2049
+ const callee = (0, tree_sitter_helpers_1.getChildByField)(value, 'function');
2050
+ if (callee?.type !== 'member_expression')
2051
+ continue;
2052
+ const method = (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(callee, 'property') ?? callee, this.source);
2053
+ if (method !== 'query' && method !== 'mutation' && method !== 'infiniteQuery')
2054
+ continue;
2055
+ const handler = this.rtkEndpointHandler(value);
2056
+ if (handler) {
2057
+ this.extractFunction(handler, this.objectKeyName(key));
2058
+ }
2059
+ else {
2060
+ // Factory / config-only handler (`queryFn: makeQueryFn(url)`): no function
2061
+ // literal to name. Mint a bare endpoint node spanning the builder call so
2062
+ // the generated hook still bridges to it, and walk the call so its handler
2063
+ // factory (and any inline transform) is captured as an outgoing edge.
2064
+ const epNode = this.createNode('function', this.objectKeyName(key), value, {
2065
+ signature: (0, tree_sitter_helpers_1.getNodeText)(value, this.source).slice(0, 80),
2066
+ });
2067
+ if (epNode) {
2068
+ this.nodeStack.push(epNode.id);
2069
+ this.visitFunctionBody(value, epNode.id);
2070
+ this.nodeStack.pop();
2071
+ }
2072
+ }
2073
+ }
2074
+ }
2075
+ /**
2076
+ * The primary handler arrow of a `build.query({ queryFn|query: (…) => … })`
2077
+ * endpoint — prefers `queryFn`, then `query`, else the first function-valued
2078
+ * property. Returns null when the endpoint is config-only (no handler arrow).
2079
+ */
2080
+ rtkEndpointHandler(callNode) {
2081
+ const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
2082
+ if (!args)
2083
+ return null;
2084
+ for (let i = 0; i < args.namedChildCount; i++) {
2085
+ const arg = args.namedChild(i);
2086
+ if (arg?.type !== 'object' && arg?.type !== 'object_expression')
2087
+ continue;
2088
+ let queryFn = null;
2089
+ let query = null;
2090
+ let firstFn = null;
2091
+ for (let j = 0; j < arg.namedChildCount; j++) {
2092
+ const member = arg.namedChild(j);
2093
+ // The handler may be `queryFn: () => …` / `query: () => …` (pair) or the
2094
+ // method-shorthand `query(arg) { … }` / `queryFn(arg) { … }`.
2095
+ let fn = null;
2096
+ let kn = '';
2097
+ if (member?.type === 'pair') {
2098
+ const v = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
2099
+ if (v?.type === 'arrow_function' || v?.type === 'function_expression') {
2100
+ fn = v;
2101
+ const k = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
2102
+ kn = k ? (0, tree_sitter_helpers_1.getNodeText)(k, this.source) : '';
2103
+ }
2104
+ }
2105
+ else if (member?.type === 'method_definition') {
2106
+ fn = member;
2107
+ const k = (0, tree_sitter_helpers_1.getChildByField)(member, 'name');
2108
+ kn = k ? (0, tree_sitter_helpers_1.getNodeText)(k, this.source) : '';
2109
+ }
2110
+ if (!fn)
2111
+ continue;
2112
+ if (kn === 'queryFn')
2113
+ queryFn = fn;
2114
+ else if (kn === 'query')
2115
+ query = fn;
2116
+ if (!firstFn)
2117
+ firstFn = fn;
2118
+ }
2119
+ if (queryFn)
2120
+ return queryFn;
2121
+ if (query)
2122
+ return query;
2123
+ if (firstFn)
2124
+ return firstFn;
2125
+ }
2126
+ return null;
2127
+ }
2128
+ /**
2129
+ * RTK Query generated-hook bindings. `export const { useGetXQuery,
2130
+ * useUpdateYMutation } = someApi` destructures the hooks RTK generates per
2131
+ * endpoint off a createApi result. They are real exported symbols that
2132
+ * components import, but destructured bindings aren't otherwise extracted —
2133
+ * mint a function node per binding matching the RTK hook convention so the hook
2134
+ * resolves and the synthesizer can bridge it to its endpoint. Gated tight by the
2135
+ * caller (object-pattern off a bare identifier) + the name convention here, so
2136
+ * ordinary destructures stay unextracted.
2137
+ */
2138
+ extractRtkHookBindings(pattern, isExported) {
2139
+ for (let i = 0; i < pattern.namedChildCount; i++) {
2140
+ const binding = pattern.namedChild(i);
2141
+ if (binding?.type !== 'shorthand_property_identifier_pattern')
2142
+ continue;
2143
+ const name = (0, tree_sitter_helpers_1.getNodeText)(binding, this.source);
2144
+ if (!RTK_HOOK_NAME_RE.test(name))
2145
+ continue;
2146
+ this.createNode('function', name, binding, {
2147
+ isExported,
2148
+ signature: '= RTK Query generated hook',
2149
+ });
2150
+ }
2151
+ }
2152
+ /** Cheap per-file heuristic: the file carries ≥2 distinct Vue-store signals
2153
+ * (defineStore/createStore/Vuex, or the actions/mutations/getters/namespaced
2154
+ * vocabulary). Gates the non-exported `const actions = {…}` Vuex-module form so
2155
+ * a stray `const actions` in unrelated code is never mistaken for a store. */
2156
+ looksLikeVueStoreFile() {
2157
+ if (this.vueStoreFile !== null)
2158
+ return this.vueStoreFile;
2159
+ const seen = new Set();
2160
+ VUE_STORE_FILE_SIGNAL.lastIndex = 0;
2161
+ let m;
2162
+ while ((m = VUE_STORE_FILE_SIGNAL.exec(this.source))) {
2163
+ seen.add(m[0]);
2164
+ if (seen.size >= 2)
2165
+ break;
2166
+ }
2167
+ this.vueStoreFile = seen.size >= 2;
2168
+ return this.vueStoreFile;
2169
+ }
2170
+ /** True if an object literal has ≥1 inline function member (`key: () => …` /
2171
+ * `method(){}`) — distinguishes an inline action map (zustand/SvelteKit form
2172
+ * actions) from a Pinia SETUP store's all-shorthand `return { foo, bar }`
2173
+ * (whose functions are body-local consts, walked normally instead). */
2174
+ objectHasInlineFunctions(obj) {
2175
+ for (let i = 0; i < obj.namedChildCount; i++) {
2176
+ const member = obj.namedChild(i);
2177
+ if (member?.type === 'method_definition')
2178
+ return true;
2179
+ if (member?.type === 'pair') {
2180
+ const v = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
2181
+ if (v?.type === 'arrow_function' || v?.type === 'function_expression')
2182
+ return true;
2183
+ }
2184
+ }
2185
+ return false;
2186
+ }
2187
+ /** Vue store action/mutation/getter collections defined INLINE in a store call:
2188
+ * `defineStore({ actions: {…}, getters: {…} })` (Pinia options form),
2189
+ * `defineStore('id', { actions: {…} })`, `createStore({ mutations: {…} })`,
2190
+ * `new Vuex.Store({ actions: {…} })`. Returns the object literals under those
2191
+ * keys so their methods become nodes. Gated on the store-factory callee. */
2192
+ findVueStoreCollectionObjects(callNode) {
2193
+ const callee = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'function') ?? (0, tree_sitter_helpers_1.getChildByField)(callNode, 'constructor');
2194
+ if (!callee)
2195
+ return [];
2196
+ const calleeName = callee.type === 'identifier'
2197
+ ? (0, tree_sitter_helpers_1.getNodeText)(callee, this.source)
2198
+ : callee.type === 'member_expression'
2199
+ ? (0, tree_sitter_helpers_1.getNodeText)((0, tree_sitter_helpers_1.getChildByField)(callee, 'property') ?? callee, this.source)
2200
+ : '';
2201
+ if (!VUE_STORE_FACTORY_CALLEES.has(calleeName) && calleeName !== 'Store')
2202
+ return [];
2203
+ const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
2204
+ if (!args)
2205
+ return [];
2206
+ const objects = [];
2207
+ for (let i = 0; i < args.namedChildCount; i++) {
2208
+ const arg = args.namedChild(i);
2209
+ if (arg?.type !== 'object' && arg?.type !== 'object_expression')
2210
+ continue;
2211
+ for (let j = 0; j < arg.namedChildCount; j++) {
2212
+ const member = arg.namedChild(j);
2213
+ if (member?.type !== 'pair')
2214
+ continue;
2215
+ const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
2216
+ if (!key || !VUE_STORE_COLLECTION_NAMES.has((0, tree_sitter_helpers_1.getNodeText)(key, this.source)))
2217
+ continue;
2218
+ const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
2219
+ if (value && (value.type === 'object' || value.type === 'object_expression')) {
2220
+ objects.push(value);
2221
+ }
2222
+ }
2223
+ }
2224
+ return objects;
2225
+ }
2226
+ /** Extract the methods of a store-config object's `actions`/`mutations`/`getters`
2227
+ * properties. Used for the canonical Vuex MODULE shape `export default {
2228
+ * namespaced, actions: {…}, mutations: {…} }` — object-literal methods aren't
2229
+ * otherwise extracted, so the actions/mutations would never be nodes. */
2230
+ extractStoreCollectionMethods(configObj) {
2231
+ for (let j = 0; j < configObj.namedChildCount; j++) {
2232
+ const member = configObj.namedChild(j);
2233
+ if (member?.type !== 'pair')
2234
+ continue;
2235
+ const key = (0, tree_sitter_helpers_1.getChildByField)(member, 'key');
2236
+ if (!key || !VUE_STORE_COLLECTION_NAMES.has((0, tree_sitter_helpers_1.getNodeText)(key, this.source)))
2237
+ continue;
2238
+ const value = (0, tree_sitter_helpers_1.getChildByField)(member, 'value');
2239
+ if (value && (value.type === 'object' || value.type === 'object_expression')) {
2240
+ this.extractObjectLiteralFunctions(value);
2241
+ }
2242
+ }
2243
+ }
2244
+ /** The SETUP function of a Pinia setup store (`defineStore('id', () => {…})`)
2245
+ * — an arrow/function arg with a block body. Returns null for the options form
2246
+ * (`defineStore({…})`) and for any non-defineStore call. The setup body's local
2247
+ * function consts are the store's actions; the generic body walk doesn't reach
2248
+ * them (nested functions are separate scopes), so they're extracted explicitly. */
2249
+ findPiniaSetupFn(callNode) {
2250
+ const callee = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'function');
2251
+ if (!callee || callee.type !== 'identifier' || (0, tree_sitter_helpers_1.getNodeText)(callee, this.source) !== 'defineStore')
2252
+ return null;
2253
+ const args = (0, tree_sitter_helpers_1.getChildByField)(callNode, 'arguments');
2254
+ if (!args)
2255
+ return null;
2256
+ for (let i = 0; i < args.namedChildCount; i++) {
2257
+ const arg = args.namedChild(i);
2258
+ if (arg?.type !== 'arrow_function' && arg?.type !== 'function_expression')
2259
+ continue;
2260
+ const body = (0, tree_sitter_helpers_1.getChildByField)(arg, 'body');
2261
+ if (body?.type === 'statement_block')
2262
+ return arg; // block body ⇒ setup form
2263
+ }
2264
+ return null;
2265
+ }
2266
+ /** Extract a Pinia setup store's actions: the body-local `const foo = () => …`
2267
+ * / `function foo(){}` declarations, named by the binding. (State refs and other
2268
+ * consts are left to the normal value-extraction; only the functions matter as
2269
+ * the store's callable surface.) */
2270
+ extractPiniaSetupBody(setupFn) {
2271
+ const body = (0, tree_sitter_helpers_1.getChildByField)(setupFn, 'body');
2272
+ if (!body || body.type !== 'statement_block')
2273
+ return;
2274
+ for (let i = 0; i < body.namedChildCount; i++) {
2275
+ const stmt = body.namedChild(i);
2276
+ if (!stmt)
2277
+ continue;
2278
+ if (stmt.type === 'function_declaration') {
2279
+ this.extractFunction(stmt);
2280
+ }
2281
+ else if (this.extractor.variableTypes.includes(stmt.type)) {
2282
+ for (let j = 0; j < stmt.namedChildCount; j++) {
2283
+ const decl = stmt.namedChild(j);
2284
+ if (decl?.type !== 'variable_declarator')
2285
+ continue;
2286
+ const v = (0, tree_sitter_helpers_1.getChildByField)(decl, 'value');
2287
+ if (v?.type === 'arrow_function' || v?.type === 'function_expression') {
2288
+ this.extractFunction(v); // name resolved from the parent declarator
2289
+ }
2290
+ }
2291
+ }
2292
+ }
2293
+ }
1546
2294
  /**
1547
2295
  * Extract a variable declaration (const, let, var, etc.)
1548
2296
  *
@@ -1572,8 +2320,15 @@ class TreeSitterExtractor {
1572
2320
  const valueNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'value');
1573
2321
  if (nameNode) {
1574
2322
  // Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte)
1575
- // These produce ugly multi-line names like "{ class: className }"
2323
+ // These produce ugly multi-line names like "{ class: className }".
2324
+ // EXCEPT `export const { useGetXQuery } = someApi` — the RTK Query
2325
+ // generated hooks: real exported symbols destructured off a createApi
2326
+ // result. Mint a node per binding matching the hook convention (gated
2327
+ // on a bare-identifier RHS so ordinary destructures stay skipped).
1576
2328
  if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
2329
+ if (nameNode.type === 'object_pattern' && valueNode?.type === 'identifier') {
2330
+ this.extractRtkHookBindings(nameNode, isExported);
2331
+ }
1577
2332
  continue;
1578
2333
  }
1579
2334
  const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
@@ -1585,6 +2340,25 @@ class TreeSitterExtractor {
1585
2340
  // Capture first 100 chars of initializer for context (stored in signature for searchability)
1586
2341
  const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
1587
2342
  const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
2343
+ // React HOC-wrapped components (`forwardRef`/`memo`/`styled`) — see
2344
+ // reactComponentHoc. The initializer is a call / tagged-template (not
2345
+ // a bare arrow), so without this the const is a plain `constant`,
2346
+ // which the JSX-render synthesizer and component resolution both skip
2347
+ // → `<Button/>` usages get no edge and callers/impact return empty
2348
+ // (the whole shadcn/ui design-system pattern, #841). PascalCase-gated
2349
+ // to the component naming convention so a memoization util
2350
+ // (`const cache = memo(fn)`) stays a constant.
2351
+ if (valueNode && /^[A-Z]/.test(name)) {
2352
+ const hoc = this.reactComponentHoc(valueNode);
2353
+ if (hoc) {
2354
+ this.extractReactComponentNode(name, child, hoc.inner, {
2355
+ docstring,
2356
+ signature: initSignature,
2357
+ isExported,
2358
+ });
2359
+ continue;
2360
+ }
2361
+ }
1588
2362
  const varNode = this.createNode(kind, name, child, {
1589
2363
  docstring,
1590
2364
  signature: initSignature,
@@ -1615,21 +2389,65 @@ class TreeSitterExtractor {
1615
2389
  : valueNode?.type === 'call_expression'
1616
2390
  ? this.findInitializerReturnedObject(valueNode)
1617
2391
  : null;
1618
- const extractObjectMethods = isExported && !!objectOfFns;
2392
+ // Only treat as an inline object-of-functions when the object actually
2393
+ // HAS inline functions. A Pinia SETUP store `defineStore('id', () => {
2394
+ // const foo = …; return { foo } })` returns an ALL-SHORTHAND object
2395
+ // whose functions are body-local consts — it must fall through to a
2396
+ // normal body walk (extracting those consts), not be skipped here.
2397
+ const hasInlineFns = !!objectOfFns && this.objectHasInlineFunctions(objectOfFns);
2398
+ const extractObjectMethods = isExported && !!objectOfFns && hasInlineFns;
2399
+ // RTK Query: `createApi`/`injectEndpoints` define endpoints as
2400
+ // object-literal properties whose values are `build.query/mutation(...)`
2401
+ // calls — nested under an `endpoints` arrow, so neither the
2402
+ // object-of-functions path above nor the normal walk extracts them.
2403
+ // Extract each endpoint as a function node (named by its key), and skip
2404
+ // walking the createApi call body (its handler arrows are extracted
2405
+ // individually below, exactly like the store-factory case).
2406
+ const rtkEndpoints = valueNode?.type === 'call_expression' ? this.findRtkEndpointsObject(valueNode) : null;
2407
+ // Pinia SETUP store: `defineStore('id', () => { const foo = …; return {…} })`.
2408
+ // Its actions are body-local consts the generic walk can't reach.
2409
+ const piniaSetup = valueNode?.type === 'call_expression' ? this.findPiniaSetupFn(valueNode) : null;
2410
+ // Vue store collections — make `actions`/`mutations`/`getters` findable
2411
+ // function nodes (the foundation under any later dispatch-bridge synth).
2412
+ // Two positions: INLINE in a store call (`defineStore({ actions: {…} })`
2413
+ // / `createStore` / `new Vuex.Store`), and the non-exported Vuex-MODULE
2414
+ // form (`const actions = {…}` at a store file's top level, wired via a
2415
+ // `export default { actions }`). The Pinia SETUP form is handled by the
2416
+ // body walk above (its actions are local consts).
2417
+ const storeCollections = [];
2418
+ if (valueNode?.type === 'call_expression' || valueNode?.type === 'new_expression') {
2419
+ storeCollections.push(...this.findVueStoreCollectionObjects(valueNode));
2420
+ }
2421
+ if (objectOfFns && !extractObjectMethods &&
2422
+ VUE_STORE_COLLECTION_NAMES.has(name) && this.looksLikeVueStoreFile()) {
2423
+ storeCollections.push(objectOfFns);
2424
+ }
1619
2425
  // Visit the initializer body for calls — EXCEPT object literals (their
1620
2426
  // function-valued properties are extracted below) and the store-factory
1621
- // call whose returned object we extract method-by-method below (walking
1622
- // the whole call would re-visit those method arrows and mis-attribute
1623
- // their inner calls to the file/module scope).
2427
+ // / createApi / store-collection call whose nested objects we extract
2428
+ // method-by-method below (walking the whole call would re-visit those
2429
+ // method arrows and mis-attribute their inner calls to the file scope).
1624
2430
  if (valueNode &&
1625
2431
  valueNode.type !== 'object' &&
1626
2432
  valueNode.type !== 'object_expression' &&
1627
- !(extractObjectMethods && valueNode.type === 'call_expression')) {
2433
+ !(extractObjectMethods && valueNode.type === 'call_expression') &&
2434
+ !rtkEndpoints &&
2435
+ !piniaSetup &&
2436
+ storeCollections.length === 0) {
1628
2437
  this.visitFunctionBody(valueNode, '');
1629
2438
  }
1630
2439
  if (extractObjectMethods && objectOfFns) {
1631
2440
  this.extractObjectLiteralFunctions(objectOfFns);
1632
2441
  }
2442
+ if (rtkEndpoints) {
2443
+ this.extractRtkEndpoints(rtkEndpoints);
2444
+ }
2445
+ if (piniaSetup) {
2446
+ this.extractPiniaSetupBody(piniaSetup);
2447
+ }
2448
+ for (const coll of storeCollections) {
2449
+ this.extractObjectLiteralFunctions(coll);
2450
+ }
1633
2451
  }
1634
2452
  }
1635
2453
  }
@@ -1638,7 +2456,9 @@ class TreeSitterExtractor {
1638
2456
  // Python/Ruby assignment: left = right
1639
2457
  const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') || node.namedChild(0);
1640
2458
  const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right') || node.namedChild(1);
1641
- if (left && left.type === 'identifier') {
2459
+ // Ruby constant assignments (`MAX = 3`) have a `constant`-typed LHS, not
2460
+ // `identifier`; without this they were never extracted as symbols at all.
2461
+ if (left && (left.type === 'identifier' || left.type === 'constant')) {
1642
2462
  const name = (0, tree_sitter_helpers_1.getNodeText)(left, this.source);
1643
2463
  // Skip if name starts with lowercase and looks like a function call result
1644
2464
  // Python constants are usually UPPER_CASE
@@ -1725,6 +2545,65 @@ class TreeSitterExtractor {
1725
2545
  this.createNode(kind, name, nameNode, { docstring, signature: initSignature, isExported });
1726
2546
  });
1727
2547
  }
2548
+ else if (this.language === 'c') {
2549
+ // C: a `declaration` node's name nests inside the `declarator` field —
2550
+ // `init_declarator` (with value) or bare/pointer/array declarators (no
2551
+ // value); a `function_declarator` is a prototype, not a variable. The
2552
+ // generic fallback below only finds a *direct* identifier child, which C
2553
+ // never has, so file-scope consts/globals went unextracted entirely (and
2554
+ // so had no impact-radius edges). Only file-scope declarations are tracked
2555
+ // — locals inside a function body are skipped (a `static const` table read
2556
+ // by same-file functions is the value the impact graph wants, not every
2557
+ // block-local). C allows several declarators per declaration
2558
+ // (`int a = 1, b = 2;`), so iterate them.
2559
+ if (!hasFunctionAncestor(node)) {
2560
+ for (let i = 0; i < node.namedChildCount; i++) {
2561
+ const child = node.namedChild(i);
2562
+ if (!child)
2563
+ continue;
2564
+ // Accept only `init_declarator` (has a value) and pointer/array
2565
+ // declarators. A *bare* `identifier` declarator is deliberately
2566
+ // skipped: an unknown leading macro (`CURL_EXTERN`, `XXH_PUBLIC_API`)
2567
+ // makes tree-sitter-c misparse a prototype `MACRO RetType fn(args);`
2568
+ // as a declaration whose "variable" is the bare return-type
2569
+ // identifier, splitting `fn(args)` off as a bogus expression — minting
2570
+ // a spurious type-named global for every macro-prefixed prototype in a
2571
+ // header. Those misparses are always bare identifiers; real
2572
+ // consts/tables always carry an initializer. The only legit loss is
2573
+ // uninitialized scalar globals (`static int g;`).
2574
+ if (child.type !== 'init_declarator' &&
2575
+ child.type !== 'pointer_declarator' &&
2576
+ child.type !== 'array_declarator') {
2577
+ continue;
2578
+ }
2579
+ const nameNode = cDeclaratorIdentifier(child);
2580
+ if (!nameNode)
2581
+ continue;
2582
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
2583
+ if (!name)
2584
+ continue;
2585
+ const valueNode = child.type === 'init_declarator' ? (0, tree_sitter_helpers_1.getChildByField)(child, 'value') : null;
2586
+ const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
2587
+ const initSignature = initValue
2588
+ ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}`
2589
+ : undefined;
2590
+ this.createNode(kind, name, child, { docstring, signature: initSignature, isExported });
2591
+ }
2592
+ }
2593
+ }
2594
+ else if (this.language === 'swift') {
2595
+ // Swift top-level property (`let X = …` / `var Y = …`). The name nests in
2596
+ // a `pattern`, which the generic fallback can't read, so top-level Swift
2597
+ // constants/globals went unextracted. A top-level `let`→`constant`,
2598
+ // `var`→`variable`; a computed property (getter, no value) is skipped.
2599
+ const { nameNode, isLet, isComputed } = swiftPropertyInfo(node, this.source);
2600
+ if (nameNode && !isComputed) {
2601
+ this.createNode(isLet ? 'constant' : 'variable', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node, {
2602
+ docstring,
2603
+ isExported,
2604
+ });
2605
+ }
2606
+ }
1728
2607
  else {
1729
2608
  // Generic fallback for other languages
1730
2609
  // Try to find identifier children