@optave/codegraph 3.6.0 → 3.8.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 (186) hide show
  1. package/README.md +32 -16
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +158 -1
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +0 -1
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.js +2 -75
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  11. package/dist/cli/commands/ast.js +2 -2
  12. package/dist/cli/commands/ast.js.map +1 -1
  13. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  14. package/dist/domain/graph/builder/pipeline.js +128 -6
  15. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  16. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  17. package/dist/domain/graph/builder/stages/build-edges.js +101 -1
  18. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  19. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  20. package/dist/domain/graph/builder/stages/collect-files.js +17 -5
  21. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  22. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  23. package/dist/domain/graph/builder/stages/detect-changes.js +98 -50
  24. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  25. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  26. package/dist/domain/graph/builder/stages/finalize.js +32 -5
  27. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  28. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  29. package/dist/domain/graph/builder/stages/insert-nodes.js +20 -7
  30. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  31. package/dist/domain/parser.d.ts +1 -1
  32. package/dist/domain/parser.d.ts.map +1 -1
  33. package/dist/domain/parser.js +129 -3
  34. package/dist/domain/parser.js.map +1 -1
  35. package/dist/extractors/clojure.d.ts +12 -0
  36. package/dist/extractors/clojure.d.ts.map +1 -0
  37. package/dist/extractors/clojure.js +245 -0
  38. package/dist/extractors/clojure.js.map +1 -0
  39. package/dist/extractors/cuda.d.ts +11 -0
  40. package/dist/extractors/cuda.d.ts.map +1 -0
  41. package/dist/extractors/cuda.js +302 -0
  42. package/dist/extractors/cuda.js.map +1 -0
  43. package/dist/extractors/dart.d.ts +6 -0
  44. package/dist/extractors/dart.d.ts.map +1 -0
  45. package/dist/extractors/dart.js +277 -0
  46. package/dist/extractors/dart.js.map +1 -0
  47. package/dist/extractors/elixir.d.ts +9 -0
  48. package/dist/extractors/elixir.d.ts.map +1 -0
  49. package/dist/extractors/elixir.js +223 -0
  50. package/dist/extractors/elixir.js.map +1 -0
  51. package/dist/extractors/erlang.d.ts +14 -0
  52. package/dist/extractors/erlang.d.ts.map +1 -0
  53. package/dist/extractors/erlang.js +239 -0
  54. package/dist/extractors/erlang.js.map +1 -0
  55. package/dist/extractors/fsharp.d.ts +13 -0
  56. package/dist/extractors/fsharp.d.ts.map +1 -0
  57. package/dist/extractors/fsharp.js +218 -0
  58. package/dist/extractors/fsharp.js.map +1 -0
  59. package/dist/extractors/gleam.d.ts +14 -0
  60. package/dist/extractors/gleam.d.ts.map +1 -0
  61. package/dist/extractors/gleam.js +229 -0
  62. package/dist/extractors/gleam.js.map +1 -0
  63. package/dist/extractors/groovy.d.ts +10 -0
  64. package/dist/extractors/groovy.d.ts.map +1 -0
  65. package/dist/extractors/groovy.js +304 -0
  66. package/dist/extractors/groovy.js.map +1 -0
  67. package/dist/extractors/haskell.d.ts +8 -0
  68. package/dist/extractors/haskell.d.ts.map +1 -0
  69. package/dist/extractors/haskell.js +217 -0
  70. package/dist/extractors/haskell.js.map +1 -0
  71. package/dist/extractors/index.d.ts +17 -0
  72. package/dist/extractors/index.d.ts.map +1 -1
  73. package/dist/extractors/index.js +17 -0
  74. package/dist/extractors/index.js.map +1 -1
  75. package/dist/extractors/julia.d.ts +16 -0
  76. package/dist/extractors/julia.d.ts.map +1 -0
  77. package/dist/extractors/julia.js +287 -0
  78. package/dist/extractors/julia.js.map +1 -0
  79. package/dist/extractors/lua.d.ts +6 -0
  80. package/dist/extractors/lua.d.ts.map +1 -0
  81. package/dist/extractors/lua.js +162 -0
  82. package/dist/extractors/lua.js.map +1 -0
  83. package/dist/extractors/objc.d.ts +9 -0
  84. package/dist/extractors/objc.d.ts.map +1 -0
  85. package/dist/extractors/objc.js +406 -0
  86. package/dist/extractors/objc.js.map +1 -0
  87. package/dist/extractors/ocaml.d.ts +6 -0
  88. package/dist/extractors/ocaml.d.ts.map +1 -0
  89. package/dist/extractors/ocaml.js +310 -0
  90. package/dist/extractors/ocaml.js.map +1 -0
  91. package/dist/extractors/r.d.ts +13 -0
  92. package/dist/extractors/r.d.ts.map +1 -0
  93. package/dist/extractors/r.js +251 -0
  94. package/dist/extractors/r.js.map +1 -0
  95. package/dist/extractors/solidity.d.ts +9 -0
  96. package/dist/extractors/solidity.d.ts.map +1 -0
  97. package/dist/extractors/solidity.js +374 -0
  98. package/dist/extractors/solidity.js.map +1 -0
  99. package/dist/extractors/verilog.d.ts +9 -0
  100. package/dist/extractors/verilog.d.ts.map +1 -0
  101. package/dist/extractors/verilog.js +286 -0
  102. package/dist/extractors/verilog.js.map +1 -0
  103. package/dist/extractors/zig.d.ts +9 -0
  104. package/dist/extractors/zig.d.ts.map +1 -0
  105. package/dist/extractors/zig.js +276 -0
  106. package/dist/extractors/zig.js.map +1 -0
  107. package/dist/features/ast.d.ts.map +1 -1
  108. package/dist/features/ast.js +1 -2
  109. package/dist/features/ast.js.map +1 -1
  110. package/dist/features/cfg.d.ts +1 -1
  111. package/dist/features/cfg.d.ts.map +1 -1
  112. package/dist/features/cfg.js +6 -51
  113. package/dist/features/cfg.js.map +1 -1
  114. package/dist/graph/algorithms/bfs.d.ts +2 -0
  115. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  116. package/dist/graph/algorithms/bfs.js +27 -0
  117. package/dist/graph/algorithms/bfs.js.map +1 -1
  118. package/dist/graph/algorithms/centrality.d.ts +2 -0
  119. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  120. package/dist/graph/algorithms/centrality.js +28 -0
  121. package/dist/graph/algorithms/centrality.js.map +1 -1
  122. package/dist/graph/algorithms/louvain.d.ts +3 -4
  123. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  124. package/dist/graph/algorithms/louvain.js +29 -0
  125. package/dist/graph/algorithms/louvain.js.map +1 -1
  126. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  127. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  128. package/dist/graph/algorithms/shortest-path.js +18 -1
  129. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  130. package/dist/types.d.ts +122 -2
  131. package/dist/types.d.ts.map +1 -1
  132. package/grammars/tree-sitter-clojure.wasm +0 -0
  133. package/grammars/tree-sitter-cuda.wasm +0 -0
  134. package/grammars/tree-sitter-dart.wasm +0 -0
  135. package/grammars/tree-sitter-elixir.wasm +0 -0
  136. package/grammars/tree-sitter-erlang.wasm +0 -0
  137. package/grammars/tree-sitter-fsharp.wasm +0 -0
  138. package/grammars/tree-sitter-gleam.wasm +0 -0
  139. package/grammars/tree-sitter-groovy.wasm +0 -0
  140. package/grammars/tree-sitter-haskell.wasm +0 -0
  141. package/grammars/tree-sitter-julia.wasm +0 -0
  142. package/grammars/tree-sitter-lua.wasm +0 -0
  143. package/grammars/tree-sitter-objc.wasm +0 -0
  144. package/grammars/tree-sitter-ocaml.wasm +0 -0
  145. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  146. package/grammars/tree-sitter-r.wasm +0 -0
  147. package/grammars/tree-sitter-solidity.wasm +0 -0
  148. package/grammars/tree-sitter-verilog.wasm +0 -0
  149. package/grammars/tree-sitter-zig.wasm +0 -0
  150. package/package.json +24 -7
  151. package/src/ast-analysis/engine.ts +183 -1
  152. package/src/ast-analysis/rules/javascript.ts +0 -1
  153. package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
  154. package/src/cli/commands/ast.ts +2 -2
  155. package/src/domain/graph/builder/pipeline.ts +142 -6
  156. package/src/domain/graph/builder/stages/build-edges.ts +158 -1
  157. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  158. package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
  159. package/src/domain/graph/builder/stages/finalize.ts +39 -9
  160. package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
  161. package/src/domain/parser.ts +161 -1
  162. package/src/extractors/clojure.ts +273 -0
  163. package/src/extractors/cuda.ts +316 -0
  164. package/src/extractors/dart.ts +304 -0
  165. package/src/extractors/elixir.ts +251 -0
  166. package/src/extractors/erlang.ts +252 -0
  167. package/src/extractors/fsharp.ts +253 -0
  168. package/src/extractors/gleam.ts +246 -0
  169. package/src/extractors/groovy.ts +332 -0
  170. package/src/extractors/haskell.ts +235 -0
  171. package/src/extractors/index.ts +17 -0
  172. package/src/extractors/julia.ts +318 -0
  173. package/src/extractors/lua.ts +169 -0
  174. package/src/extractors/objc.ts +431 -0
  175. package/src/extractors/ocaml.ts +337 -0
  176. package/src/extractors/r.ts +253 -0
  177. package/src/extractors/solidity.ts +398 -0
  178. package/src/extractors/verilog.ts +315 -0
  179. package/src/extractors/zig.ts +294 -0
  180. package/src/features/ast.ts +1 -2
  181. package/src/features/cfg.ts +6 -51
  182. package/src/graph/algorithms/bfs.ts +34 -0
  183. package/src/graph/algorithms/centrality.ts +30 -0
  184. package/src/graph/algorithms/louvain.ts +31 -4
  185. package/src/graph/algorithms/shortest-path.ts +20 -1
  186. package/src/types.ts +123 -2
