@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,316 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from CUDA files.
12
+ *
13
+ * CUDA is a C++ superset. The tree-sitter-cuda grammar extends C++ with
14
+ * __global__, __device__, __host__, __shared__ qualifiers and kernel
15
+ * launch syntax (<<<...>>>). We reuse C++ handler patterns and add
16
+ * CUDA-specific qualifier detection.
17
+ */
18
+ export function extractCudaSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
19
+ const ctx: ExtractorOutput = {
20
+ definitions: [],
21
+ calls: [],
22
+ imports: [],
23
+ classes: [],
24
+ exports: [],
25
+ typeMap: new Map(),
26
+ };
27
+
28
+ walkCudaNode(tree.rootNode, ctx);
29
+ return ctx;
30
+ }
31
+
32
+ const CUDA_QUALIFIERS = new Set([
33
+ '__global__',
34
+ '__device__',
35
+ '__host__',
36
+ '__shared__',
37
+ '__constant__',
38
+ ]);
39
+
40
+ function walkCudaNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
41
+ switch (node.type) {
42
+ case 'function_definition':
43
+ handleCudaFunctionDef(node, ctx);
44
+ break;
45
+ case 'class_specifier':
46
+ handleCudaClassSpecifier(node, ctx);
47
+ break;
48
+ case 'struct_specifier':
49
+ handleCudaStructSpecifier(node, ctx);
50
+ break;
51
+ case 'enum_specifier':
52
+ handleCudaEnumSpecifier(node, ctx);
53
+ break;
54
+ case 'namespace_definition':
55
+ handleCudaNamespaceDef(node, ctx);
56
+ break;
57
+ case 'type_definition':
58
+ handleCudaTypedef(node, ctx);
59
+ break;
60
+ case 'preproc_include':
61
+ handleCudaInclude(node, ctx);
62
+ break;
63
+ case 'call_expression':
64
+ handleCudaCallExpression(node, ctx);
65
+ break;
66
+ }
67
+
68
+ for (let i = 0; i < node.childCount; i++) {
69
+ const child = node.child(i);
70
+ if (child) walkCudaNode(child, ctx);
71
+ }
72
+ }
73
+
74
+ // ── Handlers ───────────────────────────────────────────────────────────────
75
+
76
+ function handleCudaFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
77
+ const declarator = node.childForFieldName('declarator');
78
+ if (!declarator) return;
79
+ const funcDeclarator =
80
+ declarator.type === 'function_declarator'
81
+ ? declarator
82
+ : findChild(declarator, 'function_declarator');
83
+ if (!funcDeclarator) return;
84
+ const nameNode = funcDeclarator.childForFieldName('declarator');
85
+ if (!nameNode) return;
86
+ const name = nameNode.text;
87
+
88
+ const parentClass = findCudaParentClass(node);
89
+ const fullName = parentClass ? `${parentClass}.${name}` : name;
90
+ const kind = parentClass ? 'method' : 'function';
91
+
92
+ const params = extractCudaParameters(funcDeclarator.childForFieldName('parameters'));
93
+ const decorators = extractCudaQualifiers(node);
94
+
95
+ ctx.definitions.push({
96
+ name: fullName,
97
+ kind,
98
+ line: node.startPosition.row + 1,
99
+ endLine: nodeEndLine(node),
100
+ children: params.length > 0 ? params : undefined,
101
+ visibility: parentClass ? extractModifierVisibility(node) : undefined,
102
+ decorators: decorators.length > 0 ? decorators : undefined,
103
+ });
104
+ }
105
+
106
+ function handleCudaClassSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
107
+ const nameNode = node.childForFieldName('name');
108
+ if (!nameNode) return;
109
+ const children = extractCudaClassFields(node);
110
+ ctx.definitions.push({
111
+ name: nameNode.text,
112
+ kind: 'class',
113
+ line: node.startPosition.row + 1,
114
+ endLine: nodeEndLine(node),
115
+ children: children.length > 0 ? children : undefined,
116
+ });
117
+
118
+ const baseClause = findChild(node, 'base_class_clause');
119
+ if (baseClause) {
120
+ for (let i = 0; i < baseClause.childCount; i++) {
121
+ const child = baseClause.child(i);
122
+ if (child && (child.type === 'type_identifier' || child.type === 'qualified_identifier')) {
123
+ ctx.classes.push({
124
+ name: nameNode.text,
125
+ extends: child.text,
126
+ line: node.startPosition.row + 1,
127
+ });
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ function handleCudaStructSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
134
+ const nameNode = node.childForFieldName('name');
135
+ if (!nameNode) return;
136
+ const children = extractCudaClassFields(node);
137
+ ctx.definitions.push({
138
+ name: nameNode.text,
139
+ kind: 'struct',
140
+ line: node.startPosition.row + 1,
141
+ endLine: nodeEndLine(node),
142
+ children: children.length > 0 ? children : undefined,
143
+ });
144
+ }
145
+
146
+ function handleCudaEnumSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
147
+ const nameNode = node.childForFieldName('name');
148
+ if (!nameNode) return;
149
+ const children = extractCudaEnumEntries(node);
150
+ ctx.definitions.push({
151
+ name: nameNode.text,
152
+ kind: 'enum',
153
+ line: node.startPosition.row + 1,
154
+ endLine: nodeEndLine(node),
155
+ children: children.length > 0 ? children : undefined,
156
+ });
157
+ }
158
+
159
+ function handleCudaNamespaceDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
160
+ const nameNode = node.childForFieldName('name');
161
+ if (!nameNode) return;
162
+ ctx.definitions.push({
163
+ name: nameNode.text,
164
+ kind: 'namespace',
165
+ line: node.startPosition.row + 1,
166
+ endLine: nodeEndLine(node),
167
+ });
168
+ }
169
+
170
+ function handleCudaTypedef(node: TreeSitterNode, ctx: ExtractorOutput): void {
171
+ let name: string | undefined;
172
+ for (let i = node.childCount - 1; i >= 0; i--) {
173
+ const child = node.child(i);
174
+ if (
175
+ child &&
176
+ (child.type === 'type_identifier' ||
177
+ child.type === 'identifier' ||
178
+ child.type === 'primitive_type')
179
+ ) {
180
+ name = child.text;
181
+ break;
182
+ }
183
+ }
184
+ if (!name) return;
185
+ ctx.definitions.push({
186
+ name,
187
+ kind: 'type',
188
+ line: node.startPosition.row + 1,
189
+ endLine: nodeEndLine(node),
190
+ });
191
+ }
192
+
193
+ function handleCudaInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
194
+ const pathNode = node.childForFieldName('path');
195
+ if (!pathNode) return;
196
+ const raw = pathNode.text;
197
+ const source = raw.replace(/^["<]|[">]$/g, '');
198
+ const lastName = source.split('/').pop() ?? source;
199
+ ctx.imports.push({
200
+ source,
201
+ names: [lastName],
202
+ line: node.startPosition.row + 1,
203
+ cInclude: true,
204
+ });
205
+ }
206
+
207
+ function handleCudaCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
208
+ const funcNode = node.childForFieldName('function');
209
+ if (!funcNode) return;
210
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
211
+ if (funcNode.type === 'field_expression') {
212
+ const field = funcNode.childForFieldName('field');
213
+ const argument = funcNode.childForFieldName('argument');
214
+ if (field) call.name = field.text;
215
+ if (argument) call.receiver = argument.text;
216
+ } else {
217
+ call.name = funcNode.text;
218
+ }
219
+ if (call.name) ctx.calls.push(call);
220
+ }
221
+
222
+ // ── Helpers ────────────────────────────────────────────────────────────────
223
+
224
+ function extractCudaQualifiers(node: TreeSitterNode): string[] {
225
+ const qualifiers: string[] = [];
226
+ for (let i = 0; i < node.childCount; i++) {
227
+ const child = node.child(i);
228
+ if (!child) continue;
229
+ // Check direct text match for bare qualifier tokens, or look inside
230
+ // storage_class_specifier / attribute_specifier wrapper nodes.
231
+ // Use `else if` to avoid pushing the same qualifier twice when
232
+ // wrapper-node text also matches CUDA_QUALIFIERS directly.
233
+ if (child.type === 'storage_class_specifier' || child.type === 'attribute_specifier') {
234
+ if (CUDA_QUALIFIERS.has(child.text)) qualifiers.push(child.text);
235
+ } else if (CUDA_QUALIFIERS.has(child.text)) {
236
+ qualifiers.push(child.text);
237
+ }
238
+ }
239
+ return qualifiers;
240
+ }
241
+
242
+ function findCudaParentClass(node: TreeSitterNode): string | null {
243
+ let current = node.parent;
244
+ while (current) {
245
+ if (current.type === 'field_declaration_list') {
246
+ const classNode = current.parent;
247
+ if (
248
+ classNode &&
249
+ (classNode.type === 'class_specifier' || classNode.type === 'struct_specifier')
250
+ ) {
251
+ const nameNode = classNode.childForFieldName('name');
252
+ return nameNode ? nameNode.text : null;
253
+ }
254
+ }
255
+ current = current.parent;
256
+ }
257
+ return null;
258
+ }
259
+
260
+ function extractCudaParameters(paramListNode: TreeSitterNode | null): SubDeclaration[] {
261
+ const params: SubDeclaration[] = [];
262
+ if (!paramListNode) return params;
263
+ for (let i = 0; i < paramListNode.childCount; i++) {
264
+ const param = paramListNode.child(i);
265
+ if (!param || param.type !== 'parameter_declaration') continue;
266
+ const nameNode = param.childForFieldName('declarator');
267
+ if (nameNode) {
268
+ const name =
269
+ nameNode.type === 'identifier'
270
+ ? nameNode.text
271
+ : (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
272
+ params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
273
+ }
274
+ }
275
+ return params;
276
+ }
277
+
278
+ function extractCudaClassFields(classNode: TreeSitterNode): SubDeclaration[] {
279
+ const fields: SubDeclaration[] = [];
280
+ const body =
281
+ classNode.childForFieldName('body') || findChild(classNode, 'field_declaration_list');
282
+ if (!body) return fields;
283
+ for (let i = 0; i < body.childCount; i++) {
284
+ const member = body.child(i);
285
+ if (!member || member.type !== 'field_declaration') continue;
286
+ const nameNode = member.childForFieldName('declarator');
287
+ if (nameNode) {
288
+ const name =
289
+ nameNode.type === 'identifier'
290
+ ? nameNode.text
291
+ : (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
292
+ fields.push({
293
+ name,
294
+ kind: 'property',
295
+ line: member.startPosition.row + 1,
296
+ visibility: extractModifierVisibility(member),
297
+ });
298
+ }
299
+ }
300
+ return fields;
301
+ }
302
+
303
+ function extractCudaEnumEntries(enumNode: TreeSitterNode): SubDeclaration[] {
304
+ const entries: SubDeclaration[] = [];
305
+ const body = findChild(enumNode, 'enumerator_list');
306
+ if (!body) return entries;
307
+ for (let i = 0; i < body.childCount; i++) {
308
+ const member = body.child(i);
309
+ if (!member || member.type !== 'enumerator') continue;
310
+ const nameNode = member.childForFieldName('name');
311
+ if (nameNode) {
312
+ entries.push({ name: nameNode.text, kind: 'constant', line: member.startPosition.row + 1 });
313
+ }
314
+ }
315
+ return entries;
316
+ }
@@ -0,0 +1,304 @@
1
+ import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
2
+ import { findChild, nodeEndLine } from './helpers.js';
3
+
4
+ /**
5
+ * Extract symbols from Dart files.
6
+ */
7
+ export function extractDartSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
8
+ const ctx: ExtractorOutput = {
9
+ definitions: [],
10
+ calls: [],
11
+ imports: [],
12
+ classes: [],
13
+ exports: [],
14
+ typeMap: new Map(),
15
+ };
16
+
17
+ walkDartNode(tree.rootNode, ctx);
18
+ return ctx;
19
+ }
20
+
21
+ function walkDartNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
22
+ switch (node.type) {
23
+ case 'class_definition':
24
+ handleDartClass(node, ctx);
25
+ break;
26
+ case 'enum_declaration':
27
+ handleDartEnum(node, ctx);
28
+ break;
29
+ case 'mixin_declaration':
30
+ handleDartMixin(node, ctx);
31
+ break;
32
+ case 'extension_declaration':
33
+ handleDartExtension(node, ctx);
34
+ break;
35
+ case 'function_signature':
36
+ handleDartFunction(node, ctx);
37
+ break;
38
+ case 'method_signature':
39
+ handleDartMethodSig(node, ctx);
40
+ break;
41
+ case 'library_import':
42
+ handleDartImport(node, ctx);
43
+ break;
44
+ case 'constructor_invocation':
45
+ case 'new_expression':
46
+ handleDartConstructorCall(node, ctx);
47
+ break;
48
+ case 'type_alias':
49
+ handleDartTypeAlias(node, ctx);
50
+ break;
51
+ case 'selector':
52
+ handleDartSelector(node, ctx);
53
+ break;
54
+ }
55
+
56
+ for (let i = 0; i < node.childCount; i++) {
57
+ const child = node.child(i);
58
+ if (child) walkDartNode(child, ctx);
59
+ }
60
+ }
61
+
62
+ function handleDartClass(node: TreeSitterNode, ctx: ExtractorOutput): void {
63
+ const nameNode = node.childForFieldName('name');
64
+ if (!nameNode) return;
65
+ const name = nameNode.text;
66
+ const children: SubDeclaration[] = [];
67
+
68
+ const body = node.childForFieldName('body') || findChild(node, 'class_body');
69
+ if (body) {
70
+ extractDartClassMembers(body, name, ctx, children);
71
+ }
72
+
73
+ ctx.definitions.push({
74
+ name,
75
+ kind: 'class',
76
+ line: node.startPosition.row + 1,
77
+ endLine: nodeEndLine(node),
78
+ children: children.length > 0 ? children : undefined,
79
+ });
80
+
81
+ extractDartInheritance(node, name, ctx);
82
+ }
83
+
84
+ function extractDartClassMembers(
85
+ body: TreeSitterNode,
86
+ className: string,
87
+ ctx: ExtractorOutput,
88
+ children: SubDeclaration[],
89
+ ): void {
90
+ for (let i = 0; i < body.childCount; i++) {
91
+ const member = body.child(i);
92
+ if (!member) continue;
93
+
94
+ if (member.type === 'method_signature' || member.type === 'function_signature') {
95
+ const fnName = extractDartFunctionName(member);
96
+ if (fnName) {
97
+ ctx.definitions.push({
98
+ name: `${className}.${fnName}`,
99
+ kind: 'method',
100
+ line: member.startPosition.row + 1,
101
+ endLine: nodeEndLine(member),
102
+ });
103
+ }
104
+ } else if (member.type === 'declaration') {
105
+ // Field declarations
106
+ for (let j = 0; j < member.childCount; j++) {
107
+ const decl = member.child(j);
108
+ if (decl?.type === 'identifier') {
109
+ children.push({
110
+ name: decl.text,
111
+ kind: 'property',
112
+ line: member.startPosition.row + 1,
113
+ });
114
+ break;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ function extractDartFunctionName(node: TreeSitterNode): string | null {
122
+ const nameNode = node.childForFieldName('name');
123
+ if (nameNode) return nameNode.text;
124
+
125
+ // Walk children for function_signature inside method_signature
126
+ for (let i = 0; i < node.childCount; i++) {
127
+ const child = node.child(i);
128
+ if (!child) continue;
129
+ if (
130
+ child.type === 'function_signature' ||
131
+ child.type === 'getter_signature' ||
132
+ child.type === 'setter_signature' ||
133
+ child.type === 'constructor_signature'
134
+ ) {
135
+ const name = child.childForFieldName('name');
136
+ if (name) return name.text;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+
142
+ function handleDartEnum(node: TreeSitterNode, ctx: ExtractorOutput): void {
143
+ const nameNode = node.childForFieldName('name');
144
+ if (!nameNode) return;
145
+
146
+ ctx.definitions.push({
147
+ name: nameNode.text,
148
+ kind: 'enum',
149
+ line: node.startPosition.row + 1,
150
+ endLine: nodeEndLine(node),
151
+ });
152
+ }
153
+
154
+ function handleDartMixin(node: TreeSitterNode, ctx: ExtractorOutput): void {
155
+ const nameNode = findChild(node, 'identifier');
156
+ if (!nameNode) return;
157
+
158
+ ctx.definitions.push({
159
+ name: nameNode.text,
160
+ kind: 'class',
161
+ line: node.startPosition.row + 1,
162
+ endLine: nodeEndLine(node),
163
+ });
164
+ }
165
+
166
+ function handleDartExtension(node: TreeSitterNode, ctx: ExtractorOutput): void {
167
+ const nameNode = node.childForFieldName('name');
168
+ if (!nameNode) return;
169
+
170
+ ctx.definitions.push({
171
+ name: nameNode.text,
172
+ kind: 'class',
173
+ line: node.startPosition.row + 1,
174
+ endLine: nodeEndLine(node),
175
+ });
176
+ }
177
+
178
+ function handleDartFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
179
+ // Skip methods already emitted by class handler
180
+ if (isInsideDartClass(node)) return;
181
+
182
+ const nameNode = node.childForFieldName('name');
183
+ if (!nameNode) return;
184
+
185
+ ctx.definitions.push({
186
+ name: nameNode.text,
187
+ kind: 'function',
188
+ line: node.startPosition.row + 1,
189
+ endLine: nodeEndLine(node),
190
+ });
191
+ }
192
+
193
+ function handleDartMethodSig(node: TreeSitterNode, ctx: ExtractorOutput): void {
194
+ if (isInsideDartClass(node)) return;
195
+ const fnName = extractDartFunctionName(node);
196
+ if (!fnName) return;
197
+
198
+ ctx.definitions.push({
199
+ name: fnName,
200
+ kind: 'function',
201
+ line: node.startPosition.row + 1,
202
+ endLine: nodeEndLine(node),
203
+ });
204
+ }
205
+
206
+ function isInsideDartClass(node: TreeSitterNode): boolean {
207
+ let current = node.parent;
208
+ while (current) {
209
+ if (
210
+ current.type === 'class_body' ||
211
+ current.type === 'class_definition' ||
212
+ current.type === 'enum_body' ||
213
+ current.type === 'mixin_declaration'
214
+ ) {
215
+ return true;
216
+ }
217
+ current = current.parent;
218
+ }
219
+ return false;
220
+ }
221
+
222
+ function handleDartImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
223
+ const spec = findChild(node, 'import_specification');
224
+ if (!spec) return;
225
+
226
+ const uri = findChild(spec, 'configurable_uri') || findChild(spec, 'uri');
227
+ if (!uri) return;
228
+
229
+ const source = uri.text.replace(/^['"]|['"]$/g, '');
230
+ const names: string[] = [];
231
+
232
+ // Check for `as` alias
233
+ const alias = findChild(spec, 'identifier');
234
+ if (alias) names.push(alias.text);
235
+
236
+ ctx.imports.push({
237
+ source,
238
+ names: names.length > 0 ? names : [source.split('/').pop() || source],
239
+ line: node.startPosition.row + 1,
240
+ });
241
+ }
242
+
243
+ function handleDartConstructorCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
244
+ const nameNode = findChild(node, 'type_identifier') || findChild(node, 'identifier');
245
+ if (!nameNode) return;
246
+
247
+ ctx.calls.push({
248
+ name: nameNode.text,
249
+ line: node.startPosition.row + 1,
250
+ });
251
+ }
252
+
253
+ function handleDartSelector(node: TreeSitterNode, ctx: ExtractorOutput): void {
254
+ // selector with argument_part represents a function call
255
+ const argPart = findChild(node, 'argument_part');
256
+ if (!argPart) return;
257
+
258
+ // Look for the identifier this selector belongs to
259
+ const unconditional = findChild(node, 'unconditional_assignable_selector');
260
+ if (unconditional) {
261
+ const id = findChild(unconditional, 'identifier');
262
+ if (id) {
263
+ ctx.calls.push({ name: id.text, line: node.startPosition.row + 1 });
264
+ }
265
+ }
266
+ }
267
+
268
+ function handleDartTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
269
+ const nameNode = findChild(node, 'type_identifier') || findChild(node, 'identifier');
270
+ if (!nameNode) return;
271
+
272
+ ctx.definitions.push({
273
+ name: nameNode.text,
274
+ kind: 'type',
275
+ line: node.startPosition.row + 1,
276
+ endLine: nodeEndLine(node),
277
+ });
278
+ }
279
+
280
+ function extractDartInheritance(node: TreeSitterNode, name: string, ctx: ExtractorOutput): void {
281
+ const superclass = node.childForFieldName('superclass');
282
+ if (superclass) {
283
+ const typeName =
284
+ findChild(superclass, 'type_identifier') || findChild(superclass, 'identifier');
285
+ if (typeName) {
286
+ ctx.classes.push({ name, extends: typeName.text, line: node.startPosition.row + 1 });
287
+ }
288
+ }
289
+
290
+ const interfaces = node.childForFieldName('interfaces');
291
+ if (interfaces) {
292
+ for (let i = 0; i < interfaces.childCount; i++) {
293
+ const iface = interfaces.child(i);
294
+ if (!iface) continue;
295
+ const typeName =
296
+ iface.type === 'type_identifier'
297
+ ? iface
298
+ : findChild(iface, 'type_identifier') || findChild(iface, 'identifier');
299
+ if (typeName) {
300
+ ctx.classes.push({ name, implements: typeName.text, line: node.startPosition.row + 1 });
301
+ }
302
+ }
303
+ }
304
+ }