@optave/codegraph 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +25 -14
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +158 -1
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +0 -1
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.js +2 -75
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  11. package/dist/cli/commands/ast.js +2 -2
  12. package/dist/cli/commands/ast.js.map +1 -1
  13. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  14. package/dist/domain/graph/builder/pipeline.js +128 -6
  15. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  16. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  17. package/dist/domain/graph/builder/stages/build-edges.js +101 -1
  18. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  19. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  20. package/dist/domain/graph/builder/stages/collect-files.js +17 -5
  21. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  22. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  23. package/dist/domain/graph/builder/stages/detect-changes.js +98 -50
  24. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  25. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  26. package/dist/domain/graph/builder/stages/finalize.js +32 -5
  27. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  28. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  29. package/dist/domain/graph/builder/stages/insert-nodes.js +20 -7
  30. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  31. package/dist/domain/parser.d.ts +1 -1
  32. package/dist/domain/parser.d.ts.map +1 -1
  33. package/dist/domain/parser.js +88 -4
  34. package/dist/domain/parser.js.map +1 -1
  35. package/dist/extractors/clojure.d.ts +12 -0
  36. package/dist/extractors/clojure.d.ts.map +1 -0
  37. package/dist/extractors/clojure.js +245 -0
  38. package/dist/extractors/clojure.js.map +1 -0
  39. package/dist/extractors/cuda.d.ts +11 -0
  40. package/dist/extractors/cuda.d.ts.map +1 -0
  41. package/dist/extractors/cuda.js +302 -0
  42. package/dist/extractors/cuda.js.map +1 -0
  43. package/dist/extractors/erlang.d.ts +14 -0
  44. package/dist/extractors/erlang.d.ts.map +1 -0
  45. package/dist/extractors/erlang.js +239 -0
  46. package/dist/extractors/erlang.js.map +1 -0
  47. package/dist/extractors/fsharp.d.ts +13 -0
  48. package/dist/extractors/fsharp.d.ts.map +1 -0
  49. package/dist/extractors/fsharp.js +218 -0
  50. package/dist/extractors/fsharp.js.map +1 -0
  51. package/dist/extractors/gleam.d.ts +14 -0
  52. package/dist/extractors/gleam.d.ts.map +1 -0
  53. package/dist/extractors/gleam.js +229 -0
  54. package/dist/extractors/gleam.js.map +1 -0
  55. package/dist/extractors/groovy.d.ts +10 -0
  56. package/dist/extractors/groovy.d.ts.map +1 -0
  57. package/dist/extractors/groovy.js +304 -0
  58. package/dist/extractors/groovy.js.map +1 -0
  59. package/dist/extractors/index.d.ts +11 -0
  60. package/dist/extractors/index.d.ts.map +1 -1
  61. package/dist/extractors/index.js +11 -0
  62. package/dist/extractors/index.js.map +1 -1
  63. package/dist/extractors/julia.d.ts +16 -0
  64. package/dist/extractors/julia.d.ts.map +1 -0
  65. package/dist/extractors/julia.js +287 -0
  66. package/dist/extractors/julia.js.map +1 -0
  67. package/dist/extractors/objc.d.ts +9 -0
  68. package/dist/extractors/objc.d.ts.map +1 -0
  69. package/dist/extractors/objc.js +406 -0
  70. package/dist/extractors/objc.js.map +1 -0
  71. package/dist/extractors/ocaml.js +74 -0
  72. package/dist/extractors/ocaml.js.map +1 -1
  73. package/dist/extractors/r.d.ts +13 -0
  74. package/dist/extractors/r.d.ts.map +1 -0
  75. package/dist/extractors/r.js +251 -0
  76. package/dist/extractors/r.js.map +1 -0
  77. package/dist/extractors/solidity.d.ts +9 -0
  78. package/dist/extractors/solidity.d.ts.map +1 -0
  79. package/dist/extractors/solidity.js +374 -0
  80. package/dist/extractors/solidity.js.map +1 -0
  81. package/dist/extractors/verilog.d.ts +9 -0
  82. package/dist/extractors/verilog.d.ts.map +1 -0
  83. package/dist/extractors/verilog.js +286 -0
  84. package/dist/extractors/verilog.js.map +1 -0
  85. package/dist/features/ast.d.ts.map +1 -1
  86. package/dist/features/ast.js +1 -2
  87. package/dist/features/ast.js.map +1 -1
  88. package/dist/graph/algorithms/bfs.d.ts +2 -0
  89. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  90. package/dist/graph/algorithms/bfs.js +27 -0
  91. package/dist/graph/algorithms/bfs.js.map +1 -1
  92. package/dist/graph/algorithms/centrality.d.ts +2 -0
  93. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  94. package/dist/graph/algorithms/centrality.js +28 -0
  95. package/dist/graph/algorithms/centrality.js.map +1 -1
  96. package/dist/graph/algorithms/louvain.d.ts +3 -4
  97. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  98. package/dist/graph/algorithms/louvain.js +29 -0
  99. package/dist/graph/algorithms/louvain.js.map +1 -1
  100. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  101. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  102. package/dist/graph/algorithms/shortest-path.js +18 -1
  103. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  104. package/dist/types.d.ts +122 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/grammars/tree-sitter-clojure.wasm +0 -0
  107. package/grammars/tree-sitter-cuda.wasm +0 -0
  108. package/grammars/tree-sitter-erlang.wasm +0 -0
  109. package/grammars/tree-sitter-fsharp.wasm +0 -0
  110. package/grammars/tree-sitter-gleam.wasm +0 -0
  111. package/grammars/tree-sitter-groovy.wasm +0 -0
  112. package/grammars/tree-sitter-julia.wasm +0 -0
  113. package/grammars/tree-sitter-objc.wasm +0 -0
  114. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  115. package/grammars/tree-sitter-r.wasm +0 -0
  116. package/grammars/tree-sitter-solidity.wasm +0 -0
  117. package/grammars/tree-sitter-verilog.wasm +0 -0
  118. package/package.json +18 -7
  119. package/src/ast-analysis/engine.ts +183 -1
  120. package/src/ast-analysis/rules/javascript.ts +0 -1
  121. package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
  122. package/src/cli/commands/ast.ts +2 -2
  123. package/src/domain/graph/builder/pipeline.ts +142 -6
  124. package/src/domain/graph/builder/stages/build-edges.ts +158 -1
  125. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  126. package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
  127. package/src/domain/graph/builder/stages/finalize.ts +39 -9
  128. package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
  129. package/src/domain/parser.ts +108 -2
  130. package/src/extractors/clojure.ts +273 -0
  131. package/src/extractors/cuda.ts +316 -0
  132. package/src/extractors/erlang.ts +252 -0
  133. package/src/extractors/fsharp.ts +253 -0
  134. package/src/extractors/gleam.ts +246 -0
  135. package/src/extractors/groovy.ts +332 -0
  136. package/src/extractors/index.ts +11 -0
  137. package/src/extractors/julia.ts +318 -0
  138. package/src/extractors/objc.ts +431 -0
  139. package/src/extractors/ocaml.ts +78 -0
  140. package/src/extractors/r.ts +253 -0
  141. package/src/extractors/solidity.ts +398 -0
  142. package/src/extractors/verilog.ts +315 -0
  143. package/src/features/ast.ts +1 -2
  144. package/src/graph/algorithms/bfs.ts +34 -0
  145. package/src/graph/algorithms/centrality.ts +30 -0
  146. package/src/graph/algorithms/louvain.ts +31 -4
  147. package/src/graph/algorithms/shortest-path.ts +20 -1
  148. package/src/types.ts +117 -2
