@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,431 @@
|
|
|
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 Objective-C files.
|
|
12
|
+
*
|
|
13
|
+
* The tree-sitter-objc grammar extends C with @interface, @implementation,
|
|
14
|
+
* @protocol, method declarations, #import, and message expressions.
|
|
15
|
+
*/
|
|
16
|
+
export function extractObjCSymbols(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
|
+
walkObjCNode(tree.rootNode, ctx);
|
|
27
|
+
return ctx;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function walkObjCNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
31
|
+
switch (node.type) {
|
|
32
|
+
case 'class_interface':
|
|
33
|
+
handleClassInterface(node, ctx);
|
|
34
|
+
break;
|
|
35
|
+
case 'class_implementation':
|
|
36
|
+
handleClassImplementation(node, ctx);
|
|
37
|
+
break;
|
|
38
|
+
case 'protocol_declaration':
|
|
39
|
+
handleProtocolDecl(node, ctx);
|
|
40
|
+
break;
|
|
41
|
+
case 'category_interface':
|
|
42
|
+
handleCategoryInterface(node, ctx);
|
|
43
|
+
break;
|
|
44
|
+
case 'category_implementation':
|
|
45
|
+
handleCategoryImplementation(node, ctx);
|
|
46
|
+
break;
|
|
47
|
+
case 'method_declaration':
|
|
48
|
+
case 'method_definition':
|
|
49
|
+
handleMethodDecl(node, ctx);
|
|
50
|
+
break;
|
|
51
|
+
case 'function_definition':
|
|
52
|
+
handleFunctionDef(node, ctx);
|
|
53
|
+
break;
|
|
54
|
+
case 'preproc_include':
|
|
55
|
+
case 'preproc_import':
|
|
56
|
+
handleImport(node, ctx);
|
|
57
|
+
break;
|
|
58
|
+
case 'import_declaration':
|
|
59
|
+
handleAtImport(node, ctx);
|
|
60
|
+
break;
|
|
61
|
+
case 'struct_specifier':
|
|
62
|
+
handleStructSpecifier(node, ctx);
|
|
63
|
+
break;
|
|
64
|
+
case 'enum_specifier':
|
|
65
|
+
handleEnumSpecifier(node, ctx);
|
|
66
|
+
break;
|
|
67
|
+
case 'type_definition':
|
|
68
|
+
handleTypedef(node, ctx);
|
|
69
|
+
break;
|
|
70
|
+
case 'call_expression':
|
|
71
|
+
handleCCallExpr(node, ctx);
|
|
72
|
+
break;
|
|
73
|
+
case 'message_expression':
|
|
74
|
+
handleMessageExpr(node, ctx);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
79
|
+
const child = node.child(i);
|
|
80
|
+
if (child) walkObjCNode(child, ctx);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── ObjC class/protocol handlers ──────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
function handleClassInterface(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
87
|
+
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
88
|
+
if (!nameNode) return;
|
|
89
|
+
const name = nameNode.text;
|
|
90
|
+
|
|
91
|
+
const members = collectClassMembers(node);
|
|
92
|
+
ctx.definitions.push({
|
|
93
|
+
name,
|
|
94
|
+
kind: 'class',
|
|
95
|
+
line: node.startPosition.row + 1,
|
|
96
|
+
endLine: nodeEndLine(node),
|
|
97
|
+
children: members.length > 0 ? members : undefined,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Superclass
|
|
101
|
+
const superclass = node.childForFieldName('superclass');
|
|
102
|
+
if (superclass) {
|
|
103
|
+
ctx.classes.push({ name, extends: superclass.text, line: node.startPosition.row + 1 });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Protocols
|
|
107
|
+
const protocols = findChild(node, 'protocol_qualifiers');
|
|
108
|
+
if (protocols) {
|
|
109
|
+
for (let i = 0; i < protocols.childCount; i++) {
|
|
110
|
+
const proto = protocols.child(i);
|
|
111
|
+
if (proto && proto.type === 'identifier') {
|
|
112
|
+
ctx.classes.push({ name, implements: proto.text, line: node.startPosition.row + 1 });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function handleClassImplementation(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
119
|
+
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
120
|
+
if (!nameNode) return;
|
|
121
|
+
|
|
122
|
+
ctx.definitions.push({
|
|
123
|
+
name: nameNode.text,
|
|
124
|
+
kind: 'class',
|
|
125
|
+
line: node.startPosition.row + 1,
|
|
126
|
+
endLine: nodeEndLine(node),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function handleProtocolDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
131
|
+
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
132
|
+
if (!nameNode) return;
|
|
133
|
+
|
|
134
|
+
ctx.definitions.push({
|
|
135
|
+
name: nameNode.text,
|
|
136
|
+
kind: 'interface',
|
|
137
|
+
line: node.startPosition.row + 1,
|
|
138
|
+
endLine: nodeEndLine(node),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleCategoryInterface(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
143
|
+
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
144
|
+
if (!nameNode) return;
|
|
145
|
+
const category = node.childForFieldName('category');
|
|
146
|
+
const catName = category ? `${nameNode.text}(${category.text})` : nameNode.text;
|
|
147
|
+
|
|
148
|
+
ctx.definitions.push({
|
|
149
|
+
name: catName,
|
|
150
|
+
kind: 'class',
|
|
151
|
+
line: node.startPosition.row + 1,
|
|
152
|
+
endLine: nodeEndLine(node),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleCategoryImplementation(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
157
|
+
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
158
|
+
if (!nameNode) return;
|
|
159
|
+
const category = node.childForFieldName('category');
|
|
160
|
+
const catName = category ? `${nameNode.text}(${category.text})` : nameNode.text;
|
|
161
|
+
|
|
162
|
+
ctx.definitions.push({
|
|
163
|
+
name: catName,
|
|
164
|
+
kind: 'class',
|
|
165
|
+
line: node.startPosition.row + 1,
|
|
166
|
+
endLine: nodeEndLine(node),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Method / function handlers ────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
function handleMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
173
|
+
const selector = buildSelector(node);
|
|
174
|
+
if (!selector) return;
|
|
175
|
+
|
|
176
|
+
const parentClass = findObjCParentClass(node);
|
|
177
|
+
const fullName = parentClass ? `${parentClass}.${selector}` : selector;
|
|
178
|
+
|
|
179
|
+
const params = extractMethodParams(node);
|
|
180
|
+
ctx.definitions.push({
|
|
181
|
+
name: fullName,
|
|
182
|
+
kind: 'method',
|
|
183
|
+
line: node.startPosition.row + 1,
|
|
184
|
+
endLine: nodeEndLine(node),
|
|
185
|
+
children: params.length > 0 ? params : undefined,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function handleFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
190
|
+
const declarator = node.childForFieldName('declarator');
|
|
191
|
+
if (!declarator) return;
|
|
192
|
+
const funcDeclarator =
|
|
193
|
+
declarator.type === 'function_declarator'
|
|
194
|
+
? declarator
|
|
195
|
+
: findChild(declarator, 'function_declarator');
|
|
196
|
+
if (!funcDeclarator) return;
|
|
197
|
+
const nameNode = funcDeclarator.childForFieldName('declarator');
|
|
198
|
+
if (!nameNode) return;
|
|
199
|
+
|
|
200
|
+
const params = extractCParams(funcDeclarator.childForFieldName('parameters'));
|
|
201
|
+
ctx.definitions.push({
|
|
202
|
+
name: nameNode.text,
|
|
203
|
+
kind: 'function',
|
|
204
|
+
line: node.startPosition.row + 1,
|
|
205
|
+
endLine: nodeEndLine(node),
|
|
206
|
+
children: params.length > 0 ? params : undefined,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Import handlers ───────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
213
|
+
const pathNode = node.childForFieldName('path');
|
|
214
|
+
if (!pathNode) return;
|
|
215
|
+
const raw = pathNode.text;
|
|
216
|
+
const source = raw.replace(/^["<]|[">]$/g, '');
|
|
217
|
+
const lastName = source.split('/').pop() ?? source;
|
|
218
|
+
ctx.imports.push({
|
|
219
|
+
source,
|
|
220
|
+
names: [lastName],
|
|
221
|
+
line: node.startPosition.row + 1,
|
|
222
|
+
cInclude: true,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function handleAtImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
227
|
+
// @import Foundation;
|
|
228
|
+
const moduleNode = node.childForFieldName('module') || findChild(node, 'identifier');
|
|
229
|
+
if (moduleNode) {
|
|
230
|
+
ctx.imports.push({
|
|
231
|
+
source: moduleNode.text,
|
|
232
|
+
names: [moduleNode.text],
|
|
233
|
+
line: node.startPosition.row + 1,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── C-compatible type handlers ────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
function handleStructSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
241
|
+
const nameNode = node.childForFieldName('name');
|
|
242
|
+
if (!nameNode) return;
|
|
243
|
+
ctx.definitions.push({
|
|
244
|
+
name: nameNode.text,
|
|
245
|
+
kind: 'struct',
|
|
246
|
+
line: node.startPosition.row + 1,
|
|
247
|
+
endLine: nodeEndLine(node),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function handleEnumSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
252
|
+
const nameNode = node.childForFieldName('name');
|
|
253
|
+
if (!nameNode) return;
|
|
254
|
+
ctx.definitions.push({
|
|
255
|
+
name: nameNode.text,
|
|
256
|
+
kind: 'enum',
|
|
257
|
+
line: node.startPosition.row + 1,
|
|
258
|
+
endLine: nodeEndLine(node),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function handleTypedef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
263
|
+
let name: string | undefined;
|
|
264
|
+
for (let i = node.childCount - 1; i >= 0; i--) {
|
|
265
|
+
const child = node.child(i);
|
|
266
|
+
if (
|
|
267
|
+
child &&
|
|
268
|
+
(child.type === 'type_identifier' ||
|
|
269
|
+
child.type === 'identifier' ||
|
|
270
|
+
child.type === 'primitive_type')
|
|
271
|
+
) {
|
|
272
|
+
name = child.text;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!name) return;
|
|
277
|
+
ctx.definitions.push({
|
|
278
|
+
name,
|
|
279
|
+
kind: 'type',
|
|
280
|
+
line: node.startPosition.row + 1,
|
|
281
|
+
endLine: nodeEndLine(node),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ── Call handlers ─────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
function handleCCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
288
|
+
const funcNode = node.childForFieldName('function');
|
|
289
|
+
if (!funcNode) return;
|
|
290
|
+
const call: Call = { name: '', line: node.startPosition.row + 1 };
|
|
291
|
+
if (funcNode.type === 'field_expression') {
|
|
292
|
+
const field = funcNode.childForFieldName('field');
|
|
293
|
+
const argument = funcNode.childForFieldName('argument');
|
|
294
|
+
if (field) call.name = field.text;
|
|
295
|
+
if (argument) call.receiver = argument.text;
|
|
296
|
+
} else {
|
|
297
|
+
call.name = funcNode.text;
|
|
298
|
+
}
|
|
299
|
+
if (call.name) ctx.calls.push(call);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function handleMessageExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
303
|
+
// [receiver selector:arg ...]
|
|
304
|
+
const receiver = node.childForFieldName('receiver');
|
|
305
|
+
const selector = node.childForFieldName('selector');
|
|
306
|
+
if (!selector) return;
|
|
307
|
+
|
|
308
|
+
const call: Call = { name: selector.text, line: node.startPosition.row + 1 };
|
|
309
|
+
if (receiver) call.receiver = receiver.text;
|
|
310
|
+
ctx.calls.push(call);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
function buildSelector(methodNode: TreeSitterNode): string | null {
|
|
316
|
+
const selector = methodNode.childForFieldName('selector');
|
|
317
|
+
if (selector) return selector.text;
|
|
318
|
+
|
|
319
|
+
// Build selector from keyword children: initWith:name:
|
|
320
|
+
const parts: string[] = [];
|
|
321
|
+
for (let i = 0; i < methodNode.childCount; i++) {
|
|
322
|
+
const child = methodNode.child(i);
|
|
323
|
+
if (!child) continue;
|
|
324
|
+
if (child.type === 'keyword_selector') {
|
|
325
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
326
|
+
const kw = child.child(j);
|
|
327
|
+
if (kw && kw.type === 'keyword_declarator') {
|
|
328
|
+
const kwName = kw.childForFieldName('keyword');
|
|
329
|
+
if (kwName) parts.push(kwName.text);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (child.type === 'identifier' && i === 1) {
|
|
334
|
+
// Simple unary selector
|
|
335
|
+
return child.text;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return parts.length > 0 ? `${parts.join(':')}:` : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function findObjCParentClass(node: TreeSitterNode): string | null {
|
|
342
|
+
let current = node.parent;
|
|
343
|
+
while (current) {
|
|
344
|
+
if (
|
|
345
|
+
current.type === 'class_interface' ||
|
|
346
|
+
current.type === 'class_implementation' ||
|
|
347
|
+
current.type === 'protocol_declaration' ||
|
|
348
|
+
current.type === 'category_interface' ||
|
|
349
|
+
current.type === 'category_implementation'
|
|
350
|
+
) {
|
|
351
|
+
const nameNode = current.childForFieldName('name') || findObjCDeclName(current);
|
|
352
|
+
return nameNode ? nameNode.text : null;
|
|
353
|
+
}
|
|
354
|
+
current = current.parent;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Find the declaration name for ObjC constructs where the grammar does not
|
|
361
|
+
* expose the class/protocol name as a named field. The identifier appears
|
|
362
|
+
* right after the `@interface` / `@implementation` / `@protocol` keyword.
|
|
363
|
+
*/
|
|
364
|
+
function findObjCDeclName(node: TreeSitterNode): TreeSitterNode | null {
|
|
365
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
366
|
+
const child = node.child(i);
|
|
367
|
+
if (child && child.type === 'identifier') return child;
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function collectClassMembers(classNode: TreeSitterNode): SubDeclaration[] {
|
|
373
|
+
const members: SubDeclaration[] = [];
|
|
374
|
+
for (let i = 0; i < classNode.childCount; i++) {
|
|
375
|
+
const child = classNode.child(i);
|
|
376
|
+
if (!child) continue;
|
|
377
|
+
if (child.type === 'method_declaration' || child.type === 'method_definition') {
|
|
378
|
+
const sel = buildSelector(child);
|
|
379
|
+
if (sel) {
|
|
380
|
+
members.push({ name: sel, kind: 'method', line: child.startPosition.row + 1 });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (child.type === 'property_declaration') {
|
|
384
|
+
const propName = child.childForFieldName('name');
|
|
385
|
+
if (propName) {
|
|
386
|
+
members.push({ name: propName.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return members;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function extractMethodParams(methodNode: TreeSitterNode): SubDeclaration[] {
|
|
394
|
+
const params: SubDeclaration[] = [];
|
|
395
|
+
for (let i = 0; i < methodNode.childCount; i++) {
|
|
396
|
+
const child = methodNode.child(i);
|
|
397
|
+
if (!child || child.type !== 'keyword_selector') continue;
|
|
398
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
399
|
+
const kw = child.child(j);
|
|
400
|
+
if (kw && kw.type === 'keyword_declarator') {
|
|
401
|
+
const nameNode = kw.childForFieldName('name');
|
|
402
|
+
if (nameNode) {
|
|
403
|
+
params.push({
|
|
404
|
+
name: nameNode.text,
|
|
405
|
+
kind: 'parameter',
|
|
406
|
+
line: nameNode.startPosition.row + 1,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return params;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function extractCParams(paramListNode: TreeSitterNode | null): SubDeclaration[] {
|
|
416
|
+
const params: SubDeclaration[] = [];
|
|
417
|
+
if (!paramListNode) return params;
|
|
418
|
+
for (let i = 0; i < paramListNode.childCount; i++) {
|
|
419
|
+
const param = paramListNode.child(i);
|
|
420
|
+
if (!param || param.type !== 'parameter_declaration') continue;
|
|
421
|
+
const nameNode = param.childForFieldName('declarator');
|
|
422
|
+
if (nameNode) {
|
|
423
|
+
const name =
|
|
424
|
+
nameNode.type === 'identifier'
|
|
425
|
+
? nameNode.text
|
|
426
|
+
: (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
|
|
427
|
+
params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return params;
|
|
431
|
+
}
|
package/src/extractors/ocaml.ts
CHANGED
|
@@ -50,6 +50,19 @@ function walkOCamlNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
50
50
|
case 'application_expression':
|
|
51
51
|
handleOCamlApplication(node, ctx);
|
|
52
52
|
break;
|
|
53
|
+
// Shared node types present in both .ml and .mli files
|
|
54
|
+
case 'value_specification':
|
|
55
|
+
handleOCamlValueSpec(node, ctx);
|
|
56
|
+
break;
|
|
57
|
+
case 'external':
|
|
58
|
+
handleOCamlExternal(node, ctx);
|
|
59
|
+
break;
|
|
60
|
+
case 'module_type_definition':
|
|
61
|
+
handleOCamlModuleTypeDef(node, ctx);
|
|
62
|
+
break;
|
|
63
|
+
case 'exception_definition':
|
|
64
|
+
handleOCamlExceptionDef(node, ctx);
|
|
65
|
+
break;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -232,6 +245,71 @@ function handleOCamlOpen(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
232
245
|
});
|
|
233
246
|
}
|
|
234
247
|
|
|
248
|
+
function hasDescendantType(node: TreeSitterNode, type: string): boolean {
|
|
249
|
+
if (node.type === type) return true;
|
|
250
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
251
|
+
const child = node.child(i);
|
|
252
|
+
if (child && hasDescendantType(child, type)) return true;
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function handleOCamlValueSpec(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
258
|
+
const nameNode = findChild(node, 'value_name') || findChild(node, 'parenthesized_operator');
|
|
259
|
+
if (!nameNode) return;
|
|
260
|
+
|
|
261
|
+
// Check if the type contains `->` (function_type node)
|
|
262
|
+
const typeNode = node.childForFieldName('type');
|
|
263
|
+
const isFunction = typeNode ? hasDescendantType(typeNode, 'function_type') : false;
|
|
264
|
+
|
|
265
|
+
ctx.definitions.push({
|
|
266
|
+
name: nameNode.text,
|
|
267
|
+
kind: isFunction ? 'function' : 'variable',
|
|
268
|
+
line: node.startPosition.row + 1,
|
|
269
|
+
endLine: nodeEndLine(node),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function handleOCamlExternal(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
274
|
+
const nameNode = findChild(node, 'value_name') || findChild(node, 'parenthesized_operator');
|
|
275
|
+
if (!nameNode) return;
|
|
276
|
+
|
|
277
|
+
ctx.definitions.push({
|
|
278
|
+
name: nameNode.text,
|
|
279
|
+
kind: 'function',
|
|
280
|
+
line: node.startPosition.row + 1,
|
|
281
|
+
endLine: nodeEndLine(node),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function handleOCamlModuleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
286
|
+
const nameNode = findChild(node, 'module_type_name');
|
|
287
|
+
if (!nameNode) return;
|
|
288
|
+
|
|
289
|
+
ctx.definitions.push({
|
|
290
|
+
name: nameNode.text,
|
|
291
|
+
kind: 'interface',
|
|
292
|
+
line: node.startPosition.row + 1,
|
|
293
|
+
endLine: nodeEndLine(node),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function handleOCamlExceptionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
298
|
+
// Standard: `exception Foo of bar` — name is inside constructor_declaration
|
|
299
|
+
const ctorDecl = findChild(node, 'constructor_declaration');
|
|
300
|
+
const nameNode = ctorDecl
|
|
301
|
+
? findChild(ctorDecl, 'constructor_name')
|
|
302
|
+
: findChild(node, 'constructor_name'); // fallback for `exception Foo = Bar` (alias)
|
|
303
|
+
if (!nameNode) return;
|
|
304
|
+
|
|
305
|
+
ctx.definitions.push({
|
|
306
|
+
name: nameNode.text,
|
|
307
|
+
kind: 'type',
|
|
308
|
+
line: node.startPosition.row + 1,
|
|
309
|
+
endLine: nodeEndLine(node),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
235
313
|
function handleOCamlApplication(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
236
314
|
// application_expression: first child is the function, rest are arguments
|
|
237
315
|
const funcNode = node.child(0);
|