@@ -0,0 +1,253 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { findChild, nodeEndLine } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from F# files.
12
+ *
13
+ * tree-sitter-fsharp grammar notes:
14
+ * - named_module: top-level module declaration
15
+ * - function_declaration_left: LHS of `let name params = ...`
16
+ * - import_decl: `open Namespace`
17
+ * - type_definition > union_type_defn / record_type_defn
18
+ * - application_expression: function calls
19
+ */
20
+ export function extractFSharpSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
21
+ const ctx: ExtractorOutput = {
22
+ definitions: [],
23
+ calls: [],
24
+ imports: [],
25
+ classes: [],
26
+ exports: [],
27
+ typeMap: new Map(),
28
+ };
29
+
30
+ walkFSharpNode(tree.rootNode, ctx, null);
31
+ return ctx;
32
+ }
33
+
34
+ function walkFSharpNode(
35
+ node: TreeSitterNode,
36
+ ctx: ExtractorOutput,
37
+ currentModule: string | null,
38
+ ): void {
39
+ let nextModule = currentModule;
40
+
41
+ switch (node.type) {
42
+ case 'named_module':
43
+ nextModule = handleNamedModule(node, ctx);
44
+ break;
45
+ case 'function_declaration_left':
46
+ handleFunctionDecl(node, ctx, currentModule);
47
+ break;
48
+ case 'type_definition':
49
+ handleTypeDef(node, ctx);
50
+ break;
51
+ case 'import_decl':
52
+ handleImportDecl(node, ctx);
53
+ break;
54
+ case 'application_expression':
55
+ handleApplication(node, ctx);
56
+ break;
57
+ case 'dot_expression':
58
+ handleDotExpression(node, ctx);
59
+ break;
60
+ }
61
+
62
+ for (let i = 0; i < node.childCount; i++) {
63
+ const child = node.child(i);
64
+ if (child) walkFSharpNode(child, ctx, nextModule);
65
+ }
66
+ }
67
+
68
+ function handleNamedModule(node: TreeSitterNode, ctx: ExtractorOutput): string | null {
69
+ const nameNode = findChild(node, 'long_identifier');
70
+ if (!nameNode) return null;
71
+
72
+ ctx.definitions.push({
73
+ name: nameNode.text,
74
+ kind: 'module',
75
+ line: node.startPosition.row + 1,
76
+ endLine: nodeEndLine(node),
77
+ });
78
+
79
+ return nameNode.text;
80
+ }
81
+
82
+ function handleFunctionDecl(
83
+ node: TreeSitterNode,
84
+ ctx: ExtractorOutput,
85
+ currentModule: string | null,
86
+ ): void {
87
+ // function_declaration_left: "add x y" — first child is the name identifier
88
+ const nameNode = findChild(node, 'identifier');
89
+ if (!nameNode) return;
90
+
91
+ // Avoid duplicates — the walk will also visit children
92
+ if (
93
+ ctx.definitions.some((d) => d.name === nameNode.text && d.line === node.startPosition.row + 1)
94
+ )
95
+ return;
96
+
97
+ const params = extractFSharpParams(node);
98
+ const name = currentModule ? `${currentModule}.${nameNode.text}` : nameNode.text;
99
+
100
+ ctx.definitions.push({
101
+ name,
102
+ kind: 'function',
103
+ line: node.startPosition.row + 1,
104
+ endLine: nodeEndLine(node.parent ?? node),
105
+ children: params.length > 0 ? params : undefined,
106
+ });
107
+ }
108
+
109
+ function extractFSharpParams(declLeft: TreeSitterNode): SubDeclaration[] {
110
+ const params: SubDeclaration[] = [];
111
+ const argPatterns = findChild(declLeft, 'argument_patterns');
112
+ if (!argPatterns) return params;
113
+
114
+ collectParamIdentifiers(argPatterns, params);
115
+ return params;
116
+ }
117
+
118
+ function collectParamIdentifiers(node: TreeSitterNode, params: SubDeclaration[]): void {
119
+ if (node.type === 'identifier') {
120
+ params.push({ name: node.text, kind: 'parameter', line: node.startPosition.row + 1 });
121
+ return;
122
+ }
123
+ for (let i = 0; i < node.childCount; i++) {
124
+ const child = node.child(i);
125
+ if (child) collectParamIdentifiers(child, params);
126
+ }
127
+ }
128
+
129
+ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
130
+ // type_definition contains union_type_defn, record_type_defn, etc.
131
+ for (let i = 0; i < node.childCount; i++) {
132
+ const child = node.child(i);
133
+ if (!child) continue;
134
+
135
+ if (
136
+ child.type === 'union_type_defn' ||
137
+ child.type === 'record_type_defn' ||
138
+ child.type === 'type_abbreviation_defn' ||
139
+ child.type === 'class_type_defn' ||
140
+ child.type === 'interface_type_defn' ||
141
+ child.type === 'type_defn'
142
+ ) {
143
+ const nameNode = findChild(child, 'type_name');
144
+ const name = nameNode
145
+ ? (findChild(nameNode, 'identifier')?.text ?? nameNode.text)
146
+ : findChild(child, 'identifier')?.text;
147
+ if (!name) continue;
148
+
149
+ const kind = determineFSharpTypeKind(child);
150
+ const children: SubDeclaration[] = [];
151
+ extractFSharpTypeMembers(child, children);
152
+
153
+ ctx.definitions.push({
154
+ name,
155
+ kind,
156
+ line: child.startPosition.row + 1,
157
+ endLine: nodeEndLine(child),
158
+ children: children.length > 0 ? children : undefined,
159
+ });
160
+ }
161
+ }
162
+ }
163
+
164
+ function determineFSharpTypeKind(
165
+ typeDefn: TreeSitterNode,
166
+ ): 'class' | 'type' | 'record' | 'enum' | 'interface' {
167
+ switch (typeDefn.type) {
168
+ case 'union_type_defn':
169
+ return 'enum';
170
+ case 'record_type_defn':
171
+ return 'record';
172
+ case 'class_type_defn':
173
+ return 'class';
174
+ case 'interface_type_defn':
175
+ return 'interface';
176
+ default:
177
+ return 'type';
178
+ }
179
+ }
180
+
181
+ function extractFSharpTypeMembers(typeDefn: TreeSitterNode, children: SubDeclaration[]): void {
182
+ for (let i = 0; i < typeDefn.childCount; i++) {
183
+ const child = typeDefn.child(i);
184
+ if (!child) continue;
185
+
186
+ if (child.type === 'union_type_case') {
187
+ const nameNode = findChild(child, 'identifier');
188
+ if (nameNode) {
189
+ children.push({
190
+ name: nameNode.text,
191
+ kind: 'property',
192
+ line: child.startPosition.row + 1,
193
+ });
194
+ }
195
+ }
196
+ if (child.type === 'record_field') {
197
+ const nameNode = child.childForFieldName('name') || findChild(child, 'identifier');
198
+ if (nameNode) {
199
+ children.push({
200
+ name: nameNode.text,
201
+ kind: 'property',
202
+ line: child.startPosition.row + 1,
203
+ });
204
+ }
205
+ }
206
+ // Recurse into containers like union_type_cases
207
+ if (child.type === 'union_type_cases' || child.type === 'record_fields') {
208
+ extractFSharpTypeMembers(child, children);
209
+ }
210
+ }
211
+ }
212
+
213
+ function handleImportDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
214
+ const moduleNode = findChild(node, 'long_identifier');
215
+ if (!moduleNode) return;
216
+
217
+ const source = moduleNode.text;
218
+ ctx.imports.push({
219
+ source,
220
+ names: [source.split('.').pop() || source],
221
+ line: node.startPosition.row + 1,
222
+ });
223
+ }
224
+
225
+ function handleApplication(node: TreeSitterNode, ctx: ExtractorOutput): void {
226
+ const funcNode = node.child(0);
227
+ if (!funcNode) return;
228
+
229
+ if (funcNode.type === 'identifier' || funcNode.type === 'long_identifier') {
230
+ ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
231
+ } else if (funcNode.type === 'long_identifier_or_op') {
232
+ const id = findChild(funcNode, 'identifier') || findChild(funcNode, 'long_identifier');
233
+ if (id) ctx.calls.push({ name: id.text, line: node.startPosition.row + 1 });
234
+ }
235
+ }
236
+
237
+ function handleDotExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
238
+ const parts: string[] = [];
239
+ for (let i = 0; i < node.childCount; i++) {
240
+ const child = node.child(i);
241
+ if (child && (child.type === 'identifier' || child.type === 'long_identifier')) {
242
+ parts.push(child.text);
243
+ }
244
+ }
245
+ if (parts.length >= 2) {
246
+ const call: Call = {
247
+ name: parts[parts.length - 1]!,
248
+ receiver: parts.slice(0, -1).join('.'),
249
+ line: node.startPosition.row + 1,
250
+ };
251
+ ctx.calls.push(call);
252
+ }
253
+ }
@@ -0,0 +1,246 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { findChild, nodeEndLine, stripQuotes } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from Gleam files.
12
+ *
13
+ * Gleam tree-sitter grammar (gleam-lang/tree-sitter-gleam) notes:
14
+ * - Functions: function with name, parameters, body fields
15
+ * - Types: type_definition with name, constructors
16
+ * - Type aliases: type_alias
17
+ * - Imports: import with module, unqualified_imports
18
+ * - External functions: external_function
19
+ * - Constants: constant
20
+ */
21
+ export function extractGleamSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
22
+ const ctx: ExtractorOutput = {
23
+ definitions: [],
24
+ calls: [],
25
+ imports: [],
26
+ classes: [],
27
+ exports: [],
28
+ typeMap: new Map(),
29
+ };
30
+
31
+ walkGleamNode(tree.rootNode, ctx);
32
+ return ctx;
33
+ }
34
+
35
+ function walkGleamNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
36
+ switch (node.type) {
37
+ case 'function':
38
+ handleFunction(node, ctx);
39
+ break;
40
+ case 'type_definition':
41
+ handleTypeDef(node, ctx);
42
+ break;
43
+ case 'type_alias':
44
+ handleTypeAlias(node, ctx);
45
+ break;
46
+ case 'import':
47
+ handleImport(node, ctx);
48
+ break;
49
+ case 'external_function':
50
+ handleExternalFunction(node, ctx);
51
+ break;
52
+ case 'constant':
53
+ handleConstant(node, ctx);
54
+ break;
55
+ case 'function_call':
56
+ case 'call':
57
+ handleCall(node, ctx);
58
+ break;
59
+ }
60
+
61
+ for (let i = 0; i < node.childCount; i++) {
62
+ const child = node.child(i);
63
+ if (child) walkGleamNode(child, ctx);
64
+ }
65
+ }
66
+
67
+ function handleFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
68
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
69
+ if (!nameNode) return;
70
+
71
+ const visibility = isPublic(node) ? 'public' : 'private';
72
+ const params = extractParams(node);
73
+
74
+ ctx.definitions.push({
75
+ name: nameNode.text,
76
+ kind: 'function',
77
+ line: node.startPosition.row + 1,
78
+ endLine: nodeEndLine(node),
79
+ visibility,
80
+ children: params.length > 0 ? params : undefined,
81
+ });
82
+ }
83
+
84
+ function handleExternalFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
85
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
86
+ if (!nameNode) return;
87
+
88
+ ctx.definitions.push({
89
+ name: nameNode.text,
90
+ kind: 'function',
91
+ line: node.startPosition.row + 1,
92
+ endLine: nodeEndLine(node),
93
+ visibility: isPublic(node) ? 'public' : 'private',
94
+ });
95
+ }
96
+
97
+ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
98
+ const nameNode = node.childForFieldName('name') || findChild(node, 'type_name');
99
+ if (!nameNode) return;
100
+
101
+ const children: SubDeclaration[] = [];
102
+ // Extract constructors
103
+ for (let i = 0; i < node.childCount; i++) {
104
+ const child = node.child(i);
105
+ if (!child) continue;
106
+ if (child.type === 'data_constructor' || child.type === 'type_constructor') {
107
+ const ctorName = child.childForFieldName('name') || findChild(child, 'constructor_name');
108
+ if (ctorName) {
109
+ children.push({ name: ctorName.text, kind: 'property', line: child.startPosition.row + 1 });
110
+ }
111
+ }
112
+ // Recurse into constructors block
113
+ if (child.type === 'data_constructors' || child.type === 'type_constructors') {
114
+ for (let j = 0; j < child.childCount; j++) {
115
+ const ctor = child.child(j);
116
+ if (!ctor) continue;
117
+ if (ctor.type === 'data_constructor' || ctor.type === 'type_constructor') {
118
+ const ctorName = ctor.childForFieldName('name') || findChild(ctor, 'constructor_name');
119
+ if (ctorName) {
120
+ children.push({
121
+ name: ctorName.text,
122
+ kind: 'property',
123
+ line: ctor.startPosition.row + 1,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ ctx.definitions.push({
132
+ name: nameNode.text,
133
+ kind: 'type',
134
+ line: node.startPosition.row + 1,
135
+ endLine: nodeEndLine(node),
136
+ visibility: isPublic(node) ? 'public' : 'private',
137
+ children: children.length > 0 ? children : undefined,
138
+ });
139
+ }
140
+
141
+ function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
142
+ const nameNode = node.childForFieldName('name') || findChild(node, 'type_name');
143
+ if (!nameNode) return;
144
+
145
+ ctx.definitions.push({
146
+ name: nameNode.text,
147
+ kind: 'type',
148
+ line: node.startPosition.row + 1,
149
+ endLine: nodeEndLine(node),
150
+ visibility: isPublic(node) ? 'public' : 'private',
151
+ });
152
+ }
153
+
154
+ function handleConstant(node: TreeSitterNode, ctx: ExtractorOutput): void {
155
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
156
+ if (!nameNode) return;
157
+
158
+ ctx.definitions.push({
159
+ name: nameNode.text,
160
+ kind: 'variable',
161
+ line: node.startPosition.row + 1,
162
+ endLine: nodeEndLine(node),
163
+ visibility: isPublic(node) ? 'public' : 'private',
164
+ });
165
+ }
166
+
167
+ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
168
+ const moduleNode =
169
+ node.childForFieldName('module') || findChild(node, 'module') || findChild(node, 'string');
170
+ if (!moduleNode) return;
171
+
172
+ const source = stripQuotes(moduleNode.text);
173
+ const names: string[] = [];
174
+
175
+ // Check for unqualified imports
176
+ const unqualified = findChild(node, 'unqualified_imports');
177
+ if (unqualified) {
178
+ for (let i = 0; i < unqualified.childCount; i++) {
179
+ const item = unqualified.child(i);
180
+ if (item && (item.type === 'unqualified_import' || item.type === 'identifier')) {
181
+ const nameNode = item.childForFieldName('name') || item;
182
+ if (nameNode.type !== ',') names.push(nameNode.text);
183
+ }
184
+ }
185
+ }
186
+
187
+ // Check for alias (as)
188
+ const alias = node.childForFieldName('alias') || findChild(node, 'identifier');
189
+ if (alias && alias !== moduleNode) {
190
+ names.push(alias.text);
191
+ }
192
+
193
+ ctx.imports.push({
194
+ source,
195
+ names: names.length > 0 ? names : [source.split('/').pop() || source],
196
+ line: node.startPosition.row + 1,
197
+ });
198
+ }
199
+
200
+ function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
201
+ const funcNode = node.childForFieldName('function') || node.child(0);
202
+ if (!funcNode) return;
203
+
204
+ if (funcNode.type === 'identifier' || funcNode.type === 'variable') {
205
+ ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
206
+ } else if (funcNode.type === 'field_access' || funcNode.type === 'module_select') {
207
+ const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('label');
208
+ const record = funcNode.child(0);
209
+ if (field) {
210
+ const call: Call = { name: field.text, line: node.startPosition.row + 1 };
211
+ if (record && record !== field) call.receiver = record.text;
212
+ ctx.calls.push(call);
213
+ }
214
+ }
215
+ }
216
+
217
+ function extractParams(funcNode: TreeSitterNode): SubDeclaration[] {
218
+ const params: SubDeclaration[] = [];
219
+ const paramsNode =
220
+ funcNode.childForFieldName('parameters') || findChild(funcNode, 'function_parameters');
221
+ if (!paramsNode) return params;
222
+
223
+ for (let i = 0; i < paramsNode.childCount; i++) {
224
+ const param = paramsNode.child(i);
225
+ if (!param) continue;
226
+ if (param.type === 'function_parameter' || param.type === 'parameter') {
227
+ const nameNode = param.childForFieldName('name') || findChild(param, 'identifier');
228
+ if (nameNode) {
229
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
230
+ }
231
+ }
232
+ if (param.type === 'identifier') {
233
+ params.push({ name: param.text, kind: 'parameter', line: param.startPosition.row + 1 });
234
+ }
235
+ }
236
+ return params;
237
+ }
238
+
239
+ function isPublic(node: TreeSitterNode): boolean {
240
+ for (let i = 0; i < node.childCount; i++) {
241
+ const child = node.child(i);
242
+ if (!child) continue;
243
+ if (child.type === 'visibility_modifier' || child.text === 'pub') return true;
244
+ }
245
+ return false;
246
+ }