Skip to content

Commit b9e89ff

Browse files
committed
feat: add function symbol indexing
1 parent c7bc265 commit b9e89ff

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

src/symbols.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import type { SyntaxNode } from "tree-sitter";
2+
import { childForField, parseCpp } from "./cpp-ast.js";
3+
import { readTextFile } from "./fs.js";
4+
import { normalizeNewlines } from "./text.js";
5+
import { resolveRawGithubUrl, resolveSourcePath } from "./source.js";
6+
import type { FunctionDefinition, FunctionParameter } from "./types.js";
7+
8+
export async function indexFunctions(
9+
rootDir: string,
10+
includeHelpers: readonly string[],
11+
sourceBranch: string,
12+
): Promise<ReadonlyMap<string, FunctionDefinition>> {
13+
const sourceFiles = [
14+
"src/core_api.cpp",
15+
"src/core_api_utils.cpp",
16+
"src/auth_manager.cpp",
17+
"src/collection.cpp",
18+
"src/collection_manager.cpp",
19+
"src/curation_index_manager.cpp",
20+
"src/field.cpp",
21+
"src/synonym_index_manager.cpp",
22+
"src/tsconfig.cpp",
23+
...includeHelpers,
24+
]
25+
.map((filePath) => resolveSourcePath(rootDir, filePath))
26+
.map((filePath) => resolveRawGithubUrl(sourceBranch, filePath));
27+
28+
const allDefinitions = (
29+
await Promise.all(
30+
sourceFiles.map(async (filePath) => {
31+
const sourceText = normalizeNewlines(await readTextFile(filePath));
32+
return extractFunctionDefinitions(filePath, sourceText);
33+
}),
34+
)
35+
).flat();
36+
37+
const functions = new Map<string, FunctionDefinition>();
38+
allDefinitions.forEach((definition) => {
39+
const overloadKey = serializeOverloadKey(definition.name, definition.parameters.length);
40+
if (!functions.has(overloadKey)) {
41+
functions.set(overloadKey, definition);
42+
}
43+
if (!functions.has(definition.name)) {
44+
functions.set(definition.name, definition);
45+
}
46+
});
47+
48+
return functions;
49+
}
50+
51+
function serializeOverloadKey(name: string, arity: number): string {
52+
return `${name}#${arity}`;
53+
}
54+
55+
function extractFunctionDefinitions(filePath: string, sourceText: string): readonly FunctionDefinition[] {
56+
const rootNode = parseCpp(sourceText);
57+
return collectFunctionNodes(rootNode).flatMap((functionNode) => {
58+
const definition = materializeFunctionDefinition(filePath, sourceText, functionNode);
59+
return definition !== undefined ? [definition] : [];
60+
});
61+
}
62+
63+
function collectFunctionNodes(node: SyntaxNode): readonly SyntaxNode[] {
64+
if (node.type === "function_definition") {
65+
return [node];
66+
}
67+
return node.namedChildren.flatMap(collectFunctionNodes);
68+
}
69+
70+
function materializeFunctionDefinition(
71+
filePath: string,
72+
sourceText: string,
73+
functionNode: SyntaxNode,
74+
): FunctionDefinition | undefined {
75+
const declaratorNode = childForField(functionNode, "declarator");
76+
const bodyNode = childForField(functionNode, "body");
77+
if (declaratorNode === undefined || bodyNode === undefined || bodyNode.type !== "compound_statement") {
78+
return undefined;
79+
}
80+
81+
const nameNode = childForField(declaratorNode, "declarator");
82+
const functionName = extractDeclaratorName(nameNode);
83+
if (functionName === undefined) {
84+
return undefined;
85+
}
86+
87+
return {
88+
name: functionName,
89+
filePath,
90+
declaration: sourceText.slice(functionNode.startIndex, bodyNode.startIndex).trim(),
91+
body: sourceText.slice(bodyNode.startIndex + 1, bodyNode.endIndex - 1),
92+
fullText: sourceText.slice(functionNode.startIndex, functionNode.endIndex),
93+
source: {
94+
filePath,
95+
line: functionNode.startPosition.row + 1,
96+
column: functionNode.startPosition.column + 1,
97+
},
98+
parameters: parseParametersFromDeclarator(declaratorNode),
99+
};
100+
}
101+
102+
function parseParametersFromDeclarator(declaratorNode: SyntaxNode): readonly FunctionParameter[] {
103+
const parameterListNode = childForField(declaratorNode, "parameters");
104+
if (parameterListNode === undefined) {
105+
return [];
106+
}
107+
108+
return parameterListNode.namedChildren
109+
.filter((parameterNode) => parameterNode.type === "parameter_declaration")
110+
.flatMap((parameterNode) => {
111+
const nestedDeclarator = childForField(parameterNode, "declarator");
112+
const name = extractDeclaratorName(nestedDeclarator);
113+
if (name === undefined) return [];
114+
const typeText =
115+
nestedDeclarator === undefined
116+
? parameterNode.text.trim()
117+
: parameterNode.text.slice(0, parameterNode.text.length - nestedDeclarator.text.length).trim();
118+
return [{ name, typeText }];
119+
});
120+
}
121+
122+
function extractDeclaratorName(node: SyntaxNode | undefined): string | undefined {
123+
if (node === undefined) {
124+
return undefined;
125+
}
126+
127+
if (node.type === "identifier" || node.type === "field_identifier") {
128+
return node.text;
129+
}
130+
131+
const nestedDeclarator = childForField(node, "declarator");
132+
if (nestedDeclarator !== undefined) {
133+
return extractDeclaratorName(nestedDeclarator);
134+
}
135+
136+
return node.namedChildren.find((child) => child.type === "identifier" || child.type === "field_identifier")?.text;
137+
}

0 commit comments

Comments
 (0)