pi-read-map 1.0.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/LICENSE +21 -0
- package/README.md +191 -0
- package/package.json +61 -0
- package/scripts/go_outline.go +239 -0
- package/scripts/python_outline.py +205 -0
- package/src/constants.ts +21 -0
- package/src/enums.ts +37 -0
- package/src/formatter.ts +414 -0
- package/src/index.ts +259 -0
- package/src/language-detect.ts +88 -0
- package/src/mapper.ts +116 -0
- package/src/mappers/c.ts +236 -0
- package/src/mappers/cpp.ts +850 -0
- package/src/mappers/csv.ts +144 -0
- package/src/mappers/ctags.ts +318 -0
- package/src/mappers/fallback.ts +162 -0
- package/src/mappers/go.ts +218 -0
- package/src/mappers/json.ts +183 -0
- package/src/mappers/jsonl.ts +135 -0
- package/src/mappers/markdown.ts +183 -0
- package/src/mappers/python.ts +138 -0
- package/src/mappers/rust.ts +886 -0
- package/src/mappers/sql.ts +195 -0
- package/src/mappers/toml.ts +191 -0
- package/src/mappers/typescript.ts +729 -0
- package/src/mappers/yaml.ts +185 -0
- package/src/types.ts +91 -0
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Rust mapper using tree-sitter for AST extraction.
|
|
4
|
+
*
|
|
5
|
+
* Ported from codemap's symbols-rust.ts.
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
|
|
9
|
+
import type { FileMap, FileSymbol } from "../types.js";
|
|
10
|
+
|
|
11
|
+
import { DetailLevel, SymbolKind } from "../enums.js";
|
|
12
|
+
|
|
13
|
+
type ScopeKind = "mod" | "struct" | "enum" | "trait" | "impl";
|
|
14
|
+
|
|
15
|
+
interface Scope {
|
|
16
|
+
kind: ScopeKind;
|
|
17
|
+
name: string;
|
|
18
|
+
key: string;
|
|
19
|
+
implTarget?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface InternalSymbol {
|
|
23
|
+
name: string;
|
|
24
|
+
kind: string;
|
|
25
|
+
signature?: string;
|
|
26
|
+
startLine: number;
|
|
27
|
+
endLine: number;
|
|
28
|
+
exported: boolean;
|
|
29
|
+
isAsync: boolean;
|
|
30
|
+
isStatic: boolean;
|
|
31
|
+
parentName?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Lazy-loaded parser
|
|
35
|
+
let parser: import("tree-sitter") | null = null;
|
|
36
|
+
let parserInitialized = false;
|
|
37
|
+
|
|
38
|
+
function ensureWritableTypeProperty(parserCtor: unknown): void {
|
|
39
|
+
const syntaxNode = (parserCtor as { SyntaxNode?: { prototype?: object } })
|
|
40
|
+
.SyntaxNode;
|
|
41
|
+
const proto = syntaxNode?.prototype;
|
|
42
|
+
if (!proto) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const desc = Object.getOwnPropertyDescriptor(proto, "type");
|
|
46
|
+
if (!desc || desc.set) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
Object.defineProperty(proto, "type", { ...desc, set: () => {} });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getParser(): import("tree-sitter") | null {
|
|
53
|
+
if (parserInitialized) {
|
|
54
|
+
return parser;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
parserInitialized = true;
|
|
58
|
+
|
|
59
|
+
// Check if running in Bun (tree-sitter has issues with Bun)
|
|
60
|
+
const isBun = typeof (globalThis as { Bun?: unknown }).Bun !== "undefined";
|
|
61
|
+
if (isBun) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const require = createRequire(import.meta.url);
|
|
67
|
+
const ParserCtor = require("tree-sitter") as typeof import("tree-sitter");
|
|
68
|
+
const Rust = require("tree-sitter-rust") as import("tree-sitter").Language;
|
|
69
|
+
ensureWritableTypeProperty(ParserCtor);
|
|
70
|
+
parser = new ParserCtor();
|
|
71
|
+
parser.setLanguage(Rust);
|
|
72
|
+
return parser;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeWhitespace(value: string): string {
|
|
79
|
+
return value.replaceAll(/\s+/g, " ").trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getNodeText(
|
|
83
|
+
node: import("tree-sitter").SyntaxNode,
|
|
84
|
+
source: string
|
|
85
|
+
): string {
|
|
86
|
+
return source.slice(node.startIndex, node.endIndex);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatSignature(
|
|
90
|
+
node: import("tree-sitter").SyntaxNode,
|
|
91
|
+
source: string,
|
|
92
|
+
opts?: { cutAtParen?: boolean }
|
|
93
|
+
): string {
|
|
94
|
+
let text = getNodeText(node, source);
|
|
95
|
+
const braceIndex = text.indexOf("{");
|
|
96
|
+
if (braceIndex !== -1) {
|
|
97
|
+
text = text.slice(0, braceIndex);
|
|
98
|
+
} else if (opts?.cutAtParen) {
|
|
99
|
+
const parenIndex = text.indexOf("(");
|
|
100
|
+
if (parenIndex !== -1) {
|
|
101
|
+
text = text.slice(0, parenIndex);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
text = text.replace(/;\s*$/, "");
|
|
105
|
+
return normalizeWhitespace(text);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getLineRange(node: import("tree-sitter").SyntaxNode): {
|
|
109
|
+
startLine: number;
|
|
110
|
+
endLine: number;
|
|
111
|
+
} {
|
|
112
|
+
return {
|
|
113
|
+
startLine: node.startPosition.row + 1,
|
|
114
|
+
endLine: node.endPosition.row + 1,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function findFirstDescendant(
|
|
119
|
+
node: import("tree-sitter").SyntaxNode,
|
|
120
|
+
types: string[]
|
|
121
|
+
): import("tree-sitter").SyntaxNode | null {
|
|
122
|
+
const stack = [...node.namedChildren];
|
|
123
|
+
while (stack.length > 0) {
|
|
124
|
+
const current = stack.pop();
|
|
125
|
+
if (!current) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (types.includes(current.type)) {
|
|
129
|
+
return current;
|
|
130
|
+
}
|
|
131
|
+
for (const child of current.namedChildren) {
|
|
132
|
+
stack.push(child);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasVisibilityModifier(
|
|
139
|
+
node: import("tree-sitter").SyntaxNode
|
|
140
|
+
): boolean {
|
|
141
|
+
return node.namedChildren.some(
|
|
142
|
+
(child) => child.type === "visibility_modifier"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function extractNameFromField(
|
|
147
|
+
node: import("tree-sitter").SyntaxNode,
|
|
148
|
+
field: string,
|
|
149
|
+
source: string
|
|
150
|
+
): string | null {
|
|
151
|
+
const nameNode = node.childForFieldName(field);
|
|
152
|
+
if (!nameNode) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return normalizeWhitespace(getNodeText(nameNode, source));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function extractTypeName(
|
|
159
|
+
node: import("tree-sitter").SyntaxNode,
|
|
160
|
+
source: string
|
|
161
|
+
): string | null {
|
|
162
|
+
switch (node.type) {
|
|
163
|
+
case "type_identifier":
|
|
164
|
+
case "identifier":
|
|
165
|
+
case "self":
|
|
166
|
+
case "super":
|
|
167
|
+
case "crate":
|
|
168
|
+
case "metavariable": {
|
|
169
|
+
return normalizeWhitespace(getNodeText(node, source));
|
|
170
|
+
}
|
|
171
|
+
case "scoped_type_identifier":
|
|
172
|
+
case "scoped_identifier": {
|
|
173
|
+
const pathNode = node.childForFieldName("path");
|
|
174
|
+
const nameNode = node.childForFieldName("name");
|
|
175
|
+
const pathText = pathNode ? extractTypeName(pathNode, source) : null;
|
|
176
|
+
const nameText = nameNode ? extractTypeName(nameNode, source) : null;
|
|
177
|
+
if (!nameText) {
|
|
178
|
+
return pathText;
|
|
179
|
+
}
|
|
180
|
+
return pathText ? `${pathText}::${nameText}` : nameText;
|
|
181
|
+
}
|
|
182
|
+
case "generic_type":
|
|
183
|
+
case "generic_type_with_turbofish": {
|
|
184
|
+
const typeNode = node.childForFieldName("type");
|
|
185
|
+
return typeNode ? extractTypeName(typeNode, source) : null;
|
|
186
|
+
}
|
|
187
|
+
case "reference_type":
|
|
188
|
+
case "pointer_type": {
|
|
189
|
+
const inner = node.childForFieldName("type");
|
|
190
|
+
return inner ? extractTypeName(inner, source) : null;
|
|
191
|
+
}
|
|
192
|
+
default: {
|
|
193
|
+
const candidate = findFirstDescendant(node, [
|
|
194
|
+
"scoped_type_identifier",
|
|
195
|
+
"scoped_identifier",
|
|
196
|
+
"type_identifier",
|
|
197
|
+
"identifier",
|
|
198
|
+
]);
|
|
199
|
+
return candidate ? extractTypeName(candidate, source) : null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function splitPathParts(text: string): string[] {
|
|
205
|
+
return text
|
|
206
|
+
.split("::")
|
|
207
|
+
.map((part) => part.trim())
|
|
208
|
+
.filter(Boolean);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getModuleKey(scopeStack: Scope[]): string | null {
|
|
212
|
+
for (let i = scopeStack.length - 1; i >= 0; i -= 1) {
|
|
213
|
+
const scope = scopeStack[i];
|
|
214
|
+
if (scope && scope.kind === "mod") {
|
|
215
|
+
return scope.key;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeTypePath(path: string, moduleKey: string | null): string {
|
|
222
|
+
const parts = splitPathParts(path);
|
|
223
|
+
const moduleParts = moduleKey ? splitPathParts(moduleKey) : [];
|
|
224
|
+
|
|
225
|
+
if (parts.length === 0) {
|
|
226
|
+
return moduleParts.join("::");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (parts[0] === "crate") {
|
|
230
|
+
return parts.slice(1).join("::");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (parts[0] === "self") {
|
|
234
|
+
return [...moduleParts, ...parts.slice(1)].join("::");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let superCount = 0;
|
|
238
|
+
while (parts[superCount] === "super") {
|
|
239
|
+
superCount += 1;
|
|
240
|
+
}
|
|
241
|
+
if (superCount > 0) {
|
|
242
|
+
const trimmed = moduleParts.slice(
|
|
243
|
+
0,
|
|
244
|
+
Math.max(0, moduleParts.length - superCount)
|
|
245
|
+
);
|
|
246
|
+
return [...trimmed, ...parts.slice(superCount)].join("::");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (moduleParts.length === 0) {
|
|
250
|
+
return parts.join("::");
|
|
251
|
+
}
|
|
252
|
+
return [...moduleParts, ...parts].join("::");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Extract Rust symbols using tree-sitter.
|
|
257
|
+
*/
|
|
258
|
+
function extractRustSymbols(content: string): InternalSymbol[] {
|
|
259
|
+
const p = getParser();
|
|
260
|
+
if (!p) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let tree: import("tree-sitter").Tree;
|
|
265
|
+
try {
|
|
266
|
+
tree = p.parse(content);
|
|
267
|
+
} catch {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const symbols: InternalSymbol[] = [];
|
|
272
|
+
const scopeStack: Scope[] = [];
|
|
273
|
+
|
|
274
|
+
const currentScope = (kind: ScopeKind): Scope | undefined => {
|
|
275
|
+
for (let i = scopeStack.length - 1; i >= 0; i -= 1) {
|
|
276
|
+
const scope = scopeStack[i];
|
|
277
|
+
if (scope && scope.kind === kind) {
|
|
278
|
+
return scope;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return undefined;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const addSymbol = (entry: InternalSymbol): void => {
|
|
285
|
+
symbols.push(entry);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const handleMod = (node: import("tree-sitter").SyntaxNode): void => {
|
|
289
|
+
const name = extractNameFromField(node, "name", content);
|
|
290
|
+
if (!name) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const signature = formatSignature(node, content);
|
|
294
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
295
|
+
const parentKey = moduleKey ?? undefined;
|
|
296
|
+
const key = parentKey ? `${parentKey}::${name}` : name;
|
|
297
|
+
const { startLine, endLine } = getLineRange(node);
|
|
298
|
+
|
|
299
|
+
addSymbol({
|
|
300
|
+
name,
|
|
301
|
+
kind: "namespace",
|
|
302
|
+
signature,
|
|
303
|
+
startLine,
|
|
304
|
+
endLine,
|
|
305
|
+
exported: hasVisibilityModifier(node),
|
|
306
|
+
isAsync: false,
|
|
307
|
+
isStatic: false,
|
|
308
|
+
parentName: parentKey,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const body = node.childForFieldName("body");
|
|
312
|
+
if (!body) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
scopeStack.push({ kind: "mod", name, key });
|
|
316
|
+
for (const child of body.namedChildren) {
|
|
317
|
+
visit(child);
|
|
318
|
+
}
|
|
319
|
+
scopeStack.pop();
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handleStruct = (node: import("tree-sitter").SyntaxNode): void => {
|
|
323
|
+
const name = extractNameFromField(node, "name", content);
|
|
324
|
+
if (!name) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const signature = formatSignature(node, content, { cutAtParen: true });
|
|
328
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
329
|
+
const parentKey = moduleKey ?? undefined;
|
|
330
|
+
const key = parentKey ? `${parentKey}::${name}` : name;
|
|
331
|
+
const { startLine, endLine } = getLineRange(node);
|
|
332
|
+
|
|
333
|
+
addSymbol({
|
|
334
|
+
name,
|
|
335
|
+
kind: "struct",
|
|
336
|
+
signature,
|
|
337
|
+
startLine,
|
|
338
|
+
endLine,
|
|
339
|
+
exported: hasVisibilityModifier(node),
|
|
340
|
+
isAsync: false,
|
|
341
|
+
isStatic: false,
|
|
342
|
+
parentName: parentKey,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const body = node.childForFieldName("body");
|
|
346
|
+
if (!body) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
scopeStack.push({ kind: "struct", name, key });
|
|
350
|
+
for (const child of body.namedChildren) {
|
|
351
|
+
visit(child);
|
|
352
|
+
}
|
|
353
|
+
scopeStack.pop();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const handleEnum = (node: import("tree-sitter").SyntaxNode): void => {
|
|
357
|
+
const name = extractNameFromField(node, "name", content);
|
|
358
|
+
if (!name) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const signature = formatSignature(node, content, { cutAtParen: true });
|
|
362
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
363
|
+
const parentKey = moduleKey ?? undefined;
|
|
364
|
+
const key = parentKey ? `${parentKey}::${name}` : name;
|
|
365
|
+
const { startLine, endLine } = getLineRange(node);
|
|
366
|
+
|
|
367
|
+
addSymbol({
|
|
368
|
+
name,
|
|
369
|
+
kind: "enum",
|
|
370
|
+
signature,
|
|
371
|
+
startLine,
|
|
372
|
+
endLine,
|
|
373
|
+
exported: hasVisibilityModifier(node),
|
|
374
|
+
isAsync: false,
|
|
375
|
+
isStatic: false,
|
|
376
|
+
parentName: parentKey,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const body = node.childForFieldName("body");
|
|
380
|
+
if (!body) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
scopeStack.push({ kind: "enum", name, key });
|
|
384
|
+
for (const child of body.namedChildren) {
|
|
385
|
+
visit(child);
|
|
386
|
+
}
|
|
387
|
+
scopeStack.pop();
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const handleTrait = (node: import("tree-sitter").SyntaxNode): void => {
|
|
391
|
+
const name = extractNameFromField(node, "name", content);
|
|
392
|
+
if (!name) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const signature = formatSignature(node, content);
|
|
396
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
397
|
+
const parentKey = moduleKey ?? undefined;
|
|
398
|
+
const key = parentKey ? `${parentKey}::${name}` : name;
|
|
399
|
+
const { startLine, endLine } = getLineRange(node);
|
|
400
|
+
|
|
401
|
+
addSymbol({
|
|
402
|
+
name,
|
|
403
|
+
kind: "trait",
|
|
404
|
+
signature,
|
|
405
|
+
startLine,
|
|
406
|
+
endLine,
|
|
407
|
+
exported: hasVisibilityModifier(node),
|
|
408
|
+
isAsync: false,
|
|
409
|
+
isStatic: false,
|
|
410
|
+
parentName: parentKey,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const body = node.childForFieldName("body");
|
|
414
|
+
if (!body) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
scopeStack.push({ kind: "trait", name, key });
|
|
418
|
+
for (const child of body.namedChildren) {
|
|
419
|
+
visit(child);
|
|
420
|
+
}
|
|
421
|
+
scopeStack.pop();
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const handleImpl = (node: import("tree-sitter").SyntaxNode): void => {
|
|
425
|
+
const typeNode = node.childForFieldName("type");
|
|
426
|
+
if (!typeNode) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const typeName = extractTypeName(typeNode, content);
|
|
430
|
+
if (!typeName) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
434
|
+
const implTarget = normalizeTypePath(typeName, moduleKey);
|
|
435
|
+
const body = node.childForFieldName("body");
|
|
436
|
+
if (!body) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
scopeStack.push({
|
|
441
|
+
kind: "impl",
|
|
442
|
+
name: typeName,
|
|
443
|
+
key: implTarget,
|
|
444
|
+
implTarget,
|
|
445
|
+
});
|
|
446
|
+
for (const child of body.namedChildren) {
|
|
447
|
+
visit(child);
|
|
448
|
+
}
|
|
449
|
+
scopeStack.pop();
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const handleFunction = (node: import("tree-sitter").SyntaxNode): void => {
|
|
453
|
+
const name = extractNameFromField(node, "name", content);
|
|
454
|
+
if (!name) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const signature = formatSignature(node, content);
|
|
458
|
+
const implScope = currentScope("impl");
|
|
459
|
+
const traitScope = currentScope("trait");
|
|
460
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
461
|
+
const parentName =
|
|
462
|
+
implScope?.implTarget ?? traitScope?.key ?? moduleKey ?? undefined;
|
|
463
|
+
const kind: string = implScope || traitScope ? "method" : "function";
|
|
464
|
+
const { startLine, endLine } = getLineRange(node);
|
|
465
|
+
const isAsync = /\basync\b/.test(signature);
|
|
466
|
+
|
|
467
|
+
addSymbol({
|
|
468
|
+
name,
|
|
469
|
+
kind,
|
|
470
|
+
signature,
|
|
471
|
+
startLine,
|
|
472
|
+
endLine,
|
|
473
|
+
exported: hasVisibilityModifier(node),
|
|
474
|
+
isAsync,
|
|
475
|
+
isStatic: false,
|
|
476
|
+
parentName,
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const handleConst = (node: import("tree-sitter").SyntaxNode): void => {
|
|
481
|
+
const name = extractNameFromField(node, "name", content);
|
|
482
|
+
if (!name) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const signature = formatSignature(node, content);
|
|
486
|
+
const implScope = currentScope("impl");
|
|
487
|
+
const traitScope = currentScope("trait");
|
|
488
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
489
|
+
const parentName =
|
|
490
|
+
implScope?.implTarget ?? traitScope?.key ?? moduleKey ?? undefined;
|
|
491
|
+
const { startLine, endLine } = getLineRange(node);
|
|
492
|
+
|
|
493
|
+
addSymbol({
|
|
494
|
+
name,
|
|
495
|
+
kind: "variable",
|
|
496
|
+
signature,
|
|
497
|
+
startLine,
|
|
498
|
+
endLine,
|
|
499
|
+
exported: hasVisibilityModifier(node),
|
|
500
|
+
isAsync: false,
|
|
501
|
+
isStatic: false,
|
|
502
|
+
parentName,
|
|
503
|
+
});
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const handleStatic = (node: import("tree-sitter").SyntaxNode): void => {
|
|
507
|
+
const name = extractNameFromField(node, "name", content);
|
|
508
|
+
if (!name) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const signature = formatSignature(node, content);
|
|
512
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
513
|
+
const parentName = moduleKey ?? undefined;
|
|
514
|
+
const { startLine, endLine } = getLineRange(node);
|
|
515
|
+
|
|
516
|
+
addSymbol({
|
|
517
|
+
name,
|
|
518
|
+
kind: "variable",
|
|
519
|
+
signature,
|
|
520
|
+
startLine,
|
|
521
|
+
endLine,
|
|
522
|
+
exported: hasVisibilityModifier(node),
|
|
523
|
+
isAsync: false,
|
|
524
|
+
isStatic: true,
|
|
525
|
+
parentName,
|
|
526
|
+
});
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const handleType = (node: import("tree-sitter").SyntaxNode): void => {
|
|
530
|
+
const name = extractNameFromField(node, "name", content);
|
|
531
|
+
if (!name) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const signature = formatSignature(node, content);
|
|
535
|
+
const implScope = currentScope("impl");
|
|
536
|
+
const traitScope = currentScope("trait");
|
|
537
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
538
|
+
const parentName =
|
|
539
|
+
implScope?.implTarget ?? traitScope?.key ?? moduleKey ?? undefined;
|
|
540
|
+
const { startLine, endLine } = getLineRange(node);
|
|
541
|
+
|
|
542
|
+
addSymbol({
|
|
543
|
+
name,
|
|
544
|
+
kind: "type",
|
|
545
|
+
signature,
|
|
546
|
+
startLine,
|
|
547
|
+
endLine,
|
|
548
|
+
exported: hasVisibilityModifier(node),
|
|
549
|
+
isAsync: false,
|
|
550
|
+
isStatic: false,
|
|
551
|
+
parentName,
|
|
552
|
+
});
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const handleMacro = (node: import("tree-sitter").SyntaxNode): void => {
|
|
556
|
+
const name = extractNameFromField(node, "name", content);
|
|
557
|
+
if (!name) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const signature = formatSignature(node, content, { cutAtParen: true });
|
|
561
|
+
const moduleKey = getModuleKey(scopeStack);
|
|
562
|
+
const parentName = moduleKey ?? undefined;
|
|
563
|
+
const { startLine, endLine } = getLineRange(node);
|
|
564
|
+
|
|
565
|
+
addSymbol({
|
|
566
|
+
name,
|
|
567
|
+
kind: "macro",
|
|
568
|
+
signature,
|
|
569
|
+
startLine,
|
|
570
|
+
endLine,
|
|
571
|
+
exported: false,
|
|
572
|
+
isAsync: false,
|
|
573
|
+
isStatic: false,
|
|
574
|
+
parentName,
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const handleField = (node: import("tree-sitter").SyntaxNode): void => {
|
|
579
|
+
const structScope = currentScope("struct");
|
|
580
|
+
if (!structScope) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const name = extractNameFromField(node, "name", content);
|
|
584
|
+
if (!name) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const signature = formatSignature(node, content);
|
|
588
|
+
const { startLine, endLine } = getLineRange(node);
|
|
589
|
+
|
|
590
|
+
addSymbol({
|
|
591
|
+
name,
|
|
592
|
+
kind: "property",
|
|
593
|
+
signature,
|
|
594
|
+
startLine,
|
|
595
|
+
endLine,
|
|
596
|
+
exported: hasVisibilityModifier(node),
|
|
597
|
+
isAsync: false,
|
|
598
|
+
isStatic: false,
|
|
599
|
+
parentName: structScope.key,
|
|
600
|
+
});
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const handleEnumVariant = (node: import("tree-sitter").SyntaxNode): void => {
|
|
604
|
+
const enumScope = currentScope("enum");
|
|
605
|
+
if (!enumScope) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const name = extractNameFromField(node, "name", content);
|
|
609
|
+
if (!name) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
let signature = normalizeWhitespace(getNodeText(node, content));
|
|
613
|
+
signature = signature.replace(/,\s*$/, "");
|
|
614
|
+
const { startLine, endLine } = getLineRange(node);
|
|
615
|
+
|
|
616
|
+
addSymbol({
|
|
617
|
+
name,
|
|
618
|
+
kind: "enum_member",
|
|
619
|
+
signature,
|
|
620
|
+
startLine,
|
|
621
|
+
endLine,
|
|
622
|
+
exported: hasVisibilityModifier(node),
|
|
623
|
+
isAsync: false,
|
|
624
|
+
isStatic: false,
|
|
625
|
+
parentName: enumScope.key,
|
|
626
|
+
});
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const visit = (node: import("tree-sitter").SyntaxNode): void => {
|
|
630
|
+
switch (node.type) {
|
|
631
|
+
case "use_declaration": {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
case "mod_item": {
|
|
635
|
+
handleMod(node);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
case "struct_item": {
|
|
639
|
+
handleStruct(node);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
case "enum_item": {
|
|
643
|
+
handleEnum(node);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
case "trait_item": {
|
|
647
|
+
handleTrait(node);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
case "impl_item": {
|
|
651
|
+
handleImpl(node);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
case "function_item":
|
|
655
|
+
case "function_signature_item": {
|
|
656
|
+
handleFunction(node);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
case "const_item": {
|
|
660
|
+
handleConst(node);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
case "static_item": {
|
|
664
|
+
handleStatic(node);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
case "type_item": {
|
|
668
|
+
handleType(node);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
case "macro_definition": {
|
|
672
|
+
handleMacro(node);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
case "field_declaration": {
|
|
676
|
+
handleField(node);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
case "enum_variant": {
|
|
680
|
+
handleEnumVariant(node);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
default: {
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
for (const child of node.namedChildren) {
|
|
689
|
+
visit(child);
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
visit(tree.rootNode);
|
|
694
|
+
|
|
695
|
+
return symbols;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Extract use statements for imports list.
|
|
700
|
+
*/
|
|
701
|
+
function extractUseStatements(content: string): string[] {
|
|
702
|
+
const p = getParser();
|
|
703
|
+
if (!p) {
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
let tree: import("tree-sitter").Tree;
|
|
708
|
+
try {
|
|
709
|
+
tree = p.parse(content);
|
|
710
|
+
} catch {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const imports: string[] = [];
|
|
715
|
+
|
|
716
|
+
const visit = (node: import("tree-sitter").SyntaxNode): void => {
|
|
717
|
+
if (node.type === "use_declaration") {
|
|
718
|
+
const arg = node.childForFieldName("argument");
|
|
719
|
+
if (arg) {
|
|
720
|
+
const text = normalizeWhitespace(getNodeText(arg, content));
|
|
721
|
+
imports.push(text);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
for (const child of node.namedChildren) {
|
|
725
|
+
visit(child);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
visit(tree.rootNode);
|
|
730
|
+
return imports;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Map internal symbol kinds to our SymbolKind enum.
|
|
735
|
+
*/
|
|
736
|
+
function mapKind(kind: string): SymbolKind {
|
|
737
|
+
switch (kind) {
|
|
738
|
+
case "struct": {
|
|
739
|
+
return SymbolKind.Class;
|
|
740
|
+
}
|
|
741
|
+
case "trait": {
|
|
742
|
+
return SymbolKind.Interface;
|
|
743
|
+
}
|
|
744
|
+
case "function": {
|
|
745
|
+
return SymbolKind.Function;
|
|
746
|
+
}
|
|
747
|
+
case "method": {
|
|
748
|
+
return SymbolKind.Method;
|
|
749
|
+
}
|
|
750
|
+
case "variable":
|
|
751
|
+
case "property": {
|
|
752
|
+
return SymbolKind.Variable;
|
|
753
|
+
}
|
|
754
|
+
case "type": {
|
|
755
|
+
return SymbolKind.Type;
|
|
756
|
+
}
|
|
757
|
+
case "enum": {
|
|
758
|
+
return SymbolKind.Enum;
|
|
759
|
+
}
|
|
760
|
+
case "enum_member": {
|
|
761
|
+
return SymbolKind.Variable;
|
|
762
|
+
}
|
|
763
|
+
case "namespace": {
|
|
764
|
+
return SymbolKind.Class; // mod as namespace
|
|
765
|
+
}
|
|
766
|
+
case "macro": {
|
|
767
|
+
return SymbolKind.Function;
|
|
768
|
+
}
|
|
769
|
+
default: {
|
|
770
|
+
return SymbolKind.Unknown;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Convert internal symbols to FileSymbol format.
|
|
777
|
+
* Groups children under their parents.
|
|
778
|
+
*/
|
|
779
|
+
function convertSymbols(internalSymbols: InternalSymbol[]): FileSymbol[] {
|
|
780
|
+
const symbolMap = new Map<string, FileSymbol>();
|
|
781
|
+
const rootSymbols: FileSymbol[] = [];
|
|
782
|
+
|
|
783
|
+
// First pass: create all symbols
|
|
784
|
+
for (const is of internalSymbols) {
|
|
785
|
+
const symbol: FileSymbol = {
|
|
786
|
+
name: is.name,
|
|
787
|
+
kind: mapKind(is.kind),
|
|
788
|
+
startLine: is.startLine,
|
|
789
|
+
endLine: is.endLine,
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
if (is.signature) {
|
|
793
|
+
symbol.signature = is.signature;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const modifiers: string[] = [];
|
|
797
|
+
if (is.isAsync) {
|
|
798
|
+
modifiers.push("async");
|
|
799
|
+
}
|
|
800
|
+
if (is.isStatic) {
|
|
801
|
+
modifiers.push("static");
|
|
802
|
+
}
|
|
803
|
+
if (is.exported) {
|
|
804
|
+
modifiers.push("pub");
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (modifiers.length > 0) {
|
|
808
|
+
symbol.modifiers = modifiers;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Store with a key for parent lookup
|
|
812
|
+
symbolMap.set(is.name, symbol);
|
|
813
|
+
|
|
814
|
+
if (is.parentName && symbolMap.has(is.parentName)) {
|
|
815
|
+
// Add as child of parent
|
|
816
|
+
const parent = symbolMap.get(is.parentName);
|
|
817
|
+
if (parent) {
|
|
818
|
+
if (!parent.children) {
|
|
819
|
+
parent.children = [];
|
|
820
|
+
}
|
|
821
|
+
parent.children.push(symbol);
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
rootSymbols.push(symbol);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return rootSymbols;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Generate a file map for Rust files using tree-sitter.
|
|
833
|
+
*/
|
|
834
|
+
export async function rustMapper(
|
|
835
|
+
filePath: string,
|
|
836
|
+
signal?: AbortSignal
|
|
837
|
+
): Promise<FileMap | null> {
|
|
838
|
+
try {
|
|
839
|
+
// Check if parser is available
|
|
840
|
+
if (!getParser()) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const stats = await stat(filePath);
|
|
845
|
+
const totalBytes = stats.size;
|
|
846
|
+
|
|
847
|
+
// Read file content
|
|
848
|
+
const content = await readFile(filePath, "utf8");
|
|
849
|
+
|
|
850
|
+
// Check for abort
|
|
851
|
+
if (signal?.aborted) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Extract symbols
|
|
856
|
+
const internalSymbols = extractRustSymbols(content);
|
|
857
|
+
|
|
858
|
+
if (internalSymbols.length === 0) {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Convert to FileSymbols
|
|
863
|
+
const symbols = convertSymbols(internalSymbols);
|
|
864
|
+
|
|
865
|
+
// Extract imports
|
|
866
|
+
const imports = extractUseStatements(content);
|
|
867
|
+
|
|
868
|
+
const totalLines = content.split("\n").length;
|
|
869
|
+
|
|
870
|
+
return {
|
|
871
|
+
path: filePath,
|
|
872
|
+
totalLines,
|
|
873
|
+
totalBytes,
|
|
874
|
+
language: "Rust",
|
|
875
|
+
symbols,
|
|
876
|
+
imports,
|
|
877
|
+
detailLevel: DetailLevel.Full,
|
|
878
|
+
};
|
|
879
|
+
} catch (error) {
|
|
880
|
+
if (signal?.aborted) {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
console.error(`Rust mapper failed: ${error}`);
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
}
|