@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,337 @@
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 OCaml files.
12
+ */
13
+ export function extractOCamlSymbols(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
+ walkOCamlNode(tree.rootNode, ctx);
24
+ return ctx;
25
+ }
26
+
27
+ function walkOCamlNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
28
+ switch (node.type) {
29
+ case 'value_definition':
30
+ handleOCamlValueDef(node, ctx);
31
+ break;
32
+ case 'let_binding':
33
+ // Only handle top-level let bindings not inside value_definition
34
+ if (node.parent?.type !== 'value_definition') {
35
+ handleOCamlLetBinding(node, ctx);
36
+ }
37
+ break;
38
+ case 'module_definition':
39
+ handleOCamlModuleDef(node, ctx);
40
+ break;
41
+ case 'type_definition':
42
+ handleOCamlTypeDef(node, ctx);
43
+ break;
44
+ case 'class_definition':
45
+ handleOCamlClassDef(node, ctx);
46
+ break;
47
+ case 'open_module':
48
+ handleOCamlOpen(node, ctx);
49
+ break;
50
+ case 'application_expression':
51
+ handleOCamlApplication(node, ctx);
52
+ break;
53
+ // Shared node types present in both .ml and .mli files
54
+ case 'value_specification':
55
+ handleOCamlValueSpec(node, ctx);
56
+ break;
57
+ case 'external':
58
+ handleOCamlExternal(node, ctx);
59
+ break;
60
+ case 'module_type_definition':
61
+ handleOCamlModuleTypeDef(node, ctx);
62
+ break;
63
+ case 'exception_definition':
64
+ handleOCamlExceptionDef(node, ctx);
65
+ break;
66
+ }
67
+
68
+ for (let i = 0; i < node.childCount; i++) {
69
+ const child = node.child(i);
70
+ if (child) walkOCamlNode(child, ctx);
71
+ }
72
+ }
73
+
74
+ function handleOCamlValueDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
75
+ // value_definition contains one or more let_bindings
76
+ for (let i = 0; i < node.childCount; i++) {
77
+ const child = node.child(i);
78
+ if (child && child.type === 'let_binding') {
79
+ handleOCamlLetBinding(child, ctx);
80
+ }
81
+ }
82
+ }
83
+
84
+ function handleOCamlLetBinding(node: TreeSitterNode, ctx: ExtractorOutput): void {
85
+ // let_binding has a pattern (the name) and optionally a body
86
+ const pattern = node.childForFieldName('pattern');
87
+ if (!pattern) return;
88
+
89
+ // Check if this is a function (has parameter children)
90
+ const hasParams = hasOCamlParams(node);
91
+ const name = extractOCamlPatternName(pattern);
92
+ if (!name) return;
93
+
94
+ if (hasParams) {
95
+ const params = extractOCamlParams(node);
96
+ ctx.definitions.push({
97
+ name,
98
+ kind: 'function',
99
+ line: node.startPosition.row + 1,
100
+ endLine: nodeEndLine(node),
101
+ children: params.length > 0 ? params : undefined,
102
+ });
103
+ } else {
104
+ ctx.definitions.push({
105
+ name,
106
+ kind: 'variable',
107
+ line: node.startPosition.row + 1,
108
+ endLine: nodeEndLine(node),
109
+ });
110
+ }
111
+ }
112
+
113
+ function extractOCamlPatternName(pattern: TreeSitterNode): string | null {
114
+ if (pattern.type === 'value_name' || pattern.type === 'identifier') {
115
+ return pattern.text;
116
+ }
117
+ // Operator definitions like `let (+) a b = ...`
118
+ if (pattern.type === 'parenthesized_operator') {
119
+ return pattern.text;
120
+ }
121
+ const nameNode = findChild(pattern, 'value_name') || findChild(pattern, 'identifier');
122
+ return nameNode ? nameNode.text : null;
123
+ }
124
+
125
+ function hasOCamlParams(letBinding: TreeSitterNode): boolean {
126
+ for (let i = 0; i < letBinding.childCount; i++) {
127
+ const child = letBinding.child(i);
128
+ if (!child) continue;
129
+ if (child.type === 'parameter' || child.type === 'value_pattern') return true;
130
+ }
131
+ return false;
132
+ }
133
+
134
+ function extractOCamlParams(letBinding: TreeSitterNode): SubDeclaration[] {
135
+ const params: SubDeclaration[] = [];
136
+ for (let i = 0; i < letBinding.childCount; i++) {
137
+ const child = letBinding.child(i);
138
+ if (!child) continue;
139
+ if (child.type === 'parameter' || child.type === 'value_pattern') {
140
+ const name = extractOCamlPatternName(child);
141
+ if (name) {
142
+ params.push({ name, kind: 'parameter', line: child.startPosition.row + 1 });
143
+ }
144
+ }
145
+ }
146
+ return params;
147
+ }
148
+
149
+ function handleOCamlModuleDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
150
+ const binding = findChild(node, 'module_binding');
151
+ if (!binding) return;
152
+
153
+ const nameNode =
154
+ binding.childForFieldName('name') ||
155
+ findChild(binding, 'module_name') ||
156
+ findChild(binding, 'identifier');
157
+ if (!nameNode) return;
158
+
159
+ ctx.definitions.push({
160
+ name: nameNode.text,
161
+ kind: 'module',
162
+ line: node.startPosition.row + 1,
163
+ endLine: nodeEndLine(node),
164
+ });
165
+ }
166
+
167
+ function handleOCamlTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
168
+ // type_definition contains one or more type_bindings
169
+ for (let i = 0; i < node.childCount; i++) {
170
+ const child = node.child(i);
171
+ if (!child || child.type !== 'type_binding') continue;
172
+
173
+ const nameNode =
174
+ child.childForFieldName('name') ||
175
+ findChild(child, 'type_constructor') ||
176
+ findChild(child, 'identifier');
177
+ if (!nameNode) continue;
178
+
179
+ const children: SubDeclaration[] = [];
180
+ extractOCamlTypeConstructors(child, children);
181
+
182
+ ctx.definitions.push({
183
+ name: nameNode.text,
184
+ kind: 'type',
185
+ line: child.startPosition.row + 1,
186
+ endLine: nodeEndLine(child),
187
+ children: children.length > 0 ? children : undefined,
188
+ });
189
+ }
190
+ }
191
+
192
+ function extractOCamlTypeConstructors(
193
+ typeBinding: TreeSitterNode,
194
+ children: SubDeclaration[],
195
+ ): void {
196
+ for (let i = 0; i < typeBinding.childCount; i++) {
197
+ const child = typeBinding.child(i);
198
+ if (!child) continue;
199
+ if (child.type === 'constructor_declaration') {
200
+ const nameNode = findChild(child, 'constructor_name') || findChild(child, 'identifier');
201
+ if (nameNode) {
202
+ children.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ function handleOCamlClassDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
209
+ const binding = findChild(node, 'class_binding');
210
+ if (!binding) return;
211
+
212
+ const nameNode = binding.childForFieldName('name') || findChild(binding, 'identifier');
213
+ if (!nameNode) return;
214
+
215
+ ctx.definitions.push({
216
+ name: nameNode.text,
217
+ kind: 'class',
218
+ line: node.startPosition.row + 1,
219
+ endLine: nodeEndLine(node),
220
+ });
221
+ }
222
+
223
+ function handleOCamlOpen(node: TreeSitterNode, ctx: ExtractorOutput): void {
224
+ // open_module contains a module_path
225
+ let moduleName: string | null = null;
226
+ for (let i = 0; i < node.childCount; i++) {
227
+ const child = node.child(i);
228
+ if (!child) continue;
229
+ if (
230
+ child.type === 'module_path' ||
231
+ child.type === 'module_name' ||
232
+ child.type === 'extended_module_path' ||
233
+ child.type === 'constructor_name'
234
+ ) {
235
+ moduleName = child.text;
236
+ break;
237
+ }
238
+ }
239
+ if (!moduleName) return;
240
+
241
+ ctx.imports.push({
242
+ source: moduleName,
243
+ names: [moduleName.split('.').pop() || moduleName],
244
+ line: node.startPosition.row + 1,
245
+ });
246
+ }
247
+
248
+ function hasDescendantType(node: TreeSitterNode, type: string): boolean {
249
+ if (node.type === type) return true;
250
+ for (let i = 0; i < node.childCount; i++) {
251
+ const child = node.child(i);
252
+ if (child && hasDescendantType(child, type)) return true;
253
+ }
254
+ return false;
255
+ }
256
+
257
+ function handleOCamlValueSpec(node: TreeSitterNode, ctx: ExtractorOutput): void {
258
+ const nameNode = findChild(node, 'value_name') || findChild(node, 'parenthesized_operator');
259
+ if (!nameNode) return;
260
+
261
+ // Check if the type contains `->` (function_type node)
262
+ const typeNode = node.childForFieldName('type');
263
+ const isFunction = typeNode ? hasDescendantType(typeNode, 'function_type') : false;
264
+
265
+ ctx.definitions.push({
266
+ name: nameNode.text,
267
+ kind: isFunction ? 'function' : 'variable',
268
+ line: node.startPosition.row + 1,
269
+ endLine: nodeEndLine(node),
270
+ });
271
+ }
272
+
273
+ function handleOCamlExternal(node: TreeSitterNode, ctx: ExtractorOutput): void {
274
+ const nameNode = findChild(node, 'value_name') || findChild(node, 'parenthesized_operator');
275
+ if (!nameNode) return;
276
+
277
+ ctx.definitions.push({
278
+ name: nameNode.text,
279
+ kind: 'function',
280
+ line: node.startPosition.row + 1,
281
+ endLine: nodeEndLine(node),
282
+ });
283
+ }
284
+
285
+ function handleOCamlModuleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
286
+ const nameNode = findChild(node, 'module_type_name');
287
+ if (!nameNode) return;
288
+
289
+ ctx.definitions.push({
290
+ name: nameNode.text,
291
+ kind: 'interface',
292
+ line: node.startPosition.row + 1,
293
+ endLine: nodeEndLine(node),
294
+ });
295
+ }
296
+
297
+ function handleOCamlExceptionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
298
+ // Standard: `exception Foo of bar` — name is inside constructor_declaration
299
+ const ctorDecl = findChild(node, 'constructor_declaration');
300
+ const nameNode = ctorDecl
301
+ ? findChild(ctorDecl, 'constructor_name')
302
+ : findChild(node, 'constructor_name'); // fallback for `exception Foo = Bar` (alias)
303
+ if (!nameNode) return;
304
+
305
+ ctx.definitions.push({
306
+ name: nameNode.text,
307
+ kind: 'type',
308
+ line: node.startPosition.row + 1,
309
+ endLine: nodeEndLine(node),
310
+ });
311
+ }
312
+
313
+ function handleOCamlApplication(node: TreeSitterNode, ctx: ExtractorOutput): void {
314
+ // application_expression: first child is the function, rest are arguments
315
+ const funcNode = node.child(0);
316
+ if (!funcNode) return;
317
+
318
+ if (
319
+ funcNode.type === 'value_path' ||
320
+ funcNode.type === 'value_name' ||
321
+ funcNode.type === 'identifier'
322
+ ) {
323
+ ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
324
+ } else if (funcNode.type === 'field_get_expression') {
325
+ // Module.function calls
326
+ const field =
327
+ funcNode.childForFieldName('field') ||
328
+ findChild(funcNode, 'value_name') ||
329
+ findChild(funcNode, 'identifier');
330
+ const record = funcNode.child(0);
331
+ if (field) {
332
+ const call: Call = { name: field.text, line: node.startPosition.row + 1 };
333
+ if (record && record !== field) call.receiver = record.text;
334
+ ctx.calls.push(call);
335
+ }
336
+ }
337
+ }
@@ -0,0 +1,253 @@
1
+ import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
2
+ import { findChild, nodeEndLine } from './helpers.js';
3
+
4
+ /**
5
+ * Extract symbols from R files.
6
+ *
7
+ * tree-sitter-r grammar (r-lib/tree-sitter-r) notes:
8
+ * - Assignments: binary_operator with `<-` or `=` operator
9
+ * - Functions: function_definition as RHS of assignment
10
+ * - Calls: call node with function/arguments fields
11
+ * - Imports: library() and require() calls
12
+ * - S4 classes: setClass(), setRefClass()
13
+ */
14
+ export function extractRSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
15
+ const ctx: ExtractorOutput = {
16
+ definitions: [],
17
+ calls: [],
18
+ imports: [],
19
+ classes: [],
20
+ exports: [],
21
+ typeMap: new Map(),
22
+ };
23
+
24
+ walkRNode(tree.rootNode, ctx);
25
+ return ctx;
26
+ }
27
+
28
+ function walkRNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
29
+ switch (node.type) {
30
+ case 'binary_operator':
31
+ handleBinaryOp(node, ctx);
32
+ break;
33
+ case 'call':
34
+ handleCall(node, ctx);
35
+ break;
36
+ }
37
+
38
+ for (let i = 0; i < node.childCount; i++) {
39
+ const child = node.child(i);
40
+ if (child) walkRNode(child, ctx);
41
+ }
42
+ }
43
+
44
+ function handleBinaryOp(node: TreeSitterNode, ctx: ExtractorOutput): void {
45
+ // binary_operator: child[0]=LHS, child[1]=operator (<- or =), child[2]=RHS
46
+ if (node.childCount < 3) return;
47
+
48
+ const lhs = node.child(0);
49
+ const op = node.child(1);
50
+ const rhs = node.child(2);
51
+
52
+ if (!lhs || !op || !rhs) return;
53
+ if (op.text !== '<-' && op.text !== '=' && op.text !== '<<-') return;
54
+ if (lhs.type !== 'identifier') return;
55
+
56
+ if (rhs.type === 'function_definition') {
57
+ const params = extractRParams(rhs);
58
+ ctx.definitions.push({
59
+ name: lhs.text,
60
+ kind: 'function',
61
+ line: node.startPosition.row + 1,
62
+ endLine: nodeEndLine(node),
63
+ children: params.length > 0 ? params : undefined,
64
+ });
65
+ } else {
66
+ // Variable assignment — only record top-level
67
+ if (node.parent?.type === 'program') {
68
+ ctx.definitions.push({
69
+ name: lhs.text,
70
+ kind: 'variable',
71
+ line: node.startPosition.row + 1,
72
+ endLine: nodeEndLine(node),
73
+ });
74
+ }
75
+ }
76
+ }
77
+
78
+ function extractRParams(funcDef: TreeSitterNode): SubDeclaration[] {
79
+ const params: SubDeclaration[] = [];
80
+ const paramsNode = findChild(funcDef, 'parameters');
81
+ if (!paramsNode) return params;
82
+
83
+ for (let i = 0; i < paramsNode.childCount; i++) {
84
+ const child = paramsNode.child(i);
85
+ if (!child) continue;
86
+ if (child.type === 'parameter') {
87
+ // parameter node has name and possibly default value
88
+ const nameNode = child.childForFieldName('name') || findChild(child, 'identifier');
89
+ if (nameNode) {
90
+ params.push({ name: nameNode.text, kind: 'parameter', line: child.startPosition.row + 1 });
91
+ } else if (child.text && child.text !== ',' && child.text !== '(' && child.text !== ')') {
92
+ // Some grammars have the param as plain text
93
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
94
+ }
95
+ }
96
+ if (child.type === 'identifier') {
97
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
98
+ }
99
+ }
100
+ return params;
101
+ }
102
+
103
+ function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
104
+ // call: child[0]=function, then arguments
105
+ const funcNode = node.child(0);
106
+ if (!funcNode) return;
107
+
108
+ const funcName = funcNode.text;
109
+
110
+ // library() and require() are imports
111
+ if (funcName === 'library' || funcName === 'require') {
112
+ handleLibraryCall(node, ctx);
113
+ return;
114
+ }
115
+
116
+ // source() is a file import
117
+ if (funcName === 'source') {
118
+ handleSourceCall(node, ctx);
119
+ return;
120
+ }
121
+
122
+ // setClass / setRefClass for S4
123
+ if (funcName === 'setClass' || funcName === 'setRefClass') {
124
+ handleSetClass(node, ctx);
125
+ return;
126
+ }
127
+
128
+ if (funcName === 'setGeneric' || funcName === 'setMethod') {
129
+ handleSetGeneric(node, ctx);
130
+ return;
131
+ }
132
+
133
+ // Regular call
134
+ if (funcNode.type === 'identifier') {
135
+ ctx.calls.push({ name: funcName, line: node.startPosition.row + 1 });
136
+ } else if (funcNode.type === 'namespace_operator') {
137
+ // pkg::func
138
+ const parts = funcName.split('::');
139
+ if (parts.length >= 2) {
140
+ ctx.calls.push({
141
+ name: parts[parts.length - 1]!,
142
+ receiver: parts.slice(0, -1).join('::'),
143
+ line: node.startPosition.row + 1,
144
+ });
145
+ }
146
+ }
147
+ }
148
+
149
+ function handleLibraryCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
150
+ // Find the package name in arguments
151
+ for (let i = 0; i < node.childCount; i++) {
152
+ const child = node.child(i);
153
+ if (!child) continue;
154
+ if (child.type === 'arguments') {
155
+ for (let j = 0; j < child.childCount; j++) {
156
+ const arg = child.child(j);
157
+ if (!arg) continue;
158
+ if (arg.type === 'identifier') {
159
+ ctx.imports.push({
160
+ source: arg.text,
161
+ names: [arg.text],
162
+ line: node.startPosition.row + 1,
163
+ });
164
+ return;
165
+ }
166
+ if (arg.type === 'string' || arg.type === 'string_content') {
167
+ const text = arg.text.replace(/^["']|["']$/g, '');
168
+ ctx.imports.push({
169
+ source: text,
170
+ names: [text],
171
+ line: node.startPosition.row + 1,
172
+ });
173
+ return;
174
+ }
175
+ // Argument might be wrapped
176
+ if (arg.type === 'argument') {
177
+ const id = findChild(arg, 'identifier') || findChild(arg, 'string');
178
+ if (id) {
179
+ const text = id.text.replace(/^["']|["']$/g, '');
180
+ ctx.imports.push({
181
+ source: text,
182
+ names: [text],
183
+ line: node.startPosition.row + 1,
184
+ });
185
+ return;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ function handleSourceCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
194
+ for (let i = 0; i < node.childCount; i++) {
195
+ const child = node.child(i);
196
+ if (!child || child.type !== 'arguments') continue;
197
+ for (let j = 0; j < child.childCount; j++) {
198
+ const arg = child.child(j);
199
+ if (!arg) continue;
200
+ if (arg.type === 'string') {
201
+ const text = arg.text.replace(/^["']|["']$/g, '');
202
+ ctx.imports.push({
203
+ source: text,
204
+ names: ['source'],
205
+ line: node.startPosition.row + 1,
206
+ });
207
+ return;
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ function handleSetClass(node: TreeSitterNode, ctx: ExtractorOutput): void {
214
+ for (let i = 0; i < node.childCount; i++) {
215
+ const child = node.child(i);
216
+ if (!child || child.type !== 'arguments') continue;
217
+ for (let j = 0; j < child.childCount; j++) {
218
+ const arg = child.child(j);
219
+ if (!arg) continue;
220
+ if (arg.type === 'string') {
221
+ const name = arg.text.replace(/^["']|["']$/g, '');
222
+ ctx.definitions.push({
223
+ name,
224
+ kind: 'class',
225
+ line: node.startPosition.row + 1,
226
+ endLine: nodeEndLine(node),
227
+ });
228
+ return;
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ function handleSetGeneric(node: TreeSitterNode, ctx: ExtractorOutput): void {
235
+ for (let i = 0; i < node.childCount; i++) {
236
+ const child = node.child(i);
237
+ if (!child || child.type !== 'arguments') continue;
238
+ for (let j = 0; j < child.childCount; j++) {
239
+ const arg = child.child(j);
240
+ if (!arg) continue;
241
+ if (arg.type === 'string') {
242
+ const name = arg.text.replace(/^["']|["']$/g, '');
243
+ ctx.definitions.push({
244
+ name,
245
+ kind: 'function',
246
+ line: node.startPosition.row + 1,
247
+ endLine: nodeEndLine(node),
248
+ });
249
+ return;
250
+ }
251
+ }
252
+ }
253
+ }