lat.md 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/check.d.ts +7 -5
- package/dist/src/cli/check.js +243 -74
- package/dist/src/cli/context.d.ts +3 -7
- package/dist/src/cli/context.js +15 -1
- package/dist/src/cli/expand.d.ts +7 -0
- package/dist/src/cli/expand.js +92 -0
- package/dist/src/cli/gen.js +11 -4
- package/dist/src/cli/hook.d.ts +1 -0
- package/dist/src/cli/hook.js +147 -0
- package/dist/src/cli/index.js +77 -28
- package/dist/src/cli/init.js +148 -120
- package/dist/src/cli/locate.d.ts +2 -2
- package/dist/src/cli/locate.js +9 -4
- package/dist/src/cli/refs.d.ts +20 -4
- package/dist/src/cli/refs.js +64 -42
- package/dist/src/cli/search.d.ts +25 -3
- package/dist/src/cli/search.js +87 -47
- package/dist/src/cli/section.d.ts +26 -0
- package/dist/src/cli/section.js +133 -0
- package/dist/src/code-refs.js +2 -1
- package/dist/src/config.js +3 -2
- package/dist/src/context.d.ts +21 -0
- package/dist/src/context.js +11 -0
- package/dist/src/format.d.ts +4 -3
- package/dist/src/format.js +16 -20
- package/dist/src/init-version.d.ts +10 -0
- package/dist/src/init-version.js +49 -0
- package/dist/src/lattice.d.ts +11 -5
- package/dist/src/lattice.js +87 -38
- package/dist/src/mcp/server.js +27 -279
- package/dist/src/search/index.js +5 -4
- package/dist/src/source-parser.d.ts +23 -0
- package/dist/src/source-parser.js +720 -0
- package/package.json +3 -1
- package/templates/AGENTS.md +38 -6
- package/templates/cursor-rules.md +11 -5
- package/templates/lat-prompt-hook.sh +2 -2
- package/dist/src/cli/prompt.d.ts +0 -2
- package/dist/src/cli/prompt.js +0 -60
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { Parser, Language, } from 'web-tree-sitter';
|
|
5
|
+
// Lazy singleton for the parser
|
|
6
|
+
let parserReady = null;
|
|
7
|
+
let parserInstance = null;
|
|
8
|
+
const languages = new Map();
|
|
9
|
+
function wasmDir() {
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const pkgPath = require.resolve('@repomix/tree-sitter-wasms/package.json');
|
|
12
|
+
return join(dirname(pkgPath), 'out');
|
|
13
|
+
}
|
|
14
|
+
async function ensureParser() {
|
|
15
|
+
if (!parserReady) {
|
|
16
|
+
parserReady = Parser.init();
|
|
17
|
+
}
|
|
18
|
+
await parserReady;
|
|
19
|
+
if (!parserInstance) {
|
|
20
|
+
parserInstance = new Parser();
|
|
21
|
+
}
|
|
22
|
+
return parserInstance;
|
|
23
|
+
}
|
|
24
|
+
async function getLanguage(ext) {
|
|
25
|
+
const grammarMap = {
|
|
26
|
+
'.ts': 'tree-sitter-typescript.wasm',
|
|
27
|
+
'.tsx': 'tree-sitter-tsx.wasm',
|
|
28
|
+
'.js': 'tree-sitter-javascript.wasm',
|
|
29
|
+
'.jsx': 'tree-sitter-javascript.wasm',
|
|
30
|
+
'.py': 'tree-sitter-python.wasm',
|
|
31
|
+
'.rs': 'tree-sitter-rust.wasm',
|
|
32
|
+
'.go': 'tree-sitter-go.wasm',
|
|
33
|
+
'.c': 'tree-sitter-c.wasm',
|
|
34
|
+
'.h': 'tree-sitter-c.wasm',
|
|
35
|
+
};
|
|
36
|
+
const wasmFile = grammarMap[ext];
|
|
37
|
+
if (!wasmFile)
|
|
38
|
+
return null;
|
|
39
|
+
// Ensure WASM runtime is initialized before loading languages
|
|
40
|
+
await ensureParser();
|
|
41
|
+
if (!languages.has(wasmFile)) {
|
|
42
|
+
const wasmPath = join(wasmDir(), wasmFile);
|
|
43
|
+
const lang = await Language.load(wasmPath);
|
|
44
|
+
languages.set(wasmFile, lang);
|
|
45
|
+
}
|
|
46
|
+
return languages.get(wasmFile);
|
|
47
|
+
}
|
|
48
|
+
function extractName(node) {
|
|
49
|
+
const nameNode = node.childForFieldName('name');
|
|
50
|
+
return nameNode ? nameNode.text : null;
|
|
51
|
+
}
|
|
52
|
+
function extractTsSymbols(tree) {
|
|
53
|
+
const symbols = [];
|
|
54
|
+
const root = tree.rootNode;
|
|
55
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
56
|
+
let node = root.child(i);
|
|
57
|
+
// Unwrap export_statement to get the inner declaration
|
|
58
|
+
const isExport = node.type === 'export_statement';
|
|
59
|
+
if (isExport) {
|
|
60
|
+
const inner = node.namedChildren.find((c) => c.type === 'function_declaration' ||
|
|
61
|
+
c.type === 'class_declaration' ||
|
|
62
|
+
c.type === 'lexical_declaration' ||
|
|
63
|
+
c.type === 'type_alias_declaration' ||
|
|
64
|
+
c.type === 'interface_declaration' ||
|
|
65
|
+
c.type === 'abstract_class_declaration');
|
|
66
|
+
if (inner)
|
|
67
|
+
node = inner;
|
|
68
|
+
}
|
|
69
|
+
const startLine = node.startPosition.row + 1;
|
|
70
|
+
const endLine = node.endPosition.row + 1;
|
|
71
|
+
if (node.type === 'function_declaration' ||
|
|
72
|
+
node.type === 'generator_function_declaration') {
|
|
73
|
+
const name = extractName(node);
|
|
74
|
+
if (name) {
|
|
75
|
+
symbols.push({
|
|
76
|
+
name,
|
|
77
|
+
kind: 'function',
|
|
78
|
+
startLine,
|
|
79
|
+
endLine,
|
|
80
|
+
signature: firstLine(node.text),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (node.type === 'class_declaration' ||
|
|
85
|
+
node.type === 'abstract_class_declaration') {
|
|
86
|
+
const name = extractName(node);
|
|
87
|
+
if (name) {
|
|
88
|
+
symbols.push({
|
|
89
|
+
name,
|
|
90
|
+
kind: 'class',
|
|
91
|
+
startLine,
|
|
92
|
+
endLine,
|
|
93
|
+
signature: firstLine(node.text),
|
|
94
|
+
});
|
|
95
|
+
// Extract methods
|
|
96
|
+
const body = node.childForFieldName('body');
|
|
97
|
+
if (body) {
|
|
98
|
+
extractClassMethods(body, name, symbols);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (node.type === 'lexical_declaration') {
|
|
103
|
+
// const/let declarations
|
|
104
|
+
for (const decl of node.namedChildren) {
|
|
105
|
+
if (decl.type === 'variable_declarator') {
|
|
106
|
+
const name = extractName(decl);
|
|
107
|
+
if (name) {
|
|
108
|
+
symbols.push({
|
|
109
|
+
name,
|
|
110
|
+
kind: 'const',
|
|
111
|
+
startLine,
|
|
112
|
+
endLine,
|
|
113
|
+
signature: firstLine(node.text),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (node.type === 'type_alias_declaration') {
|
|
120
|
+
const name = extractName(node);
|
|
121
|
+
if (name) {
|
|
122
|
+
symbols.push({
|
|
123
|
+
name,
|
|
124
|
+
kind: 'type',
|
|
125
|
+
startLine,
|
|
126
|
+
endLine,
|
|
127
|
+
signature: firstLine(node.text),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (node.type === 'interface_declaration') {
|
|
132
|
+
const name = extractName(node);
|
|
133
|
+
if (name) {
|
|
134
|
+
symbols.push({
|
|
135
|
+
name,
|
|
136
|
+
kind: 'interface',
|
|
137
|
+
startLine,
|
|
138
|
+
endLine,
|
|
139
|
+
signature: firstLine(node.text),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return symbols;
|
|
145
|
+
}
|
|
146
|
+
function extractClassMethods(body, className, symbols) {
|
|
147
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
148
|
+
const member = body.namedChild(i);
|
|
149
|
+
if (member.type === 'method_definition' ||
|
|
150
|
+
member.type === 'public_field_definition') {
|
|
151
|
+
const name = extractName(member);
|
|
152
|
+
if (name) {
|
|
153
|
+
symbols.push({
|
|
154
|
+
name,
|
|
155
|
+
kind: 'method',
|
|
156
|
+
parent: className,
|
|
157
|
+
startLine: member.startPosition.row + 1,
|
|
158
|
+
endLine: member.endPosition.row + 1,
|
|
159
|
+
signature: firstLine(member.text),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function extractPySymbols(tree) {
|
|
166
|
+
const symbols = [];
|
|
167
|
+
const root = tree.rootNode;
|
|
168
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
169
|
+
const node = root.child(i);
|
|
170
|
+
const startLine = node.startPosition.row + 1;
|
|
171
|
+
const endLine = node.endPosition.row + 1;
|
|
172
|
+
if (node.type === 'function_definition') {
|
|
173
|
+
const name = extractName(node);
|
|
174
|
+
if (name) {
|
|
175
|
+
symbols.push({
|
|
176
|
+
name,
|
|
177
|
+
kind: 'function',
|
|
178
|
+
startLine,
|
|
179
|
+
endLine,
|
|
180
|
+
signature: firstLine(node.text),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (node.type === 'class_definition') {
|
|
185
|
+
const name = extractName(node);
|
|
186
|
+
if (name) {
|
|
187
|
+
symbols.push({
|
|
188
|
+
name,
|
|
189
|
+
kind: 'class',
|
|
190
|
+
startLine,
|
|
191
|
+
endLine,
|
|
192
|
+
signature: firstLine(node.text),
|
|
193
|
+
});
|
|
194
|
+
// Extract methods
|
|
195
|
+
const body = node.childForFieldName('body');
|
|
196
|
+
if (body) {
|
|
197
|
+
for (let j = 0; j < body.namedChildCount; j++) {
|
|
198
|
+
const member = body.namedChild(j);
|
|
199
|
+
if (member.type === 'function_definition') {
|
|
200
|
+
const methodName = extractName(member);
|
|
201
|
+
if (methodName) {
|
|
202
|
+
symbols.push({
|
|
203
|
+
name: methodName,
|
|
204
|
+
kind: 'method',
|
|
205
|
+
parent: name,
|
|
206
|
+
startLine: member.startPosition.row + 1,
|
|
207
|
+
endLine: member.endPosition.row + 1,
|
|
208
|
+
signature: firstLine(member.text),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (node.type === 'expression_statement' &&
|
|
217
|
+
node.namedChildCount === 1 &&
|
|
218
|
+
node.namedChild(0).type === 'assignment') {
|
|
219
|
+
// Top-level assignment: FOO = ...
|
|
220
|
+
const assign = node.namedChild(0);
|
|
221
|
+
const left = assign.childForFieldName('left');
|
|
222
|
+
if (left && left.type === 'identifier') {
|
|
223
|
+
symbols.push({
|
|
224
|
+
name: left.text,
|
|
225
|
+
kind: 'variable',
|
|
226
|
+
startLine,
|
|
227
|
+
endLine,
|
|
228
|
+
signature: firstLine(node.text),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return symbols;
|
|
234
|
+
}
|
|
235
|
+
function extractRustSymbols(tree) {
|
|
236
|
+
const symbols = [];
|
|
237
|
+
const root = tree.rootNode;
|
|
238
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
239
|
+
const node = root.child(i);
|
|
240
|
+
const startLine = node.startPosition.row + 1;
|
|
241
|
+
const endLine = node.endPosition.row + 1;
|
|
242
|
+
if (node.type === 'function_item') {
|
|
243
|
+
const name = extractName(node);
|
|
244
|
+
if (name) {
|
|
245
|
+
symbols.push({
|
|
246
|
+
name,
|
|
247
|
+
kind: 'function',
|
|
248
|
+
startLine,
|
|
249
|
+
endLine,
|
|
250
|
+
signature: firstLine(node.text),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else if (node.type === 'struct_item') {
|
|
255
|
+
const name = extractName(node);
|
|
256
|
+
if (name) {
|
|
257
|
+
symbols.push({
|
|
258
|
+
name,
|
|
259
|
+
kind: 'class',
|
|
260
|
+
startLine,
|
|
261
|
+
endLine,
|
|
262
|
+
signature: firstLine(node.text),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else if (node.type === 'enum_item') {
|
|
267
|
+
const name = extractName(node);
|
|
268
|
+
if (name) {
|
|
269
|
+
symbols.push({
|
|
270
|
+
name,
|
|
271
|
+
kind: 'class',
|
|
272
|
+
startLine,
|
|
273
|
+
endLine,
|
|
274
|
+
signature: firstLine(node.text),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (node.type === 'trait_item') {
|
|
279
|
+
const name = extractName(node);
|
|
280
|
+
if (name) {
|
|
281
|
+
symbols.push({
|
|
282
|
+
name,
|
|
283
|
+
kind: 'interface',
|
|
284
|
+
startLine,
|
|
285
|
+
endLine,
|
|
286
|
+
signature: firstLine(node.text),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else if (node.type === 'impl_item') {
|
|
291
|
+
// impl Type { ... } or impl Trait for Type { ... }
|
|
292
|
+
const typeName = node.childForFieldName('type')?.text;
|
|
293
|
+
if (!typeName)
|
|
294
|
+
continue;
|
|
295
|
+
const body = node.childForFieldName('body');
|
|
296
|
+
if (!body)
|
|
297
|
+
continue;
|
|
298
|
+
for (let j = 0; j < body.namedChildCount; j++) {
|
|
299
|
+
const member = body.namedChild(j);
|
|
300
|
+
if (member.type === 'function_item') {
|
|
301
|
+
const name = extractName(member);
|
|
302
|
+
if (name) {
|
|
303
|
+
symbols.push({
|
|
304
|
+
name,
|
|
305
|
+
kind: 'method',
|
|
306
|
+
parent: typeName,
|
|
307
|
+
startLine: member.startPosition.row + 1,
|
|
308
|
+
endLine: member.endPosition.row + 1,
|
|
309
|
+
signature: firstLine(member.text),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (node.type === 'const_item') {
|
|
316
|
+
const name = extractName(node);
|
|
317
|
+
if (name) {
|
|
318
|
+
symbols.push({
|
|
319
|
+
name,
|
|
320
|
+
kind: 'const',
|
|
321
|
+
startLine,
|
|
322
|
+
endLine,
|
|
323
|
+
signature: firstLine(node.text),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (node.type === 'static_item') {
|
|
328
|
+
const name = extractName(node);
|
|
329
|
+
if (name) {
|
|
330
|
+
symbols.push({
|
|
331
|
+
name,
|
|
332
|
+
kind: 'variable',
|
|
333
|
+
startLine,
|
|
334
|
+
endLine,
|
|
335
|
+
signature: firstLine(node.text),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (node.type === 'type_item') {
|
|
340
|
+
const name = extractName(node);
|
|
341
|
+
if (name) {
|
|
342
|
+
symbols.push({
|
|
343
|
+
name,
|
|
344
|
+
kind: 'type',
|
|
345
|
+
startLine,
|
|
346
|
+
endLine,
|
|
347
|
+
signature: firstLine(node.text),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return symbols;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Extract the receiver type name from a Go method declaration's receiver node.
|
|
356
|
+
* Handles both value receivers (Greeter) and pointer receivers (*Greeter).
|
|
357
|
+
*/
|
|
358
|
+
function goReceiverType(receiverNode) {
|
|
359
|
+
const param = receiverNode.namedChild(0);
|
|
360
|
+
if (!param)
|
|
361
|
+
return null;
|
|
362
|
+
const typeNode = param.childForFieldName('type');
|
|
363
|
+
if (!typeNode)
|
|
364
|
+
return null;
|
|
365
|
+
// pointer_type -> child is the actual type name
|
|
366
|
+
if (typeNode.type === 'pointer_type') {
|
|
367
|
+
return typeNode.namedChild(0)?.text ?? null;
|
|
368
|
+
}
|
|
369
|
+
return typeNode.text;
|
|
370
|
+
}
|
|
371
|
+
function extractGoSymbols(tree) {
|
|
372
|
+
const symbols = [];
|
|
373
|
+
const root = tree.rootNode;
|
|
374
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
375
|
+
const node = root.child(i);
|
|
376
|
+
const startLine = node.startPosition.row + 1;
|
|
377
|
+
const endLine = node.endPosition.row + 1;
|
|
378
|
+
if (node.type === 'function_declaration') {
|
|
379
|
+
const name = extractName(node);
|
|
380
|
+
if (name) {
|
|
381
|
+
symbols.push({
|
|
382
|
+
name,
|
|
383
|
+
kind: 'function',
|
|
384
|
+
startLine,
|
|
385
|
+
endLine,
|
|
386
|
+
signature: firstLine(node.text),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (node.type === 'method_declaration') {
|
|
391
|
+
const name = extractName(node);
|
|
392
|
+
const receiver = node.childForFieldName('receiver');
|
|
393
|
+
const typeName = receiver ? goReceiverType(receiver) : null;
|
|
394
|
+
if (name && typeName) {
|
|
395
|
+
symbols.push({
|
|
396
|
+
name,
|
|
397
|
+
kind: 'method',
|
|
398
|
+
parent: typeName,
|
|
399
|
+
startLine,
|
|
400
|
+
endLine,
|
|
401
|
+
signature: firstLine(node.text),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else if (node.type === 'type_declaration') {
|
|
406
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
407
|
+
const spec = node.namedChild(j);
|
|
408
|
+
if (spec.type !== 'type_spec')
|
|
409
|
+
continue;
|
|
410
|
+
const name = spec.childForFieldName('name')?.text;
|
|
411
|
+
if (!name)
|
|
412
|
+
continue;
|
|
413
|
+
const typeNode = spec.childForFieldName('type');
|
|
414
|
+
const kind = typeNode?.type === 'interface_type' ? 'interface' : 'class';
|
|
415
|
+
symbols.push({
|
|
416
|
+
name,
|
|
417
|
+
kind,
|
|
418
|
+
startLine: spec.startPosition.row + 1,
|
|
419
|
+
endLine: spec.endPosition.row + 1,
|
|
420
|
+
signature: firstLine(node.text),
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else if (node.type === 'const_declaration') {
|
|
425
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
426
|
+
const spec = node.namedChild(j);
|
|
427
|
+
if (spec.type !== 'const_spec')
|
|
428
|
+
continue;
|
|
429
|
+
const name = spec.childForFieldName('name')?.text;
|
|
430
|
+
if (name) {
|
|
431
|
+
symbols.push({
|
|
432
|
+
name,
|
|
433
|
+
kind: 'const',
|
|
434
|
+
startLine: spec.startPosition.row + 1,
|
|
435
|
+
endLine: spec.endPosition.row + 1,
|
|
436
|
+
signature: firstLine(node.text),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else if (node.type === 'var_declaration') {
|
|
442
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
443
|
+
const spec = node.namedChild(j);
|
|
444
|
+
if (spec.type !== 'var_spec')
|
|
445
|
+
continue;
|
|
446
|
+
const name = spec.childForFieldName('name')?.text;
|
|
447
|
+
if (name) {
|
|
448
|
+
symbols.push({
|
|
449
|
+
name,
|
|
450
|
+
kind: 'variable',
|
|
451
|
+
startLine: spec.startPosition.row + 1,
|
|
452
|
+
endLine: spec.endPosition.row + 1,
|
|
453
|
+
signature: firstLine(node.text),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return symbols;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Extract the declarator name from a C function_declarator node.
|
|
463
|
+
* Handles plain identifiers and pointer declarators (*name).
|
|
464
|
+
*/
|
|
465
|
+
function cFuncName(declarator) {
|
|
466
|
+
if (declarator.type === 'function_declarator') {
|
|
467
|
+
const inner = declarator.childForFieldName('declarator');
|
|
468
|
+
if (!inner)
|
|
469
|
+
return null;
|
|
470
|
+
if (inner.type === 'identifier')
|
|
471
|
+
return inner.text;
|
|
472
|
+
if (inner.type === 'pointer_declarator') {
|
|
473
|
+
// *name — dig through pointer layers
|
|
474
|
+
let cur = inner;
|
|
475
|
+
while (cur.type === 'pointer_declarator') {
|
|
476
|
+
const child = cur.childForFieldName('declarator');
|
|
477
|
+
if (!child)
|
|
478
|
+
return null;
|
|
479
|
+
cur = child;
|
|
480
|
+
}
|
|
481
|
+
return cur.type === 'identifier' ? cur.text : null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Extract the variable name from a C init_declarator or plain declarator.
|
|
488
|
+
* Handles pointers like `*DEFAULT_NAME = "World"`.
|
|
489
|
+
*/
|
|
490
|
+
function cVarName(declarator) {
|
|
491
|
+
let node = declarator;
|
|
492
|
+
// Unwrap init_declarator to get the declarator part
|
|
493
|
+
if (node.type === 'init_declarator') {
|
|
494
|
+
const inner = node.childForFieldName('declarator');
|
|
495
|
+
if (!inner)
|
|
496
|
+
return null;
|
|
497
|
+
node = inner;
|
|
498
|
+
}
|
|
499
|
+
if (node.type === 'identifier')
|
|
500
|
+
return node.text;
|
|
501
|
+
if (node.type === 'pointer_declarator') {
|
|
502
|
+
let cur = node;
|
|
503
|
+
while (cur.type === 'pointer_declarator') {
|
|
504
|
+
const child = cur.childForFieldName('declarator');
|
|
505
|
+
if (!child)
|
|
506
|
+
return null;
|
|
507
|
+
cur = child;
|
|
508
|
+
}
|
|
509
|
+
return cur.type === 'identifier' ? cur.text : null;
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
function extractCSymbols(tree) {
|
|
514
|
+
const symbols = [];
|
|
515
|
+
collectCNodes(tree.rootNode, symbols);
|
|
516
|
+
return symbols;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Walk C AST nodes, collecting symbols. Recurses into preproc_ifdef /
|
|
520
|
+
* preproc_ifndef blocks so header include guards don't hide declarations.
|
|
521
|
+
*/
|
|
522
|
+
function collectCNodes(parent, symbols) {
|
|
523
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
524
|
+
const node = parent.child(i);
|
|
525
|
+
const startLine = node.startPosition.row + 1;
|
|
526
|
+
const endLine = node.endPosition.row + 1;
|
|
527
|
+
if (node.type === 'function_definition') {
|
|
528
|
+
const declarator = node.childForFieldName('declarator');
|
|
529
|
+
const name = declarator ? cFuncName(declarator) : null;
|
|
530
|
+
if (name) {
|
|
531
|
+
symbols.push({
|
|
532
|
+
name,
|
|
533
|
+
kind: 'function',
|
|
534
|
+
startLine,
|
|
535
|
+
endLine,
|
|
536
|
+
signature: firstLine(node.text),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else if (node.type === 'struct_specifier') {
|
|
541
|
+
const name = extractName(node);
|
|
542
|
+
if (name) {
|
|
543
|
+
symbols.push({
|
|
544
|
+
name,
|
|
545
|
+
kind: 'class',
|
|
546
|
+
startLine,
|
|
547
|
+
endLine,
|
|
548
|
+
signature: firstLine(node.text),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
else if (node.type === 'enum_specifier') {
|
|
553
|
+
const name = extractName(node);
|
|
554
|
+
if (name) {
|
|
555
|
+
symbols.push({
|
|
556
|
+
name,
|
|
557
|
+
kind: 'class',
|
|
558
|
+
startLine,
|
|
559
|
+
endLine,
|
|
560
|
+
signature: firstLine(node.text),
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else if (node.type === 'type_definition') {
|
|
565
|
+
const declarator = node.childForFieldName('declarator');
|
|
566
|
+
const name = declarator?.type === 'type_identifier' ? declarator.text : null;
|
|
567
|
+
if (name) {
|
|
568
|
+
symbols.push({
|
|
569
|
+
name,
|
|
570
|
+
kind: 'type',
|
|
571
|
+
startLine,
|
|
572
|
+
endLine,
|
|
573
|
+
signature: firstLine(node.text),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (node.type === 'declaration') {
|
|
578
|
+
const declarator = node.childForFieldName('declarator');
|
|
579
|
+
const name = declarator ? cVarName(declarator) : null;
|
|
580
|
+
if (name) {
|
|
581
|
+
symbols.push({
|
|
582
|
+
name,
|
|
583
|
+
kind: 'variable',
|
|
584
|
+
startLine,
|
|
585
|
+
endLine,
|
|
586
|
+
signature: firstLine(node.text),
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else if (node.type === 'preproc_def') {
|
|
591
|
+
const name = extractName(node);
|
|
592
|
+
if (name) {
|
|
593
|
+
symbols.push({
|
|
594
|
+
name,
|
|
595
|
+
kind: 'const',
|
|
596
|
+
startLine,
|
|
597
|
+
endLine,
|
|
598
|
+
signature: firstLine(node.text),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else if (node.type === 'preproc_ifdef' ||
|
|
603
|
+
node.type === 'preproc_ifndef') {
|
|
604
|
+
// Recurse into include guard / conditional blocks
|
|
605
|
+
collectCNodes(node, symbols);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function firstLine(text) {
|
|
610
|
+
const nl = text.indexOf('\n');
|
|
611
|
+
return nl === -1 ? text : text.slice(0, nl);
|
|
612
|
+
}
|
|
613
|
+
export async function parseSourceSymbols(filePath, content) {
|
|
614
|
+
const ext = filePath.match(/\.[^.]+$/)?.[0] ?? '';
|
|
615
|
+
const lang = await getLanguage(ext);
|
|
616
|
+
if (!lang)
|
|
617
|
+
return [];
|
|
618
|
+
const p = await ensureParser();
|
|
619
|
+
p.setLanguage(lang);
|
|
620
|
+
const tree = p.parse(content);
|
|
621
|
+
if (!tree)
|
|
622
|
+
return [];
|
|
623
|
+
if (ext === '.py') {
|
|
624
|
+
return extractPySymbols(tree);
|
|
625
|
+
}
|
|
626
|
+
if (ext === '.rs') {
|
|
627
|
+
return extractRustSymbols(tree);
|
|
628
|
+
}
|
|
629
|
+
if (ext === '.go') {
|
|
630
|
+
return extractGoSymbols(tree);
|
|
631
|
+
}
|
|
632
|
+
if (ext === '.c' || ext === '.h') {
|
|
633
|
+
return extractCSymbols(tree);
|
|
634
|
+
}
|
|
635
|
+
return extractTsSymbols(tree);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Check whether a source file path (relative to projectRoot) has a given symbol.
|
|
639
|
+
* Used by lat check to validate source code wiki links lazily.
|
|
640
|
+
*/
|
|
641
|
+
export async function resolveSourceSymbol(filePath, symbolPath, projectRoot) {
|
|
642
|
+
const absPath = join(projectRoot, filePath);
|
|
643
|
+
let content;
|
|
644
|
+
try {
|
|
645
|
+
content = readFileSync(absPath, 'utf-8');
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return { found: false, symbols: [] };
|
|
649
|
+
}
|
|
650
|
+
let symbols;
|
|
651
|
+
try {
|
|
652
|
+
symbols = await parseSourceSymbols(filePath, content);
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
return {
|
|
656
|
+
found: false,
|
|
657
|
+
symbols: [],
|
|
658
|
+
error: `failed to parse "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const parts = symbolPath.split('#');
|
|
662
|
+
if (parts.length === 1) {
|
|
663
|
+
// Simple symbol: getConfigDir
|
|
664
|
+
const found = symbols.some((s) => s.name === parts[0] && !s.parent);
|
|
665
|
+
return { found, symbols };
|
|
666
|
+
}
|
|
667
|
+
if (parts.length === 2) {
|
|
668
|
+
// Nested symbol: MyClass#myMethod
|
|
669
|
+
const found = symbols.some((s) => s.name === parts[1] && s.parent === parts[0]);
|
|
670
|
+
return { found, symbols };
|
|
671
|
+
}
|
|
672
|
+
return { found: false, symbols };
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Convert source symbols to Section objects for uniform handling.
|
|
676
|
+
*/
|
|
677
|
+
export function sourceSymbolsToSections(symbols, filePath) {
|
|
678
|
+
const sections = [];
|
|
679
|
+
const classMap = new Map();
|
|
680
|
+
for (const sym of symbols) {
|
|
681
|
+
if (sym.parent)
|
|
682
|
+
continue; // Handle methods after their class
|
|
683
|
+
const section = {
|
|
684
|
+
id: `${filePath}#${sym.name}`,
|
|
685
|
+
heading: sym.name,
|
|
686
|
+
depth: 1,
|
|
687
|
+
file: filePath,
|
|
688
|
+
filePath,
|
|
689
|
+
children: [],
|
|
690
|
+
startLine: sym.startLine,
|
|
691
|
+
endLine: sym.endLine,
|
|
692
|
+
firstParagraph: sym.signature,
|
|
693
|
+
};
|
|
694
|
+
sections.push(section);
|
|
695
|
+
if (sym.kind === 'class') {
|
|
696
|
+
classMap.set(sym.name, section);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Add methods as children
|
|
700
|
+
for (const sym of symbols) {
|
|
701
|
+
if (!sym.parent)
|
|
702
|
+
continue;
|
|
703
|
+
const parentSection = classMap.get(sym.parent);
|
|
704
|
+
if (!parentSection)
|
|
705
|
+
continue;
|
|
706
|
+
const section = {
|
|
707
|
+
id: `${filePath}#${sym.parent}#${sym.name}`,
|
|
708
|
+
heading: sym.name,
|
|
709
|
+
depth: 2,
|
|
710
|
+
file: filePath,
|
|
711
|
+
filePath,
|
|
712
|
+
children: [],
|
|
713
|
+
startLine: sym.startLine,
|
|
714
|
+
endLine: sym.endLine,
|
|
715
|
+
firstParagraph: sym.signature,
|
|
716
|
+
};
|
|
717
|
+
parentSection.children.push(section);
|
|
718
|
+
}
|
|
719
|
+
return sections;
|
|
720
|
+
}
|