@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,318 @@
1
+ import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
2
+ import { findChild, nodeEndLine } from './helpers.js';
3
+
4
+ /**
5
+ * Extract symbols from Julia files.
6
+ *
7
+ * tree-sitter-julia grammar notes:
8
+ * - function_definition: `function name(params)...end`
9
+ * - assignment: `name(params) = expr` (short form), LHS is call_expression
10
+ * - struct_definition: `struct TypeHead...end`, name is in type_head
11
+ * - module_definition: `module Name...end`
12
+ * - import_statement / using_statement
13
+ * - macro_definition: `macro name(params)...end`
14
+ * - abstract_definition: `abstract type Name end`
15
+ * - call_expression: function calls
16
+ */
17
+ export function extractJuliaSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
18
+ const ctx: ExtractorOutput = {
19
+ definitions: [],
20
+ calls: [],
21
+ imports: [],
22
+ classes: [],
23
+ exports: [],
24
+ typeMap: new Map(),
25
+ };
26
+
27
+ walkJuliaNode(tree.rootNode, ctx, null);
28
+ return ctx;
29
+ }
30
+
31
+ function walkJuliaNode(
32
+ node: TreeSitterNode,
33
+ ctx: ExtractorOutput,
34
+ currentModule: string | null,
35
+ ): void {
36
+ let nextModule = currentModule;
37
+
38
+ switch (node.type) {
39
+ case 'module_definition':
40
+ nextModule = handleModuleDef(node, ctx);
41
+ break;
42
+ case 'function_definition':
43
+ handleFunctionDef(node, ctx, currentModule);
44
+ break;
45
+ case 'assignment':
46
+ handleAssignment(node, ctx, currentModule);
47
+ break;
48
+ case 'struct_definition':
49
+ handleStructDef(node, ctx);
50
+ break;
51
+ case 'abstract_definition':
52
+ handleAbstractDef(node, ctx);
53
+ break;
54
+ case 'macro_definition':
55
+ handleMacroDef(node, ctx, currentModule);
56
+ break;
57
+ case 'import_statement':
58
+ case 'using_statement':
59
+ handleImport(node, ctx);
60
+ break;
61
+ case 'call_expression':
62
+ handleCall(node, ctx);
63
+ break;
64
+ }
65
+
66
+ for (let i = 0; i < node.childCount; i++) {
67
+ const child = node.child(i);
68
+ if (child) walkJuliaNode(child, ctx, nextModule);
69
+ }
70
+ }
71
+
72
+ function handleModuleDef(node: TreeSitterNode, ctx: ExtractorOutput): string | null {
73
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
74
+ if (!nameNode) return null;
75
+
76
+ ctx.definitions.push({
77
+ name: nameNode.text,
78
+ kind: 'module',
79
+ line: node.startPosition.row + 1,
80
+ endLine: nodeEndLine(node),
81
+ });
82
+
83
+ return nameNode.text;
84
+ }
85
+
86
+ function handleFunctionDef(
87
+ node: TreeSitterNode,
88
+ ctx: ExtractorOutput,
89
+ currentModule: string | null,
90
+ ): void {
91
+ // function_definition may have a call_expression child as the signature
92
+ const callSig = findChild(node, 'call_expression');
93
+ if (callSig) {
94
+ const funcNameNode = callSig.child(0);
95
+ if (funcNameNode) {
96
+ const name = currentModule ? `${currentModule}.${funcNameNode.text}` : funcNameNode.text;
97
+ const params = extractJuliaParams(callSig);
98
+ ctx.definitions.push({
99
+ name,
100
+ kind: 'function',
101
+ line: node.startPosition.row + 1,
102
+ endLine: nodeEndLine(node),
103
+ children: params.length > 0 ? params : undefined,
104
+ });
105
+ return;
106
+ }
107
+ }
108
+
109
+ // Fallback: look for identifier directly
110
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
111
+ if (!nameNode) return;
112
+
113
+ const name = currentModule ? `${currentModule}.${nameNode.text}` : nameNode.text;
114
+ ctx.definitions.push({
115
+ name,
116
+ kind: 'function',
117
+ line: node.startPosition.row + 1,
118
+ endLine: nodeEndLine(node),
119
+ });
120
+ }
121
+
122
+ function handleAssignment(
123
+ node: TreeSitterNode,
124
+ ctx: ExtractorOutput,
125
+ currentModule: string | null,
126
+ ): void {
127
+ // assignment: LHS operator RHS
128
+ // Short function form: add(x, y) = x + y → LHS is call_expression
129
+ const lhs = node.child(0);
130
+ if (!lhs) return;
131
+
132
+ if (lhs.type === 'call_expression') {
133
+ const funcNameNode = lhs.child(0);
134
+ if (!funcNameNode) return;
135
+
136
+ const name = currentModule ? `${currentModule}.${funcNameNode.text}` : funcNameNode.text;
137
+ const params = extractJuliaParams(lhs);
138
+
139
+ ctx.definitions.push({
140
+ name,
141
+ kind: 'function',
142
+ line: node.startPosition.row + 1,
143
+ endLine: nodeEndLine(node),
144
+ children: params.length > 0 ? params : undefined,
145
+ });
146
+ }
147
+ }
148
+
149
+ function handleStructDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
150
+ // struct_definition: struct type_head fields... end
151
+ const typeHead = findChild(node, 'type_head');
152
+ const nameNode = typeHead
153
+ ? (findChild(typeHead, 'identifier') ?? typeHead)
154
+ : findChild(node, 'identifier');
155
+ if (!nameNode) return;
156
+
157
+ const children: SubDeclaration[] = [];
158
+ // Fields are typed_expression children of struct_definition
159
+ for (let i = 0; i < node.childCount; i++) {
160
+ const child = node.child(i);
161
+ if (!child) continue;
162
+ if (child.type === 'typed_expression') {
163
+ const fieldName = findChild(child, 'identifier');
164
+ if (fieldName) {
165
+ children.push({
166
+ name: fieldName.text,
167
+ kind: 'property',
168
+ line: child.startPosition.row + 1,
169
+ });
170
+ }
171
+ }
172
+ // Plain identifier fields (no type annotation)
173
+ if (child.type === 'identifier' && child !== nameNode && typeHead && child !== typeHead) {
174
+ children.push({ name: child.text, kind: 'property', line: child.startPosition.row + 1 });
175
+ }
176
+ }
177
+
178
+ // Check for supertype in type_head (Point <: AbstractPoint)
179
+ if (typeHead) {
180
+ const subtypeExpr = findChild(typeHead, 'subtype_expression');
181
+ if (subtypeExpr) {
182
+ // Find the supertype identifier
183
+ for (let i = 0; i < subtypeExpr.childCount; i++) {
184
+ const child = subtypeExpr.child(i);
185
+ if (child?.type === 'identifier' && i > 0) {
186
+ ctx.classes.push({
187
+ name: nameNode.text,
188
+ extends: child.text,
189
+ line: node.startPosition.row + 1,
190
+ });
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ ctx.definitions.push({
197
+ name: nameNode.text,
198
+ kind: 'struct',
199
+ line: node.startPosition.row + 1,
200
+ endLine: nodeEndLine(node),
201
+ children: children.length > 0 ? children : undefined,
202
+ });
203
+ }
204
+
205
+ function handleAbstractDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
206
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
207
+ if (!nameNode) return;
208
+
209
+ ctx.definitions.push({
210
+ name: nameNode.text,
211
+ kind: 'type',
212
+ line: node.startPosition.row + 1,
213
+ endLine: nodeEndLine(node),
214
+ });
215
+ }
216
+
217
+ function handleMacroDef(
218
+ node: TreeSitterNode,
219
+ ctx: ExtractorOutput,
220
+ currentModule: string | null,
221
+ ): void {
222
+ const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
223
+ if (!nameNode) return;
224
+
225
+ const name = currentModule ? `${currentModule}.@${nameNode.text}` : `@${nameNode.text}`;
226
+ ctx.definitions.push({
227
+ name,
228
+ kind: 'function',
229
+ line: node.startPosition.row + 1,
230
+ endLine: nodeEndLine(node),
231
+ });
232
+ }
233
+
234
+ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
235
+ const names: string[] = [];
236
+ let source = '';
237
+
238
+ for (let i = 0; i < node.childCount; i++) {
239
+ const child = node.child(i);
240
+ if (!child) continue;
241
+ if (
242
+ child.type === 'identifier' ||
243
+ child.type === 'scoped_identifier' ||
244
+ child.type === 'selected_import'
245
+ ) {
246
+ if (!source) source = child.text;
247
+ names.push(child.text.split('.').pop() || child.text);
248
+ }
249
+ }
250
+
251
+ if (source) {
252
+ ctx.imports.push({
253
+ source,
254
+ names: names.length > 0 ? names : [source],
255
+ line: node.startPosition.row + 1,
256
+ });
257
+ }
258
+ }
259
+
260
+ function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
261
+ // Don't record if parent is assignment LHS (that's a function definition)
262
+ if (node.parent?.type === 'assignment' && node === node.parent.child(0)) return;
263
+ // Don't record if parent is function_definition (that's a signature)
264
+ if (node.parent?.type === 'function_definition') return;
265
+
266
+ const funcNode = node.child(0);
267
+ if (!funcNode) return;
268
+
269
+ if (funcNode.type === 'identifier') {
270
+ ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
271
+ } else if (funcNode.type === 'field_expression' || funcNode.type === 'scoped_identifier') {
272
+ const parts = funcNode.text.split('.');
273
+ if (parts.length >= 2) {
274
+ ctx.calls.push({
275
+ name: parts[parts.length - 1]!,
276
+ receiver: parts.slice(0, -1).join('.'),
277
+ line: node.startPosition.row + 1,
278
+ });
279
+ } else {
280
+ ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
281
+ }
282
+ }
283
+ }
284
+
285
+ function extractJuliaParams(callExpr: TreeSitterNode): SubDeclaration[] {
286
+ const params: SubDeclaration[] = [];
287
+ const argList = findChild(callExpr, 'argument_list') || findChild(callExpr, 'tuple_expression');
288
+ if (!argList) return params;
289
+
290
+ for (let i = 0; i < argList.childCount; i++) {
291
+ const child = argList.child(i);
292
+ if (!child) continue;
293
+ if (child.type === 'identifier') {
294
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
295
+ }
296
+ if (child.type === 'typed_parameter' || child.type === 'typed_expression') {
297
+ const nameNode = findChild(child, 'identifier');
298
+ if (nameNode) {
299
+ params.push({
300
+ name: nameNode.text,
301
+ kind: 'parameter',
302
+ line: child.startPosition.row + 1,
303
+ });
304
+ }
305
+ }
306
+ if (child.type === 'optional_parameter' || child.type === 'default_parameter') {
307
+ const nameNode = findChild(child, 'identifier');
308
+ if (nameNode) {
309
+ params.push({
310
+ name: nameNode.text,
311
+ kind: 'parameter',
312
+ line: child.startPosition.row + 1,
313
+ });
314
+ }
315
+ }
316
+ }
317
+ return params;
318
+ }
@@ -0,0 +1,169 @@
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 Lua files.
12
+ */
13
+ export function extractLuaSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
14
+ const ctx: ExtractorOutput = {
15
+ definitions: [],
16
+ calls: [],
17
+ imports: [],
18
+ classes: [],
19
+ exports: [],
20
+ typeMap: new Map(),
21
+ };
22
+
23
+ walkLuaNode(tree.rootNode, ctx);
24
+ return ctx;
25
+ }
26
+
27
+ function walkLuaNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
28
+ switch (node.type) {
29
+ case 'function_declaration':
30
+ handleLuaFunctionDecl(node, ctx);
31
+ break;
32
+ case 'variable_declaration':
33
+ handleLuaVariableDecl(node, ctx);
34
+ break;
35
+ case 'function_call':
36
+ handleLuaFunctionCall(node, ctx);
37
+ break;
38
+ }
39
+
40
+ for (let i = 0; i < node.childCount; i++) {
41
+ const child = node.child(i);
42
+ if (child) walkLuaNode(child, ctx);
43
+ }
44
+ }
45
+
46
+ function handleLuaFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
47
+ const nameNode = node.childForFieldName('name');
48
+ if (!nameNode) return;
49
+
50
+ let name: string;
51
+ let kind: 'function' | 'method' = 'function';
52
+
53
+ if (nameNode.type === 'method_index_expression') {
54
+ const table = nameNode.childForFieldName('table');
55
+ const method = nameNode.childForFieldName('method');
56
+ if (table && method) {
57
+ name = `${table.text}.${method.text}`;
58
+ kind = 'method';
59
+ } else {
60
+ name = nameNode.text;
61
+ }
62
+ } else if (nameNode.type === 'dot_index_expression') {
63
+ const table = nameNode.childForFieldName('table');
64
+ const field = nameNode.childForFieldName('field');
65
+ if (table && field) {
66
+ name = `${table.text}.${field.text}`;
67
+ kind = 'method';
68
+ } else {
69
+ name = nameNode.text;
70
+ }
71
+ } else {
72
+ name = nameNode.text;
73
+ }
74
+
75
+ const params = extractLuaParams(node);
76
+
77
+ ctx.definitions.push({
78
+ name,
79
+ kind,
80
+ line: node.startPosition.row + 1,
81
+ endLine: nodeEndLine(node),
82
+ children: params.length > 0 ? params : undefined,
83
+ });
84
+ }
85
+
86
+ function extractLuaParams(funcNode: TreeSitterNode): SubDeclaration[] {
87
+ const params: SubDeclaration[] = [];
88
+ const paramList = funcNode.childForFieldName('parameters');
89
+ if (!paramList) return params;
90
+
91
+ for (let i = 0; i < paramList.childCount; i++) {
92
+ const param = paramList.child(i);
93
+ if (!param || param.type !== 'identifier') continue;
94
+ params.push({ name: param.text, kind: 'parameter', line: param.startPosition.row + 1 });
95
+ }
96
+ return params;
97
+ }
98
+
99
+ function handleLuaVariableDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
100
+ // Check for require calls in the assignment
101
+ const assignment = findChild(node, 'assignment_statement');
102
+ if (assignment) {
103
+ checkForRequire(assignment, ctx);
104
+ }
105
+ }
106
+
107
+ function checkForRequire(node: TreeSitterNode, ctx: ExtractorOutput): void {
108
+ for (let i = 0; i < node.childCount; i++) {
109
+ const child = node.child(i);
110
+ if (!child) continue;
111
+ if (child.type === 'function_call') {
112
+ const nameNode = child.childForFieldName('name');
113
+ if (nameNode && nameNode.type === 'identifier' && nameNode.text === 'require') {
114
+ const args = child.childForFieldName('arguments');
115
+ if (args) {
116
+ const strArg = findChild(args, 'string');
117
+ if (strArg) {
118
+ const source = strArg.text.replace(/^['"]|['"]$/g, '');
119
+ ctx.imports.push({
120
+ source,
121
+ names: ['require'],
122
+ line: child.startPosition.row + 1,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ function handleLuaFunctionCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
132
+ const nameNode = node.childForFieldName('name');
133
+ if (!nameNode) return;
134
+
135
+ // Check for require() as import
136
+ if (nameNode.type === 'identifier' && nameNode.text === 'require') {
137
+ const args = node.childForFieldName('arguments');
138
+ if (args) {
139
+ const strArg = findChild(args, 'string');
140
+ if (strArg) {
141
+ const source = strArg.text.replace(/^['"]|['"]$/g, '');
142
+ ctx.imports.push({
143
+ source,
144
+ names: ['require'],
145
+ line: node.startPosition.row + 1,
146
+ });
147
+ return;
148
+ }
149
+ }
150
+ }
151
+
152
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
153
+
154
+ if (nameNode.type === 'method_index_expression') {
155
+ const table = nameNode.childForFieldName('table');
156
+ const method = nameNode.childForFieldName('method');
157
+ if (method) call.name = method.text;
158
+ if (table) call.receiver = table.text;
159
+ } else if (nameNode.type === 'dot_index_expression') {
160
+ const table = nameNode.childForFieldName('table');
161
+ const field = nameNode.childForFieldName('field');
162
+ if (field) call.name = field.text;
163
+ if (table) call.receiver = table.text;
164
+ } else {
165
+ call.name = nameNode.text;
166
+ }
167
+
168
+ if (call.name) ctx.calls.push(call);
169
+ }