@optave/codegraph 3.7.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 (148) hide show
  1. package/README.md +25 -14
  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 +88 -4
  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/erlang.d.ts +14 -0
  44. package/dist/extractors/erlang.d.ts.map +1 -0
  45. package/dist/extractors/erlang.js +239 -0
  46. package/dist/extractors/erlang.js.map +1 -0
  47. package/dist/extractors/fsharp.d.ts +13 -0
  48. package/dist/extractors/fsharp.d.ts.map +1 -0
  49. package/dist/extractors/fsharp.js +218 -0
  50. package/dist/extractors/fsharp.js.map +1 -0
  51. package/dist/extractors/gleam.d.ts +14 -0
  52. package/dist/extractors/gleam.d.ts.map +1 -0
  53. package/dist/extractors/gleam.js +229 -0
  54. package/dist/extractors/gleam.js.map +1 -0
  55. package/dist/extractors/groovy.d.ts +10 -0
  56. package/dist/extractors/groovy.d.ts.map +1 -0
  57. package/dist/extractors/groovy.js +304 -0
  58. package/dist/extractors/groovy.js.map +1 -0
  59. package/dist/extractors/index.d.ts +11 -0
  60. package/dist/extractors/index.d.ts.map +1 -1
  61. package/dist/extractors/index.js +11 -0
  62. package/dist/extractors/index.js.map +1 -1
  63. package/dist/extractors/julia.d.ts +16 -0
  64. package/dist/extractors/julia.d.ts.map +1 -0
  65. package/dist/extractors/julia.js +287 -0
  66. package/dist/extractors/julia.js.map +1 -0
  67. package/dist/extractors/objc.d.ts +9 -0
  68. package/dist/extractors/objc.d.ts.map +1 -0
  69. package/dist/extractors/objc.js +406 -0
  70. package/dist/extractors/objc.js.map +1 -0
  71. package/dist/extractors/ocaml.js +74 -0
  72. package/dist/extractors/ocaml.js.map +1 -1
  73. package/dist/extractors/r.d.ts +13 -0
  74. package/dist/extractors/r.d.ts.map +1 -0
  75. package/dist/extractors/r.js +251 -0
  76. package/dist/extractors/r.js.map +1 -0
  77. package/dist/extractors/solidity.d.ts +9 -0
  78. package/dist/extractors/solidity.d.ts.map +1 -0
  79. package/dist/extractors/solidity.js +374 -0
  80. package/dist/extractors/solidity.js.map +1 -0
  81. package/dist/extractors/verilog.d.ts +9 -0
  82. package/dist/extractors/verilog.d.ts.map +1 -0
  83. package/dist/extractors/verilog.js +286 -0
  84. package/dist/extractors/verilog.js.map +1 -0
  85. package/dist/features/ast.d.ts.map +1 -1
  86. package/dist/features/ast.js +1 -2
  87. package/dist/features/ast.js.map +1 -1
  88. package/dist/graph/algorithms/bfs.d.ts +2 -0
  89. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  90. package/dist/graph/algorithms/bfs.js +27 -0
  91. package/dist/graph/algorithms/bfs.js.map +1 -1
  92. package/dist/graph/algorithms/centrality.d.ts +2 -0
  93. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  94. package/dist/graph/algorithms/centrality.js +28 -0
  95. package/dist/graph/algorithms/centrality.js.map +1 -1
  96. package/dist/graph/algorithms/louvain.d.ts +3 -4
  97. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  98. package/dist/graph/algorithms/louvain.js +29 -0
  99. package/dist/graph/algorithms/louvain.js.map +1 -1
  100. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  101. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  102. package/dist/graph/algorithms/shortest-path.js +18 -1
  103. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  104. package/dist/types.d.ts +122 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/grammars/tree-sitter-clojure.wasm +0 -0
  107. package/grammars/tree-sitter-cuda.wasm +0 -0
  108. package/grammars/tree-sitter-erlang.wasm +0 -0
  109. package/grammars/tree-sitter-fsharp.wasm +0 -0
  110. package/grammars/tree-sitter-gleam.wasm +0 -0
  111. package/grammars/tree-sitter-groovy.wasm +0 -0
  112. package/grammars/tree-sitter-julia.wasm +0 -0
  113. package/grammars/tree-sitter-objc.wasm +0 -0
  114. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  115. package/grammars/tree-sitter-r.wasm +0 -0
  116. package/grammars/tree-sitter-solidity.wasm +0 -0
  117. package/grammars/tree-sitter-verilog.wasm +0 -0
  118. package/package.json +18 -7
  119. package/src/ast-analysis/engine.ts +183 -1
  120. package/src/ast-analysis/rules/javascript.ts +0 -1
  121. package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
  122. package/src/cli/commands/ast.ts +2 -2
  123. package/src/domain/graph/builder/pipeline.ts +142 -6
  124. package/src/domain/graph/builder/stages/build-edges.ts +158 -1
  125. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  126. package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
  127. package/src/domain/graph/builder/stages/finalize.ts +39 -9
  128. package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
  129. package/src/domain/parser.ts +108 -2
  130. package/src/extractors/clojure.ts +273 -0
  131. package/src/extractors/cuda.ts +316 -0
  132. package/src/extractors/erlang.ts +252 -0
  133. package/src/extractors/fsharp.ts +253 -0
  134. package/src/extractors/gleam.ts +246 -0
  135. package/src/extractors/groovy.ts +332 -0
  136. package/src/extractors/index.ts +11 -0
  137. package/src/extractors/julia.ts +318 -0
  138. package/src/extractors/objc.ts +431 -0
  139. package/src/extractors/ocaml.ts +78 -0
  140. package/src/extractors/r.ts +253 -0
  141. package/src/extractors/solidity.ts +398 -0
  142. package/src/extractors/verilog.ts +315 -0
  143. package/src/features/ast.ts +1 -2
  144. package/src/graph/algorithms/bfs.ts +34 -0
  145. package/src/graph/algorithms/centrality.ts +30 -0
  146. package/src/graph/algorithms/louvain.ts +31 -4
  147. package/src/graph/algorithms/shortest-path.ts +20 -1
  148. package/src/types.ts +117 -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
+ }