@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.
- package/README.md +25 -14
- 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 +88 -4
- 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/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/index.d.ts +11 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +11 -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/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.js +74 -0
- package/dist/extractors/ocaml.js.map +1 -1
- 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/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +1 -2
- package/dist/features/ast.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-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-julia.wasm +0 -0
- package/grammars/tree-sitter-objc.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/package.json +18 -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 +108 -2
- package/src/extractors/clojure.ts +273 -0
- package/src/extractors/cuda.ts +316 -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/index.ts +11 -0
- package/src/extractors/julia.ts +318 -0
- package/src/extractors/objc.ts +431 -0
- package/src/extractors/ocaml.ts +78 -0
- package/src/extractors/r.ts +253 -0
- package/src/extractors/solidity.ts +398 -0
- package/src/extractors/verilog.ts +315 -0
- package/src/features/ast.ts +1 -2
- 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 +117 -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,252 @@
|
|
|
1
|
+
import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
|
|
2
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract symbols from Erlang files.
|
|
6
|
+
*
|
|
7
|
+
* tree-sitter-erlang (WhatsApp) grammar notes:
|
|
8
|
+
* - module_attribute: -module(name).
|
|
9
|
+
* - record_decl: -record(name, {fields}).
|
|
10
|
+
* - fun_decl: contains function_clause children
|
|
11
|
+
* - function_clause: atom expr_args clause_body
|
|
12
|
+
* - call: function calls, with remote child for module:func
|
|
13
|
+
* - expr_args: parenthesized argument lists
|
|
14
|
+
*/
|
|
15
|
+
export function extractErlangSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
|
|
16
|
+
const ctx: ExtractorOutput = {
|
|
17
|
+
definitions: [],
|
|
18
|
+
calls: [],
|
|
19
|
+
imports: [],
|
|
20
|
+
classes: [],
|
|
21
|
+
exports: [],
|
|
22
|
+
typeMap: new Map(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
walkErlangNode(tree.rootNode, ctx);
|
|
26
|
+
return ctx;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function walkErlangNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
30
|
+
switch (node.type) {
|
|
31
|
+
case 'module_attribute':
|
|
32
|
+
handleModuleAttr(node, ctx);
|
|
33
|
+
break;
|
|
34
|
+
case 'record_decl':
|
|
35
|
+
handleRecordDecl(node, ctx);
|
|
36
|
+
break;
|
|
37
|
+
case 'type_alias':
|
|
38
|
+
case 'opaque':
|
|
39
|
+
handleTypeAlias(node, ctx);
|
|
40
|
+
break;
|
|
41
|
+
case 'fun_decl':
|
|
42
|
+
handleFunDecl(node, ctx);
|
|
43
|
+
break;
|
|
44
|
+
case 'function_clause':
|
|
45
|
+
// Only handle if not inside fun_decl (fun_decl handles its own clauses)
|
|
46
|
+
if (node.parent?.type !== 'fun_decl') {
|
|
47
|
+
handleFunctionClause(node, ctx);
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
case 'pp_define':
|
|
51
|
+
handleDefine(node, ctx);
|
|
52
|
+
break;
|
|
53
|
+
case 'pp_include':
|
|
54
|
+
case 'pp_include_lib':
|
|
55
|
+
handleInclude(node, ctx);
|
|
56
|
+
break;
|
|
57
|
+
case 'import_attribute':
|
|
58
|
+
handleImportAttr(node, ctx);
|
|
59
|
+
break;
|
|
60
|
+
case 'call':
|
|
61
|
+
handleCall(node, ctx);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
66
|
+
const child = node.child(i);
|
|
67
|
+
if (child) walkErlangNode(child, ctx);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleModuleAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
72
|
+
// module_attribute: - module ( atom ) .
|
|
73
|
+
const nameNode = findChild(node, 'atom');
|
|
74
|
+
if (!nameNode) return;
|
|
75
|
+
|
|
76
|
+
ctx.definitions.push({
|
|
77
|
+
name: nameNode.text,
|
|
78
|
+
kind: 'module',
|
|
79
|
+
line: node.startPosition.row + 1,
|
|
80
|
+
endLine: nodeEndLine(node),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleRecordDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
85
|
+
// record_decl: - record ( atom , { record_field, ... } ) .
|
|
86
|
+
const nameNode = findChild(node, 'atom');
|
|
87
|
+
if (!nameNode) return;
|
|
88
|
+
|
|
89
|
+
const children: SubDeclaration[] = [];
|
|
90
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
91
|
+
const child = node.child(i);
|
|
92
|
+
if (!child) continue;
|
|
93
|
+
if (child.type === 'record_field' || child.type === 'typed_record_field') {
|
|
94
|
+
const fieldName = findChild(child, 'atom');
|
|
95
|
+
if (fieldName) {
|
|
96
|
+
children.push({
|
|
97
|
+
name: fieldName.text,
|
|
98
|
+
kind: 'property',
|
|
99
|
+
line: child.startPosition.row + 1,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ctx.definitions.push({
|
|
106
|
+
name: nameNode.text,
|
|
107
|
+
kind: 'record',
|
|
108
|
+
line: node.startPosition.row + 1,
|
|
109
|
+
endLine: nodeEndLine(node),
|
|
110
|
+
children: children.length > 0 ? children : undefined,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
115
|
+
const nameNode = findChild(node, 'atom');
|
|
116
|
+
if (!nameNode) return;
|
|
117
|
+
|
|
118
|
+
ctx.definitions.push({
|
|
119
|
+
name: nameNode.text,
|
|
120
|
+
kind: 'type',
|
|
121
|
+
line: node.startPosition.row + 1,
|
|
122
|
+
endLine: nodeEndLine(node),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handleFunDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
127
|
+
// fun_decl contains one or more function_clause children + dots
|
|
128
|
+
// Extract from the first function_clause
|
|
129
|
+
const clause = findChild(node, 'function_clause');
|
|
130
|
+
if (!clause) return;
|
|
131
|
+
|
|
132
|
+
handleFunctionClause(clause, ctx);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function handleFunctionClause(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
136
|
+
// function_clause: atom expr_args clause_body
|
|
137
|
+
const nameNode = findChild(node, 'atom');
|
|
138
|
+
if (!nameNode) return;
|
|
139
|
+
|
|
140
|
+
// Don't duplicate if we already have this function
|
|
141
|
+
if (ctx.definitions.some((d) => d.name === nameNode.text && d.kind === 'function')) return;
|
|
142
|
+
|
|
143
|
+
const params = extractErlangParams(node);
|
|
144
|
+
|
|
145
|
+
ctx.definitions.push({
|
|
146
|
+
name: nameNode.text,
|
|
147
|
+
kind: 'function',
|
|
148
|
+
line: node.startPosition.row + 1,
|
|
149
|
+
endLine: nodeEndLine(node.parent?.type === 'fun_decl' ? node.parent : node),
|
|
150
|
+
children: params.length > 0 ? params : undefined,
|
|
151
|
+
visibility: 'public',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function extractErlangParams(clauseNode: TreeSitterNode): SubDeclaration[] {
|
|
156
|
+
const params: SubDeclaration[] = [];
|
|
157
|
+
const argsNode = findChild(clauseNode, 'expr_args');
|
|
158
|
+
if (!argsNode) return params;
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < argsNode.childCount; i++) {
|
|
161
|
+
const child = argsNode.child(i);
|
|
162
|
+
if (!child) continue;
|
|
163
|
+
if (child.type === 'var') {
|
|
164
|
+
params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
|
|
165
|
+
}
|
|
166
|
+
if (child.type === 'atom') {
|
|
167
|
+
params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return params;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function handleDefine(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
174
|
+
// pp_define: -define(NAME, value).
|
|
175
|
+
const nameNode =
|
|
176
|
+
findChild(node, 'var') || findChild(node, 'atom') || findChild(node, 'macro_lhs');
|
|
177
|
+
if (!nameNode) return;
|
|
178
|
+
|
|
179
|
+
const name =
|
|
180
|
+
nameNode.type === 'macro_lhs'
|
|
181
|
+
? (findChild(nameNode, 'var')?.text ?? nameNode.text)
|
|
182
|
+
: nameNode.text;
|
|
183
|
+
|
|
184
|
+
ctx.definitions.push({
|
|
185
|
+
name,
|
|
186
|
+
kind: 'variable',
|
|
187
|
+
line: node.startPosition.row + 1,
|
|
188
|
+
endLine: nodeEndLine(node),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
193
|
+
const strNode = findChild(node, 'string');
|
|
194
|
+
if (!strNode) return;
|
|
195
|
+
|
|
196
|
+
const source = strNode.text.replace(/^"|"$/g, '');
|
|
197
|
+
ctx.imports.push({
|
|
198
|
+
source,
|
|
199
|
+
names: ['include'],
|
|
200
|
+
line: node.startPosition.row + 1,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function handleImportAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
205
|
+
const moduleNode = findChild(node, 'atom');
|
|
206
|
+
if (!moduleNode) return;
|
|
207
|
+
|
|
208
|
+
const names: string[] = [];
|
|
209
|
+
// Find exported function names
|
|
210
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
211
|
+
const child = node.child(i);
|
|
212
|
+
if (!child) continue;
|
|
213
|
+
if (child.type === 'fa') {
|
|
214
|
+
const fnName = findChild(child, 'atom');
|
|
215
|
+
if (fnName) names.push(fnName.text);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
ctx.imports.push({
|
|
220
|
+
source: moduleNode.text,
|
|
221
|
+
names: names.length > 0 ? names : [moduleNode.text],
|
|
222
|
+
line: node.startPosition.row + 1,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
227
|
+
// call: first child is function ref (atom or remote), then expr_args
|
|
228
|
+
const funcNode = node.child(0);
|
|
229
|
+
if (!funcNode) return;
|
|
230
|
+
|
|
231
|
+
if (funcNode.type === 'atom' || funcNode.type === 'identifier') {
|
|
232
|
+
ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
|
|
233
|
+
} else if (funcNode.type === 'remote') {
|
|
234
|
+
// module:function — remote has atom : atom children
|
|
235
|
+
const atoms: string[] = [];
|
|
236
|
+
for (let i = 0; i < funcNode.childCount; i++) {
|
|
237
|
+
const child = funcNode.child(i);
|
|
238
|
+
if (child && (child.type === 'atom' || child.type === 'var')) {
|
|
239
|
+
atoms.push(child.text);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (atoms.length >= 2) {
|
|
243
|
+
ctx.calls.push({
|
|
244
|
+
name: atoms[atoms.length - 1]!,
|
|
245
|
+
receiver: atoms.slice(0, -1).join(':'),
|
|
246
|
+
line: node.startPosition.row + 1,
|
|
247
|
+
});
|
|
248
|
+
} else if (atoms.length === 1) {
|
|
249
|
+
ctx.calls.push({ name: atoms[0]!, line: node.startPosition.row + 1 });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|