@@ -0,0 +1,253 @@
1
+ import type { 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
+ }
@@ -0,0 +1,398 @@
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
+ nodeEndLine,
13
+ stripQuotes,
14
+ } from './helpers.js';
15
+
16
+ /**
17
+ * Extract symbols from Solidity files.
18
+ *
19
+ * Solidity's tree-sitter grammar covers contracts, interfaces, libraries,
20
+ * structs, enums, events, errors, functions, modifiers, and import paths.
21
+ */
22
+ export function extractSoliditySymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
23
+ const ctx: ExtractorOutput = {
24
+ definitions: [],
25
+ calls: [],
26
+ imports: [],
27
+ classes: [],
28
+ exports: [],
29
+ typeMap: new Map(),
30
+ };
31
+
32
+ walkSolidityNode(tree.rootNode, ctx);
33
+ return ctx;
34
+ }
35
+
36
+ function walkSolidityNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
37
+ switch (node.type) {
38
+ case 'contract_declaration':
39
+ handleContractDecl(node, ctx, 'class');
40
+ break;
41
+ case 'interface_declaration':
42
+ handleContractDecl(node, ctx, 'interface');
43
+ break;
44
+ case 'library_declaration':
45
+ handleContractDecl(node, ctx, 'module');
46
+ break;
47
+ case 'struct_declaration':
48
+ handleStructDecl(node, ctx);
49
+ break;
50
+ case 'enum_declaration':
51
+ handleEnumDecl(node, ctx);
52
+ break;
53
+ case 'function_definition':
54
+ handleFunctionDef(node, ctx);
55
+ break;
56
+ case 'modifier_definition':
57
+ handleModifierDef(node, ctx);
58
+ break;
59
+ case 'event_definition':
60
+ handleEventDef(node, ctx);
61
+ break;
62
+ case 'error_declaration':
63
+ handleErrorDecl(node, ctx);
64
+ break;
65
+ case 'state_variable_declaration':
66
+ handleStateVarDecl(node, ctx);
67
+ break;
68
+ case 'import_directive':
69
+ handleImportDirective(node, ctx);
70
+ break;
71
+ case 'call_expression':
72
+ case 'function_call':
73
+ handleCallExpression(node, ctx);
74
+ break;
75
+ }
76
+
77
+ for (let i = 0; i < node.childCount; i++) {
78
+ const child = node.child(i);
79
+ if (child) walkSolidityNode(child, ctx);
80
+ }
81
+ }
82
+
83
+ // ── Handlers ───────────────────────────────────────────────────────────────
84
+
85
+ const SOL_PARENT_TYPES = [
86
+ 'contract_declaration',
87
+ 'interface_declaration',
88
+ 'library_declaration',
89
+ ] as const;
90
+
91
+ function handleContractDecl(
92
+ node: TreeSitterNode,
93
+ ctx: ExtractorOutput,
94
+ kind: 'class' | 'interface' | 'module',
95
+ ): void {
96
+ const nameNode = node.childForFieldName('name');
97
+ if (!nameNode) return;
98
+ const name = nameNode.text;
99
+
100
+ const members: SubDeclaration[] = [];
101
+ const body = node.childForFieldName('body') || findChild(node, 'contract_body');
102
+ if (body) {
103
+ for (let i = 0; i < body.childCount; i++) {
104
+ const child = body.child(i);
105
+ if (!child) continue;
106
+ if (child.type === 'function_definition') {
107
+ const fnName = child.childForFieldName('name');
108
+ if (fnName) {
109
+ members.push({ name: fnName.text, kind: 'method', line: child.startPosition.row + 1 });
110
+ }
111
+ } else if (child.type === 'state_variable_declaration') {
112
+ const varName = child.childForFieldName('name');
113
+ if (varName) {
114
+ members.push({
115
+ name: varName.text,
116
+ kind: 'property',
117
+ line: child.startPosition.row + 1,
118
+ visibility: extractSolVisibility(child),
119
+ });
120
+ }
121
+ } else if (child.type === 'event_definition') {
122
+ const evName = child.childForFieldName('name');
123
+ if (evName) {
124
+ members.push({
125
+ name: evName.text,
126
+ kind: 'property',
127
+ decorators: ['event'],
128
+ line: child.startPosition.row + 1,
129
+ });
130
+ }
131
+ } else if (child.type === 'error_declaration') {
132
+ const errName = child.childForFieldName('name');
133
+ if (errName) {
134
+ members.push({
135
+ name: errName.text,
136
+ kind: 'property',
137
+ decorators: ['error'],
138
+ line: child.startPosition.row + 1,
139
+ });
140
+ }
141
+ } else if (child.type === 'modifier_definition') {
142
+ const modName = child.childForFieldName('name');
143
+ if (modName) {
144
+ members.push({
145
+ name: modName.text,
146
+ kind: 'method',
147
+ decorators: ['modifier'],
148
+ line: child.startPosition.row + 1,
149
+ });
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ ctx.definitions.push({
156
+ name,
157
+ kind,
158
+ line: node.startPosition.row + 1,
159
+ endLine: nodeEndLine(node),
160
+ children: members.length > 0 ? members : undefined,
161
+ });
162
+
163
+ // Inheritance
164
+ const inheritance = findChild(node, 'inheritance_specifier');
165
+ if (inheritance) {
166
+ for (let i = 0; i < inheritance.childCount; i++) {
167
+ const child = inheritance.child(i);
168
+ if (!child) continue;
169
+ if (child.type === 'user_defined_type' || child.type === 'identifier') {
170
+ ctx.classes.push({ name, extends: child.text, line: node.startPosition.row + 1 });
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ function handleStructDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
177
+ const nameNode = node.childForFieldName('name');
178
+ if (!nameNode) return;
179
+
180
+ const members: SubDeclaration[] = [];
181
+ for (let i = 0; i < node.childCount; i++) {
182
+ const child = node.child(i);
183
+ if (child && child.type === 'struct_member') {
184
+ const memberName = child.childForFieldName('name');
185
+ if (memberName) {
186
+ members.push({
187
+ name: memberName.text,
188
+ kind: 'property',
189
+ line: child.startPosition.row + 1,
190
+ });
191
+ }
192
+ }
193
+ }
194
+
195
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
196
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
197
+
198
+ ctx.definitions.push({
199
+ name: fullName,
200
+ kind: 'struct',
201
+ line: node.startPosition.row + 1,
202
+ endLine: nodeEndLine(node),
203
+ children: members.length > 0 ? members : undefined,
204
+ });
205
+ }
206
+
207
+ function handleEnumDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
208
+ const nameNode = node.childForFieldName('name');
209
+ if (!nameNode) return;
210
+
211
+ const members: SubDeclaration[] = [];
212
+ for (let i = 0; i < node.childCount; i++) {
213
+ const child = node.child(i);
214
+ if (child && child.type === 'enum_value') {
215
+ members.push({ name: child.text, kind: 'constant', line: child.startPosition.row + 1 });
216
+ }
217
+ }
218
+
219
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
220
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
221
+
222
+ ctx.definitions.push({
223
+ name: fullName,
224
+ kind: 'enum',
225
+ line: node.startPosition.row + 1,
226
+ endLine: nodeEndLine(node),
227
+ children: members.length > 0 ? members : undefined,
228
+ });
229
+ }
230
+
231
+ function handleFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
232
+ const nameNode = node.childForFieldName('name');
233
+ if (!nameNode) return;
234
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
235
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
236
+ const kind = parent ? 'method' : 'function';
237
+
238
+ const params = extractSolParams(node);
239
+ ctx.definitions.push({
240
+ name: fullName,
241
+ kind,
242
+ line: node.startPosition.row + 1,
243
+ endLine: nodeEndLine(node),
244
+ children: params.length > 0 ? params : undefined,
245
+ visibility: extractSolVisibility(node),
246
+ });
247
+ }
248
+
249
+ function handleModifierDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
250
+ const nameNode = node.childForFieldName('name');
251
+ if (!nameNode) return;
252
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
253
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
254
+
255
+ ctx.definitions.push({
256
+ name: fullName,
257
+ kind: 'function',
258
+ line: node.startPosition.row + 1,
259
+ endLine: nodeEndLine(node),
260
+ decorators: ['modifier'],
261
+ });
262
+ }
263
+
264
+ function handleEventDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
265
+ const nameNode = node.childForFieldName('name');
266
+ if (!nameNode) return;
267
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
268
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
269
+
270
+ ctx.definitions.push({
271
+ name: fullName,
272
+ kind: 'type',
273
+ line: node.startPosition.row + 1,
274
+ endLine: nodeEndLine(node),
275
+ decorators: ['event'],
276
+ });
277
+ }
278
+
279
+ function handleErrorDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
280
+ const nameNode = node.childForFieldName('name');
281
+ if (!nameNode) return;
282
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
283
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
284
+
285
+ ctx.definitions.push({
286
+ name: fullName,
287
+ kind: 'type',
288
+ line: node.startPosition.row + 1,
289
+ endLine: nodeEndLine(node),
290
+ decorators: ['error'],
291
+ });
292
+ }
293
+
294
+ function handleStateVarDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
295
+ const nameNode = node.childForFieldName('name');
296
+ if (!nameNode) return;
297
+ const parent = findParentNode(node, SOL_PARENT_TYPES);
298
+ const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
299
+
300
+ ctx.definitions.push({
301
+ name: fullName,
302
+ kind: 'variable',
303
+ line: node.startPosition.row + 1,
304
+ endLine: nodeEndLine(node),
305
+ visibility: extractSolVisibility(node),
306
+ });
307
+ }
308
+
309
+ function handleImportDirective(node: TreeSitterNode, ctx: ExtractorOutput): void {
310
+ // import "path"; or import { X } from "path"; or import "path" as Alias;
311
+ for (let i = 0; i < node.childCount; i++) {
312
+ const child = node.child(i);
313
+ if (!child) continue;
314
+ if (child.type === 'string' || child.type === 'string_literal') {
315
+ const source = stripQuotes(child.text);
316
+ const names: string[] = [];
317
+ // Look for imported symbols
318
+ for (let j = 0; j < node.childCount; j++) {
319
+ const sibling = node.child(j);
320
+ if (sibling && sibling.type === 'identifier') names.push(sibling.text);
321
+ if (sibling && sibling.type === 'import_declaration') {
322
+ const id = findChild(sibling, 'identifier');
323
+ if (id) names.push(id.text);
324
+ }
325
+ }
326
+ ctx.imports.push({
327
+ source,
328
+ names: names.length > 0 ? names : ['*'],
329
+ line: node.startPosition.row + 1,
330
+ });
331
+ return;
332
+ }
333
+ // source_import: handles `import * as X from "path"`
334
+ if (child.type === 'source_import' || child.type === 'import_clause') {
335
+ const strNode = findChild(child, 'string') || findChild(child, 'string_literal');
336
+ if (strNode) {
337
+ ctx.imports.push({
338
+ source: stripQuotes(strNode.text),
339
+ names: ['*'],
340
+ line: node.startPosition.row + 1,
341
+ });
342
+ return;
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ function handleCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
349
+ const funcNode = node.childForFieldName('function') || node.childForFieldName('callee');
350
+ if (!funcNode) return;
351
+
352
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
353
+ if (funcNode.type === 'member_expression' || funcNode.type === 'member_access') {
354
+ const prop = funcNode.childForFieldName('property') || funcNode.childForFieldName('member');
355
+ const obj = funcNode.childForFieldName('object') || funcNode.childForFieldName('expression');
356
+ if (prop) call.name = prop.text;
357
+ if (obj) call.receiver = obj.text;
358
+ } else {
359
+ call.name = funcNode.text;
360
+ }
361
+ if (call.name) ctx.calls.push(call);
362
+ }
363
+
364
+ // ── Helpers ────────────────────────────────────────────────────────────────
365
+
366
+ function extractSolParams(funcNode: TreeSitterNode): SubDeclaration[] {
367
+ const params: SubDeclaration[] = [];
368
+ const paramList =
369
+ funcNode.childForFieldName('parameters') || findChild(funcNode, 'parameter_list');
370
+ if (!paramList) return params;
371
+
372
+ for (let i = 0; i < paramList.childCount; i++) {
373
+ const param = paramList.child(i);
374
+ if (!param || param.type !== 'parameter') continue;
375
+ const nameNode = param.childForFieldName('name');
376
+ if (nameNode) {
377
+ params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
378
+ }
379
+ }
380
+ return params;
381
+ }
382
+
383
+ function extractSolVisibility(
384
+ node: TreeSitterNode,
385
+ ): 'public' | 'private' | 'protected' | undefined {
386
+ // Solidity visibility is embedded as child keywords or visibility nodes
387
+ const vis = extractModifierVisibility(node);
388
+ if (vis) return vis;
389
+ for (let i = 0; i < node.childCount; i++) {
390
+ const child = node.child(i);
391
+ if (!child) continue;
392
+ const t = child.text;
393
+ if (t === 'public' || t === 'external') return 'public';
394
+ if (t === 'private') return 'private';
395
+ if (t === 'internal') return 'protected';
396
+ }
397
+ return undefined;
398
+ }