@optave/codegraph 3.6.0 → 3.7.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 +20 -15
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +44 -2
- package/dist/domain/parser.js.map +1 -1
- 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/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 +6 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +6 -0
- package/dist/extractors/index.js.map +1 -1
- 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/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +236 -0
- package/dist/extractors/ocaml.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/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/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +13 -7
- package/src/domain/parser.ts +54 -0
- package/src/extractors/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/index.ts +6 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/ocaml.ts +259 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/cfg.ts +6 -51
- package/src/types.ts +7 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
|
|
2
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract symbols from Dart files.
|
|
6
|
+
*/
|
|
7
|
+
export function extractDartSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
|
|
8
|
+
const ctx: ExtractorOutput = {
|
|
9
|
+
definitions: [],
|
|
10
|
+
calls: [],
|
|
11
|
+
imports: [],
|
|
12
|
+
classes: [],
|
|
13
|
+
exports: [],
|
|
14
|
+
typeMap: new Map(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
walkDartNode(tree.rootNode, ctx);
|
|
18
|
+
return ctx;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function walkDartNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
22
|
+
switch (node.type) {
|
|
23
|
+
case 'class_definition':
|
|
24
|
+
handleDartClass(node, ctx);
|
|
25
|
+
break;
|
|
26
|
+
case 'enum_declaration':
|
|
27
|
+
handleDartEnum(node, ctx);
|
|
28
|
+
break;
|
|
29
|
+
case 'mixin_declaration':
|
|
30
|
+
handleDartMixin(node, ctx);
|
|
31
|
+
break;
|
|
32
|
+
case 'extension_declaration':
|
|
33
|
+
handleDartExtension(node, ctx);
|
|
34
|
+
break;
|
|
35
|
+
case 'function_signature':
|
|
36
|
+
handleDartFunction(node, ctx);
|
|
37
|
+
break;
|
|
38
|
+
case 'method_signature':
|
|
39
|
+
handleDartMethodSig(node, ctx);
|
|
40
|
+
break;
|
|
41
|
+
case 'library_import':
|
|
42
|
+
handleDartImport(node, ctx);
|
|
43
|
+
break;
|
|
44
|
+
case 'constructor_invocation':
|
|
45
|
+
case 'new_expression':
|
|
46
|
+
handleDartConstructorCall(node, ctx);
|
|
47
|
+
break;
|
|
48
|
+
case 'type_alias':
|
|
49
|
+
handleDartTypeAlias(node, ctx);
|
|
50
|
+
break;
|
|
51
|
+
case 'selector':
|
|
52
|
+
handleDartSelector(node, ctx);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
57
|
+
const child = node.child(i);
|
|
58
|
+
if (child) walkDartNode(child, ctx);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function handleDartClass(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
63
|
+
const nameNode = node.childForFieldName('name');
|
|
64
|
+
if (!nameNode) return;
|
|
65
|
+
const name = nameNode.text;
|
|
66
|
+
const children: SubDeclaration[] = [];
|
|
67
|
+
|
|
68
|
+
const body = node.childForFieldName('body') || findChild(node, 'class_body');
|
|
69
|
+
if (body) {
|
|
70
|
+
extractDartClassMembers(body, name, ctx, children);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ctx.definitions.push({
|
|
74
|
+
name,
|
|
75
|
+
kind: 'class',
|
|
76
|
+
line: node.startPosition.row + 1,
|
|
77
|
+
endLine: nodeEndLine(node),
|
|
78
|
+
children: children.length > 0 ? children : undefined,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
extractDartInheritance(node, name, ctx);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractDartClassMembers(
|
|
85
|
+
body: TreeSitterNode,
|
|
86
|
+
className: string,
|
|
87
|
+
ctx: ExtractorOutput,
|
|
88
|
+
children: SubDeclaration[],
|
|
89
|
+
): void {
|
|
90
|
+
for (let i = 0; i < body.childCount; i++) {
|
|
91
|
+
const member = body.child(i);
|
|
92
|
+
if (!member) continue;
|
|
93
|
+
|
|
94
|
+
if (member.type === 'method_signature' || member.type === 'function_signature') {
|
|
95
|
+
const fnName = extractDartFunctionName(member);
|
|
96
|
+
if (fnName) {
|
|
97
|
+
ctx.definitions.push({
|
|
98
|
+
name: `${className}.${fnName}`,
|
|
99
|
+
kind: 'method',
|
|
100
|
+
line: member.startPosition.row + 1,
|
|
101
|
+
endLine: nodeEndLine(member),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
} else if (member.type === 'declaration') {
|
|
105
|
+
// Field declarations
|
|
106
|
+
for (let j = 0; j < member.childCount; j++) {
|
|
107
|
+
const decl = member.child(j);
|
|
108
|
+
if (decl?.type === 'identifier') {
|
|
109
|
+
children.push({
|
|
110
|
+
name: decl.text,
|
|
111
|
+
kind: 'property',
|
|
112
|
+
line: member.startPosition.row + 1,
|
|
113
|
+
});
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractDartFunctionName(node: TreeSitterNode): string | null {
|
|
122
|
+
const nameNode = node.childForFieldName('name');
|
|
123
|
+
if (nameNode) return nameNode.text;
|
|
124
|
+
|
|
125
|
+
// Walk children for function_signature inside method_signature
|
|
126
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
127
|
+
const child = node.child(i);
|
|
128
|
+
if (!child) continue;
|
|
129
|
+
if (
|
|
130
|
+
child.type === 'function_signature' ||
|
|
131
|
+
child.type === 'getter_signature' ||
|
|
132
|
+
child.type === 'setter_signature' ||
|
|
133
|
+
child.type === 'constructor_signature'
|
|
134
|
+
) {
|
|
135
|
+
const name = child.childForFieldName('name');
|
|
136
|
+
if (name) return name.text;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleDartEnum(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
143
|
+
const nameNode = node.childForFieldName('name');
|
|
144
|
+
if (!nameNode) return;
|
|
145
|
+
|
|
146
|
+
ctx.definitions.push({
|
|
147
|
+
name: nameNode.text,
|
|
148
|
+
kind: 'enum',
|
|
149
|
+
line: node.startPosition.row + 1,
|
|
150
|
+
endLine: nodeEndLine(node),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleDartMixin(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
155
|
+
const nameNode = findChild(node, 'identifier');
|
|
156
|
+
if (!nameNode) return;
|
|
157
|
+
|
|
158
|
+
ctx.definitions.push({
|
|
159
|
+
name: nameNode.text,
|
|
160
|
+
kind: 'class',
|
|
161
|
+
line: node.startPosition.row + 1,
|
|
162
|
+
endLine: nodeEndLine(node),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function handleDartExtension(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
167
|
+
const nameNode = node.childForFieldName('name');
|
|
168
|
+
if (!nameNode) return;
|
|
169
|
+
|
|
170
|
+
ctx.definitions.push({
|
|
171
|
+
name: nameNode.text,
|
|
172
|
+
kind: 'class',
|
|
173
|
+
line: node.startPosition.row + 1,
|
|
174
|
+
endLine: nodeEndLine(node),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleDartFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
179
|
+
// Skip methods already emitted by class handler
|
|
180
|
+
if (isInsideDartClass(node)) return;
|
|
181
|
+
|
|
182
|
+
const nameNode = node.childForFieldName('name');
|
|
183
|
+
if (!nameNode) return;
|
|
184
|
+
|
|
185
|
+
ctx.definitions.push({
|
|
186
|
+
name: nameNode.text,
|
|
187
|
+
kind: 'function',
|
|
188
|
+
line: node.startPosition.row + 1,
|
|
189
|
+
endLine: nodeEndLine(node),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function handleDartMethodSig(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
194
|
+
if (isInsideDartClass(node)) return;
|
|
195
|
+
const fnName = extractDartFunctionName(node);
|
|
196
|
+
if (!fnName) return;
|
|
197
|
+
|
|
198
|
+
ctx.definitions.push({
|
|
199
|
+
name: fnName,
|
|
200
|
+
kind: 'function',
|
|
201
|
+
line: node.startPosition.row + 1,
|
|
202
|
+
endLine: nodeEndLine(node),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isInsideDartClass(node: TreeSitterNode): boolean {
|
|
207
|
+
let current = node.parent;
|
|
208
|
+
while (current) {
|
|
209
|
+
if (
|
|
210
|
+
current.type === 'class_body' ||
|
|
211
|
+
current.type === 'class_definition' ||
|
|
212
|
+
current.type === 'enum_body' ||
|
|
213
|
+
current.type === 'mixin_declaration'
|
|
214
|
+
) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
current = current.parent;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function handleDartImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
223
|
+
const spec = findChild(node, 'import_specification');
|
|
224
|
+
if (!spec) return;
|
|
225
|
+
|
|
226
|
+
const uri = findChild(spec, 'configurable_uri') || findChild(spec, 'uri');
|
|
227
|
+
if (!uri) return;
|
|
228
|
+
|
|
229
|
+
const source = uri.text.replace(/^['"]|['"]$/g, '');
|
|
230
|
+
const names: string[] = [];
|
|
231
|
+
|
|
232
|
+
// Check for `as` alias
|
|
233
|
+
const alias = findChild(spec, 'identifier');
|
|
234
|
+
if (alias) names.push(alias.text);
|
|
235
|
+
|
|
236
|
+
ctx.imports.push({
|
|
237
|
+
source,
|
|
238
|
+
names: names.length > 0 ? names : [source.split('/').pop() || source],
|
|
239
|
+
line: node.startPosition.row + 1,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function handleDartConstructorCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
244
|
+
const nameNode = findChild(node, 'type_identifier') || findChild(node, 'identifier');
|
|
245
|
+
if (!nameNode) return;
|
|
246
|
+
|
|
247
|
+
ctx.calls.push({
|
|
248
|
+
name: nameNode.text,
|
|
249
|
+
line: node.startPosition.row + 1,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function handleDartSelector(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
254
|
+
// selector with argument_part represents a function call
|
|
255
|
+
const argPart = findChild(node, 'argument_part');
|
|
256
|
+
if (!argPart) return;
|
|
257
|
+
|
|
258
|
+
// Look for the identifier this selector belongs to
|
|
259
|
+
const unconditional = findChild(node, 'unconditional_assignable_selector');
|
|
260
|
+
if (unconditional) {
|
|
261
|
+
const id = findChild(unconditional, 'identifier');
|
|
262
|
+
if (id) {
|
|
263
|
+
ctx.calls.push({ name: id.text, line: node.startPosition.row + 1 });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function handleDartTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
269
|
+
const nameNode = findChild(node, 'type_identifier') || findChild(node, 'identifier');
|
|
270
|
+
if (!nameNode) return;
|
|
271
|
+
|
|
272
|
+
ctx.definitions.push({
|
|
273
|
+
name: nameNode.text,
|
|
274
|
+
kind: 'type',
|
|
275
|
+
line: node.startPosition.row + 1,
|
|
276
|
+
endLine: nodeEndLine(node),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function extractDartInheritance(node: TreeSitterNode, name: string, ctx: ExtractorOutput): void {
|
|
281
|
+
const superclass = node.childForFieldName('superclass');
|
|
282
|
+
if (superclass) {
|
|
283
|
+
const typeName =
|
|
284
|
+
findChild(superclass, 'type_identifier') || findChild(superclass, 'identifier');
|
|
285
|
+
if (typeName) {
|
|
286
|
+
ctx.classes.push({ name, extends: typeName.text, line: node.startPosition.row + 1 });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const interfaces = node.childForFieldName('interfaces');
|
|
291
|
+
if (interfaces) {
|
|
292
|
+
for (let i = 0; i < interfaces.childCount; i++) {
|
|
293
|
+
const iface = interfaces.child(i);
|
|
294
|
+
if (!iface) continue;
|
|
295
|
+
const typeName =
|
|
296
|
+
iface.type === 'type_identifier'
|
|
297
|
+
? iface
|
|
298
|
+
: findChild(iface, 'type_identifier') || findChild(iface, 'identifier');
|
|
299
|
+
if (typeName) {
|
|
300
|
+
ctx.classes.push({ name, implements: typeName.text, line: node.startPosition.row + 1 });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Call,
|
|
3
|
+
ExtractorOutput,
|
|
4
|
+
SubDeclaration,
|
|
5
|
+
TreeSitterNode,
|
|
6
|
+
TreeSitterTree,
|
|
7
|
+
} from '../types.js';
|
|
8
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract symbols from Elixir files.
|
|
12
|
+
*
|
|
13
|
+
* Elixir's tree-sitter grammar represents most constructs as generic `call` nodes.
|
|
14
|
+
* We distinguish modules, functions, imports etc. by the call target's identifier text.
|
|
15
|
+
*/
|
|
16
|
+
export function extractElixirSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
|
|
17
|
+
const ctx: ExtractorOutput = {
|
|
18
|
+
definitions: [],
|
|
19
|
+
calls: [],
|
|
20
|
+
imports: [],
|
|
21
|
+
classes: [],
|
|
22
|
+
exports: [],
|
|
23
|
+
typeMap: new Map(),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
walkElixirNode(tree.rootNode, ctx, null);
|
|
27
|
+
return ctx;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function walkElixirNode(
|
|
31
|
+
node: TreeSitterNode,
|
|
32
|
+
ctx: ExtractorOutput,
|
|
33
|
+
currentModule: string | null,
|
|
34
|
+
): void {
|
|
35
|
+
let nextModule = currentModule;
|
|
36
|
+
|
|
37
|
+
if (node.type === 'call') {
|
|
38
|
+
const target = node.childForFieldName('target');
|
|
39
|
+
if (target?.type === 'identifier' && target.text === 'defmodule') {
|
|
40
|
+
const args = findChild(node, 'arguments');
|
|
41
|
+
const aliasNode = args && findChild(args, 'alias');
|
|
42
|
+
if (aliasNode) nextModule = aliasNode.text;
|
|
43
|
+
}
|
|
44
|
+
handleElixirCall(node, ctx, nextModule);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
48
|
+
const child = node.child(i);
|
|
49
|
+
if (child) walkElixirNode(child, ctx, nextModule);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleElixirCall(
|
|
54
|
+
node: TreeSitterNode,
|
|
55
|
+
ctx: ExtractorOutput,
|
|
56
|
+
currentModule: string | null,
|
|
57
|
+
): void {
|
|
58
|
+
const target = node.childForFieldName('target');
|
|
59
|
+
if (!target) return;
|
|
60
|
+
|
|
61
|
+
if (target.type === 'identifier') {
|
|
62
|
+
const keyword = target.text;
|
|
63
|
+
switch (keyword) {
|
|
64
|
+
case 'defmodule':
|
|
65
|
+
handleDefmodule(node, ctx);
|
|
66
|
+
return;
|
|
67
|
+
case 'def':
|
|
68
|
+
case 'defp':
|
|
69
|
+
handleDefFunction(node, ctx, currentModule, keyword === 'defp' ? 'private' : 'public');
|
|
70
|
+
return;
|
|
71
|
+
case 'defprotocol':
|
|
72
|
+
handleDefprotocol(node, ctx);
|
|
73
|
+
return;
|
|
74
|
+
case 'defimpl':
|
|
75
|
+
handleDefimpl(node, ctx);
|
|
76
|
+
return;
|
|
77
|
+
case 'import':
|
|
78
|
+
case 'use':
|
|
79
|
+
case 'require':
|
|
80
|
+
case 'alias':
|
|
81
|
+
handleElixirImport(node, ctx, keyword);
|
|
82
|
+
return;
|
|
83
|
+
default:
|
|
84
|
+
// Regular function call
|
|
85
|
+
ctx.calls.push({ name: keyword, line: node.startPosition.row + 1 });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (target.type === 'dot') {
|
|
91
|
+
handleDotCall(node, target, ctx);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleDefmodule(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
96
|
+
const args = findChild(node, 'arguments');
|
|
97
|
+
if (!args) return;
|
|
98
|
+
const aliasNode = findChild(args, 'alias');
|
|
99
|
+
if (!aliasNode) return;
|
|
100
|
+
const name = aliasNode.text;
|
|
101
|
+
|
|
102
|
+
const children: SubDeclaration[] = [];
|
|
103
|
+
const doBlock = findChild(node, 'do_block');
|
|
104
|
+
if (doBlock) {
|
|
105
|
+
collectModuleMembers(doBlock, ctx, name, children);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx.definitions.push({
|
|
109
|
+
name,
|
|
110
|
+
kind: 'module',
|
|
111
|
+
line: node.startPosition.row + 1,
|
|
112
|
+
endLine: nodeEndLine(node),
|
|
113
|
+
children: children.length > 0 ? children : undefined,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function collectModuleMembers(
|
|
118
|
+
doBlock: TreeSitterNode,
|
|
119
|
+
_ctx: ExtractorOutput,
|
|
120
|
+
_moduleName: string,
|
|
121
|
+
children: SubDeclaration[],
|
|
122
|
+
): void {
|
|
123
|
+
for (let i = 0; i < doBlock.childCount; i++) {
|
|
124
|
+
const child = doBlock.child(i);
|
|
125
|
+
if (!child || child.type !== 'call') continue;
|
|
126
|
+
const target = child.childForFieldName('target');
|
|
127
|
+
if (!target || target.type !== 'identifier') continue;
|
|
128
|
+
|
|
129
|
+
if (target.text === 'def' || target.text === 'defp') {
|
|
130
|
+
const fnName = extractFunctionName(child);
|
|
131
|
+
if (fnName) {
|
|
132
|
+
children.push({
|
|
133
|
+
name: fnName,
|
|
134
|
+
kind: 'property',
|
|
135
|
+
line: child.startPosition.row + 1,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleDefFunction(
|
|
143
|
+
node: TreeSitterNode,
|
|
144
|
+
ctx: ExtractorOutput,
|
|
145
|
+
currentModule: string | null,
|
|
146
|
+
visibility: 'public' | 'private',
|
|
147
|
+
): void {
|
|
148
|
+
const fnName = extractFunctionName(node);
|
|
149
|
+
if (!fnName) return;
|
|
150
|
+
|
|
151
|
+
const fullName = currentModule ? `${currentModule}.${fnName}` : fnName;
|
|
152
|
+
const params = extractElixirParams(node);
|
|
153
|
+
|
|
154
|
+
ctx.definitions.push({
|
|
155
|
+
name: fullName,
|
|
156
|
+
kind: 'function',
|
|
157
|
+
line: node.startPosition.row + 1,
|
|
158
|
+
endLine: nodeEndLine(node),
|
|
159
|
+
visibility,
|
|
160
|
+
children: params.length > 0 ? params : undefined,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function extractFunctionName(defCallNode: TreeSitterNode): string | null {
|
|
165
|
+
const args = findChild(defCallNode, 'arguments');
|
|
166
|
+
if (!args) return null;
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < args.childCount; i++) {
|
|
169
|
+
const child = args.child(i);
|
|
170
|
+
if (!child) continue;
|
|
171
|
+
if (child.type === 'call') {
|
|
172
|
+
const target = child.childForFieldName('target');
|
|
173
|
+
if (target?.type === 'identifier') return target.text;
|
|
174
|
+
}
|
|
175
|
+
if (child.type === 'identifier') return child.text;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function extractElixirParams(defCallNode: TreeSitterNode): SubDeclaration[] {
|
|
181
|
+
const params: SubDeclaration[] = [];
|
|
182
|
+
const args = findChild(defCallNode, 'arguments');
|
|
183
|
+
if (!args) return params;
|
|
184
|
+
|
|
185
|
+
for (let i = 0; i < args.childCount; i++) {
|
|
186
|
+
const child = args.child(i);
|
|
187
|
+
if (!child || child.type !== 'call') continue;
|
|
188
|
+
const innerArgs = findChild(child, 'arguments');
|
|
189
|
+
if (!innerArgs) continue;
|
|
190
|
+
for (let j = 0; j < innerArgs.childCount; j++) {
|
|
191
|
+
const param = innerArgs.child(j);
|
|
192
|
+
if (!param) continue;
|
|
193
|
+
if (param.type === 'identifier') {
|
|
194
|
+
params.push({ name: param.text, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return params;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function handleDefprotocol(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
202
|
+
const args = findChild(node, 'arguments');
|
|
203
|
+
if (!args) return;
|
|
204
|
+
const aliasNode = findChild(args, 'alias');
|
|
205
|
+
if (!aliasNode) return;
|
|
206
|
+
|
|
207
|
+
ctx.definitions.push({
|
|
208
|
+
name: aliasNode.text,
|
|
209
|
+
kind: 'interface',
|
|
210
|
+
line: node.startPosition.row + 1,
|
|
211
|
+
endLine: nodeEndLine(node),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function handleDefimpl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
216
|
+
const args = findChild(node, 'arguments');
|
|
217
|
+
if (!args) return;
|
|
218
|
+
const aliasNode = findChild(args, 'alias');
|
|
219
|
+
if (!aliasNode) return;
|
|
220
|
+
|
|
221
|
+
ctx.definitions.push({
|
|
222
|
+
name: aliasNode.text,
|
|
223
|
+
kind: 'class',
|
|
224
|
+
line: node.startPosition.row + 1,
|
|
225
|
+
endLine: nodeEndLine(node),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function handleElixirImport(node: TreeSitterNode, ctx: ExtractorOutput, keyword: string): void {
|
|
230
|
+
const args = findChild(node, 'arguments');
|
|
231
|
+
if (!args) return;
|
|
232
|
+
const aliasNode = findChild(args, 'alias');
|
|
233
|
+
if (!aliasNode) return;
|
|
234
|
+
|
|
235
|
+
ctx.imports.push({
|
|
236
|
+
source: aliasNode.text,
|
|
237
|
+
names: [keyword],
|
|
238
|
+
line: node.startPosition.row + 1,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function handleDotCall(node: TreeSitterNode, dotNode: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
243
|
+
const call: Call = { name: '', line: node.startPosition.row + 1 };
|
|
244
|
+
const right = findChild(dotNode, 'identifier');
|
|
245
|
+
const left = findChild(dotNode, 'alias');
|
|
246
|
+
|
|
247
|
+
if (right) call.name = right.text;
|
|
248
|
+
if (left) call.receiver = left.text;
|
|
249
|
+
|
|
250
|
+
if (call.name) ctx.calls.push(call);
|
|
251
|
+
}
|