@abhinav2203/codeflow-core 0.1.1 → 1.0.1
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/dist/analyzer/index.d.ts +2 -0
- package/dist/analyzer/index.js +1 -0
- package/dist/analyzer/repo-multi.d.ts +16 -0
- package/dist/analyzer/repo-multi.js +257 -0
- package/dist/analyzer/repo-multi.test.d.ts +1 -0
- package/dist/analyzer/repo-multi.test.js +208 -0
- package/dist/analyzer/test-hook-check.d.ts +1 -0
- package/dist/analyzer/test-hook-check.js +2 -0
- package/dist/analyzer/tree-sitter-analyzer.d.ts +36 -0
- package/dist/analyzer/tree-sitter-analyzer.js +632 -0
- package/dist/analyzer/tree-sitter-loader.d.ts +8 -0
- package/dist/analyzer/tree-sitter-loader.js +97 -0
- package/dist/analyzer/tree-sitter-queries.d.ts +10 -0
- package/dist/analyzer/tree-sitter-queries.js +285 -0
- package/package.json +13 -2
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { createNodeId } from "../internal/utils.js";
|
|
2
|
+
import { getLanguageFromPath, createParser } from "./tree-sitter-loader.js";
|
|
3
|
+
const HTTP_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
4
|
+
const namedChildren = (node) => {
|
|
5
|
+
if (!node)
|
|
6
|
+
return [];
|
|
7
|
+
return node.namedChildren.filter((c) => c !== null);
|
|
8
|
+
};
|
|
9
|
+
const extractCommentText = (node) => {
|
|
10
|
+
const comments = [];
|
|
11
|
+
let sibling = node.previousNamedSibling;
|
|
12
|
+
while (sibling) {
|
|
13
|
+
if (sibling.type.includes("comment") || sibling.type.includes("block_comment") || sibling.type.includes("line_comment")) {
|
|
14
|
+
const text = sibling.text
|
|
15
|
+
.replace(/^(\/\/|\/\*|\*|#)/g, "")
|
|
16
|
+
.replace(/(\*\/|\*|\n)/g, " ")
|
|
17
|
+
.trim();
|
|
18
|
+
if (text)
|
|
19
|
+
comments.unshift(text);
|
|
20
|
+
sibling = sibling.previousNamedSibling;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return comments.join(" ").trim();
|
|
27
|
+
};
|
|
28
|
+
const extractPythonDocstring = (bodyNode) => {
|
|
29
|
+
if (!bodyNode)
|
|
30
|
+
return "";
|
|
31
|
+
for (const child of namedChildren(bodyNode)) {
|
|
32
|
+
if (child.type === "expression_statement") {
|
|
33
|
+
const strNode = child.namedChild(0);
|
|
34
|
+
if (strNode && (strNode.type === "string" || strNode.type === "string_content")) {
|
|
35
|
+
return strNode.text.replace(/^("""|'''|\"|')/, "").replace(/("""|'''|\"|')$/, "").trim().split("\n")[0].trim();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return "";
|
|
40
|
+
};
|
|
41
|
+
const buildSummary = (name, node, bodyNode, params, returnType) => {
|
|
42
|
+
const comment = extractCommentText(node);
|
|
43
|
+
if (comment)
|
|
44
|
+
return comment.split("\n")[0].trim();
|
|
45
|
+
const docstring = extractPythonDocstring(bodyNode);
|
|
46
|
+
if (docstring)
|
|
47
|
+
return docstring;
|
|
48
|
+
const paramPart = params.length > 0 ? ` that takes ${params.length} param${params.length > 1 ? "s" : ""}` : "";
|
|
49
|
+
const returnPart = returnType && returnType !== "void" && returnType !== "unknown" && returnType !== "None"
|
|
50
|
+
? ` and returns ${returnType}`
|
|
51
|
+
: "";
|
|
52
|
+
return `${name}${paramPart}${returnPart}.`;
|
|
53
|
+
};
|
|
54
|
+
const buildSignature = (name, params, returnType, language) => {
|
|
55
|
+
const paramStr = params.map(p => p.type !== "unknown" ? `${p.name}: ${p.type}` : p.name).join(", ");
|
|
56
|
+
const ret = returnType && returnType !== "unknown" && returnType !== "void" ? returnType : "";
|
|
57
|
+
switch (language) {
|
|
58
|
+
case "go": return `func ${name}(${paramStr})${ret ? " " + ret : ""}`;
|
|
59
|
+
case "python": return `def ${name}(${paramStr})${ret ? ` -> ${ret}` : ""}`;
|
|
60
|
+
case "c":
|
|
61
|
+
case "cpp": return `${ret || "void"} ${name}(${paramStr})`;
|
|
62
|
+
case "rust": return `fn ${name}(${paramStr})${ret ? ` -> ${ret}` : ""}`;
|
|
63
|
+
default: return `${name}(${paramStr})${ret ? `: ${ret}` : ""}`;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const findChild = (node, ...types) => {
|
|
67
|
+
if (!node)
|
|
68
|
+
return null;
|
|
69
|
+
for (const child of namedChildren(node)) {
|
|
70
|
+
if (types.includes(child.type))
|
|
71
|
+
return child;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
const extractParams = (paramNode) => {
|
|
76
|
+
if (!paramNode)
|
|
77
|
+
return [];
|
|
78
|
+
const params = [];
|
|
79
|
+
const walk = (n) => {
|
|
80
|
+
if (n.type === "parameter_declaration" || n.type === "typed_parameter" || n.type === "parameter") {
|
|
81
|
+
let name = "";
|
|
82
|
+
let type = "";
|
|
83
|
+
for (const c of namedChildren(n)) {
|
|
84
|
+
if (c.type === "identifier" || c.type === "variable_declarator")
|
|
85
|
+
name = c.text;
|
|
86
|
+
else if (c.type === "type_identifier" || c.type === "primitive_type" || c.type === "sized_type_specifier")
|
|
87
|
+
type = c.text;
|
|
88
|
+
else if (c.type === "type_annotation")
|
|
89
|
+
type = c.text.replace(/^:\s*/, "").trim();
|
|
90
|
+
}
|
|
91
|
+
const nameNode = n.childForFieldName("name") || n.childForFieldName("pattern");
|
|
92
|
+
if (nameNode && !name)
|
|
93
|
+
name = nameNode.text;
|
|
94
|
+
const typeNode = n.childForFieldName("type");
|
|
95
|
+
if (typeNode && !type)
|
|
96
|
+
type = typeNode.text;
|
|
97
|
+
if (name)
|
|
98
|
+
params.push({ name, type: type || "unknown" });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const c of namedChildren(n))
|
|
102
|
+
walk(c);
|
|
103
|
+
};
|
|
104
|
+
walk(paramNode);
|
|
105
|
+
return params;
|
|
106
|
+
};
|
|
107
|
+
const extractReturnType = (returnNode) => {
|
|
108
|
+
if (!returnNode)
|
|
109
|
+
return "";
|
|
110
|
+
const typeNode = returnNode.childForFieldName("type")
|
|
111
|
+
|| returnNode.childForFieldName("value")
|
|
112
|
+
|| returnNode.namedChild(0);
|
|
113
|
+
if (typeNode)
|
|
114
|
+
return typeNode.text.replace(/^\(|\)$/g, "").trim();
|
|
115
|
+
return returnNode.text.replace(/^\(|\)$/g, "").trim() || "";
|
|
116
|
+
};
|
|
117
|
+
const detectKind = (name, relativePath, language) => {
|
|
118
|
+
if (language === "typescript" || language === "javascript") {
|
|
119
|
+
const baseName = relativePath.split("/").pop()?.replace(/\.[^.]+$/, "") || "";
|
|
120
|
+
if (baseName === "route" && HTTP_METHODS.has(name))
|
|
121
|
+
return "api";
|
|
122
|
+
if (baseName === "page" && name.toLowerCase().includes("page"))
|
|
123
|
+
return "ui-screen";
|
|
124
|
+
}
|
|
125
|
+
return "function";
|
|
126
|
+
};
|
|
127
|
+
export const extractNodesFromFile = async (filePath, relativePath) => {
|
|
128
|
+
const language = getLanguageFromPath(filePath);
|
|
129
|
+
if (!language) {
|
|
130
|
+
return { nodes: [], edges: [], symbolIndex: new Map(), callEdges: [], importEdges: [], inheritEdges: [] };
|
|
131
|
+
}
|
|
132
|
+
const parser = await createParser(language);
|
|
133
|
+
const fs = await import("node:fs/promises");
|
|
134
|
+
const source = await fs.readFile(filePath, "utf-8");
|
|
135
|
+
const tree = parser.parse(source);
|
|
136
|
+
// Clean up parser instance after use
|
|
137
|
+
parser.delete();
|
|
138
|
+
if (!tree) {
|
|
139
|
+
return { nodes: [], edges: [], symbolIndex: new Map(), callEdges: [], importEdges: [], inheritEdges: [] };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const root = tree.rootNode;
|
|
143
|
+
const nodes = [];
|
|
144
|
+
const edges = [];
|
|
145
|
+
const symbolIndex = new Map();
|
|
146
|
+
const callEdges = [];
|
|
147
|
+
const importEdges = [];
|
|
148
|
+
const inheritEdges = [];
|
|
149
|
+
const moduleId = createNodeId("module", relativePath, relativePath);
|
|
150
|
+
const moduleEndLine = root.endPosition.row + 1;
|
|
151
|
+
nodes.push({
|
|
152
|
+
nodeId: moduleId,
|
|
153
|
+
kind: "module",
|
|
154
|
+
name: relativePath,
|
|
155
|
+
summary: `Source module ${relativePath}.`,
|
|
156
|
+
path: relativePath,
|
|
157
|
+
signature: "",
|
|
158
|
+
sourceRefs: [{ kind: "repo", path: relativePath }],
|
|
159
|
+
startLine: 1,
|
|
160
|
+
endLine: moduleEndLine
|
|
161
|
+
});
|
|
162
|
+
const recordFunc = (funcName, funcNode, paramsNode, returnTypeNode, bodyNode, ownerName, ownerId) => {
|
|
163
|
+
if (!funcName)
|
|
164
|
+
return;
|
|
165
|
+
const params = extractParams(paramsNode);
|
|
166
|
+
const returnType = returnTypeNode ? extractReturnType(returnTypeNode) : "";
|
|
167
|
+
const displayName = ownerName ? `${ownerName}.${funcName}` : funcName;
|
|
168
|
+
const summary = buildSummary(displayName, funcNode, bodyNode, params, returnType);
|
|
169
|
+
const signature = buildSignature(funcName, params, returnType, language);
|
|
170
|
+
const kind = detectKind(funcName, relativePath, language);
|
|
171
|
+
const isMethod = !!ownerName;
|
|
172
|
+
const nodeId = createNodeId(kind, `${relativePath}:${displayName}`, `${relativePath}:${displayName}`);
|
|
173
|
+
nodes.push({
|
|
174
|
+
nodeId,
|
|
175
|
+
kind,
|
|
176
|
+
name: displayName,
|
|
177
|
+
summary,
|
|
178
|
+
path: relativePath,
|
|
179
|
+
signature,
|
|
180
|
+
sourceRefs: [{ kind: "repo", path: relativePath, symbol: displayName }],
|
|
181
|
+
ownerId: isMethod ? ownerId : (kind === "function" ? moduleId : undefined),
|
|
182
|
+
startLine: funcNode.startPosition.row + 1,
|
|
183
|
+
endLine: funcNode.endPosition.row + 1
|
|
184
|
+
});
|
|
185
|
+
// Only store fully-qualified keys for methods to avoid collisions
|
|
186
|
+
symbolIndex.set(`${relativePath}::${displayName}`, nodeId);
|
|
187
|
+
if (!isMethod) {
|
|
188
|
+
symbolIndex.set(`${relativePath}::${funcName}`, nodeId);
|
|
189
|
+
}
|
|
190
|
+
collectCalls(funcNode, nodeId);
|
|
191
|
+
};
|
|
192
|
+
const collectCalls = (node, callerId) => {
|
|
193
|
+
for (const child of namedChildren(node)) {
|
|
194
|
+
let calleeName = "";
|
|
195
|
+
if (child.type === "call_expression" || child.type === "call") {
|
|
196
|
+
const funcNode = child.namedChild(0);
|
|
197
|
+
if (funcNode) {
|
|
198
|
+
if (funcNode.type === "identifier" || funcNode.type === "variable_identifier") {
|
|
199
|
+
calleeName = funcNode.text;
|
|
200
|
+
}
|
|
201
|
+
else if (funcNode.type === "member_expression" || funcNode.type === "selector_expression" || funcNode.type === "field_expression" || funcNode.type === "attribute") {
|
|
202
|
+
const propNode = findChild(funcNode, "property_identifier", "field_identifier", "identifier");
|
|
203
|
+
if (propNode)
|
|
204
|
+
calleeName = propNode.text;
|
|
205
|
+
}
|
|
206
|
+
else if (funcNode.type === "scoped_identifier" || funcNode.type === "qualified_identifier") {
|
|
207
|
+
const nameNode = findChild(funcNode, "identifier", "field_identifier");
|
|
208
|
+
if (nameNode)
|
|
209
|
+
calleeName = nameNode.text;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (calleeName) {
|
|
214
|
+
callEdges.push({ fromId: callerId, toName: calleeName, callText: child.text });
|
|
215
|
+
}
|
|
216
|
+
collectCalls(child, callerId);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const findFuncName = (node) => {
|
|
220
|
+
// Direct identifier child
|
|
221
|
+
const direct = findChild(node, "identifier");
|
|
222
|
+
if (direct)
|
|
223
|
+
return direct;
|
|
224
|
+
// C/C++: function_declarator -> identifier (no pointer)
|
|
225
|
+
const fnDecl = findChild(node, "function_declarator");
|
|
226
|
+
if (fnDecl) {
|
|
227
|
+
const id = findChild(fnDecl, "identifier", "field_identifier");
|
|
228
|
+
if (id)
|
|
229
|
+
return id;
|
|
230
|
+
}
|
|
231
|
+
// C/C++: pointer_declarator -> function_declarator -> identifier
|
|
232
|
+
const ptrDecl = findChild(node, "pointer_declarator", "array_declarator", "fn_pointer");
|
|
233
|
+
if (ptrDecl) {
|
|
234
|
+
const innerFnDecl = findChild(ptrDecl, "function_declarator") || findChild(ptrDecl, "declarator");
|
|
235
|
+
if (innerFnDecl) {
|
|
236
|
+
return findChild(innerFnDecl, "identifier") || findChild(innerFnDecl, "field_identifier");
|
|
237
|
+
}
|
|
238
|
+
for (const c of namedChildren(ptrDecl)) {
|
|
239
|
+
if (c.type === "function_declarator")
|
|
240
|
+
return findChild(c, "identifier");
|
|
241
|
+
if (c.type === "identifier")
|
|
242
|
+
return c;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
};
|
|
247
|
+
const findParamsNode = (node) => {
|
|
248
|
+
const direct = findChild(node, "parameter_list", "parameters", "formal_parameters");
|
|
249
|
+
if (direct)
|
|
250
|
+
return direct;
|
|
251
|
+
// C/C++: inside function_declarator
|
|
252
|
+
const fnDecl = findChild(node, "function_declarator");
|
|
253
|
+
if (fnDecl)
|
|
254
|
+
return findChild(fnDecl, "parameter_list");
|
|
255
|
+
return null;
|
|
256
|
+
};
|
|
257
|
+
const findReturnTypeInfo = (node) => {
|
|
258
|
+
let returnTypeNode = null;
|
|
259
|
+
let bodyNode = null;
|
|
260
|
+
bodyNode = findChild(node, "block", "statement_block", "compound_statement");
|
|
261
|
+
// Go: result node
|
|
262
|
+
const resultNode = findChild(node, "result");
|
|
263
|
+
if (resultNode) {
|
|
264
|
+
returnTypeNode = findChild(resultNode, "type_identifier", "primitive_type", "sized_type_specifier", "tuple_type", "generic_type", "pointer_type", "array_type");
|
|
265
|
+
}
|
|
266
|
+
// C/C++: primitive_type is a direct child (return type before function name)
|
|
267
|
+
const primType = findChild(node, "primitive_type", "sized_type_specifier");
|
|
268
|
+
if (primType)
|
|
269
|
+
returnTypeNode = primType;
|
|
270
|
+
// TS: type_annotation child
|
|
271
|
+
if (!returnTypeNode) {
|
|
272
|
+
const retNode = findChild(node, "type_annotation");
|
|
273
|
+
if (retNode) {
|
|
274
|
+
returnTypeNode = findChild(retNode, "type_identifier", "primitive_type", "predefined_type") || retNode;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Rust: look for -> pattern
|
|
278
|
+
if (!returnTypeNode) {
|
|
279
|
+
for (let i = 0; i < node.namedChildren.length - 1; i++) {
|
|
280
|
+
const n = node.namedChildren[i];
|
|
281
|
+
if (n && n.type === "->" && node.namedChildren[i + 1]) {
|
|
282
|
+
returnTypeNode = node.namedChildren[i + 1];
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { returnTypeNode, bodyNode };
|
|
288
|
+
};
|
|
289
|
+
const walkFunctions = (node, parentOwnerName, parentOwnerId) => {
|
|
290
|
+
for (const child of namedChildren(node)) {
|
|
291
|
+
let funcName = "";
|
|
292
|
+
let paramsNode = null;
|
|
293
|
+
let returnTypeNode = null;
|
|
294
|
+
let bodyNode = null;
|
|
295
|
+
let isMethod = false;
|
|
296
|
+
let currentOwnerName = parentOwnerName;
|
|
297
|
+
let currentOwnerId = parentOwnerId;
|
|
298
|
+
// Skip functions inside class bodies when at root level (handled by walkClasses instead)
|
|
299
|
+
// But process them when walkClasses passes owner context
|
|
300
|
+
const grandParent = child.parent?.parent;
|
|
301
|
+
const greatGrandParent = child.parent?.parent?.parent;
|
|
302
|
+
const isInsideClassBody = !parentOwnerName && (grandParent?.type === "class_definition" || grandParent?.type === "class_declaration" ||
|
|
303
|
+
grandParent?.type === "class_specifier" || grandParent?.type === "struct_specifier" ||
|
|
304
|
+
greatGrandParent?.type === "impl_item");
|
|
305
|
+
if (child.type === "function_declaration" && !isInsideClassBody) {
|
|
306
|
+
const nameNode = findChild(child, "identifier");
|
|
307
|
+
if (nameNode) {
|
|
308
|
+
funcName = nameNode.text;
|
|
309
|
+
paramsNode = findChild(child, "parameter_list", "formal_parameters");
|
|
310
|
+
const { returnTypeNode: rt, bodyNode: bd } = findReturnTypeInfo(child);
|
|
311
|
+
returnTypeNode = rt;
|
|
312
|
+
bodyNode = bd;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
walkFunctions(child, currentOwnerName, currentOwnerId);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (child.type === "function_definition" && !isInsideClassBody) {
|
|
320
|
+
const nameNode = findFuncName(child);
|
|
321
|
+
if (nameNode) {
|
|
322
|
+
funcName = nameNode.text;
|
|
323
|
+
paramsNode = findParamsNode(child);
|
|
324
|
+
const { returnTypeNode: rt, bodyNode: bd } = findReturnTypeInfo(child);
|
|
325
|
+
returnTypeNode = rt;
|
|
326
|
+
bodyNode = bd;
|
|
327
|
+
if (currentOwnerName)
|
|
328
|
+
isMethod = true;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
walkFunctions(child, currentOwnerName, currentOwnerId);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else if (child.type === "method_declaration") {
|
|
336
|
+
funcName = findChild(child, "field_identifier")?.text || "";
|
|
337
|
+
paramsNode = findChild(child, "parameter_list", "parameters");
|
|
338
|
+
const resultNode = findChild(child, "result");
|
|
339
|
+
if (resultNode) {
|
|
340
|
+
returnTypeNode = findChild(resultNode, "type_identifier", "primitive_type", "sized_type_specifier", "tuple_type", "generic_type", "pointer_type", "array_type");
|
|
341
|
+
}
|
|
342
|
+
bodyNode = findChild(child, "block");
|
|
343
|
+
isMethod = true;
|
|
344
|
+
// Go: extract receiver type from first parameter_list (the receiver)
|
|
345
|
+
const recvParam = child.namedChildren[0];
|
|
346
|
+
if (recvParam && recvParam.type === "parameter_list") {
|
|
347
|
+
const recvDecl = recvParam.namedChildren[0];
|
|
348
|
+
if (recvDecl) {
|
|
349
|
+
const recvTypeNode = recvDecl.childForFieldName("type");
|
|
350
|
+
if (recvTypeNode) {
|
|
351
|
+
// Handle *Type -> Type
|
|
352
|
+
let typeName = recvTypeNode.text;
|
|
353
|
+
if (typeName.startsWith("*"))
|
|
354
|
+
typeName = typeName.substring(1);
|
|
355
|
+
currentOwnerName = typeName;
|
|
356
|
+
// Find the matching class node id
|
|
357
|
+
const classKey = `${relativePath}::${typeName}`;
|
|
358
|
+
currentOwnerId = symbolIndex.get(classKey);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
else if (child.type === "method_definition" && !isInsideClassBody) {
|
|
364
|
+
const nameNode = findChild(child, "identifier", "property_identifier");
|
|
365
|
+
if (nameNode) {
|
|
366
|
+
funcName = nameNode.text;
|
|
367
|
+
paramsNode = findChild(child, "parameters", "formal_parameters");
|
|
368
|
+
const { returnTypeNode: rt, bodyNode: bd } = findReturnTypeInfo(child);
|
|
369
|
+
returnTypeNode = rt;
|
|
370
|
+
bodyNode = bd;
|
|
371
|
+
isMethod = true;
|
|
372
|
+
currentOwnerName = parentOwnerName;
|
|
373
|
+
currentOwnerId = parentOwnerId;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else if (child.type === "function_item" && !isInsideClassBody) {
|
|
377
|
+
const nameNode = findChild(child, "identifier");
|
|
378
|
+
if (nameNode) {
|
|
379
|
+
funcName = nameNode.text;
|
|
380
|
+
paramsNode = findChild(child, "parameters");
|
|
381
|
+
const { returnTypeNode: rt, bodyNode: bd } = findReturnTypeInfo(child);
|
|
382
|
+
returnTypeNode = rt;
|
|
383
|
+
bodyNode = bd;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
walkFunctions(child, currentOwnerName, currentOwnerId);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (child.type === "arrow_function" || child.type === "function_expression") {
|
|
391
|
+
const parent = child.parent;
|
|
392
|
+
if (parent?.type === "variable_declarator") {
|
|
393
|
+
const nameN = findChild(parent, "identifier");
|
|
394
|
+
if (nameN)
|
|
395
|
+
funcName = nameN.text;
|
|
396
|
+
}
|
|
397
|
+
if (!funcName) {
|
|
398
|
+
walkFunctions(child, currentOwnerName, currentOwnerId);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
paramsNode = findChild(child, "formal_parameters", "parameters");
|
|
402
|
+
bodyNode = findChild(child, "statement_block", "block", "expression");
|
|
403
|
+
}
|
|
404
|
+
if (funcName) {
|
|
405
|
+
recordFunc(funcName, child, paramsNode, returnTypeNode, bodyNode, currentOwnerName, currentOwnerId);
|
|
406
|
+
}
|
|
407
|
+
walkFunctions(child, currentOwnerName, currentOwnerId);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const walkClasses = (node) => {
|
|
411
|
+
for (const child of namedChildren(node)) {
|
|
412
|
+
let className = "";
|
|
413
|
+
let parentClass = "";
|
|
414
|
+
// TS/JS: class_declaration, C++: class_specifier/struct_specifier
|
|
415
|
+
if (child.type === "class_declaration" || child.type === "class_specifier" || child.type === "struct_specifier") {
|
|
416
|
+
const nameNode = findChild(child, "type_identifier", "identifier");
|
|
417
|
+
if (!nameNode) {
|
|
418
|
+
walkClasses(child);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
className = nameNode.text;
|
|
422
|
+
const heritage = findChild(child, "class_heritage", "base_class_clause");
|
|
423
|
+
if (heritage) {
|
|
424
|
+
// TS: class_heritage -> extends_clause -> identifier
|
|
425
|
+
const parentNode = findChild(heritage, "type_identifier", "identifier");
|
|
426
|
+
if (parentNode) {
|
|
427
|
+
parentClass = parentNode.text;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// Deeper search
|
|
431
|
+
for (const h of namedChildren(heritage)) {
|
|
432
|
+
const p = findChild(h, "type_identifier", "identifier");
|
|
433
|
+
if (p) {
|
|
434
|
+
parentClass = p.text;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const classId = createNodeId("class", `${relativePath}:${className}`, `${relativePath}:${className}`);
|
|
441
|
+
nodes.push({
|
|
442
|
+
nodeId: classId,
|
|
443
|
+
kind: "class",
|
|
444
|
+
name: className,
|
|
445
|
+
summary: buildSummary(className, child, null, [], ""),
|
|
446
|
+
path: relativePath,
|
|
447
|
+
signature: `class ${className}`,
|
|
448
|
+
sourceRefs: [{ kind: "repo", path: relativePath, symbol: className }],
|
|
449
|
+
ownerId: moduleId,
|
|
450
|
+
startLine: child.startPosition.row + 1,
|
|
451
|
+
endLine: child.endPosition.row + 1
|
|
452
|
+
});
|
|
453
|
+
symbolIndex.set(`${relativePath}::${className}`, classId);
|
|
454
|
+
if (parentClass)
|
|
455
|
+
inheritEdges.push({ fromId: classId, toName: parentClass });
|
|
456
|
+
walkFunctions(child, className, classId);
|
|
457
|
+
}
|
|
458
|
+
// Python: class_definition
|
|
459
|
+
else if (child.type === "class_definition") {
|
|
460
|
+
const nameNode = findChild(child, "identifier");
|
|
461
|
+
if (!nameNode) {
|
|
462
|
+
walkClasses(child);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
className = nameNode.text;
|
|
466
|
+
// Python inheritance: argument_list directly under class_definition
|
|
467
|
+
// (some grammars wrap in superclasses, others don't)
|
|
468
|
+
const argList = findChild(child, "argument_list", "superclasses");
|
|
469
|
+
if (argList) {
|
|
470
|
+
const parentId = findChild(argList, "identifier", "type_identifier");
|
|
471
|
+
if (parentId)
|
|
472
|
+
parentClass = parentId.text;
|
|
473
|
+
}
|
|
474
|
+
const classId = createNodeId("class", `${relativePath}:${className}`, `${relativePath}:${className}`);
|
|
475
|
+
nodes.push({
|
|
476
|
+
nodeId: classId,
|
|
477
|
+
kind: "class",
|
|
478
|
+
name: className,
|
|
479
|
+
summary: buildSummary(className, child, null, [], ""),
|
|
480
|
+
path: relativePath,
|
|
481
|
+
signature: `class ${className}`,
|
|
482
|
+
sourceRefs: [{ kind: "repo", path: relativePath, symbol: className }],
|
|
483
|
+
ownerId: moduleId,
|
|
484
|
+
startLine: child.startPosition.row + 1,
|
|
485
|
+
endLine: child.endPosition.row + 1
|
|
486
|
+
});
|
|
487
|
+
symbolIndex.set(`${relativePath}::${className}`, classId);
|
|
488
|
+
if (parentClass)
|
|
489
|
+
inheritEdges.push({ fromId: classId, toName: parentClass });
|
|
490
|
+
walkFunctions(child, className, classId);
|
|
491
|
+
}
|
|
492
|
+
// Go: type_declaration -> type_spec -> type_identifier + struct_type
|
|
493
|
+
else if (child.type === "type_declaration") {
|
|
494
|
+
const typeSpec = findChild(child, "type_spec");
|
|
495
|
+
if (typeSpec) {
|
|
496
|
+
const structType = findChild(typeSpec, "struct_type");
|
|
497
|
+
const nameNode = findChild(typeSpec, "type_identifier");
|
|
498
|
+
if (nameNode && structType) {
|
|
499
|
+
className = nameNode.text;
|
|
500
|
+
const classId = createNodeId("class", `${relativePath}:${className}`, `${relativePath}:${className}`);
|
|
501
|
+
nodes.push({
|
|
502
|
+
nodeId: classId,
|
|
503
|
+
kind: "class",
|
|
504
|
+
name: className,
|
|
505
|
+
summary: buildSummary(className, child, null, [], ""),
|
|
506
|
+
path: relativePath,
|
|
507
|
+
signature: `type ${className} struct`,
|
|
508
|
+
sourceRefs: [{ kind: "repo", path: relativePath, symbol: className }],
|
|
509
|
+
startLine: structType.startPosition.row + 1,
|
|
510
|
+
endLine: structType.endPosition.row + 1
|
|
511
|
+
});
|
|
512
|
+
symbolIndex.set(`${relativePath}::${className}`, classId);
|
|
513
|
+
walkFunctions(child, className, classId);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Rust: struct_item, impl_item
|
|
518
|
+
else if (child.type === "struct_item") {
|
|
519
|
+
const nameNode = findChild(child, "type_identifier");
|
|
520
|
+
if (!nameNode) {
|
|
521
|
+
walkClasses(child);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
className = nameNode.text;
|
|
525
|
+
const classId = createNodeId("class", `${relativePath}:${className}`, `${relativePath}:${className}`);
|
|
526
|
+
nodes.push({
|
|
527
|
+
nodeId: classId,
|
|
528
|
+
kind: "class",
|
|
529
|
+
name: className,
|
|
530
|
+
summary: buildSummary(className, child, null, [], ""),
|
|
531
|
+
path: relativePath,
|
|
532
|
+
signature: `struct ${className}`,
|
|
533
|
+
sourceRefs: [{ kind: "repo", path: relativePath, symbol: className }],
|
|
534
|
+
startLine: child.startPosition.row + 1,
|
|
535
|
+
endLine: child.endPosition.row + 1
|
|
536
|
+
});
|
|
537
|
+
symbolIndex.set(`${relativePath}::${className}`, classId);
|
|
538
|
+
walkFunctions(child, className, classId);
|
|
539
|
+
}
|
|
540
|
+
else if (child.type === "impl_item") {
|
|
541
|
+
const nameNode = findChild(child, "type_identifier");
|
|
542
|
+
if (!nameNode) {
|
|
543
|
+
walkClasses(child);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
className = nameNode.text;
|
|
547
|
+
// Check if struct node already exists
|
|
548
|
+
const existingId = symbolIndex.get(`${relativePath}::${className}`);
|
|
549
|
+
if (existingId) {
|
|
550
|
+
// Use existing node ID and just walk functions
|
|
551
|
+
walkFunctions(child, className, existingId);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
// Create new node if struct doesn't exist
|
|
555
|
+
const classId = createNodeId("class", `${relativePath}:${className}`, `${relativePath}:${className}`);
|
|
556
|
+
nodes.push({
|
|
557
|
+
nodeId: classId,
|
|
558
|
+
kind: "class",
|
|
559
|
+
name: className,
|
|
560
|
+
summary: buildSummary(className, child, null, [], ""),
|
|
561
|
+
path: relativePath,
|
|
562
|
+
signature: `impl ${className}`,
|
|
563
|
+
sourceRefs: [{ kind: "repo", path: relativePath, symbol: className }],
|
|
564
|
+
startLine: child.startPosition.row + 1,
|
|
565
|
+
endLine: child.endPosition.row + 1
|
|
566
|
+
});
|
|
567
|
+
symbolIndex.set(`${relativePath}::${className}`, classId);
|
|
568
|
+
walkFunctions(child, className, classId);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
walkClasses(child);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
const walkImports = (node) => {
|
|
575
|
+
for (const child of namedChildren(node)) {
|
|
576
|
+
let importPath = "";
|
|
577
|
+
if (child.type === "import_statement") {
|
|
578
|
+
const stringNode = findChild(child, "string");
|
|
579
|
+
if (stringNode)
|
|
580
|
+
importPath = stringNode.text.replace(/^["']|["']$/g, "");
|
|
581
|
+
}
|
|
582
|
+
else if (child.type === "import_declaration") {
|
|
583
|
+
const specList = findChild(child, "import_spec_list");
|
|
584
|
+
if (specList) {
|
|
585
|
+
for (const spec of namedChildren(specList)) {
|
|
586
|
+
if (spec.type === "import_spec") {
|
|
587
|
+
const pathNode = findChild(spec, "interpreted_string_literal", "string_literal", "string");
|
|
588
|
+
if (pathNode)
|
|
589
|
+
importPath = pathNode.text.replace(/^["']|["']$/g, "");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
const specNode = findChild(child, "import_spec");
|
|
595
|
+
if (specNode) {
|
|
596
|
+
const pathNode = findChild(specNode, "interpreted_string_literal", "string_literal", "string");
|
|
597
|
+
if (pathNode)
|
|
598
|
+
importPath = pathNode.text.replace(/^["']|["']$/g, "");
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else if (child.type === "preproc_include") {
|
|
603
|
+
const pathNode = findChild(child, "system_lib_string", "string_literal", "string");
|
|
604
|
+
if (pathNode)
|
|
605
|
+
importPath = pathNode.text.replace(/^<|>$/g, "").replace(/^["']|["']$/g, "");
|
|
606
|
+
}
|
|
607
|
+
else if (child.type === "import_from_statement") {
|
|
608
|
+
const modNode = findChild(child, "dotted_name", "identifier");
|
|
609
|
+
if (modNode)
|
|
610
|
+
importPath = modNode.text;
|
|
611
|
+
}
|
|
612
|
+
else if (child.type === "use_declaration") {
|
|
613
|
+
const argNode = child.namedChild(0);
|
|
614
|
+
if (argNode)
|
|
615
|
+
importPath = argNode.text;
|
|
616
|
+
}
|
|
617
|
+
if (importPath) {
|
|
618
|
+
importEdges.push({ fromModuleId: moduleId, importPath });
|
|
619
|
+
}
|
|
620
|
+
walkImports(child);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
walkClasses(root);
|
|
624
|
+
walkFunctions(root);
|
|
625
|
+
walkImports(root);
|
|
626
|
+
return { nodes, edges, symbolIndex, callEdges, importEdges, inheritEdges };
|
|
627
|
+
}
|
|
628
|
+
finally {
|
|
629
|
+
// Clean up syntax tree
|
|
630
|
+
tree.delete();
|
|
631
|
+
}
|
|
632
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Language, Parser } from "web-tree-sitter";
|
|
2
|
+
export type SupportedLanguage = "go" | "python" | "c" | "cpp" | "rust" | "typescript" | "javascript";
|
|
3
|
+
export declare const loadLanguage: (lang: SupportedLanguage) => Promise<Language>;
|
|
4
|
+
export declare const createParser: (lang: SupportedLanguage) => Promise<Parser>;
|
|
5
|
+
export declare const extensionToLanguage: (ext: string) => SupportedLanguage | null;
|
|
6
|
+
export declare const SUPPORTED_EXTENSIONS: Set<string>;
|
|
7
|
+
export declare const getLanguageFromPath: (filePath: string) => SupportedLanguage | null;
|
|
8
|
+
export declare const resetLoader: () => void;
|