@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,332 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import {
9
+ extractModifierVisibility,
10
+ findChild,
11
+ findParentNode,
12
+ lastPathSegment,
13
+ nodeEndLine,
14
+ } from './helpers.js';
15
+
16
+ /**
17
+ * Extract symbols from Groovy files.
18
+ *
19
+ * Groovy is a JVM language with Java-like class/interface/enum structures
20
+ * plus closures, traits, and dynamic typing. The tree-sitter-groovy grammar
21
+ * models classes, methods, imports, and call expressions similarly to Java.
22
+ */
23
+ export function extractGroovySymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
24
+ const ctx: ExtractorOutput = {
25
+ definitions: [],
26
+ calls: [],
27
+ imports: [],
28
+ classes: [],
29
+ exports: [],
30
+ typeMap: new Map(),
31
+ };
32
+
33
+ walkGroovyNode(tree.rootNode, ctx);
34
+ return ctx;
35
+ }
36
+
37
+ function walkGroovyNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
38
+ switch (node.type) {
39
+ case 'class_definition':
40
+ case 'class_declaration':
41
+ handleGroovyClassDecl(node, ctx);
42
+ break;
43
+ case 'interface_definition':
44
+ case 'interface_declaration':
45
+ handleGroovyInterfaceDecl(node, ctx);
46
+ break;
47
+ case 'enum_definition':
48
+ case 'enum_declaration':
49
+ handleGroovyEnumDecl(node, ctx);
50
+ break;
51
+ case 'method_definition':
52
+ case 'method_declaration':
53
+ handleGroovyMethodDecl(node, ctx);
54
+ break;
55
+ case 'constructor_definition':
56
+ case 'constructor_declaration':
57
+ handleGroovyConstructorDecl(node, ctx);
58
+ break;
59
+ case 'function_definition':
60
+ case 'function_declaration':
61
+ handleGroovyFunctionDecl(node, ctx);
62
+ break;
63
+ case 'import_statement':
64
+ case 'import_declaration':
65
+ handleGroovyImport(node, ctx);
66
+ break;
67
+ case 'method_call':
68
+ case 'method_invocation':
69
+ case 'call_expression':
70
+ case 'function_call':
71
+ handleGroovyCallExpr(node, ctx);
72
+ break;
73
+ case 'object_creation_expression':
74
+ handleGroovyObjectCreation(node, ctx);
75
+ break;
76
+ }
77
+
78
+ for (let i = 0; i < node.childCount; i++) {
79
+ const child = node.child(i);
80
+ if (child) walkGroovyNode(child, ctx);
81
+ }
82
+ }
83
+
84
+ // ── Handlers ───────────────────────────────────────────────────────────────
85
+
86
+ const GROOVY_PARENT_TYPES = [
87
+ 'class_definition',
88
+ 'class_declaration',
89
+ 'enum_definition',
90
+ 'enum_declaration',
91
+ 'interface_definition',
92
+ 'interface_declaration',
93
+ ] as const;
94
+
95
+ function handleGroovyClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
96
+ const nameNode = node.childForFieldName('name');
97
+ if (!nameNode) return;
98
+ const name = nameNode.text;
99
+
100
+ const members = extractGroovyClassMembers(node);
101
+ ctx.definitions.push({
102
+ name,
103
+ kind: 'class',
104
+ line: node.startPosition.row + 1,
105
+ endLine: nodeEndLine(node),
106
+ children: members.length > 0 ? members : undefined,
107
+ visibility: extractModifierVisibility(node),
108
+ });
109
+
110
+ // Superclass
111
+ const superclass = node.childForFieldName('superclass');
112
+ if (superclass) {
113
+ const superName =
114
+ superclass.type === 'generic_type' ? superclass.child(0)?.text : superclass.text;
115
+ if (superName) {
116
+ ctx.classes.push({ name, extends: superName, line: node.startPosition.row + 1 });
117
+ }
118
+ }
119
+
120
+ // Interfaces
121
+ const interfaces = node.childForFieldName('interfaces');
122
+ if (interfaces) {
123
+ for (let i = 0; i < interfaces.childCount; i++) {
124
+ const iface = interfaces.child(i);
125
+ if (
126
+ iface &&
127
+ (iface.type === 'type_identifier' ||
128
+ iface.type === 'identifier' ||
129
+ iface.type === 'generic_type')
130
+ ) {
131
+ const ifaceName = iface.type === 'generic_type' ? iface.child(0)?.text : iface.text;
132
+ if (ifaceName) {
133
+ ctx.classes.push({ name, implements: ifaceName, line: node.startPosition.row + 1 });
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ function handleGroovyInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
141
+ const nameNode = node.childForFieldName('name');
142
+ if (!nameNode) return;
143
+
144
+ ctx.definitions.push({
145
+ name: nameNode.text,
146
+ kind: 'interface',
147
+ line: node.startPosition.row + 1,
148
+ endLine: nodeEndLine(node),
149
+ visibility: extractModifierVisibility(node),
150
+ });
151
+ }
152
+
153
+ function handleGroovyEnumDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
154
+ const nameNode = node.childForFieldName('name');
155
+ if (!nameNode) return;
156
+
157
+ const members: SubDeclaration[] = [];
158
+ const body = node.childForFieldName('body') || findChild(node, 'enum_body');
159
+ if (body) {
160
+ for (let i = 0; i < body.childCount; i++) {
161
+ const child = body.child(i);
162
+ if (!child) continue;
163
+ if (child.type === 'enum_constant' || child.type === 'identifier') {
164
+ const constName = child.childForFieldName('name') || child;
165
+ members.push({ name: constName.text, kind: 'constant', line: child.startPosition.row + 1 });
166
+ }
167
+ }
168
+ }
169
+
170
+ ctx.definitions.push({
171
+ name: nameNode.text,
172
+ kind: 'enum',
173
+ line: node.startPosition.row + 1,
174
+ endLine: nodeEndLine(node),
175
+ children: members.length > 0 ? members : undefined,
176
+ });
177
+ }
178
+
179
+ function handleGroovyMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
180
+ const nameNode = node.childForFieldName('name');
181
+ if (!nameNode) return;
182
+ const parentClass = findParentNode(node, GROOVY_PARENT_TYPES);
183
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
184
+
185
+ const params = extractGroovyParams(node);
186
+ ctx.definitions.push({
187
+ name: fullName,
188
+ kind: 'method',
189
+ line: node.startPosition.row + 1,
190
+ endLine: nodeEndLine(node),
191
+ children: params.length > 0 ? params : undefined,
192
+ visibility: extractModifierVisibility(node),
193
+ });
194
+ }
195
+
196
+ function handleGroovyConstructorDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
197
+ const nameNode = node.childForFieldName('name');
198
+ if (!nameNode) return;
199
+ const parentClass = findParentNode(node, GROOVY_PARENT_TYPES);
200
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
201
+
202
+ const params = extractGroovyParams(node);
203
+ ctx.definitions.push({
204
+ name: fullName,
205
+ kind: 'method',
206
+ line: node.startPosition.row + 1,
207
+ endLine: nodeEndLine(node),
208
+ children: params.length > 0 ? params : undefined,
209
+ visibility: extractModifierVisibility(node),
210
+ });
211
+ }
212
+
213
+ function handleGroovyFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
214
+ const nameNode = node.childForFieldName('name');
215
+ if (!nameNode) return;
216
+
217
+ const params = extractGroovyParams(node);
218
+ ctx.definitions.push({
219
+ name: nameNode.text,
220
+ kind: 'function',
221
+ line: node.startPosition.row + 1,
222
+ endLine: nodeEndLine(node),
223
+ children: params.length > 0 ? params : undefined,
224
+ });
225
+ }
226
+
227
+ function handleGroovyImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
228
+ // import foo.bar.Baz or import foo.bar.*
229
+ for (let i = 0; i < node.childCount; i++) {
230
+ const child = node.child(i);
231
+ if (!child) continue;
232
+ if (
233
+ child.type === 'dotted_identifier' ||
234
+ child.type === 'scoped_identifier' ||
235
+ child.type === 'identifier' ||
236
+ child.type === 'qualified_name'
237
+ ) {
238
+ const fullPath = child.text;
239
+ const lastName = lastPathSegment(fullPath, '.');
240
+ ctx.imports.push({
241
+ source: fullPath,
242
+ names: [lastName],
243
+ line: node.startPosition.row + 1,
244
+ javaImport: true,
245
+ });
246
+ return;
247
+ }
248
+ }
249
+ }
250
+
251
+ function handleGroovyCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
252
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
253
+
254
+ // Try standard call_expression pattern
255
+ const funcNode = node.childForFieldName('function') || node.childForFieldName('method');
256
+ if (funcNode) {
257
+ if (funcNode.type === 'field_expression' || funcNode.type === 'member_access') {
258
+ const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('property');
259
+ const obj = funcNode.childForFieldName('argument') || funcNode.childForFieldName('object');
260
+ if (field) call.name = field.text;
261
+ if (obj) call.receiver = obj.text;
262
+ } else {
263
+ call.name = funcNode.text;
264
+ }
265
+ } else {
266
+ // method_call: first child is receiver/name
267
+ const nameNode = node.childForFieldName('name');
268
+ const obj = node.childForFieldName('object');
269
+ if (nameNode) {
270
+ call.name = nameNode.text;
271
+ if (obj) call.receiver = obj.text;
272
+ }
273
+ }
274
+
275
+ if (call.name) ctx.calls.push(call);
276
+ }
277
+
278
+ function handleGroovyObjectCreation(node: TreeSitterNode, ctx: ExtractorOutput): void {
279
+ const typeNode = node.childForFieldName('type');
280
+ if (!typeNode) return;
281
+ const typeName = typeNode.type === 'generic_type' ? typeNode.child(0)?.text : typeNode.text;
282
+ if (typeName) ctx.calls.push({ name: typeName, line: node.startPosition.row + 1 });
283
+ }
284
+
285
+ // ── Helpers ────────────────────────────────────────────────────────────────
286
+
287
+ function extractGroovyParams(funcNode: TreeSitterNode): SubDeclaration[] {
288
+ const params: SubDeclaration[] = [];
289
+ const paramList =
290
+ funcNode.childForFieldName('parameters') || findChild(funcNode, 'formal_parameters');
291
+ if (!paramList) return params;
292
+
293
+ for (let i = 0; i < paramList.childCount; i++) {
294
+ const param = paramList.child(i);
295
+ if (!param) continue;
296
+ if (param.type === 'formal_parameter' || param.type === 'parameter') {
297
+ const nameNode = param.childForFieldName('name');
298
+ if (nameNode) {
299
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
300
+ }
301
+ }
302
+ }
303
+ return params;
304
+ }
305
+
306
+ function extractGroovyClassMembers(classNode: TreeSitterNode): SubDeclaration[] {
307
+ const members: SubDeclaration[] = [];
308
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'class_body');
309
+ if (!body) return members;
310
+
311
+ for (let i = 0; i < body.childCount; i++) {
312
+ const child = body.child(i);
313
+ if (!child) continue;
314
+ if (child.type === 'field_declaration') {
315
+ for (let j = 0; j < child.childCount; j++) {
316
+ const varDecl = child.child(j);
317
+ if (varDecl?.type === 'variable_declarator') {
318
+ const nameNode = varDecl.childForFieldName('name');
319
+ if (nameNode) {
320
+ members.push({
321
+ name: nameNode.text,
322
+ kind: 'property',
323
+ line: child.startPosition.row + 1,
324
+ visibility: extractModifierVisibility(child),
325
+ });
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ return members;
332
+ }
@@ -1,21 +1,32 @@
1
1
  export { extractBashSymbols } from './bash.js';
2
2
  export { extractCSymbols } from './c.js';
3
+ export { extractClojureSymbols } from './clojure.js';
3
4
  export { extractCppSymbols } from './cpp.js';
4
5
  export { extractCSharpSymbols } from './csharp.js';
6
+ export { extractCudaSymbols } from './cuda.js';
5
7
  export { extractDartSymbols } from './dart.js';
6
8
  export { extractElixirSymbols } from './elixir.js';
9
+ export { extractErlangSymbols } from './erlang.js';
10
+ export { extractFSharpSymbols } from './fsharp.js';
11
+ export { extractGleamSymbols } from './gleam.js';
7
12
  export { extractGoSymbols } from './go.js';
13
+ export { extractGroovySymbols } from './groovy.js';
8
14
  export { extractHaskellSymbols } from './haskell.js';
9
15
  export { extractHCLSymbols } from './hcl.js';
10
16
  export { extractJavaSymbols } from './java.js';
11
17
  export { extractSymbols } from './javascript.js';
18
+ export { extractJuliaSymbols } from './julia.js';
12
19
  export { extractKotlinSymbols } from './kotlin.js';
13
20
  export { extractLuaSymbols } from './lua.js';
21
+ export { extractObjCSymbols } from './objc.js';
14
22
  export { extractOCamlSymbols } from './ocaml.js';
15
23
  export { extractPHPSymbols } from './php.js';
16
24
  export { extractPythonSymbols } from './python.js';
25
+ export { extractRSymbols } from './r.js';
17
26
  export { extractRubySymbols } from './ruby.js';
18
27
  export { extractRustSymbols } from './rust.js';
19
28
  export { extractScalaSymbols } from './scala.js';
29
+ export { extractSoliditySymbols } from './solidity.js';
20
30
  export { extractSwiftSymbols } from './swift.js';
31
+ export { extractVerilogSymbols } from './verilog.js';
21
32
  export { extractZigSymbols } from './zig.js';
@@ -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
+ }