@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.
- package/README.md +32 -16
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +158 -1
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +0 -1
- package/dist/ast-analysis/rules/javascript.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +2 -75
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/cli/commands/ast.js +2 -2
- package/dist/cli/commands/ast.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +128 -6
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +101 -1
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +17 -5
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +98 -50
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +32 -5
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +20 -7
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +129 -3
- package/dist/domain/parser.js.map +1 -1
- package/dist/extractors/clojure.d.ts +12 -0
- package/dist/extractors/clojure.d.ts.map +1 -0
- package/dist/extractors/clojure.js +245 -0
- package/dist/extractors/clojure.js.map +1 -0
- package/dist/extractors/cuda.d.ts +11 -0
- package/dist/extractors/cuda.d.ts.map +1 -0
- package/dist/extractors/cuda.js +302 -0
- package/dist/extractors/cuda.js.map +1 -0
- package/dist/extractors/dart.d.ts +6 -0
- package/dist/extractors/dart.d.ts.map +1 -0
- package/dist/extractors/dart.js +277 -0
- package/dist/extractors/dart.js.map +1 -0
- package/dist/extractors/elixir.d.ts +9 -0
- package/dist/extractors/elixir.d.ts.map +1 -0
- package/dist/extractors/elixir.js +223 -0
- package/dist/extractors/elixir.js.map +1 -0
- package/dist/extractors/erlang.d.ts +14 -0
- package/dist/extractors/erlang.d.ts.map +1 -0
- package/dist/extractors/erlang.js +239 -0
- package/dist/extractors/erlang.js.map +1 -0
- package/dist/extractors/fsharp.d.ts +13 -0
- package/dist/extractors/fsharp.d.ts.map +1 -0
- package/dist/extractors/fsharp.js +218 -0
- package/dist/extractors/fsharp.js.map +1 -0
- package/dist/extractors/gleam.d.ts +14 -0
- package/dist/extractors/gleam.d.ts.map +1 -0
- package/dist/extractors/gleam.js +229 -0
- package/dist/extractors/gleam.js.map +1 -0
- package/dist/extractors/groovy.d.ts +10 -0
- package/dist/extractors/groovy.d.ts.map +1 -0
- package/dist/extractors/groovy.js +304 -0
- package/dist/extractors/groovy.js.map +1 -0
- package/dist/extractors/haskell.d.ts +8 -0
- package/dist/extractors/haskell.d.ts.map +1 -0
- package/dist/extractors/haskell.js +217 -0
- package/dist/extractors/haskell.js.map +1 -0
- package/dist/extractors/index.d.ts +17 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +17 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/julia.d.ts +16 -0
- package/dist/extractors/julia.d.ts.map +1 -0
- package/dist/extractors/julia.js +287 -0
- package/dist/extractors/julia.js.map +1 -0
- package/dist/extractors/lua.d.ts +6 -0
- package/dist/extractors/lua.d.ts.map +1 -0
- package/dist/extractors/lua.js +162 -0
- package/dist/extractors/lua.js.map +1 -0
- package/dist/extractors/objc.d.ts +9 -0
- package/dist/extractors/objc.d.ts.map +1 -0
- package/dist/extractors/objc.js +406 -0
- package/dist/extractors/objc.js.map +1 -0
- package/dist/extractors/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +310 -0
- package/dist/extractors/ocaml.js.map +1 -0
- package/dist/extractors/r.d.ts +13 -0
- package/dist/extractors/r.d.ts.map +1 -0
- package/dist/extractors/r.js +251 -0
- package/dist/extractors/r.js.map +1 -0
- package/dist/extractors/solidity.d.ts +9 -0
- package/dist/extractors/solidity.d.ts.map +1 -0
- package/dist/extractors/solidity.js +374 -0
- package/dist/extractors/solidity.js.map +1 -0
- package/dist/extractors/verilog.d.ts +9 -0
- package/dist/extractors/verilog.d.ts.map +1 -0
- package/dist/extractors/verilog.js +286 -0
- package/dist/extractors/verilog.js.map +1 -0
- package/dist/extractors/zig.d.ts +9 -0
- package/dist/extractors/zig.d.ts.map +1 -0
- package/dist/extractors/zig.js +276 -0
- package/dist/extractors/zig.js.map +1 -0
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +1 -2
- package/dist/features/ast.js.map +1 -1
- package/dist/features/cfg.d.ts +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +6 -51
- package/dist/features/cfg.js.map +1 -1
- package/dist/graph/algorithms/bfs.d.ts +2 -0
- package/dist/graph/algorithms/bfs.d.ts.map +1 -1
- package/dist/graph/algorithms/bfs.js +27 -0
- package/dist/graph/algorithms/bfs.js.map +1 -1
- package/dist/graph/algorithms/centrality.d.ts +2 -0
- package/dist/graph/algorithms/centrality.d.ts.map +1 -1
- package/dist/graph/algorithms/centrality.js +28 -0
- package/dist/graph/algorithms/centrality.js.map +1 -1
- package/dist/graph/algorithms/louvain.d.ts +3 -4
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +29 -0
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/graph/algorithms/shortest-path.d.ts +2 -0
- package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
- package/dist/graph/algorithms/shortest-path.js +18 -1
- package/dist/graph/algorithms/shortest-path.js.map +1 -1
- package/dist/types.d.ts +122 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-clojure.wasm +0 -0
- package/grammars/tree-sitter-cuda.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/grammars/tree-sitter-groovy.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-julia.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-objc.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
- package/grammars/tree-sitter-r.wasm +0 -0
- package/grammars/tree-sitter-solidity.wasm +0 -0
- package/grammars/tree-sitter-verilog.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +24 -7
- package/src/ast-analysis/engine.ts +183 -1
- package/src/ast-analysis/rules/javascript.ts +0 -1
- package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
- package/src/cli/commands/ast.ts +2 -2
- package/src/domain/graph/builder/pipeline.ts +142 -6
- package/src/domain/graph/builder/stages/build-edges.ts +158 -1
- package/src/domain/graph/builder/stages/collect-files.ts +18 -7
- package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
- package/src/domain/graph/builder/stages/finalize.ts +39 -9
- package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
- package/src/domain/parser.ts +161 -1
- package/src/extractors/clojure.ts +273 -0
- package/src/extractors/cuda.ts +316 -0
- package/src/extractors/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/erlang.ts +252 -0
- package/src/extractors/fsharp.ts +253 -0
- package/src/extractors/gleam.ts +246 -0
- package/src/extractors/groovy.ts +332 -0
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/index.ts +17 -0
- package/src/extractors/julia.ts +318 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/objc.ts +431 -0
- package/src/extractors/ocaml.ts +337 -0
- package/src/extractors/r.ts +253 -0
- package/src/extractors/solidity.ts +398 -0
- package/src/extractors/verilog.ts +315 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/ast.ts +1 -2
- package/src/features/cfg.ts +6 -51
- package/src/graph/algorithms/bfs.ts +34 -0
- package/src/graph/algorithms/centrality.ts +30 -0
- package/src/graph/algorithms/louvain.ts +31 -4
- package/src/graph/algorithms/shortest-path.ts +20 -1
- package/src/types.ts +123 -2
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
|
|
2
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract symbols from Verilog/SystemVerilog files.
|
|
6
|
+
*
|
|
7
|
+
* The tree-sitter-verilog grammar covers modules, interfaces, packages,
|
|
8
|
+
* tasks, functions, classes, always blocks, and instantiations.
|
|
9
|
+
*/
|
|
10
|
+
export function extractVerilogSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
|
|
11
|
+
const ctx: ExtractorOutput = {
|
|
12
|
+
definitions: [],
|
|
13
|
+
calls: [],
|
|
14
|
+
imports: [],
|
|
15
|
+
classes: [],
|
|
16
|
+
exports: [],
|
|
17
|
+
typeMap: new Map(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
walkVerilogNode(tree.rootNode, ctx);
|
|
21
|
+
return ctx;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function walkVerilogNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
25
|
+
switch (node.type) {
|
|
26
|
+
case 'module_declaration':
|
|
27
|
+
handleModuleDecl(node, ctx);
|
|
28
|
+
break;
|
|
29
|
+
case 'interface_declaration':
|
|
30
|
+
handleInterfaceDecl(node, ctx);
|
|
31
|
+
break;
|
|
32
|
+
case 'package_declaration':
|
|
33
|
+
handlePackageDecl(node, ctx);
|
|
34
|
+
break;
|
|
35
|
+
case 'class_declaration':
|
|
36
|
+
handleClassDecl(node, ctx);
|
|
37
|
+
break;
|
|
38
|
+
case 'function_declaration':
|
|
39
|
+
handleFunctionDecl(node, ctx);
|
|
40
|
+
break;
|
|
41
|
+
case 'task_declaration':
|
|
42
|
+
handleTaskDecl(node, ctx);
|
|
43
|
+
break;
|
|
44
|
+
case 'module_instantiation':
|
|
45
|
+
handleModuleInstantiation(node, ctx);
|
|
46
|
+
break;
|
|
47
|
+
case 'package_import_declaration':
|
|
48
|
+
handlePackageImport(node, ctx);
|
|
49
|
+
break;
|
|
50
|
+
case 'include_compiler_directive':
|
|
51
|
+
handleIncludeDirective(node, ctx);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
56
|
+
const child = node.child(i);
|
|
57
|
+
if (child) walkVerilogNode(child, ctx);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Handlers ───────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
function handleModuleDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
64
|
+
const nameNode = findModuleName(node);
|
|
65
|
+
if (!nameNode) return;
|
|
66
|
+
|
|
67
|
+
const ports = extractPorts(node);
|
|
68
|
+
ctx.definitions.push({
|
|
69
|
+
name: nameNode.text,
|
|
70
|
+
kind: 'module',
|
|
71
|
+
line: node.startPosition.row + 1,
|
|
72
|
+
endLine: nodeEndLine(node),
|
|
73
|
+
children: ports.length > 0 ? ports : undefined,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
78
|
+
const nameNode = findDeclName(node);
|
|
79
|
+
if (!nameNode) return;
|
|
80
|
+
|
|
81
|
+
ctx.definitions.push({
|
|
82
|
+
name: nameNode.text,
|
|
83
|
+
kind: 'interface',
|
|
84
|
+
line: node.startPosition.row + 1,
|
|
85
|
+
endLine: nodeEndLine(node),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function handlePackageDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
90
|
+
const nameNode = findDeclName(node);
|
|
91
|
+
if (!nameNode) return;
|
|
92
|
+
|
|
93
|
+
ctx.definitions.push({
|
|
94
|
+
name: nameNode.text,
|
|
95
|
+
kind: 'module',
|
|
96
|
+
line: node.startPosition.row + 1,
|
|
97
|
+
endLine: nodeEndLine(node),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
102
|
+
const nameNode = node.childForFieldName('name');
|
|
103
|
+
if (!nameNode) return;
|
|
104
|
+
|
|
105
|
+
ctx.definitions.push({
|
|
106
|
+
name: nameNode.text,
|
|
107
|
+
kind: 'class',
|
|
108
|
+
line: node.startPosition.row + 1,
|
|
109
|
+
endLine: nodeEndLine(node),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Superclass via extends
|
|
113
|
+
const superclass = node.childForFieldName('superclass');
|
|
114
|
+
if (superclass) {
|
|
115
|
+
ctx.classes.push({
|
|
116
|
+
name: nameNode.text,
|
|
117
|
+
extends: superclass.text,
|
|
118
|
+
line: node.startPosition.row + 1,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function handleFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
124
|
+
const nameNode = findFunctionOrTaskName(node, 'function_identifier');
|
|
125
|
+
if (!nameNode) return;
|
|
126
|
+
|
|
127
|
+
const parentModule = findVerilogParent(node);
|
|
128
|
+
const fullName = parentModule ? `${parentModule}.${nameNode.text}` : nameNode.text;
|
|
129
|
+
|
|
130
|
+
ctx.definitions.push({
|
|
131
|
+
name: fullName,
|
|
132
|
+
kind: 'function',
|
|
133
|
+
line: node.startPosition.row + 1,
|
|
134
|
+
endLine: nodeEndLine(node),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleTaskDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
139
|
+
const nameNode = findFunctionOrTaskName(node, 'task_identifier');
|
|
140
|
+
if (!nameNode) return;
|
|
141
|
+
|
|
142
|
+
const parentModule = findVerilogParent(node);
|
|
143
|
+
const fullName = parentModule ? `${parentModule}.${nameNode.text}` : nameNode.text;
|
|
144
|
+
|
|
145
|
+
ctx.definitions.push({
|
|
146
|
+
name: fullName,
|
|
147
|
+
kind: 'function',
|
|
148
|
+
line: node.startPosition.row + 1,
|
|
149
|
+
endLine: nodeEndLine(node),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleModuleInstantiation(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
154
|
+
// Module instantiations are like function calls: `ModuleName instance_name(...);`
|
|
155
|
+
const moduleType = node.childForFieldName('type') || node.child(0);
|
|
156
|
+
if (!moduleType) return;
|
|
157
|
+
|
|
158
|
+
ctx.calls.push({
|
|
159
|
+
name: moduleType.text,
|
|
160
|
+
line: node.startPosition.row + 1,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handlePackageImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
165
|
+
// import pkg::item; or import pkg::*;
|
|
166
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
167
|
+
const child = node.child(i);
|
|
168
|
+
if (!child) continue;
|
|
169
|
+
if (child.type === 'package_import_item') {
|
|
170
|
+
const text = child.text;
|
|
171
|
+
const parts = text.split('::');
|
|
172
|
+
const pkg = parts[0] ?? text;
|
|
173
|
+
const item = parts[1] ?? '*';
|
|
174
|
+
ctx.imports.push({
|
|
175
|
+
source: pkg,
|
|
176
|
+
names: [item],
|
|
177
|
+
line: node.startPosition.row + 1,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function handleIncludeDirective(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
184
|
+
// `include "file.vh"
|
|
185
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
186
|
+
const child = node.child(i);
|
|
187
|
+
if (child && (child.type === 'string_literal' || child.type === 'quoted_string')) {
|
|
188
|
+
const source = child.text.replace(/^["']|["']$/g, '');
|
|
189
|
+
ctx.imports.push({
|
|
190
|
+
source,
|
|
191
|
+
names: [source.split('/').pop() ?? source],
|
|
192
|
+
line: node.startPosition.row + 1,
|
|
193
|
+
cInclude: true,
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
function findModuleName(node: TreeSitterNode): TreeSitterNode | null {
|
|
203
|
+
// Try field name first, then look for module_header > identifier
|
|
204
|
+
const nameNode = node.childForFieldName('name');
|
|
205
|
+
if (nameNode) return nameNode;
|
|
206
|
+
|
|
207
|
+
const header = findChild(node, 'module_header');
|
|
208
|
+
if (header) {
|
|
209
|
+
const id = findChild(header, 'simple_identifier') || findChild(header, 'identifier');
|
|
210
|
+
if (id) return id;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Direct child identifier after `module` keyword
|
|
214
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
215
|
+
const child = node.child(i);
|
|
216
|
+
if (child && (child.type === 'simple_identifier' || child.type === 'identifier')) return child;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function findDeclName(node: TreeSitterNode): TreeSitterNode | null {
|
|
222
|
+
const nameNode = node.childForFieldName('name');
|
|
223
|
+
if (nameNode) return nameNode;
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
226
|
+
const child = node.child(i);
|
|
227
|
+
if (child && (child.type === 'simple_identifier' || child.type === 'identifier')) return child;
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Find a function or task name by searching for the dedicated identifier node
|
|
234
|
+
* type (e.g. `function_identifier`, `task_identifier`) recursively through
|
|
235
|
+
* body declarations. Falls back to `findDeclName` for grammars that use
|
|
236
|
+
* plain identifiers.
|
|
237
|
+
*/
|
|
238
|
+
function findFunctionOrTaskName(
|
|
239
|
+
node: TreeSitterNode,
|
|
240
|
+
identifierType: string,
|
|
241
|
+
): TreeSitterNode | null {
|
|
242
|
+
// Try the standard approach first
|
|
243
|
+
const simple = findDeclName(node);
|
|
244
|
+
if (simple) return simple;
|
|
245
|
+
|
|
246
|
+
// Search children (including body declarations) for the dedicated identifier node
|
|
247
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
248
|
+
const child = node.child(i);
|
|
249
|
+
if (!child) continue;
|
|
250
|
+
if (child.type === identifierType) return child;
|
|
251
|
+
// Look one level deeper into body declarations
|
|
252
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
253
|
+
const grandchild = child.child(j);
|
|
254
|
+
if (grandchild && grandchild.type === identifierType) return grandchild;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function findVerilogParent(node: TreeSitterNode): string | null {
|
|
261
|
+
let current = node.parent;
|
|
262
|
+
while (current) {
|
|
263
|
+
if (
|
|
264
|
+
current.type === 'module_declaration' ||
|
|
265
|
+
current.type === 'interface_declaration' ||
|
|
266
|
+
current.type === 'package_declaration' ||
|
|
267
|
+
current.type === 'class_declaration'
|
|
268
|
+
) {
|
|
269
|
+
const name = findDeclName(current) || findModuleName(current);
|
|
270
|
+
return name ? name.text : null;
|
|
271
|
+
}
|
|
272
|
+
current = current.parent;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function extractPorts(moduleNode: TreeSitterNode): SubDeclaration[] {
|
|
278
|
+
const ports: SubDeclaration[] = [];
|
|
279
|
+
|
|
280
|
+
// Look for port declarations in the module header or body
|
|
281
|
+
const collectFromNode = (node: TreeSitterNode): void => {
|
|
282
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
283
|
+
const child = node.child(i);
|
|
284
|
+
if (!child) continue;
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
child.type === 'ansi_port_declaration' ||
|
|
288
|
+
child.type === 'port_declaration' ||
|
|
289
|
+
child.type === 'input_declaration' ||
|
|
290
|
+
child.type === 'output_declaration' ||
|
|
291
|
+
child.type === 'inout_declaration'
|
|
292
|
+
) {
|
|
293
|
+
const nameNode =
|
|
294
|
+
child.childForFieldName('name') ||
|
|
295
|
+
findChild(child, 'simple_identifier') ||
|
|
296
|
+
findChild(child, 'identifier');
|
|
297
|
+
if (nameNode) {
|
|
298
|
+
ports.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Recurse into port list containers
|
|
303
|
+
if (
|
|
304
|
+
child.type === 'list_of_port_declarations' ||
|
|
305
|
+
child.type === 'module_header' ||
|
|
306
|
+
child.type === 'port_declaration_list'
|
|
307
|
+
) {
|
|
308
|
+
collectFromNode(child);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
collectFromNode(moduleNode);
|
|
314
|
+
return ports;
|
|
315
|
+
}
|