@wrongstack/tools 0.309.0 → 0.310.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/_regex.d.ts +6 -34
- package/dist/audit.js +3 -3
- package/dist/bash.js +4 -4
- package/dist/builtin.js +3020 -1680
- package/dist/codebase-index/binary-frame.d.ts +57 -8
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-search-tool.d.ts +15 -5
- package/dist/codebase-index/index-service.d.ts +3 -19
- package/dist/codebase-index/index.js +2784 -1464
- package/dist/codebase-index/indexer.d.ts +3 -0
- package/dist/codebase-index/parser-batch.d.ts +53 -0
- package/dist/codebase-index/parser-dispatch.d.ts +32 -0
- package/dist/codebase-index/parser-output.d.ts +14 -0
- package/dist/codebase-index/parser-worker-pool.d.ts +57 -4
- package/dist/codebase-index/parser-worker-script.d.ts +5 -2
- package/dist/codebase-index/parser-worker-script.js +4042 -0
- package/dist/codebase-index/project-server-cache.d.ts +16 -0
- package/dist/codebase-index/project-server-client.d.ts +2 -2
- package/dist/codebase-index/project-server-query-cache.d.ts +88 -0
- package/dist/codebase-index/project-server.js +2846 -1308
- package/dist/codebase-index/py-parser.d.ts +5 -0
- package/dist/codebase-index/schema.d.ts +14 -1
- package/dist/codebase-index/sqlite-runtime.d.ts +2 -2
- package/dist/codebase-index/tree-sitter/queries.d.ts +30 -3
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/vector-search.d.ts +12 -0
- package/dist/codebase-index/wal-maintenance.d.ts +58 -0
- package/dist/codebase-index/worker-protocol/contracts.d.ts +44 -0
- package/dist/codebase-index/worker-protocol.d.ts +17 -1
- package/dist/codebase-index/worker.js +2300 -1020
- package/dist/codebase-index/writer-admin.d.ts +11 -0
- package/dist/codebase-index/writer-helpers.d.ts +31 -1
- package/dist/codebase-index/writer-mutations.d.ts +0 -6
- package/dist/codebase-index/writer-schema.d.ts +2 -2
- package/dist/codebase-index/writer.d.ts +15 -0
- package/dist/edit.js +2511 -1203
- package/dist/exec.js +8 -6
- package/dist/format.js +3 -3
- package/dist/git.js +6 -0
- package/dist/grep.js +5 -124
- package/dist/index.js +3016 -1739
- package/dist/install.js +3 -3
- package/dist/json.js +5 -124
- package/dist/kanban.js +130 -0
- package/dist/languages/index.js +3 -3
- package/dist/lint.js +3 -3
- package/dist/logs.js +5 -121
- package/dist/outdated.js +3 -3
- package/dist/pack.js +3020 -1680
- package/dist/patch.js +2524 -1216
- package/dist/plan.js +106 -0
- package/dist/process-registry.js +1 -1
- package/dist/read.js +2506 -1198
- package/dist/replace.js +2487 -1295
- package/dist/search.js +6 -2
- package/dist/session-kanban.js +24 -16
- package/dist/task.js +106 -0
- package/dist/test.js +3 -3
- package/dist/todo.js +106 -0
- package/dist/tool-tier.d.ts +11 -0
- package/dist/tool-tier.js +3028 -1680
- package/dist/tree.js +14 -3
- package/dist/typecheck.js +3 -3
- package/dist/win32.js +5 -5
- package/dist/write.js +2513 -1205
- package/package.json +5 -4
package/dist/replace.js
CHANGED
|
@@ -173,238 +173,133 @@ var init_languages = __esm({
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
// src/codebase-index/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
tsLoad ??= import("@typescript/typescript6").then((m) => {
|
|
184
|
-
ts = m.default ?? m;
|
|
185
|
-
return ts;
|
|
186
|
-
});
|
|
187
|
-
return tsLoad;
|
|
188
|
-
}
|
|
189
|
-
function kindMap() {
|
|
190
|
-
kindMapCache ??= {
|
|
191
|
-
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
192
|
-
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
193
|
-
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
194
|
-
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
195
|
-
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
196
|
-
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
197
|
-
[ts.SyntaxKind.GetAccessor]: "property",
|
|
198
|
-
[ts.SyntaxKind.SetAccessor]: "property",
|
|
199
|
-
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
200
|
-
[ts.SyntaxKind.Parameter]: "parameter",
|
|
201
|
-
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
202
|
-
};
|
|
203
|
-
return kindMapCache;
|
|
204
|
-
}
|
|
205
|
-
function kindOf(node) {
|
|
206
|
-
if (ts.isVariableDeclaration(node)) {
|
|
207
|
-
const parent = node.parent;
|
|
208
|
-
if (ts.isVariableDeclarationList(parent)) {
|
|
209
|
-
const flags = parent.flags;
|
|
210
|
-
if (flags & ts.NodeFlags.Let) return "let";
|
|
211
|
-
if (flags & ts.NodeFlags.Const) return "const";
|
|
212
|
-
return "var";
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
216
|
-
return kindMap()[node.kind] ?? null;
|
|
217
|
-
}
|
|
218
|
-
function getSignature(printer, node, sourceFile) {
|
|
219
|
-
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
220
|
-
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
221
|
-
}
|
|
222
|
-
function getJsDoc(node, sourceFile) {
|
|
223
|
-
const fullText = sourceFile.getFullText();
|
|
224
|
-
const nodePos = node.getFullStart();
|
|
225
|
-
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
226
|
-
if (!comments) return "";
|
|
227
|
-
for (const range of comments) {
|
|
228
|
-
const commentText = fullText.slice(range.pos, range.end);
|
|
229
|
-
const trimmed = commentText.trim();
|
|
230
|
-
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
231
|
-
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
232
|
-
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
233
|
-
}
|
|
176
|
+
// src/codebase-index/import-extractor.ts
|
|
177
|
+
function lastSegment(specifier) {
|
|
178
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
179
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
180
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
181
|
+
if (last === "*" || last === "_") {
|
|
182
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
234
183
|
}
|
|
235
|
-
return "";
|
|
184
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
185
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
186
|
+
return dotted[dotted.length - 1] ?? last;
|
|
236
187
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
242
|
-
parts.push(node.name.text);
|
|
243
|
-
}
|
|
188
|
+
function newlineOffsets(content) {
|
|
189
|
+
const offsets = [];
|
|
190
|
+
for (let i = 0; i < content.length; i++) {
|
|
191
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
244
192
|
}
|
|
193
|
+
return offsets;
|
|
245
194
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
254
|
-
}
|
|
255
|
-
const symbols = [];
|
|
256
|
-
const refs = [];
|
|
257
|
-
const printer = ts.createPrinter({});
|
|
258
|
-
function visit(node, funcDepth, scopeParts) {
|
|
259
|
-
const kind = kindOf(node);
|
|
260
|
-
if (kind) {
|
|
261
|
-
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
262
|
-
} else {
|
|
263
|
-
const nameNode = node.name;
|
|
264
|
-
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const name = nameNode.text;
|
|
268
|
-
const pos2 = nameNode.getStart(sourceFile);
|
|
269
|
-
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
270
|
-
const scope = scopeParts.join(".");
|
|
271
|
-
const signature = getSignature(printer, node, sourceFile);
|
|
272
|
-
const docComment = getJsDoc(node, sourceFile);
|
|
273
|
-
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
274
|
-
symbols.push({
|
|
275
|
-
id: 0,
|
|
276
|
-
lang,
|
|
277
|
-
kind,
|
|
278
|
-
name,
|
|
279
|
-
file,
|
|
280
|
-
line: line2 + 1,
|
|
281
|
-
col: character,
|
|
282
|
-
signature,
|
|
283
|
-
docComment,
|
|
284
|
-
scope,
|
|
285
|
-
text
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const pos = node.getStart(sourceFile);
|
|
290
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
291
|
-
const lineNum = line + 1;
|
|
292
|
-
if (ts.isCallExpression(node)) {
|
|
293
|
-
const expr = node.expression;
|
|
294
|
-
if (ts.isIdentifier(expr)) {
|
|
295
|
-
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
296
|
-
}
|
|
297
|
-
} else if (ts.isPropertyAccessExpression(node)) {
|
|
298
|
-
if (ts.isIdentifier(node.expression)) {
|
|
299
|
-
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
300
|
-
}
|
|
301
|
-
} else if (ts.isTypeReferenceNode(node)) {
|
|
302
|
-
const name = getTypeName(node.typeName);
|
|
303
|
-
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
304
|
-
} else if (ts.isHeritageClause(node)) {
|
|
305
|
-
for (const t of node.types) {
|
|
306
|
-
const name = getTypeName(t.expression);
|
|
307
|
-
if (name)
|
|
308
|
-
refs.push({
|
|
309
|
-
fromId: 0,
|
|
310
|
-
toName: name,
|
|
311
|
-
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
312
|
-
line: lineNum
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
} else if (ts.isImportDeclaration(node)) {
|
|
316
|
-
emitImportSpecifierRefs(node, refs, lineNum);
|
|
317
|
-
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
318
|
-
emitExportSpecifierRefs(node, refs, lineNum);
|
|
319
|
-
}
|
|
320
|
-
const scopeIdx = scopeParts.length;
|
|
321
|
-
pushScopeName(node, scopeParts);
|
|
322
|
-
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
323
|
-
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
324
|
-
scopeParts.length = scopeIdx;
|
|
195
|
+
function lineAt(offsets, index) {
|
|
196
|
+
let low = 0;
|
|
197
|
+
let high = offsets.length;
|
|
198
|
+
while (low < high) {
|
|
199
|
+
const mid = low + high >>> 1;
|
|
200
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
201
|
+
else high = mid;
|
|
325
202
|
}
|
|
326
|
-
|
|
327
|
-
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
203
|
+
return low + 1;
|
|
328
204
|
}
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
332
|
-
return "";
|
|
205
|
+
function hasImportPatterns(lang) {
|
|
206
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
333
207
|
}
|
|
334
|
-
function
|
|
208
|
+
function extractImports(opts) {
|
|
209
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
210
|
+
if (!patterns || !opts.content) return [];
|
|
211
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
212
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
213
|
+
const refs = [];
|
|
335
214
|
const seen = /* @__PURE__ */ new Set();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!clause) {
|
|
350
|
-
if (module) {
|
|
351
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
352
|
-
}
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (clause.name) {
|
|
356
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
357
|
-
}
|
|
358
|
-
const bindings = clause.namedBindings;
|
|
359
|
-
if (!bindings) return;
|
|
360
|
-
if (ts.isNamedImports(bindings)) {
|
|
361
|
-
for (const element of bindings.elements) {
|
|
215
|
+
const offsets = newlineOffsets(content);
|
|
216
|
+
for (const pattern of patterns) {
|
|
217
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
218
|
+
for (const match of content.matchAll(re)) {
|
|
219
|
+
if (refs.length >= limit) return refs;
|
|
220
|
+
const specifier = match[1]?.trim();
|
|
221
|
+
if (!specifier) continue;
|
|
222
|
+
const module = specifier;
|
|
223
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
224
|
+
if (!toName) continue;
|
|
225
|
+
const key = `${module}\0${toName}`;
|
|
226
|
+
if (seen.has(key)) continue;
|
|
227
|
+
seen.add(key);
|
|
362
228
|
refs.push({
|
|
363
229
|
fromId: 0,
|
|
364
|
-
toName
|
|
230
|
+
toName,
|
|
365
231
|
callType: "import",
|
|
366
|
-
line:
|
|
232
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
233
|
+
lang: opts.lang,
|
|
367
234
|
module
|
|
368
235
|
});
|
|
369
236
|
}
|
|
370
|
-
} else if (ts.isNamespaceImport(bindings)) {
|
|
371
|
-
refs.push({
|
|
372
|
-
fromId: 0,
|
|
373
|
-
toName: bindings.name.text,
|
|
374
|
-
callType: "import",
|
|
375
|
-
line: lineNum,
|
|
376
|
-
module
|
|
377
|
-
});
|
|
378
237
|
}
|
|
238
|
+
return refs;
|
|
379
239
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
384
|
-
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
385
|
-
const clause = node.exportClause;
|
|
386
|
-
if (clause && ts.isNamespaceExport(clause)) {
|
|
387
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (clause && ts.isNamedExports(clause)) {
|
|
391
|
-
for (const element of clause.elements) {
|
|
392
|
-
const originalName = element.propertyName?.text ?? element.name.text;
|
|
393
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (module) {
|
|
398
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
var ts, tsLoad, kindMapCache;
|
|
402
|
-
var init_ts_parser = __esm({
|
|
403
|
-
"src/codebase-index/ts-parser.ts"() {
|
|
240
|
+
var IMPORT_MAX_FILE_CHARS, IMPORT_MAX_PER_FILE, DOTTED_IMPORT, LANG_IMPORTS;
|
|
241
|
+
var init_import_extractor = __esm({
|
|
242
|
+
"src/codebase-index/import-extractor.ts"() {
|
|
404
243
|
"use strict";
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
244
|
+
IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
245
|
+
IMPORT_MAX_PER_FILE = 400;
|
|
246
|
+
DOTTED_IMPORT = [
|
|
247
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
248
|
+
];
|
|
249
|
+
LANG_IMPORTS = {
|
|
250
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
251
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
252
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
253
|
+
go: [
|
|
254
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
255
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
256
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
257
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
258
|
+
],
|
|
259
|
+
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
260
|
+
rs: [
|
|
261
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
262
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
263
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
264
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
265
|
+
],
|
|
266
|
+
java: DOTTED_IMPORT,
|
|
267
|
+
kotlin: DOTTED_IMPORT,
|
|
268
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
269
|
+
csharp: [
|
|
270
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
271
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
272
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
273
|
+
],
|
|
274
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
275
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
276
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
277
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
278
|
+
php: [
|
|
279
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
280
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
281
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
282
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
283
|
+
],
|
|
284
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
285
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
286
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
287
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
288
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
289
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
290
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
291
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
292
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
293
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
294
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
295
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
296
|
+
html: [
|
|
297
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
298
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
299
|
+
],
|
|
300
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
301
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
302
|
+
};
|
|
408
303
|
}
|
|
409
304
|
});
|
|
410
305
|
|
|
@@ -490,6 +385,32 @@ function parseParserOutput(stdout, lang) {
|
|
|
490
385
|
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
491
386
|
};
|
|
492
387
|
}
|
|
388
|
+
function parseParserBatchOutput(stdout, lang) {
|
|
389
|
+
const trimmed = stdout.trim();
|
|
390
|
+
if (!trimmed) return [];
|
|
391
|
+
let parsed;
|
|
392
|
+
try {
|
|
393
|
+
parsed = JSON.parse(trimmed);
|
|
394
|
+
} catch {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
398
|
+
const results = parsed.results;
|
|
399
|
+
if (!Array.isArray(results)) return [];
|
|
400
|
+
return results.flatMap((entry) => {
|
|
401
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
402
|
+
const candidate = entry;
|
|
403
|
+
if (typeof candidate.file !== "string" || !candidate.file) return [];
|
|
404
|
+
return [
|
|
405
|
+
{
|
|
406
|
+
file: candidate.file,
|
|
407
|
+
error: typeof candidate.error === "string" && candidate.error ? candidate.error : void 0,
|
|
408
|
+
symbols: coerceSymbols(candidate.symbols),
|
|
409
|
+
refs: dedupeRefs(coerceRefs(candidate.refs, lang))
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
});
|
|
413
|
+
}
|
|
493
414
|
function dedupeRefs(refs) {
|
|
494
415
|
const seen = /* @__PURE__ */ new Set();
|
|
495
416
|
return refs.filter((ref) => {
|
|
@@ -530,187 +451,172 @@ var init_spawn_gate = __esm({
|
|
|
530
451
|
}
|
|
531
452
|
});
|
|
532
453
|
|
|
533
|
-
// src/codebase-index/
|
|
534
|
-
var go_parser_exports = {};
|
|
535
|
-
__export(go_parser_exports, {
|
|
536
|
-
detectLang: () => detectLang,
|
|
537
|
-
parseSymbols: () => parseSymbols2
|
|
538
|
-
});
|
|
454
|
+
// src/codebase-index/parser-batch.ts
|
|
539
455
|
import { spawn } from "node:child_process";
|
|
456
|
+
import * as fsSync from "node:fs";
|
|
457
|
+
import * as fs4 from "node:fs/promises";
|
|
540
458
|
import * as os from "node:os";
|
|
541
459
|
import * as path7 from "node:path";
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
460
|
+
function chunkBatchFiles(files) {
|
|
461
|
+
const chunks = [];
|
|
462
|
+
let current = [];
|
|
463
|
+
let bytes = 0;
|
|
464
|
+
for (const file of files) {
|
|
465
|
+
const size = Buffer.byteLength(file.content, "utf8");
|
|
466
|
+
if (current.length > 0 && (current.length >= MAX_BATCH_FILES || bytes + size > MAX_BATCH_BYTES)) {
|
|
467
|
+
chunks.push(current);
|
|
468
|
+
current = [];
|
|
469
|
+
bytes = 0;
|
|
470
|
+
}
|
|
471
|
+
current.push(file);
|
|
472
|
+
bytes += size;
|
|
473
|
+
}
|
|
474
|
+
if (current.length > 0) chunks.push(current);
|
|
475
|
+
return chunks;
|
|
476
|
+
}
|
|
477
|
+
function batchTimeoutMs(fileCount) {
|
|
478
|
+
return Math.min(12e4, 15e3 + fileCount * 1500);
|
|
479
|
+
}
|
|
480
|
+
async function ensureScriptPath(cached, prefix, fileName, script) {
|
|
481
|
+
if (cached) return { path: cached, wrote: false };
|
|
482
|
+
const dir = await fs4.mkdtemp(path7.join(os.tmpdir(), prefix));
|
|
483
|
+
const scriptPath = path7.join(dir, fileName);
|
|
484
|
+
await fs4.writeFile(scriptPath, script, { encoding: "utf8", flag: "wx" });
|
|
485
|
+
process.once("exit", () => {
|
|
486
|
+
try {
|
|
487
|
+
fsSync.rmSync(dir, { recursive: true, force: true });
|
|
488
|
+
} catch {
|
|
549
489
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
} catch {
|
|
553
|
-
return fallbackParse(file, content, lang);
|
|
554
|
-
}
|
|
490
|
+
});
|
|
491
|
+
return { path: scriptPath, wrote: true };
|
|
555
492
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
567
|
-
if (fn?.[1]) {
|
|
568
|
-
addFallbackSymbol(symbols, {
|
|
569
|
-
filePath,
|
|
570
|
-
lang,
|
|
571
|
-
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
572
|
-
name: fn[1],
|
|
573
|
-
line: idx + 1,
|
|
574
|
-
col,
|
|
575
|
-
signature: trimmed,
|
|
576
|
-
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
577
|
-
});
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
581
|
-
if (typeDecl?.[1]) {
|
|
582
|
-
addFallbackSymbol(symbols, {
|
|
583
|
-
filePath,
|
|
584
|
-
lang,
|
|
585
|
-
kind: "type",
|
|
586
|
-
name: typeDecl[1],
|
|
587
|
-
line: idx + 1,
|
|
588
|
-
col,
|
|
589
|
-
signature: trimmed,
|
|
590
|
-
scope: packageName
|
|
591
|
-
});
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
595
|
-
if (valueDecl?.[1] && valueDecl[2]) {
|
|
596
|
-
addFallbackSymbol(symbols, {
|
|
597
|
-
filePath,
|
|
598
|
-
lang,
|
|
599
|
-
kind: valueDecl[1],
|
|
600
|
-
name: valueDecl[2],
|
|
601
|
-
line: idx + 1,
|
|
602
|
-
col,
|
|
603
|
-
signature: trimmed,
|
|
604
|
-
scope: packageName
|
|
605
|
-
});
|
|
493
|
+
function runToolchainChild(binary, args, stdinPayload, timeoutMs) {
|
|
494
|
+
return new Promise((resolve4) => {
|
|
495
|
+
let settled = false;
|
|
496
|
+
let stdout = "";
|
|
497
|
+
let proc;
|
|
498
|
+
try {
|
|
499
|
+
proc = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
500
|
+
} catch {
|
|
501
|
+
resolve4(null);
|
|
502
|
+
return;
|
|
606
503
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
504
|
+
const finish = (value) => {
|
|
505
|
+
if (settled) return;
|
|
506
|
+
settled = true;
|
|
507
|
+
clearTimeout(timer);
|
|
508
|
+
resolve4(value);
|
|
509
|
+
};
|
|
510
|
+
const timer = setTimeout(() => {
|
|
511
|
+
proc.kill("SIGKILL");
|
|
512
|
+
finish(null);
|
|
513
|
+
}, timeoutMs);
|
|
514
|
+
timer.unref?.();
|
|
515
|
+
proc.on("error", () => finish(null));
|
|
516
|
+
proc.stdout?.on("data", (chunk) => {
|
|
517
|
+
stdout += chunk.toString();
|
|
518
|
+
});
|
|
519
|
+
proc.stderr?.resume();
|
|
520
|
+
proc.stdin?.on("error", () => {
|
|
521
|
+
});
|
|
522
|
+
proc.stdin?.write(stdinPayload);
|
|
523
|
+
proc.stdin?.end();
|
|
524
|
+
proc.on("close", (code) => finish({ code, stdout }));
|
|
623
525
|
});
|
|
624
526
|
}
|
|
625
|
-
function
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
527
|
+
async function runGoBatch(files, goBinary) {
|
|
528
|
+
const out = /* @__PURE__ */ new Map();
|
|
529
|
+
if (files.length === 0) return out;
|
|
530
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
531
|
+
_goBatchScriptPath,
|
|
532
|
+
"ws-go-parse",
|
|
533
|
+
"batch.go",
|
|
534
|
+
GO_BATCH_SCRIPT
|
|
535
|
+
);
|
|
536
|
+
_goBatchScriptPath = scriptPath;
|
|
537
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
538
|
+
const result = await withSpawnGate(
|
|
539
|
+
() => runToolchainChild(
|
|
540
|
+
goBinary ?? resolveWin32Command("go"),
|
|
541
|
+
["run", scriptPath],
|
|
542
|
+
payload,
|
|
543
|
+
batchTimeoutMs(files.length)
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
547
|
+
for (const entry of parseParserBatchOutput(result.stdout, "go")) {
|
|
548
|
+
if (entry.error !== void 0) continue;
|
|
549
|
+
out.set(entry.file, {
|
|
550
|
+
file: entry.file,
|
|
551
|
+
lang: "go",
|
|
552
|
+
symbols: entry.symbols.map((s) => ({
|
|
553
|
+
id: 0,
|
|
554
|
+
lang: "go",
|
|
555
|
+
kind: s.kind,
|
|
556
|
+
name: s.name,
|
|
557
|
+
file: entry.file,
|
|
558
|
+
line: s.line,
|
|
559
|
+
col: s.col,
|
|
560
|
+
signature: s.signature ?? "",
|
|
561
|
+
docComment: "",
|
|
562
|
+
scope: s.scope ?? "",
|
|
563
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
564
|
+
})),
|
|
565
|
+
refs: entry.refs,
|
|
566
|
+
mtimeMs: Date.now()
|
|
567
|
+
});
|
|
635
568
|
}
|
|
636
|
-
return
|
|
569
|
+
return out;
|
|
637
570
|
}
|
|
638
|
-
async function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
proc.on("close", (code2) => {
|
|
675
|
-
if (settled) return;
|
|
676
|
-
settled = true;
|
|
677
|
-
clearTimeout(timer);
|
|
678
|
-
resolve4({ code: code2, stdout: stdout2 });
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
);
|
|
682
|
-
const { code, stdout } = goResult;
|
|
683
|
-
if (code !== 0 || !stdout.trim()) {
|
|
684
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
685
|
-
}
|
|
686
|
-
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
687
|
-
const symbols = rawSymbols.map((s) => ({
|
|
688
|
-
id: 0,
|
|
689
|
-
lang,
|
|
690
|
-
kind: s.kind,
|
|
691
|
-
name: s.name,
|
|
692
|
-
file: filePath,
|
|
693
|
-
line: s.line,
|
|
694
|
-
col: s.col,
|
|
695
|
-
signature: s.signature ?? "",
|
|
696
|
-
docComment: "",
|
|
697
|
-
scope: s.scope ?? "",
|
|
698
|
-
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
699
|
-
}));
|
|
700
|
-
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
701
|
-
} catch {
|
|
702
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
571
|
+
async function runPyBatch(files, pythonBinary) {
|
|
572
|
+
const out = /* @__PURE__ */ new Map();
|
|
573
|
+
if (files.length === 0) return out;
|
|
574
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
575
|
+
_pyBatchScriptPath,
|
|
576
|
+
"ws-py-parse",
|
|
577
|
+
"batch.py",
|
|
578
|
+
PY_BATCH_SCRIPT
|
|
579
|
+
);
|
|
580
|
+
_pyBatchScriptPath = scriptPath;
|
|
581
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
582
|
+
const result = await withSpawnGate(
|
|
583
|
+
() => runToolchainChild(pythonBinary, [scriptPath], payload, batchTimeoutMs(files.length))
|
|
584
|
+
);
|
|
585
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
586
|
+
for (const entry of parseParserBatchOutput(result.stdout, "py")) {
|
|
587
|
+
if (entry.error !== void 0) continue;
|
|
588
|
+
out.set(entry.file, {
|
|
589
|
+
file: entry.file,
|
|
590
|
+
lang: "py",
|
|
591
|
+
symbols: entry.symbols.map((s) => ({
|
|
592
|
+
id: 0,
|
|
593
|
+
lang: "py",
|
|
594
|
+
kind: s.kind,
|
|
595
|
+
name: s.name,
|
|
596
|
+
file: entry.file,
|
|
597
|
+
line: s.line,
|
|
598
|
+
col: s.col,
|
|
599
|
+
signature: s.signature ?? "",
|
|
600
|
+
docComment: "",
|
|
601
|
+
scope: s.scope ?? "",
|
|
602
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
603
|
+
})),
|
|
604
|
+
refs: entry.refs,
|
|
605
|
+
mtimeMs: Date.now()
|
|
606
|
+
});
|
|
703
607
|
}
|
|
608
|
+
return out;
|
|
704
609
|
}
|
|
705
|
-
var
|
|
706
|
-
var
|
|
707
|
-
"src/codebase-index/
|
|
610
|
+
var MAX_BATCH_FILES, MAX_BATCH_BYTES, GO_BATCH_SCRIPT, PY_BATCH_SCRIPT, _goBatchScriptPath, _pyBatchScriptPath;
|
|
611
|
+
var init_parser_batch = __esm({
|
|
612
|
+
"src/codebase-index/parser-batch.ts"() {
|
|
708
613
|
"use strict";
|
|
709
614
|
init_win32_resolve();
|
|
710
615
|
init_parser_output();
|
|
711
616
|
init_spawn_gate();
|
|
712
|
-
|
|
713
|
-
|
|
617
|
+
MAX_BATCH_FILES = 100;
|
|
618
|
+
MAX_BATCH_BYTES = 8 * 1024 * 1024;
|
|
619
|
+
GO_BATCH_SCRIPT = `
|
|
714
620
|
package main
|
|
715
621
|
|
|
716
622
|
import (
|
|
@@ -734,8 +640,6 @@ type Sym struct {
|
|
|
734
640
|
Scope string \`json:"scope"\`
|
|
735
641
|
}
|
|
736
642
|
|
|
737
|
-
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
738
|
-
// yields both. Module is the import path for CallType "import", else empty.
|
|
739
643
|
type Ref struct {
|
|
740
644
|
ToName string \`json:"toName"\`
|
|
741
645
|
CallType string \`json:"callType"\`
|
|
@@ -743,57 +647,53 @@ type Ref struct {
|
|
|
743
647
|
Module string \`json:"module"\`
|
|
744
648
|
}
|
|
745
649
|
|
|
746
|
-
type
|
|
747
|
-
|
|
748
|
-
|
|
650
|
+
type FileResult struct {
|
|
651
|
+
File string \`json:"file"\`
|
|
652
|
+
Error string \`json:"error,omitempty"\`
|
|
653
|
+
Symbols []Sym \`json:"symbols"\`
|
|
654
|
+
Refs []Ref \`json:"refs"\`
|
|
749
655
|
}
|
|
750
656
|
|
|
751
|
-
|
|
752
|
-
|
|
657
|
+
type BatchResult struct {
|
|
658
|
+
Version int \`json:"version"\`
|
|
659
|
+
Results []FileResult \`json:"results"\`
|
|
753
660
|
}
|
|
754
661
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
662
|
+
type inputFile struct {
|
|
663
|
+
File string \`json:"file"\`
|
|
664
|
+
Content string \`json:"content"\`
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
func parseOne(name string, src []byte) FileResult {
|
|
668
|
+
res := FileResult{File: name, Symbols: []Sym{}, Refs: []Ref{}}
|
|
761
669
|
fset := token.NewFileSet()
|
|
762
670
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
763
671
|
if err != nil {
|
|
764
|
-
|
|
765
|
-
return
|
|
672
|
+
res.Error = err.Error()
|
|
673
|
+
return res
|
|
766
674
|
}
|
|
767
|
-
|
|
768
|
-
var syms []Sym
|
|
769
|
-
|
|
770
|
-
// Package-level scope
|
|
771
675
|
pkgScope := node.Name.Name
|
|
772
|
-
|
|
773
|
-
// Collect all top-level declarations
|
|
774
676
|
for _, decl := range node.Decls {
|
|
775
677
|
switch d := decl.(type) {
|
|
776
678
|
case *ast.FuncDecl:
|
|
777
|
-
|
|
679
|
+
symName := d.Name.Name
|
|
778
680
|
kind := "function"
|
|
779
681
|
scope := pkgScope
|
|
780
682
|
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
781
|
-
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." +
|
|
683
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + symName
|
|
782
684
|
kind = "method"
|
|
783
685
|
} else {
|
|
784
|
-
scope = pkgScope + "." +
|
|
686
|
+
scope = pkgScope + "." + symName
|
|
785
687
|
}
|
|
786
688
|
pos := fset.Position(d.Pos())
|
|
787
|
-
|
|
788
|
-
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
789
|
-
|
|
689
|
+
res.Symbols = append(res.Symbols, Sym{Name: symName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: formatFuncSig(d), Scope: scope})
|
|
790
690
|
case *ast.GenDecl:
|
|
791
691
|
for _, spec := range d.Specs {
|
|
792
692
|
switch s := spec.(type) {
|
|
793
693
|
case *ast.TypeSpec:
|
|
794
|
-
|
|
694
|
+
typeName := s.Name.Name
|
|
795
695
|
pos := fset.Position(s.Pos())
|
|
796
|
-
sig := "type " +
|
|
696
|
+
sig := "type " + typeName
|
|
797
697
|
if s.TypeParams != nil {
|
|
798
698
|
sig += formatTypeParams(s.TypeParams)
|
|
799
699
|
}
|
|
@@ -804,64 +704,72 @@ func main() {
|
|
|
804
704
|
} else {
|
|
805
705
|
sig += " = " + formatType(s.Type)
|
|
806
706
|
}
|
|
807
|
-
|
|
808
|
-
|
|
707
|
+
res.Symbols = append(res.Symbols, Sym{Name: typeName, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
809
708
|
case *ast.ValueSpec:
|
|
810
709
|
for _, n := range s.Names {
|
|
811
|
-
|
|
710
|
+
valueName := n.Name
|
|
812
711
|
pos := fset.Position(n.Pos())
|
|
813
712
|
kind := "var"
|
|
814
713
|
if d.Tok == token.CONST {
|
|
815
714
|
kind = "const"
|
|
816
715
|
}
|
|
817
|
-
sig := kind + " " +
|
|
716
|
+
sig := kind + " " + valueName
|
|
818
717
|
if s.Type != nil {
|
|
819
718
|
sig += " " + formatType(s.Type)
|
|
820
719
|
}
|
|
821
|
-
|
|
720
|
+
res.Symbols = append(res.Symbols, Sym{Name: valueName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
822
721
|
}
|
|
823
722
|
}
|
|
824
723
|
}
|
|
825
724
|
}
|
|
826
725
|
}
|
|
827
|
-
|
|
828
|
-
refs := []Ref{}
|
|
829
726
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
830
727
|
switch expr := n.(type) {
|
|
831
728
|
case *ast.CallExpr:
|
|
832
729
|
line := fset.Position(expr.Pos()).Line
|
|
833
730
|
switch fun := expr.Fun.(type) {
|
|
834
731
|
case *ast.Ident:
|
|
835
|
-
|
|
732
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
836
733
|
case *ast.SelectorExpr:
|
|
837
|
-
|
|
838
|
-
// declared symbol name, so it resolves the same way the TypeScript
|
|
839
|
-
// and Python extractors' call refs do.
|
|
840
|
-
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
734
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
841
735
|
}
|
|
842
736
|
case *ast.ImportSpec:
|
|
843
737
|
if expr.Path != nil {
|
|
844
738
|
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
845
739
|
line := fset.Position(expr.Pos()).Line
|
|
846
|
-
// A Go import names a package, not a symbol; the package's
|
|
847
|
-
// last path segment is the name it is referenced by.
|
|
848
740
|
name := importPath
|
|
849
741
|
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
850
742
|
name = importPath[idx+1:]
|
|
851
743
|
}
|
|
852
|
-
|
|
744
|
+
res.Refs = append(res.Refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
853
745
|
}
|
|
854
746
|
}
|
|
855
747
|
}
|
|
856
748
|
return true
|
|
857
749
|
})
|
|
750
|
+
return res
|
|
751
|
+
}
|
|
858
752
|
|
|
859
|
-
|
|
860
|
-
|
|
753
|
+
func main() {
|
|
754
|
+
raw, err := io.ReadAll(os.Stdin)
|
|
755
|
+
if err != nil {
|
|
756
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
757
|
+
fmt.Print(string(out))
|
|
758
|
+
return
|
|
861
759
|
}
|
|
862
|
-
|
|
760
|
+
var inputs []inputFile
|
|
761
|
+
if err := json.Unmarshal(raw, &inputs); err != nil {
|
|
762
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
763
|
+
fmt.Print(string(out))
|
|
764
|
+
return
|
|
765
|
+
}
|
|
766
|
+
results := make([]FileResult, 0, len(inputs))
|
|
767
|
+
for _, in := range inputs {
|
|
768
|
+
results = append(results, parseOne(in.File, []byte(in.Content)))
|
|
769
|
+
}
|
|
770
|
+
data, err := json.Marshal(BatchResult{Version: 1, Results: results})
|
|
863
771
|
if err != nil {
|
|
864
|
-
fmt.Print(
|
|
772
|
+
fmt.Print("{\\"version\\":1,\\"results\\":[]}")
|
|
865
773
|
return
|
|
866
774
|
}
|
|
867
775
|
fmt.Print(string(data))
|
|
@@ -981,10 +889,8 @@ func formatType(t ast.Expr) string {
|
|
|
981
889
|
case *ast.BasicLit:
|
|
982
890
|
return v.Value
|
|
983
891
|
case *ast.IndexExpr:
|
|
984
|
-
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
985
892
|
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
986
893
|
case *ast.IndexListExpr:
|
|
987
|
-
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
988
894
|
args := make([]string, len(v.Indices))
|
|
989
895
|
for i, idx := range v.Indices {
|
|
990
896
|
args[i] = formatType(idx)
|
|
@@ -995,7 +901,187 @@ func formatType(t ast.Expr) string {
|
|
|
995
901
|
}
|
|
996
902
|
}
|
|
997
903
|
`;
|
|
998
|
-
|
|
904
|
+
PY_BATCH_SCRIPT = `import ast, json, sys
|
|
905
|
+
|
|
906
|
+
def get_name(node):
|
|
907
|
+
if isinstance(node, ast.Name):
|
|
908
|
+
return node.id
|
|
909
|
+
elif isinstance(node, ast.Attribute):
|
|
910
|
+
return get_name(node.value) + "." + node.attr
|
|
911
|
+
elif isinstance(node, ast.Subscript):
|
|
912
|
+
return get_name(node.value)
|
|
913
|
+
elif isinstance(node, ast.Call):
|
|
914
|
+
return get_name(node.func)
|
|
915
|
+
elif isinstance(node, ast.Constant):
|
|
916
|
+
return str(node.value)
|
|
917
|
+
return ""
|
|
918
|
+
|
|
919
|
+
def leaf_name(node):
|
|
920
|
+
if isinstance(node, ast.Attribute):
|
|
921
|
+
return node.attr
|
|
922
|
+
if isinstance(node, ast.Name):
|
|
923
|
+
return node.id
|
|
924
|
+
return get_name(node).split(".")[-1]
|
|
925
|
+
|
|
926
|
+
def is_private(name):
|
|
927
|
+
return name.startswith("__") and not name.endswith("__")
|
|
928
|
+
|
|
929
|
+
def parse_one(name, source, module_name):
|
|
930
|
+
result = {"file": name, "symbols": [], "refs": []}
|
|
931
|
+
try:
|
|
932
|
+
tree = ast.parse(source, filename=name)
|
|
933
|
+
except Exception as e:
|
|
934
|
+
result["error"] = str(e)
|
|
935
|
+
return result
|
|
936
|
+
syms = []
|
|
937
|
+
refs = []
|
|
938
|
+
scope_stack = [module_name]
|
|
939
|
+
|
|
940
|
+
def sym(d):
|
|
941
|
+
return {
|
|
942
|
+
"name": d["name"], "kind": d["kind"], "line": d["line"], "col": d["col"],
|
|
943
|
+
"signature": d["signature"], "scope": d["scope"],
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
class Visitor(ast.NodeVisitor):
|
|
947
|
+
def visit_ClassDef(self, node):
|
|
948
|
+
bases = [get_name(b) for b in node.bases]
|
|
949
|
+
sig = "class " + node.name
|
|
950
|
+
if bases:
|
|
951
|
+
sig += "(" + ", ".join(bases) + ")"
|
|
952
|
+
sig += ": ..."
|
|
953
|
+
syms.append(sym({
|
|
954
|
+
"name": node.name, "kind": "class", "line": node.lineno,
|
|
955
|
+
"col": node.col_offset, "signature": sig,
|
|
956
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
957
|
+
}))
|
|
958
|
+
scope_stack.append(node.name)
|
|
959
|
+
self.generic_visit(node)
|
|
960
|
+
scope_stack.pop()
|
|
961
|
+
|
|
962
|
+
def visit_FunctionDef(self, node):
|
|
963
|
+
args = ", ".join(a.arg for a in node.args.args)
|
|
964
|
+
returns = get_name(node.returns) if node.returns is not None else ""
|
|
965
|
+
is_async = isinstance(node, ast.AsyncFunctionDef)
|
|
966
|
+
kind = "function"
|
|
967
|
+
prefix = "def "
|
|
968
|
+
for dec in node.decorator_list:
|
|
969
|
+
d = get_name(dec)
|
|
970
|
+
if d.endswith(".staticmethod"):
|
|
971
|
+
kind = "staticmethod"
|
|
972
|
+
elif d.endswith(".classmethod"):
|
|
973
|
+
kind = "classmethod"
|
|
974
|
+
elif d == "property":
|
|
975
|
+
kind = "property"
|
|
976
|
+
if is_async:
|
|
977
|
+
kind = "async_" + kind
|
|
978
|
+
sig = f"{prefix}{node.name}({args})"
|
|
979
|
+
if returns:
|
|
980
|
+
sig += f" -> {returns}"
|
|
981
|
+
syms.append(sym({
|
|
982
|
+
"name": node.name, "kind": kind, "line": node.lineno,
|
|
983
|
+
"col": node.col_offset, "signature": sig,
|
|
984
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
985
|
+
}))
|
|
986
|
+
|
|
987
|
+
def visit_AsyncFunctionDef(self, node):
|
|
988
|
+
self.visit_FunctionDef(node)
|
|
989
|
+
|
|
990
|
+
def visit_Assign(self, node):
|
|
991
|
+
for target in node.targets:
|
|
992
|
+
if isinstance(target, ast.Name):
|
|
993
|
+
tname = target.id
|
|
994
|
+
if is_private(tname):
|
|
995
|
+
continue
|
|
996
|
+
kind = "const" if tname.isupper() else "var"
|
|
997
|
+
col = target.col_offset if hasattr(target, "col_offset") else 0
|
|
998
|
+
syms.append(sym({
|
|
999
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1000
|
+
"signature": f"{tname} = ...", "scope": ".".join(scope_stack),
|
|
1001
|
+
}))
|
|
1002
|
+
|
|
1003
|
+
def visit_AnnAssign(self, node):
|
|
1004
|
+
if isinstance(node.target, ast.Name):
|
|
1005
|
+
tname = node.target.id
|
|
1006
|
+
if is_private(tname):
|
|
1007
|
+
return
|
|
1008
|
+
kind = "const" if tname.isupper() else "var"
|
|
1009
|
+
col = node.target.col_offset if hasattr(node.target, "col_offset") else 0
|
|
1010
|
+
sig = f"{tname}: {get_name(node.annotation)}"
|
|
1011
|
+
if node.value:
|
|
1012
|
+
sig += " = ..."
|
|
1013
|
+
syms.append(sym({
|
|
1014
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1015
|
+
"signature": sig, "scope": ".".join(scope_stack),
|
|
1016
|
+
}))
|
|
1017
|
+
|
|
1018
|
+
def visit_Import(self, node):
|
|
1019
|
+
# Parity with the single-file parser: imports are symbols too.
|
|
1020
|
+
for alias in node.names:
|
|
1021
|
+
name = alias.asname or alias.name
|
|
1022
|
+
syms.append(sym({
|
|
1023
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1024
|
+
"col": node.col_offset, "signature": f"import {alias.name}",
|
|
1025
|
+
"scope": ".".join(scope_stack),
|
|
1026
|
+
}))
|
|
1027
|
+
|
|
1028
|
+
def visit_ImportFrom(self, node):
|
|
1029
|
+
module = node.module or ""
|
|
1030
|
+
for alias in node.names:
|
|
1031
|
+
name = alias.asname or alias.name
|
|
1032
|
+
syms.append(sym({
|
|
1033
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1034
|
+
"col": node.col_offset, "signature": f"from {module} import {alias.name}",
|
|
1035
|
+
"scope": ".".join(scope_stack),
|
|
1036
|
+
}))
|
|
1037
|
+
|
|
1038
|
+
Visitor().visit(tree)
|
|
1039
|
+
|
|
1040
|
+
for node in ast.walk(tree):
|
|
1041
|
+
if isinstance(node, ast.Call):
|
|
1042
|
+
cname = leaf_name(node.func)
|
|
1043
|
+
if cname:
|
|
1044
|
+
refs.append({"toName": cname, "callType": "call", "line": node.lineno})
|
|
1045
|
+
elif isinstance(node, ast.Import):
|
|
1046
|
+
for alias in node.names:
|
|
1047
|
+
refs.append({
|
|
1048
|
+
"toName": alias.name.split(".")[-1], "callType": "import",
|
|
1049
|
+
"line": node.lineno, "module": alias.name,
|
|
1050
|
+
})
|
|
1051
|
+
elif isinstance(node, ast.ImportFrom):
|
|
1052
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
1053
|
+
for alias in node.names:
|
|
1054
|
+
refs.append({
|
|
1055
|
+
"toName": alias.name, "callType": "import",
|
|
1056
|
+
"line": node.lineno, "module": module,
|
|
1057
|
+
})
|
|
1058
|
+
elif isinstance(node, ast.ClassDef):
|
|
1059
|
+
for base in node.bases:
|
|
1060
|
+
bname = leaf_name(base)
|
|
1061
|
+
if bname:
|
|
1062
|
+
refs.append({"toName": bname, "callType": "inherit", "line": node.lineno})
|
|
1063
|
+
|
|
1064
|
+
result["symbols"] = syms
|
|
1065
|
+
result["refs"] = refs
|
|
1066
|
+
return result
|
|
1067
|
+
|
|
1068
|
+
def main():
|
|
1069
|
+
try:
|
|
1070
|
+
inputs = json.loads(sys.stdin.read())
|
|
1071
|
+
except Exception:
|
|
1072
|
+
print(json.dumps({"version": 1, "results": []}))
|
|
1073
|
+
return
|
|
1074
|
+
results = []
|
|
1075
|
+
for entry in inputs:
|
|
1076
|
+
name = entry.get("file", "")
|
|
1077
|
+
module_name = name.rsplit("/", 1)[-1].rsplit("\\\\", 1)[-1][:-3]
|
|
1078
|
+
results.append(parse_one(name, entry.get("content", ""), module_name))
|
|
1079
|
+
print(json.dumps({"version": 1, "results": results}))
|
|
1080
|
+
|
|
1081
|
+
main()
|
|
1082
|
+
`;
|
|
1083
|
+
_goBatchScriptPath = null;
|
|
1084
|
+
_pyBatchScriptPath = null;
|
|
999
1085
|
}
|
|
1000
1086
|
});
|
|
1001
1087
|
|
|
@@ -1005,7 +1091,7 @@ __export(generic_parser_exports, {
|
|
|
1005
1091
|
GENERIC_MAX_FILE_CHARS: () => GENERIC_MAX_FILE_CHARS,
|
|
1006
1092
|
GENERIC_MAX_SYMBOLS_DEFAULT: () => GENERIC_MAX_SYMBOLS_DEFAULT,
|
|
1007
1093
|
parseGeneric: () => parseGeneric,
|
|
1008
|
-
parseSymbols: () =>
|
|
1094
|
+
parseSymbols: () => parseSymbols
|
|
1009
1095
|
});
|
|
1010
1096
|
function patternsFor(lang) {
|
|
1011
1097
|
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
@@ -1093,7 +1179,7 @@ function parseGeneric(opts) {
|
|
|
1093
1179
|
}
|
|
1094
1180
|
return { file, lang, symbols, mtimeMs };
|
|
1095
1181
|
}
|
|
1096
|
-
async function
|
|
1182
|
+
async function parseSymbols(opts) {
|
|
1097
1183
|
return parseGeneric(opts);
|
|
1098
1184
|
}
|
|
1099
1185
|
var C_LIKE, LANG_PATTERNS, KEYWORDS, GENERIC_MAX_SYMBOLS_DEFAULT, GENERIC_MAX_FILE_CHARS;
|
|
@@ -1326,13 +1412,14 @@ var init_generic_parser = __esm({
|
|
|
1326
1412
|
var py_parser_exports = {};
|
|
1327
1413
|
__export(py_parser_exports, {
|
|
1328
1414
|
detectLang: () => detectLang,
|
|
1329
|
-
parseSymbols: () =>
|
|
1415
|
+
parseSymbols: () => parseSymbols2,
|
|
1416
|
+
resolvePythonBinary: () => resolvePythonBinary
|
|
1330
1417
|
});
|
|
1331
1418
|
import { spawn as spawn2 } from "node:child_process";
|
|
1332
1419
|
import * as fs5 from "node:fs/promises";
|
|
1333
1420
|
import * as os2 from "node:os";
|
|
1334
1421
|
import * as path8 from "node:path";
|
|
1335
|
-
async function
|
|
1422
|
+
async function parseSymbols2(opts) {
|
|
1336
1423
|
const { file, content, lang } = opts;
|
|
1337
1424
|
try {
|
|
1338
1425
|
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
@@ -1406,6 +1493,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
1406
1493
|
});
|
|
1407
1494
|
});
|
|
1408
1495
|
}
|
|
1496
|
+
function resolvePythonBinary() {
|
|
1497
|
+
cachedPyBinary ??= resolvePython();
|
|
1498
|
+
return cachedPyBinary;
|
|
1499
|
+
}
|
|
1409
1500
|
async function syncPyParse(filePath, content, lang) {
|
|
1410
1501
|
try {
|
|
1411
1502
|
if (!_cachedScriptPath) {
|
|
@@ -1696,9 +1787,713 @@ for node in ast.walk(tree):
|
|
|
1696
1787
|
if name:
|
|
1697
1788
|
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
1698
1789
|
|
|
1699
|
-
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1790
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1791
|
+
`;
|
|
1792
|
+
_cachedScriptPath = null;
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
// src/codebase-index/ts-parser.ts
|
|
1797
|
+
var ts_parser_exports = {};
|
|
1798
|
+
__export(ts_parser_exports, {
|
|
1799
|
+
detectLang: () => detectLang,
|
|
1800
|
+
parseSymbols: () => parseSymbols3
|
|
1801
|
+
});
|
|
1802
|
+
function loadTypescript() {
|
|
1803
|
+
tsLoad ??= import("@typescript/typescript6").then((m) => {
|
|
1804
|
+
ts = m.default ?? m;
|
|
1805
|
+
return ts;
|
|
1806
|
+
});
|
|
1807
|
+
return tsLoad;
|
|
1808
|
+
}
|
|
1809
|
+
function kindMap() {
|
|
1810
|
+
kindMapCache ??= {
|
|
1811
|
+
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
1812
|
+
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
1813
|
+
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
1814
|
+
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
1815
|
+
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
1816
|
+
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
1817
|
+
[ts.SyntaxKind.GetAccessor]: "property",
|
|
1818
|
+
[ts.SyntaxKind.SetAccessor]: "property",
|
|
1819
|
+
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
1820
|
+
[ts.SyntaxKind.Parameter]: "parameter",
|
|
1821
|
+
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
1822
|
+
};
|
|
1823
|
+
return kindMapCache;
|
|
1824
|
+
}
|
|
1825
|
+
function kindOf(node) {
|
|
1826
|
+
if (ts.isVariableDeclaration(node)) {
|
|
1827
|
+
const parent = node.parent;
|
|
1828
|
+
if (ts.isVariableDeclarationList(parent)) {
|
|
1829
|
+
const flags = parent.flags;
|
|
1830
|
+
if (flags & ts.NodeFlags.Let) return "let";
|
|
1831
|
+
if (flags & ts.NodeFlags.Const) return "const";
|
|
1832
|
+
return "var";
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
1836
|
+
return kindMap()[node.kind] ?? null;
|
|
1837
|
+
}
|
|
1838
|
+
function getSignature(printer, node, sourceFile) {
|
|
1839
|
+
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1840
|
+
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
1841
|
+
}
|
|
1842
|
+
function getJsDoc(node, sourceFile) {
|
|
1843
|
+
const fullText = sourceFile.getFullText();
|
|
1844
|
+
const nodePos = node.getFullStart();
|
|
1845
|
+
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
1846
|
+
if (!comments) return "";
|
|
1847
|
+
for (const range of comments) {
|
|
1848
|
+
const commentText = fullText.slice(range.pos, range.end);
|
|
1849
|
+
const trimmed = commentText.trim();
|
|
1850
|
+
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
1851
|
+
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
1852
|
+
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
return "";
|
|
1856
|
+
}
|
|
1857
|
+
function pushScopeName(node, parts) {
|
|
1858
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1859
|
+
parts.push(node.name?.text ?? "Anon");
|
|
1860
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
1861
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
1862
|
+
parts.push(node.name.text);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async function parseSymbols3(opts) {
|
|
1867
|
+
const { file, content, lang } = opts;
|
|
1868
|
+
await loadTypescript();
|
|
1869
|
+
let sourceFile;
|
|
1870
|
+
try {
|
|
1871
|
+
sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1874
|
+
}
|
|
1875
|
+
const symbols = [];
|
|
1876
|
+
const refs = [];
|
|
1877
|
+
const printer = ts.createPrinter({});
|
|
1878
|
+
function visit(node, funcDepth, scopeParts) {
|
|
1879
|
+
const kind = kindOf(node);
|
|
1880
|
+
if (kind) {
|
|
1881
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
1882
|
+
} else {
|
|
1883
|
+
const nameNode = node.name;
|
|
1884
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
const name = nameNode.text;
|
|
1888
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
1889
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
1890
|
+
const scope = scopeParts.join(".");
|
|
1891
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
1892
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
1893
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
1894
|
+
symbols.push({
|
|
1895
|
+
id: 0,
|
|
1896
|
+
lang,
|
|
1897
|
+
kind,
|
|
1898
|
+
name,
|
|
1899
|
+
file,
|
|
1900
|
+
line: line2 + 1,
|
|
1901
|
+
col: character,
|
|
1902
|
+
signature,
|
|
1903
|
+
docComment,
|
|
1904
|
+
scope,
|
|
1905
|
+
text
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const pos = node.getStart(sourceFile);
|
|
1910
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
1911
|
+
const lineNum = line + 1;
|
|
1912
|
+
if (ts.isCallExpression(node)) {
|
|
1913
|
+
const expr = node.expression;
|
|
1914
|
+
if (ts.isIdentifier(expr)) {
|
|
1915
|
+
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
1916
|
+
}
|
|
1917
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
1918
|
+
if (ts.isIdentifier(node.expression)) {
|
|
1919
|
+
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
1920
|
+
}
|
|
1921
|
+
} else if (ts.isTypeReferenceNode(node)) {
|
|
1922
|
+
const name = getTypeName(node.typeName);
|
|
1923
|
+
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
1924
|
+
} else if (ts.isHeritageClause(node)) {
|
|
1925
|
+
for (const t of node.types) {
|
|
1926
|
+
const name = getTypeName(t.expression);
|
|
1927
|
+
if (name)
|
|
1928
|
+
refs.push({
|
|
1929
|
+
fromId: 0,
|
|
1930
|
+
toName: name,
|
|
1931
|
+
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
1932
|
+
line: lineNum
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
} else if (ts.isImportDeclaration(node)) {
|
|
1936
|
+
emitImportSpecifierRefs(node, refs, lineNum);
|
|
1937
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
1938
|
+
emitExportSpecifierRefs(node, refs, lineNum);
|
|
1939
|
+
}
|
|
1940
|
+
const scopeIdx = scopeParts.length;
|
|
1941
|
+
pushScopeName(node, scopeParts);
|
|
1942
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
1943
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
1944
|
+
scopeParts.length = scopeIdx;
|
|
1945
|
+
}
|
|
1946
|
+
visit(sourceFile, 0, []);
|
|
1947
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
1948
|
+
}
|
|
1949
|
+
function getTypeName(name) {
|
|
1950
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
1951
|
+
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
1952
|
+
return "";
|
|
1953
|
+
}
|
|
1954
|
+
function deduplicateRefs(refs) {
|
|
1955
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1956
|
+
return refs.filter((r) => {
|
|
1957
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
1958
|
+
if (seen.has(key)) return false;
|
|
1959
|
+
seen.add(key);
|
|
1960
|
+
return true;
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
function getImportSpecifierName(spec) {
|
|
1964
|
+
return spec.propertyName?.text ?? spec.name.text;
|
|
1965
|
+
}
|
|
1966
|
+
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
1967
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
1968
|
+
const clause = node.importClause;
|
|
1969
|
+
if (!clause) {
|
|
1970
|
+
if (module) {
|
|
1971
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
1972
|
+
}
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
if (clause.name) {
|
|
1976
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
1977
|
+
}
|
|
1978
|
+
const bindings = clause.namedBindings;
|
|
1979
|
+
if (!bindings) return;
|
|
1980
|
+
if (ts.isNamedImports(bindings)) {
|
|
1981
|
+
for (const element of bindings.elements) {
|
|
1982
|
+
refs.push({
|
|
1983
|
+
fromId: 0,
|
|
1984
|
+
toName: getImportSpecifierName(element),
|
|
1985
|
+
callType: "import",
|
|
1986
|
+
line: lineNum,
|
|
1987
|
+
module
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
} else if (ts.isNamespaceImport(bindings)) {
|
|
1991
|
+
refs.push({
|
|
1992
|
+
fromId: 0,
|
|
1993
|
+
toName: bindings.name.text,
|
|
1994
|
+
callType: "import",
|
|
1995
|
+
line: lineNum,
|
|
1996
|
+
module
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
function moduleSpecifierOf(node) {
|
|
2001
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
2002
|
+
}
|
|
2003
|
+
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
2004
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
2005
|
+
const clause = node.exportClause;
|
|
2006
|
+
if (clause && ts.isNamespaceExport(clause)) {
|
|
2007
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
if (clause && ts.isNamedExports(clause)) {
|
|
2011
|
+
for (const element of clause.elements) {
|
|
2012
|
+
const originalName = element.propertyName?.text ?? element.name.text;
|
|
2013
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
2014
|
+
}
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (module) {
|
|
2018
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
var ts, tsLoad, kindMapCache;
|
|
2022
|
+
var init_ts_parser = __esm({
|
|
2023
|
+
"src/codebase-index/ts-parser.ts"() {
|
|
2024
|
+
"use strict";
|
|
2025
|
+
init_languages();
|
|
2026
|
+
tsLoad = null;
|
|
2027
|
+
kindMapCache = null;
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
// src/codebase-index/go-parser.ts
|
|
2032
|
+
var go_parser_exports = {};
|
|
2033
|
+
__export(go_parser_exports, {
|
|
2034
|
+
detectLang: () => detectLang,
|
|
2035
|
+
parseSymbols: () => parseSymbols4
|
|
2036
|
+
});
|
|
2037
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2038
|
+
import * as os3 from "node:os";
|
|
2039
|
+
import * as path9 from "node:path";
|
|
2040
|
+
import * as fs6 from "node:fs/promises";
|
|
2041
|
+
async function parseSymbols4(opts) {
|
|
2042
|
+
const { file, content, lang } = opts;
|
|
2043
|
+
try {
|
|
2044
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
2045
|
+
if (parsed.symbols.length > 0) {
|
|
2046
|
+
return parsed;
|
|
2047
|
+
}
|
|
2048
|
+
const fallback = fallbackParse(file, content, lang);
|
|
2049
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
2050
|
+
} catch {
|
|
2051
|
+
return fallbackParse(file, content, lang);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function fallbackParse(filePath, content, lang) {
|
|
2055
|
+
if (!/^\s*package\s+[A-Za-z_]\w*/m.test(content) || hasUnbalancedDelimiters(content)) {
|
|
2056
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2057
|
+
}
|
|
2058
|
+
const symbols = [];
|
|
2059
|
+
const packageName = content.match(/^\s*package\s+([A-Za-z_]\w*)/m)?.[1] ?? "";
|
|
2060
|
+
const lines = content.split(/\r?\n/);
|
|
2061
|
+
for (const [idx, line] of lines.entries()) {
|
|
2062
|
+
const trimmed = line.trimStart();
|
|
2063
|
+
const col = line.length - trimmed.length + 1;
|
|
2064
|
+
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
2065
|
+
if (fn?.[1]) {
|
|
2066
|
+
addFallbackSymbol(symbols, {
|
|
2067
|
+
filePath,
|
|
2068
|
+
lang,
|
|
2069
|
+
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
2070
|
+
name: fn[1],
|
|
2071
|
+
line: idx + 1,
|
|
2072
|
+
col,
|
|
2073
|
+
signature: trimmed,
|
|
2074
|
+
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
2075
|
+
});
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2079
|
+
if (typeDecl?.[1]) {
|
|
2080
|
+
addFallbackSymbol(symbols, {
|
|
2081
|
+
filePath,
|
|
2082
|
+
lang,
|
|
2083
|
+
kind: "type",
|
|
2084
|
+
name: typeDecl[1],
|
|
2085
|
+
line: idx + 1,
|
|
2086
|
+
col,
|
|
2087
|
+
signature: trimmed,
|
|
2088
|
+
scope: packageName
|
|
2089
|
+
});
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2093
|
+
if (valueDecl?.[1] && valueDecl[2]) {
|
|
2094
|
+
addFallbackSymbol(symbols, {
|
|
2095
|
+
filePath,
|
|
2096
|
+
lang,
|
|
2097
|
+
kind: valueDecl[1],
|
|
2098
|
+
name: valueDecl[2],
|
|
2099
|
+
line: idx + 1,
|
|
2100
|
+
col,
|
|
2101
|
+
signature: trimmed,
|
|
2102
|
+
scope: packageName
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2107
|
+
}
|
|
2108
|
+
function addFallbackSymbol(symbols, opts) {
|
|
2109
|
+
symbols.push({
|
|
2110
|
+
id: 0,
|
|
2111
|
+
lang: opts.lang,
|
|
2112
|
+
kind: opts.kind,
|
|
2113
|
+
name: opts.name,
|
|
2114
|
+
file: opts.filePath,
|
|
2115
|
+
line: opts.line,
|
|
2116
|
+
col: opts.col,
|
|
2117
|
+
signature: opts.signature,
|
|
2118
|
+
docComment: "",
|
|
2119
|
+
scope: opts.scope,
|
|
2120
|
+
text: `${opts.name} ${opts.signature}`.trim()
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
function hasUnbalancedDelimiters(content) {
|
|
2124
|
+
const pairs = { "(": ")", "[": "]", "{": "}" };
|
|
2125
|
+
const closers = new Set(Object.values(pairs));
|
|
2126
|
+
const stack = [];
|
|
2127
|
+
for (const ch of content) {
|
|
2128
|
+
if (pairs[ch]) {
|
|
2129
|
+
stack.push(pairs[ch]);
|
|
2130
|
+
} else if (closers.has(ch) && stack.pop() !== ch) {
|
|
2131
|
+
return true;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return stack.length > 0;
|
|
2135
|
+
}
|
|
2136
|
+
async function syncGoParse(filePath, content, lang) {
|
|
2137
|
+
try {
|
|
2138
|
+
let scriptPath = _cachedGoScriptPath;
|
|
2139
|
+
if (!scriptPath) {
|
|
2140
|
+
const tmpDir = await fs6.mkdtemp(path9.join(os3.tmpdir(), "ws-go-parse-"));
|
|
2141
|
+
scriptPath = path9.join(tmpDir, "parse.go");
|
|
2142
|
+
await fs6.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
2143
|
+
_cachedGoScriptPath = scriptPath;
|
|
2144
|
+
}
|
|
2145
|
+
const goBinary = resolveWin32Command("go");
|
|
2146
|
+
const goResult = await new Promise(
|
|
2147
|
+
(resolve4, reject) => {
|
|
2148
|
+
let settled = false;
|
|
2149
|
+
const proc = spawn3(goBinary, ["run", scriptPath], {
|
|
2150
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2151
|
+
windowsHide: true
|
|
2152
|
+
});
|
|
2153
|
+
proc.on("error", (err) => {
|
|
2154
|
+
if (settled) return;
|
|
2155
|
+
settled = true;
|
|
2156
|
+
reject(err);
|
|
2157
|
+
});
|
|
2158
|
+
let stdout2 = "";
|
|
2159
|
+
proc.stdout?.on("data", (chunk) => {
|
|
2160
|
+
stdout2 += chunk.toString();
|
|
2161
|
+
});
|
|
2162
|
+
proc.stderr?.resume();
|
|
2163
|
+
proc.stdin?.write(content);
|
|
2164
|
+
proc.stdin?.end();
|
|
2165
|
+
const timer = setTimeout(() => {
|
|
2166
|
+
if (settled) return;
|
|
2167
|
+
settled = true;
|
|
2168
|
+
proc.kill("SIGKILL");
|
|
2169
|
+
reject(new Error("timeout"));
|
|
2170
|
+
}, 15e3);
|
|
2171
|
+
timer.unref?.();
|
|
2172
|
+
proc.on("close", (code2) => {
|
|
2173
|
+
if (settled) return;
|
|
2174
|
+
settled = true;
|
|
2175
|
+
clearTimeout(timer);
|
|
2176
|
+
resolve4({ code: code2, stdout: stdout2 });
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
);
|
|
2180
|
+
const { code, stdout } = goResult;
|
|
2181
|
+
if (code !== 0 || !stdout.trim()) {
|
|
2182
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2183
|
+
}
|
|
2184
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
2185
|
+
const symbols = rawSymbols.map((s) => ({
|
|
2186
|
+
id: 0,
|
|
2187
|
+
lang,
|
|
2188
|
+
kind: s.kind,
|
|
2189
|
+
name: s.name,
|
|
2190
|
+
file: filePath,
|
|
2191
|
+
line: s.line,
|
|
2192
|
+
col: s.col,
|
|
2193
|
+
signature: s.signature ?? "",
|
|
2194
|
+
docComment: "",
|
|
2195
|
+
scope: s.scope ?? "",
|
|
2196
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
2197
|
+
}));
|
|
2198
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
2199
|
+
} catch {
|
|
2200
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
var GO_PARSE_SCRIPT, _cachedGoScriptPath;
|
|
2204
|
+
var init_go_parser = __esm({
|
|
2205
|
+
"src/codebase-index/go-parser.ts"() {
|
|
2206
|
+
"use strict";
|
|
2207
|
+
init_win32_resolve();
|
|
2208
|
+
init_parser_output();
|
|
2209
|
+
init_spawn_gate();
|
|
2210
|
+
init_languages();
|
|
2211
|
+
GO_PARSE_SCRIPT = `
|
|
2212
|
+
package main
|
|
2213
|
+
|
|
2214
|
+
import (
|
|
2215
|
+
"encoding/json"
|
|
2216
|
+
"fmt"
|
|
2217
|
+
"go/ast"
|
|
2218
|
+
"go/parser"
|
|
2219
|
+
"go/token"
|
|
2220
|
+
"io"
|
|
2221
|
+
"os"
|
|
2222
|
+
"strconv"
|
|
2223
|
+
"strings"
|
|
2224
|
+
)
|
|
2225
|
+
|
|
2226
|
+
type Sym struct {
|
|
2227
|
+
Name string \`json:"name"\`
|
|
2228
|
+
Kind string \`json:"kind"\`
|
|
2229
|
+
Line int \`json:"line"\`
|
|
2230
|
+
Col int \`json:"col"\`
|
|
2231
|
+
Signature string \`json:"signature"\`
|
|
2232
|
+
Scope string \`json:"scope"\`
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
2236
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
2237
|
+
type Ref struct {
|
|
2238
|
+
ToName string \`json:"toName"\`
|
|
2239
|
+
CallType string \`json:"callType"\`
|
|
2240
|
+
Line int \`json:"line"\`
|
|
2241
|
+
Module string \`json:"module"\`
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
type Result struct {
|
|
2245
|
+
Symbols []Sym \`json:"symbols"\`
|
|
2246
|
+
Refs []Ref \`json:"refs"\`
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
func emptyResult() string {
|
|
2250
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
func main() {
|
|
2254
|
+
src, err := io.ReadAll(os.Stdin)
|
|
2255
|
+
if err != nil {
|
|
2256
|
+
fmt.Print(emptyResult())
|
|
2257
|
+
return
|
|
2258
|
+
}
|
|
2259
|
+
fset := token.NewFileSet()
|
|
2260
|
+
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
2261
|
+
if err != nil {
|
|
2262
|
+
fmt.Print(emptyResult())
|
|
2263
|
+
return
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
var syms []Sym
|
|
2267
|
+
|
|
2268
|
+
// Package-level scope
|
|
2269
|
+
pkgScope := node.Name.Name
|
|
2270
|
+
|
|
2271
|
+
// Collect all top-level declarations
|
|
2272
|
+
for _, decl := range node.Decls {
|
|
2273
|
+
switch d := decl.(type) {
|
|
2274
|
+
case *ast.FuncDecl:
|
|
2275
|
+
name := d.Name.Name
|
|
2276
|
+
kind := "function"
|
|
2277
|
+
scope := pkgScope
|
|
2278
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2279
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + name
|
|
2280
|
+
kind = "method"
|
|
2281
|
+
} else {
|
|
2282
|
+
scope = pkgScope + "." + name
|
|
2283
|
+
}
|
|
2284
|
+
pos := fset.Position(d.Pos())
|
|
2285
|
+
sig := formatFuncSig(d)
|
|
2286
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
2287
|
+
|
|
2288
|
+
case *ast.GenDecl:
|
|
2289
|
+
for _, spec := range d.Specs {
|
|
2290
|
+
switch s := spec.(type) {
|
|
2291
|
+
case *ast.TypeSpec:
|
|
2292
|
+
name := s.Name.Name
|
|
2293
|
+
pos := fset.Position(s.Pos())
|
|
2294
|
+
sig := "type " + name
|
|
2295
|
+
if s.TypeParams != nil {
|
|
2296
|
+
sig += formatTypeParams(s.TypeParams)
|
|
2297
|
+
}
|
|
2298
|
+
if st, ok := s.Type.(*ast.StructType); ok {
|
|
2299
|
+
sig += " = struct { " + formatFields(st.Fields.List) + " }"
|
|
2300
|
+
} else if it, ok := s.Type.(*ast.InterfaceType); ok {
|
|
2301
|
+
sig += " = interface { " + formatMethods(it.Methods.List) + " }"
|
|
2302
|
+
} else {
|
|
2303
|
+
sig += " = " + formatType(s.Type)
|
|
2304
|
+
}
|
|
2305
|
+
syms = append(syms, Sym{Name: name, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2306
|
+
|
|
2307
|
+
case *ast.ValueSpec:
|
|
2308
|
+
for _, n := range s.Names {
|
|
2309
|
+
name := n.Name
|
|
2310
|
+
pos := fset.Position(n.Pos())
|
|
2311
|
+
kind := "var"
|
|
2312
|
+
if d.Tok == token.CONST {
|
|
2313
|
+
kind = "const"
|
|
2314
|
+
}
|
|
2315
|
+
sig := kind + " " + name
|
|
2316
|
+
if s.Type != nil {
|
|
2317
|
+
sig += " " + formatType(s.Type)
|
|
2318
|
+
}
|
|
2319
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
refs := []Ref{}
|
|
2327
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
2328
|
+
switch expr := n.(type) {
|
|
2329
|
+
case *ast.CallExpr:
|
|
2330
|
+
line := fset.Position(expr.Pos()).Line
|
|
2331
|
+
switch fun := expr.Fun.(type) {
|
|
2332
|
+
case *ast.Ident:
|
|
2333
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
2334
|
+
case *ast.SelectorExpr:
|
|
2335
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
2336
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
2337
|
+
// and Python extractors' call refs do.
|
|
2338
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
2339
|
+
}
|
|
2340
|
+
case *ast.ImportSpec:
|
|
2341
|
+
if expr.Path != nil {
|
|
2342
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
2343
|
+
line := fset.Position(expr.Pos()).Line
|
|
2344
|
+
// A Go import names a package, not a symbol; the package's
|
|
2345
|
+
// last path segment is the name it is referenced by.
|
|
2346
|
+
name := importPath
|
|
2347
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
2348
|
+
name = importPath[idx+1:]
|
|
2349
|
+
}
|
|
2350
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return true
|
|
2355
|
+
})
|
|
2356
|
+
|
|
2357
|
+
if syms == nil {
|
|
2358
|
+
syms = []Sym{}
|
|
2359
|
+
}
|
|
2360
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
2361
|
+
if err != nil {
|
|
2362
|
+
fmt.Print(emptyResult())
|
|
2363
|
+
return
|
|
2364
|
+
}
|
|
2365
|
+
fmt.Print(string(data))
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
func recvTypeName(t ast.Expr) string {
|
|
2369
|
+
switch v := t.(type) {
|
|
2370
|
+
case *ast.Ident:
|
|
2371
|
+
return v.Name
|
|
2372
|
+
case *ast.StarExpr:
|
|
2373
|
+
return recvTypeName(v.X)
|
|
2374
|
+
default:
|
|
2375
|
+
return "?"
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
func formatFuncSig(d *ast.FuncDecl) string {
|
|
2380
|
+
scope := ""
|
|
2381
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2382
|
+
scope = "(" + formatFieldList(d.Recv.List) + ") "
|
|
2383
|
+
}
|
|
2384
|
+
scope += formatFuncType(d.Type)
|
|
2385
|
+
return "func " + scope
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
func formatFuncType(f *ast.FuncType) string {
|
|
2389
|
+
params := formatFieldList(f.Params.List)
|
|
2390
|
+
results := ""
|
|
2391
|
+
if f.Results != nil {
|
|
2392
|
+
results = " -> " + formatFieldList(f.Results.List)
|
|
2393
|
+
}
|
|
2394
|
+
return params + results
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
func formatFieldList(fields []*ast.Field) string {
|
|
2398
|
+
if len(fields) == 0 {
|
|
2399
|
+
return "()"
|
|
2400
|
+
}
|
|
2401
|
+
names := make([]string, 0, len(fields))
|
|
2402
|
+
for _, f := range fields {
|
|
2403
|
+
name := ""
|
|
2404
|
+
if len(f.Names) > 0 {
|
|
2405
|
+
name = f.Names[0].Name
|
|
2406
|
+
}
|
|
2407
|
+
t := formatType(f.Type)
|
|
2408
|
+
if name != "" {
|
|
2409
|
+
names = append(names, name+" "+t)
|
|
2410
|
+
} else {
|
|
2411
|
+
names = append(names, t)
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return "(" + strings.Join(names, ", ") + ")"
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
func formatFields(fields []*ast.Field) string {
|
|
2418
|
+
lines := make([]string, 0)
|
|
2419
|
+
for _, f := range fields {
|
|
2420
|
+
name := ""
|
|
2421
|
+
if len(f.Names) > 0 {
|
|
2422
|
+
name = f.Names[0].Name
|
|
2423
|
+
}
|
|
2424
|
+
t := formatType(f.Type)
|
|
2425
|
+
if name != "" {
|
|
2426
|
+
lines = append(lines, name+" "+t)
|
|
2427
|
+
} else {
|
|
2428
|
+
lines = append(lines, t)
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
return strings.Join(lines, "; ")
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
func formatMethods(fields []*ast.Field) string {
|
|
2435
|
+
return formatFields(fields)
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
func formatTypeParams(tp *ast.FieldList) string {
|
|
2439
|
+
if tp == nil || len(tp.List) == 0 {
|
|
2440
|
+
return ""
|
|
2441
|
+
}
|
|
2442
|
+
params := make([]string, len(tp.List))
|
|
2443
|
+
for i, p := range tp.List {
|
|
2444
|
+
if len(p.Names) > 0 {
|
|
2445
|
+
params[i] = p.Names[0].Name
|
|
2446
|
+
} else {
|
|
2447
|
+
params[i] = "T"
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return "[" + strings.Join(params, ", ") + "]"
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
func formatType(t ast.Expr) string {
|
|
2454
|
+
if t == nil {
|
|
2455
|
+
return "?"
|
|
2456
|
+
}
|
|
2457
|
+
switch v := t.(type) {
|
|
2458
|
+
case *ast.Ident:
|
|
2459
|
+
return v.Name
|
|
2460
|
+
case *ast.SelectorExpr:
|
|
2461
|
+
return formatType(v.X) + "." + v.Sel.Name
|
|
2462
|
+
case *ast.StarExpr:
|
|
2463
|
+
return "*" + formatType(v.X)
|
|
2464
|
+
case *ast.ArrayType:
|
|
2465
|
+
if v.Len == nil {
|
|
2466
|
+
return "[]" + formatType(v.Elt)
|
|
2467
|
+
}
|
|
2468
|
+
return "[...]" + formatType(v.Elt)
|
|
2469
|
+
case *ast.MapType:
|
|
2470
|
+
return "map[" + formatType(v.Key) + "]" + formatType(v.Value)
|
|
2471
|
+
case *ast.InterfaceType:
|
|
2472
|
+
return "interface{}"
|
|
2473
|
+
case *ast.StructType:
|
|
2474
|
+
return "struct{}"
|
|
2475
|
+
case *ast.FuncType:
|
|
2476
|
+
return formatFuncType(v)
|
|
2477
|
+
case *ast.ChanType:
|
|
2478
|
+
return "chan " + formatType(v.Value)
|
|
2479
|
+
case *ast.BasicLit:
|
|
2480
|
+
return v.Value
|
|
2481
|
+
case *ast.IndexExpr:
|
|
2482
|
+
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
2483
|
+
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
2484
|
+
case *ast.IndexListExpr:
|
|
2485
|
+
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
2486
|
+
args := make([]string, len(v.Indices))
|
|
2487
|
+
for i, idx := range v.Indices {
|
|
2488
|
+
args[i] = formatType(idx)
|
|
2489
|
+
}
|
|
2490
|
+
return formatType(v.X) + "[" + strings.Join(args, ", ") + "]"
|
|
2491
|
+
default:
|
|
2492
|
+
return "?"
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
1700
2495
|
`;
|
|
1701
|
-
|
|
2496
|
+
_cachedGoScriptPath = null;
|
|
1702
2497
|
}
|
|
1703
2498
|
});
|
|
1704
2499
|
|
|
@@ -1794,7 +2589,7 @@ __export(json_parser_exports, {
|
|
|
1794
2589
|
parseSymbols: () => parseSymbols6
|
|
1795
2590
|
});
|
|
1796
2591
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
1797
|
-
import * as
|
|
2592
|
+
import * as path10 from "node:path";
|
|
1798
2593
|
function parseSymbols6(opts) {
|
|
1799
2594
|
const { file, content, lang } = opts;
|
|
1800
2595
|
try {
|
|
@@ -1806,7 +2601,7 @@ function parseSymbols6(opts) {
|
|
|
1806
2601
|
function regexParse2(opts) {
|
|
1807
2602
|
const { file, content, lang } = opts;
|
|
1808
2603
|
const symbols = [];
|
|
1809
|
-
const basename7 =
|
|
2604
|
+
const basename7 = path10.basename(file).toLowerCase();
|
|
1810
2605
|
const isPackageJson = basename7 === "package.json";
|
|
1811
2606
|
const isTsconfig = basename7 === "tsconfig.json" || basename7 === "tsconfig.build.json";
|
|
1812
2607
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
@@ -1832,11 +2627,11 @@ function regexParse2(opts) {
|
|
|
1832
2627
|
const line = lineFromOffset(offset);
|
|
1833
2628
|
symbols.push(
|
|
1834
2629
|
makeSymbol({
|
|
1835
|
-
name:
|
|
2630
|
+
name: path10.basename(file),
|
|
1836
2631
|
kind: "object",
|
|
1837
2632
|
line,
|
|
1838
2633
|
col: 0,
|
|
1839
|
-
signature: `"${
|
|
2634
|
+
signature: `"${path10.basename(file)}" = { ... }`,
|
|
1840
2635
|
file,
|
|
1841
2636
|
lang
|
|
1842
2637
|
})
|
|
@@ -2164,6 +2959,106 @@ var init_yaml_parser = __esm({
|
|
|
2164
2959
|
});
|
|
2165
2960
|
|
|
2166
2961
|
// src/codebase-index/tree-sitter/queries.ts
|
|
2962
|
+
function parseGroupedUse(text) {
|
|
2963
|
+
const open = text.indexOf("{");
|
|
2964
|
+
const close = text.lastIndexOf("}");
|
|
2965
|
+
if (open < 0 || close <= open) return null;
|
|
2966
|
+
const prefix = text.slice(0, open).replace(/[\\/]+$/, "");
|
|
2967
|
+
const out = [];
|
|
2968
|
+
for (const rawMember of text.slice(open + 1, close).split(",")) {
|
|
2969
|
+
let member = rawMember.trim();
|
|
2970
|
+
if (!member) continue;
|
|
2971
|
+
member = member.replace(
|
|
2972
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2973
|
+
""
|
|
2974
|
+
).trim();
|
|
2975
|
+
if (!member) continue;
|
|
2976
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(member);
|
|
2977
|
+
if (aliasMatch) member = member.slice(0, aliasMatch.index).trim();
|
|
2978
|
+
if (!member) continue;
|
|
2979
|
+
const module = prefix ? `${prefix}\\${member}` : member;
|
|
2980
|
+
const toName = member.split(/[\\/]/).filter(Boolean).pop();
|
|
2981
|
+
if (toName) out.push({ toName, callType: "import", module });
|
|
2982
|
+
}
|
|
2983
|
+
return out.length ? out : null;
|
|
2984
|
+
}
|
|
2985
|
+
function importFromText(prefixes) {
|
|
2986
|
+
return (node) => {
|
|
2987
|
+
let text = node.text.replace(/\s+/g, " ").trim();
|
|
2988
|
+
for (const prefix of prefixes) {
|
|
2989
|
+
if (text.startsWith(prefix)) text = text.slice(prefix.length).trim();
|
|
2990
|
+
}
|
|
2991
|
+
text = text.replace(
|
|
2992
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2993
|
+
""
|
|
2994
|
+
).trim();
|
|
2995
|
+
if (text.includes("{")) return parseGroupedUse(text);
|
|
2996
|
+
if (text.includes(",") && !text.includes("<") && !text.includes("=")) {
|
|
2997
|
+
const out = [];
|
|
2998
|
+
for (const clause of text.split(",")) {
|
|
2999
|
+
const one = oneImportClause(clause.trim());
|
|
3000
|
+
if (one) out.push(one);
|
|
3001
|
+
}
|
|
3002
|
+
return out.length ? out : null;
|
|
3003
|
+
}
|
|
3004
|
+
const single = oneImportClause(text);
|
|
3005
|
+
return single ? [single] : null;
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
function oneImportClause(rawClause) {
|
|
3009
|
+
let text = rawClause;
|
|
3010
|
+
text = text.replace(
|
|
3011
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
3012
|
+
""
|
|
3013
|
+
).trim();
|
|
3014
|
+
text = text.replace(/[;}]+$/g, "").trim();
|
|
3015
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(text);
|
|
3016
|
+
if (aliasMatch) text = text.slice(0, aliasMatch.index).trim();
|
|
3017
|
+
const eqMatch = /^([A-Za-z_]\w*)\s*=\s*(.+)$/.exec(text);
|
|
3018
|
+
if (eqMatch) text = eqMatch[2].trim();
|
|
3019
|
+
if (!text) return null;
|
|
3020
|
+
if (text.endsWith("*")) text = text.slice(0, -1).replace(/[.]$/, "");
|
|
3021
|
+
if (!text) return null;
|
|
3022
|
+
const module = text;
|
|
3023
|
+
const toName = module.split(/[.\\/]/).filter(Boolean).pop()?.replace(/<.*>$/s, "");
|
|
3024
|
+
if (!toName) return null;
|
|
3025
|
+
return { toName, callType: "import", module };
|
|
3026
|
+
}
|
|
3027
|
+
function heritageLeaf(node, depth) {
|
|
3028
|
+
if (depth > 6) return null;
|
|
3029
|
+
const named = node.childForFieldName("name");
|
|
3030
|
+
if (named) {
|
|
3031
|
+
if (named.type === "scoped_type_identifier" || named.type === "qualified_name" || named.type === "scope_resolution" || named.type === "user_type") {
|
|
3032
|
+
return heritageLeaf(named, depth + 1);
|
|
3033
|
+
}
|
|
3034
|
+
return named.text;
|
|
3035
|
+
}
|
|
3036
|
+
const children = [];
|
|
3037
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3038
|
+
const c = node.namedChild(i);
|
|
3039
|
+
if (c) children.push(c);
|
|
3040
|
+
}
|
|
3041
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
3042
|
+
const c = children[i];
|
|
3043
|
+
if (c.type === "type_arguments" || c.type === "type_argument_list" || // cpp: (template_type arguments: (template_argument_list …)) — the
|
|
3044
|
+
// descriptor's type_identifier inside it is never the declared base.
|
|
3045
|
+
c.type === "template_argument_list" || c.type === "type_parameter_list" || c.type === "type_projection" || c.type === "value_arguments") {
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
if (c.type === "type_identifier" || c.type === "identifier" || c.type === "constant" || c.type === "name") {
|
|
3049
|
+
return c.text;
|
|
3050
|
+
}
|
|
3051
|
+
if (c.type === "scoped_type_identifier" || c.type === "qualified_name" || c.type === "scope_resolution" || c.type === "user_type") {
|
|
3052
|
+
return heritageLeaf(c, depth + 1);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
return leafSegment(
|
|
3056
|
+
node.text.replace(/\\/g, ".").replace(/::/g, ".").replace(/<[^<>]*>$/, "")
|
|
3057
|
+
);
|
|
3058
|
+
}
|
|
3059
|
+
function leafSegment(text) {
|
|
3060
|
+
return text.split(".").filter(Boolean).pop() ?? text;
|
|
3061
|
+
}
|
|
2167
3062
|
function getQueries(lang) {
|
|
2168
3063
|
return LANG_QUERIES[lang] ?? DEFAULT_QUERIES;
|
|
2169
3064
|
}
|
|
@@ -2175,10 +3070,127 @@ function readFirstString(node) {
|
|
|
2175
3070
|
const child = node.namedChild(0);
|
|
2176
3071
|
return child ? readFirstString(child) : null;
|
|
2177
3072
|
}
|
|
2178
|
-
var DEFAULT_QUERIES, LANG_QUERIES;
|
|
3073
|
+
var heritageExtractor, cCallExtractor, rubyCallExtractor, firstIdentifierCallExtractor, cIncludeExtractor, phpConstructorExtractor, DEFAULT_QUERIES, LANG_QUERIES;
|
|
2179
3074
|
var init_queries = __esm({
|
|
2180
3075
|
"src/codebase-index/tree-sitter/queries.ts"() {
|
|
2181
3076
|
"use strict";
|
|
3077
|
+
heritageExtractor = (node) => {
|
|
3078
|
+
const out = [];
|
|
3079
|
+
const SKIP_SUBTREES = /* @__PURE__ */ new Set([
|
|
3080
|
+
"type_arguments",
|
|
3081
|
+
"type_argument_list",
|
|
3082
|
+
// tree-sitter-cpp names its argument subtree template_argument_list —
|
|
3083
|
+
// verified AST: (base_class_clause (template_type name:
|
|
3084
|
+
// (type_identifier) arguments: (template_argument_list
|
|
3085
|
+
// (type_descriptor type: (type_identifier))))). Without this entry
|
|
3086
|
+
// `class D : Base<Foo>` recurses into the descriptor and emits Foo as a
|
|
3087
|
+
// phantom inherit ref.
|
|
3088
|
+
"template_argument_list",
|
|
3089
|
+
"type_parameter_list",
|
|
3090
|
+
"type_projection",
|
|
3091
|
+
"value_arguments"
|
|
3092
|
+
]);
|
|
3093
|
+
const collect = (current, depth) => {
|
|
3094
|
+
if (depth > 4) return;
|
|
3095
|
+
for (let i = 0; i < current.namedChildCount; i++) {
|
|
3096
|
+
const child = current.namedChild(i);
|
|
3097
|
+
if (!child) continue;
|
|
3098
|
+
if (SKIP_SUBTREES.has(child.type)) continue;
|
|
3099
|
+
if (child.type === "type_identifier" || child.type === "identifier" || child.type === "named_type" || child.type === "type" || // PHP heritage carries `name`; Ruby a `constant`.
|
|
3100
|
+
child.type === "constant" || child.type === "name") {
|
|
3101
|
+
const name = child.type === "named_type" ? leafSegment(child.text) : child.text;
|
|
3102
|
+
if (name) out.push({ toName: name });
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
if (child.type === "generic_type" || child.type === "generic_name") {
|
|
3106
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
3107
|
+
const inner = child.namedChild(j);
|
|
3108
|
+
if (inner && !SKIP_SUBTREES.has(inner.type) && (inner.type === "type_identifier" || inner.type === "identifier" || inner.type === "name")) {
|
|
3109
|
+
out.push({ toName: inner.text });
|
|
3110
|
+
break;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
if (child.type === "qualified_name" || child.type === "scoped_type_identifier" || child.type === "user_type" || child.type === "scope_resolution") {
|
|
3116
|
+
const leaf = heritageLeaf(child, 0);
|
|
3117
|
+
if (leaf) out.push({ toName: leaf });
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
collect(child, depth + 1);
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
collect(node, 0);
|
|
3124
|
+
return out;
|
|
3125
|
+
};
|
|
3126
|
+
cCallExtractor = (node) => {
|
|
3127
|
+
const fn = node.childForFieldName("function");
|
|
3128
|
+
if (!fn) return null;
|
|
3129
|
+
if (fn.type === "field_expression") {
|
|
3130
|
+
const field = fn.childForFieldName("field");
|
|
3131
|
+
if (field) return [{ toName: field.text, callType: "call" }];
|
|
3132
|
+
const seg = fn.text.split("->").filter(Boolean).pop();
|
|
3133
|
+
if (seg) return [{ toName: leafSegment(seg.split(".")[0] ?? seg), callType: "call" }];
|
|
3134
|
+
return null;
|
|
3135
|
+
}
|
|
3136
|
+
if (fn.type === "qualified_identifier") {
|
|
3137
|
+
const name = fn.childForFieldName("name");
|
|
3138
|
+
if (name) return [{ toName: name.text, callType: "call" }];
|
|
3139
|
+
const seg = fn.text.split("::").filter(Boolean).pop();
|
|
3140
|
+
if (seg) return [{ toName: seg.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
return [{ toName: fn.text.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3144
|
+
};
|
|
3145
|
+
rubyCallExtractor = (node) => {
|
|
3146
|
+
const emissions = [];
|
|
3147
|
+
const method = node.childForFieldName("method");
|
|
3148
|
+
if (method) {
|
|
3149
|
+
const name = method.text;
|
|
3150
|
+
if (name && !name.includes(" ")) emissions.push({ toName: name, callType: "call" });
|
|
3151
|
+
if (name === "require" || name === "require_relative") {
|
|
3152
|
+
const args = node.childForFieldName("arguments");
|
|
3153
|
+
const first = args?.namedChild(0);
|
|
3154
|
+
if (first) {
|
|
3155
|
+
const raw = first.text.replace(/^['"]|['"]$/g, "");
|
|
3156
|
+
const toName = raw.split("/").filter(Boolean).pop();
|
|
3157
|
+
if (toName) emissions.push({ toName, callType: "import", module: raw });
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return emissions;
|
|
3162
|
+
};
|
|
3163
|
+
firstIdentifierCallExtractor = (node) => {
|
|
3164
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3165
|
+
const child = node.namedChild(i);
|
|
3166
|
+
if (child && (child.type === "simple_identifier" || child.type === "identifier")) {
|
|
3167
|
+
return [{ toName: child.text, callType: "call" }];
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
const first = node.namedChild(0);
|
|
3171
|
+
if (!first) return null;
|
|
3172
|
+
const leaf = leafSegment(first.text.split(/[<(]/)[0] ?? first.text);
|
|
3173
|
+
if (!leaf) return null;
|
|
3174
|
+
return [{ toName: leaf, callType: "call" }];
|
|
3175
|
+
};
|
|
3176
|
+
cIncludeExtractor = (node) => {
|
|
3177
|
+
const raw = node.text.replace(/^#\s*include\s*/i, "").trim();
|
|
3178
|
+
const module = raw.replace(/^["'<]|["'>]$/g, "");
|
|
3179
|
+
if (!module) return null;
|
|
3180
|
+
const toName = module.split("/").pop()?.replace(/\.h$/, "");
|
|
3181
|
+
if (!toName) return null;
|
|
3182
|
+
return [{ toName, callType: "import", module }];
|
|
3183
|
+
};
|
|
3184
|
+
phpConstructorExtractor = (node) => {
|
|
3185
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3186
|
+
const child = node.namedChild(i);
|
|
3187
|
+
if (child && (child.type === "qualified_name" || child.type === "name")) {
|
|
3188
|
+
const leaf = child.text.split(/[\\]/).filter(Boolean).pop();
|
|
3189
|
+
if (leaf) return [{ toName: leaf, callType: "call" }];
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return null;
|
|
3193
|
+
};
|
|
2182
3194
|
DEFAULT_QUERIES = {
|
|
2183
3195
|
declKinds: {}
|
|
2184
3196
|
};
|
|
@@ -2211,7 +3223,13 @@ var init_queries = __esm({
|
|
|
2211
3223
|
"struct_specifier",
|
|
2212
3224
|
"union_specifier",
|
|
2213
3225
|
"enum_specifier"
|
|
2214
|
-
])
|
|
3226
|
+
]),
|
|
3227
|
+
refRules: {
|
|
3228
|
+
// `obj->run()` and `Cls::stat()` carry structured function fields —
|
|
3229
|
+
// cCallExtractor handles all three AST shapes.
|
|
3230
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3231
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor }
|
|
3232
|
+
}
|
|
2215
3233
|
},
|
|
2216
3234
|
cpp: {
|
|
2217
3235
|
declKinds: {
|
|
@@ -2242,7 +3260,13 @@ var init_queries = __esm({
|
|
|
2242
3260
|
"union_specifier",
|
|
2243
3261
|
"enum_specifier",
|
|
2244
3262
|
"namespace_definition"
|
|
2245
|
-
])
|
|
3263
|
+
]),
|
|
3264
|
+
refRules: {
|
|
3265
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3266
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor },
|
|
3267
|
+
// `class Foo : public Bar, private Baz` — the base-class clause.
|
|
3268
|
+
base_class_clause: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3269
|
+
}
|
|
2246
3270
|
},
|
|
2247
3271
|
java: {
|
|
2248
3272
|
declKinds: {
|
|
@@ -2277,7 +3301,22 @@ var init_queries = __esm({
|
|
|
2277
3301
|
"interface_declaration",
|
|
2278
3302
|
"enum_declaration",
|
|
2279
3303
|
"record_declaration"
|
|
2280
|
-
])
|
|
3304
|
+
]),
|
|
3305
|
+
refRules: {
|
|
3306
|
+
method_invocation: { callType: "call", field: "name" },
|
|
3307
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3308
|
+
// Verified AST: `superclass: (superclass (type_identifier))` and
|
|
3309
|
+
// `interfaces: (super_interfaces (type_list ...))` — no underscores.
|
|
3310
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3311
|
+
super_interfaces: {
|
|
3312
|
+
callType: "implement",
|
|
3313
|
+
nameExtractor: heritageExtractor
|
|
3314
|
+
},
|
|
3315
|
+
import_declaration: {
|
|
3316
|
+
callType: "import",
|
|
3317
|
+
nameExtractor: importFromText(["import "])
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
2281
3320
|
},
|
|
2282
3321
|
csharp: {
|
|
2283
3322
|
// C# 10+ `namespace Foo.Bar;` produces this node type. The legacy block
|
|
@@ -2315,7 +3354,18 @@ var init_queries = __esm({
|
|
|
2315
3354
|
"struct_declaration",
|
|
2316
3355
|
"enum_declaration",
|
|
2317
3356
|
"record_declaration"
|
|
2318
|
-
])
|
|
3357
|
+
]),
|
|
3358
|
+
refRules: {
|
|
3359
|
+
// Verified AST: `invocation_expression function: (identifier)` — the
|
|
3360
|
+
// callee field is `function` (C-style), not `name`.
|
|
3361
|
+
invocation_expression: { callType: "call", field: "function" },
|
|
3362
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3363
|
+
base_list: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3364
|
+
using_directive: {
|
|
3365
|
+
callType: "import",
|
|
3366
|
+
nameExtractor: importFromText(["using "])
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
2319
3369
|
},
|
|
2320
3370
|
php: {
|
|
2321
3371
|
declKinds: {
|
|
@@ -2334,7 +3384,7 @@ var init_queries = __esm({
|
|
|
2334
3384
|
interface_declaration: "name",
|
|
2335
3385
|
trait_declaration: "name",
|
|
2336
3386
|
enum_declaration: "name",
|
|
2337
|
-
|
|
3387
|
+
namespace_definition: "name"
|
|
2338
3388
|
},
|
|
2339
3389
|
scopeNodes: /* @__PURE__ */ new Set([
|
|
2340
3390
|
"program",
|
|
@@ -2343,7 +3393,24 @@ var init_queries = __esm({
|
|
|
2343
3393
|
"interface_declaration",
|
|
2344
3394
|
"trait_declaration",
|
|
2345
3395
|
"enum_declaration"
|
|
2346
|
-
])
|
|
3396
|
+
]),
|
|
3397
|
+
refRules: {
|
|
3398
|
+
function_call_expression: { callType: "call", field: "function" },
|
|
3399
|
+
// Verified AST: `new App\Model\User()` carries a BARE qualified_name
|
|
3400
|
+
// child (no `name:` field), so the field default never fires.
|
|
3401
|
+
object_creation_expression: { callType: "call", nameExtractor: phpConstructorExtractor },
|
|
3402
|
+
base_clause: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3403
|
+
class_interface_clause: {
|
|
3404
|
+
callType: "implement",
|
|
3405
|
+
nameExtractor: heritageExtractor
|
|
3406
|
+
},
|
|
3407
|
+
// Verified AST: `namespace_use_declaration (namespace_use_clause
|
|
3408
|
+
// (qualified_name ...))` — not `use_declaration`.
|
|
3409
|
+
namespace_use_declaration: {
|
|
3410
|
+
callType: "import",
|
|
3411
|
+
nameExtractor: importFromText(["use "])
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
2347
3414
|
},
|
|
2348
3415
|
// ─── Scripting / mobile ────────────────────────────────────────────────────
|
|
2349
3416
|
ruby: {
|
|
@@ -2361,7 +3428,13 @@ var init_queries = __esm({
|
|
|
2361
3428
|
module: "name",
|
|
2362
3429
|
constant: "name"
|
|
2363
3430
|
},
|
|
2364
|
-
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"])
|
|
3431
|
+
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"]),
|
|
3432
|
+
refRules: {
|
|
3433
|
+
// `call` covers both `foo(...)` and `obj.foo(...)` — the extractor
|
|
3434
|
+
// records the method leaf, plus `require`/`require_relative` imports.
|
|
3435
|
+
call: { callType: "call", nameExtractor: rubyCallExtractor },
|
|
3436
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3437
|
+
}
|
|
2365
3438
|
},
|
|
2366
3439
|
swift: {
|
|
2367
3440
|
declKinds: {
|
|
@@ -2394,7 +3467,18 @@ var init_queries = __esm({
|
|
|
2394
3467
|
"protocol_declaration",
|
|
2395
3468
|
"actor_declaration",
|
|
2396
3469
|
"extension_declaration"
|
|
2397
|
-
])
|
|
3470
|
+
]),
|
|
3471
|
+
refRules: {
|
|
3472
|
+
// Verified AST: `call_expression (simple_identifier) (call_suffix …)` —
|
|
3473
|
+
// the callee is a bare first child, no field name.
|
|
3474
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3475
|
+
// Verified AST: `inheritance_specifier inherits_from: (user_type …)`.
|
|
3476
|
+
inheritance_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3477
|
+
import_declaration: {
|
|
3478
|
+
callType: "import",
|
|
3479
|
+
nameExtractor: importFromText(["import ", "import type ", "@testable import "])
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
2398
3482
|
},
|
|
2399
3483
|
kotlin: {
|
|
2400
3484
|
declKinds: {
|
|
@@ -2419,7 +3503,17 @@ var init_queries = __esm({
|
|
|
2419
3503
|
"object_declaration",
|
|
2420
3504
|
"interface_declaration",
|
|
2421
3505
|
"function_declaration"
|
|
2422
|
-
])
|
|
3506
|
+
]),
|
|
3507
|
+
refRules: {
|
|
3508
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3509
|
+
// Verified AST: `delegation_specifier (user_type (type_identifier))` —
|
|
3510
|
+
// the `: Handler` / `: Base()` clause.
|
|
3511
|
+
delegation_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3512
|
+
import_header: {
|
|
3513
|
+
callType: "import",
|
|
3514
|
+
nameExtractor: importFromText(["import "])
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
2423
3517
|
},
|
|
2424
3518
|
elixir: {
|
|
2425
3519
|
declKinds: {
|
|
@@ -2491,7 +3585,38 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2491
3585
|
const boundedContent = content.length > TREE_SITTER_MAX_FILE_CHARS ? content.slice(0, TREE_SITTER_MAX_FILE_CHARS) : content;
|
|
2492
3586
|
const nlOffsets = newlineOffsets3(boundedContent);
|
|
2493
3587
|
const symbols = [];
|
|
3588
|
+
const refs = [];
|
|
3589
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
2494
3590
|
const scopeStack = [];
|
|
3591
|
+
function emitRefsForNode(node, rule) {
|
|
3592
|
+
const emissions = rule.nameExtractor?.(node) ?? defaultRefTarget(node, rule);
|
|
3593
|
+
if (!emissions) return;
|
|
3594
|
+
const { line } = lineColAt2(nlOffsets, node.startIndex);
|
|
3595
|
+
for (const emission of emissions) {
|
|
3596
|
+
if (!emission.toName) continue;
|
|
3597
|
+
const callType = emission.callType ?? rule.callType;
|
|
3598
|
+
const key = `${emission.toName}:${callType}:${line}:${emission.module ?? ""}:${node.startIndex}`;
|
|
3599
|
+
if (seenRefs.has(key)) continue;
|
|
3600
|
+
seenRefs.add(key);
|
|
3601
|
+
refs.push({
|
|
3602
|
+
fromId: 0,
|
|
3603
|
+
// assignRefsToSymbols attaches owners after insertion
|
|
3604
|
+
toName: emission.toName.slice(0, 200),
|
|
3605
|
+
callType,
|
|
3606
|
+
line,
|
|
3607
|
+
lang,
|
|
3608
|
+
module: emission.module
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
function defaultRefTarget(node, rule) {
|
|
3613
|
+
if (!rule.field) return null;
|
|
3614
|
+
const field = node.childForFieldName(rule.field);
|
|
3615
|
+
if (!field) return null;
|
|
3616
|
+
const leaf = field.text.split(/[.:\\]/).filter(Boolean).pop()?.split(/[<(]/)[0];
|
|
3617
|
+
if (!leaf) return null;
|
|
3618
|
+
return [{ toName: leaf.trim() }];
|
|
3619
|
+
}
|
|
2495
3620
|
function visit(node, depth) {
|
|
2496
3621
|
if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) return;
|
|
2497
3622
|
if (node.isMissing || node.isError) {
|
|
@@ -2510,6 +3635,8 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2510
3635
|
);
|
|
2511
3636
|
if (emitted) symbols.push(emitted);
|
|
2512
3637
|
}
|
|
3638
|
+
const refRule = queries.refRules?.[node.type];
|
|
3639
|
+
if (refRule) emitRefsForNode(node, refRule);
|
|
2513
3640
|
}
|
|
2514
3641
|
const pushesScope = queries.scopeNodes?.has(node.type) ?? false;
|
|
2515
3642
|
const pushIdx = pushesScope ? pushScope(scopeStack, node, queries) : -1;
|
|
@@ -2527,7 +3654,7 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2527
3654
|
if (pushIdx !== -1) scopeStack.pop();
|
|
2528
3655
|
}
|
|
2529
3656
|
visit(tree.rootNode, 0);
|
|
2530
|
-
return { symbols };
|
|
3657
|
+
return { symbols, refs };
|
|
2531
3658
|
}
|
|
2532
3659
|
function pushScope(scopeStack, node, queries) {
|
|
2533
3660
|
const name = extractName(node, queries);
|
|
@@ -2614,7 +3741,7 @@ __export(tree_sitter_parser_exports, {
|
|
|
2614
3741
|
parseSymbols: () => parseSymbols8,
|
|
2615
3742
|
parseTreeSitterAst: () => parseTreeSitterAst
|
|
2616
3743
|
});
|
|
2617
|
-
import * as
|
|
3744
|
+
import * as path11 from "node:path";
|
|
2618
3745
|
import { fileURLToPath } from "node:url";
|
|
2619
3746
|
function optInEnabled(env) {
|
|
2620
3747
|
return process.env[env] === "1" || process.env[env] === "true";
|
|
@@ -2637,7 +3764,7 @@ async function loadLanguage(lang) {
|
|
|
2637
3764
|
if (!grammarName) {
|
|
2638
3765
|
throw new Error(`tree-sitter: no grammar registered for lang "${lang}"`);
|
|
2639
3766
|
}
|
|
2640
|
-
const wasmPath =
|
|
3767
|
+
const wasmPath = path11.join(WASM_DIR, grammarName, `tree-sitter-${grammarName}.wasm`);
|
|
2641
3768
|
const { Language, init } = await getRuntime();
|
|
2642
3769
|
await init();
|
|
2643
3770
|
const languageObj = await Language.load(wasmPath);
|
|
@@ -2658,7 +3785,7 @@ function isTreeSitterSupported(lang) {
|
|
|
2658
3785
|
function getGrammarWasmPath(lang) {
|
|
2659
3786
|
const name = resolveGrammarName(lang);
|
|
2660
3787
|
if (!name) return void 0;
|
|
2661
|
-
return
|
|
3788
|
+
return path11.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
|
|
2662
3789
|
}
|
|
2663
3790
|
async function parseSymbols8(opts) {
|
|
2664
3791
|
const { file, content, lang } = opts;
|
|
@@ -2674,10 +3801,10 @@ async function parseSymbols8(opts) {
|
|
|
2674
3801
|
if (!tree) {
|
|
2675
3802
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2676
3803
|
}
|
|
2677
|
-
const { symbols } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
3804
|
+
const { symbols, refs } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
2678
3805
|
parser.delete();
|
|
2679
3806
|
tree.delete();
|
|
2680
|
-
return { file, lang, symbols, refs
|
|
3807
|
+
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
2681
3808
|
} catch {
|
|
2682
3809
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2683
3810
|
}
|
|
@@ -2710,7 +3837,7 @@ async function parseTreeSitterAst(opts) {
|
|
|
2710
3837
|
try {
|
|
2711
3838
|
const { Parser, Language, init } = await getRuntime();
|
|
2712
3839
|
await init();
|
|
2713
|
-
const wasmPath =
|
|
3840
|
+
const wasmPath = path11.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
2714
3841
|
const languageObj = await Language.load(wasmPath);
|
|
2715
3842
|
const parser = new Parser();
|
|
2716
3843
|
parser.setLanguage(languageObj);
|
|
@@ -2719,46 +3846,186 @@ async function parseTreeSitterAst(opts) {
|
|
|
2719
3846
|
parser.delete();
|
|
2720
3847
|
return null;
|
|
2721
3848
|
}
|
|
2722
|
-
return { tree, parser };
|
|
2723
|
-
} catch {
|
|
2724
|
-
return null;
|
|
3849
|
+
return { tree, parser };
|
|
3850
|
+
} catch {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
var WASM_DIR, RUNTIME_WASM, LANG_TO_GRAMMAR, GO_OPT_IN, PY_OPT_IN, RS_OPT_IN, runtimePromise, languageCache;
|
|
3855
|
+
var init_tree_sitter_parser = __esm({
|
|
3856
|
+
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
3857
|
+
"use strict";
|
|
3858
|
+
init_queries();
|
|
3859
|
+
init_visitor();
|
|
3860
|
+
WASM_DIR = fileURLToPath(new URL("./wasm/", import.meta.url));
|
|
3861
|
+
RUNTIME_WASM = path11.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
3862
|
+
LANG_TO_GRAMMAR = {
|
|
3863
|
+
c: "c",
|
|
3864
|
+
cpp: "cpp",
|
|
3865
|
+
java: "java",
|
|
3866
|
+
csharp: "c_sharp",
|
|
3867
|
+
// tree-sitter directory uses underscore
|
|
3868
|
+
php: "php",
|
|
3869
|
+
ruby: "ruby",
|
|
3870
|
+
swift: "swift",
|
|
3871
|
+
kotlin: "kotlin",
|
|
3872
|
+
shell: "bash",
|
|
3873
|
+
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
3874
|
+
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
3875
|
+
elixir: "elixir"
|
|
3876
|
+
};
|
|
3877
|
+
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
3878
|
+
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
3879
|
+
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
3880
|
+
runtimePromise = null;
|
|
3881
|
+
languageCache = /* @__PURE__ */ new Map();
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
|
|
3885
|
+
// src/codebase-index/parser-dispatch.ts
|
|
3886
|
+
var parser_dispatch_exports = {};
|
|
3887
|
+
__export(parser_dispatch_exports, {
|
|
3888
|
+
parseFileContent: () => parseFileContent,
|
|
3889
|
+
parseFilesContent: () => parseFilesContent
|
|
3890
|
+
});
|
|
3891
|
+
async function parseFileContent(file, content, lang) {
|
|
3892
|
+
const parsed = await dispatch(file, content, lang);
|
|
3893
|
+
return withRelations(parsed, content, lang);
|
|
3894
|
+
}
|
|
3895
|
+
async function parseFilesContent(files) {
|
|
3896
|
+
if (files.length === 0) return [];
|
|
3897
|
+
const slots = files.map(() => ({ result: null }));
|
|
3898
|
+
const batchingEnabled = process.env["WRONGSTACK_TOOLCHAIN_BATCH"] !== "0";
|
|
3899
|
+
if (batchingEnabled) {
|
|
3900
|
+
const goFiles = [];
|
|
3901
|
+
const pyFiles = [];
|
|
3902
|
+
files.forEach((f, index) => {
|
|
3903
|
+
if (f.lang === "go") goFiles.push({ ...f, index });
|
|
3904
|
+
else if (f.lang === "py") pyFiles.push({ ...f, index });
|
|
3905
|
+
});
|
|
3906
|
+
if (goFiles.length > 0) {
|
|
3907
|
+
await applyBatchResults(slots, goFiles, (chunks) => runGoBatch(chunks), "go");
|
|
3908
|
+
}
|
|
3909
|
+
if (pyFiles.length > 0) {
|
|
3910
|
+
const pyBinary = await resolvePythonBinary();
|
|
3911
|
+
if (pyBinary) {
|
|
3912
|
+
await applyBatchResults(slots, pyFiles, (chunks) => runPyBatch(chunks, pyBinary), "py");
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
const jobs = [];
|
|
3917
|
+
for (let i = 0; i < files.length; i++) {
|
|
3918
|
+
if (slots[i].result !== null) continue;
|
|
3919
|
+
const { file, content, lang } = files[i];
|
|
3920
|
+
const slot = slots[i];
|
|
3921
|
+
jobs.push(
|
|
3922
|
+
(async () => {
|
|
3923
|
+
try {
|
|
3924
|
+
slot.result = await parseFileContent(file, content, lang);
|
|
3925
|
+
} catch (err) {
|
|
3926
|
+
slot.error = err instanceof Error ? err.message : String(err);
|
|
3927
|
+
}
|
|
3928
|
+
})()
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3931
|
+
await Promise.all(jobs);
|
|
3932
|
+
return slots;
|
|
3933
|
+
}
|
|
3934
|
+
async function applyBatchResults(slots, batchFiles, runBatch, lang) {
|
|
3935
|
+
for (const chunk of chunkBatchFiles(batchFiles)) {
|
|
3936
|
+
let byFile = null;
|
|
3937
|
+
try {
|
|
3938
|
+
byFile = await runBatch(chunk);
|
|
3939
|
+
} catch {
|
|
3940
|
+
byFile = null;
|
|
3941
|
+
}
|
|
3942
|
+
if (!byFile) continue;
|
|
3943
|
+
for (const item of chunk) {
|
|
3944
|
+
const parsed = byFile.get(item.file);
|
|
3945
|
+
if (!parsed) continue;
|
|
3946
|
+
slots[item.index] = { result: withRelations(parsed, item.content, lang) };
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
async function dispatch(file, content, lang) {
|
|
3951
|
+
switch (lang) {
|
|
3952
|
+
case "ts":
|
|
3953
|
+
case "tsx":
|
|
3954
|
+
case "js":
|
|
3955
|
+
case "jsx": {
|
|
3956
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3957
|
+
return parseSymbols9({ file, content, lang });
|
|
3958
|
+
}
|
|
3959
|
+
case "go": {
|
|
3960
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3961
|
+
return parseSymbols9({ file, content, lang: "go" });
|
|
3962
|
+
}
|
|
3963
|
+
case "py": {
|
|
3964
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3965
|
+
return parseSymbols9({ file, content, lang: "py" });
|
|
3966
|
+
}
|
|
3967
|
+
case "rs": {
|
|
3968
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3969
|
+
return parseSymbols9({ file, content, lang: "rs" });
|
|
3970
|
+
}
|
|
3971
|
+
case "json": {
|
|
3972
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
3973
|
+
return parseSymbols9({ file, content, lang: "json" });
|
|
3974
|
+
}
|
|
3975
|
+
case "yaml": {
|
|
3976
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
3977
|
+
return parseSymbols9({ file, content, lang: "yaml" });
|
|
3978
|
+
}
|
|
3979
|
+
// Phase 1: ten languages now route through the Tree-Sitter WASM
|
|
3980
|
+
// universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
|
|
3981
|
+
// the regex extractor in `generic-parser.ts` whenever WASM loading fails
|
|
3982
|
+
// or the parser returns zero symbols — preserving the indexable-file
|
|
3983
|
+
// contract that "missing a parser must never mean skipping the file".
|
|
3984
|
+
case "c":
|
|
3985
|
+
case "cpp":
|
|
3986
|
+
case "java":
|
|
3987
|
+
case "csharp":
|
|
3988
|
+
case "php":
|
|
3989
|
+
case "ruby":
|
|
3990
|
+
case "swift":
|
|
3991
|
+
case "kotlin":
|
|
3992
|
+
case "shell":
|
|
3993
|
+
case "elixir": {
|
|
3994
|
+
try {
|
|
3995
|
+
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3996
|
+
const parsed = await parseSymbols10({ file, content, lang });
|
|
3997
|
+
if (parsed.symbols.length > 0) return parsed;
|
|
3998
|
+
} catch {
|
|
3999
|
+
}
|
|
4000
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4001
|
+
return parseSymbols9({ file, content, lang });
|
|
4002
|
+
}
|
|
4003
|
+
default: {
|
|
4004
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4005
|
+
return parseSymbols9({ file, content, lang });
|
|
4006
|
+
}
|
|
2725
4007
|
}
|
|
2726
4008
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
4009
|
+
function withRelations(parsed, content, lang) {
|
|
4010
|
+
let refs = parsed.refs ?? [];
|
|
4011
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
4012
|
+
refs = extractImports({ content, lang });
|
|
4013
|
+
}
|
|
4014
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
4015
|
+
}
|
|
4016
|
+
var init_parser_dispatch = __esm({
|
|
4017
|
+
"src/codebase-index/parser-dispatch.ts"() {
|
|
2730
4018
|
"use strict";
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
RUNTIME_WASM = path10.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
2735
|
-
LANG_TO_GRAMMAR = {
|
|
2736
|
-
c: "c",
|
|
2737
|
-
cpp: "cpp",
|
|
2738
|
-
java: "java",
|
|
2739
|
-
csharp: "c_sharp",
|
|
2740
|
-
// tree-sitter directory uses underscore
|
|
2741
|
-
php: "php",
|
|
2742
|
-
ruby: "ruby",
|
|
2743
|
-
swift: "swift",
|
|
2744
|
-
kotlin: "kotlin",
|
|
2745
|
-
shell: "bash",
|
|
2746
|
-
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
2747
|
-
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
2748
|
-
elixir: "elixir"
|
|
2749
|
-
};
|
|
2750
|
-
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
2751
|
-
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
2752
|
-
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
2753
|
-
runtimePromise = null;
|
|
2754
|
-
languageCache = /* @__PURE__ */ new Map();
|
|
4019
|
+
init_import_extractor();
|
|
4020
|
+
init_parser_batch();
|
|
4021
|
+
init_py_parser();
|
|
2755
4022
|
}
|
|
2756
4023
|
});
|
|
2757
4024
|
|
|
2758
4025
|
// src/replace.ts
|
|
2759
|
-
import { spawn as
|
|
2760
|
-
import * as
|
|
2761
|
-
import * as
|
|
4026
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
4027
|
+
import * as fs15 from "node:fs/promises";
|
|
4028
|
+
import * as path16 from "node:path";
|
|
2762
4029
|
import { ToolValidationError } from "@wrongstack/core/types";
|
|
2763
4030
|
import {
|
|
2764
4031
|
atomicWrite,
|
|
@@ -2772,127 +4039,11 @@ import {
|
|
|
2772
4039
|
} from "@wrongstack/core/utils";
|
|
2773
4040
|
|
|
2774
4041
|
// src/_regex.ts
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
// Adjacent quantifiers: a++ a*+
|
|
2781
|
-
/[+*]{2,}/,
|
|
2782
|
-
// Quantifier on alternation with length 2+
|
|
2783
|
-
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
2784
|
-
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
2785
|
-
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
2786
|
-
];
|
|
2787
|
-
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
2788
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
2789
|
-
if (pattern[i] !== "(") continue;
|
|
2790
|
-
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
2791
|
-
let depth = 0;
|
|
2792
|
-
let inClass = false;
|
|
2793
|
-
let j = i;
|
|
2794
|
-
for (; j < pattern.length; j++) {
|
|
2795
|
-
const ch = pattern[j];
|
|
2796
|
-
if (ch === "\\") {
|
|
2797
|
-
j++;
|
|
2798
|
-
continue;
|
|
2799
|
-
}
|
|
2800
|
-
if (inClass) {
|
|
2801
|
-
if (ch === "]") inClass = false;
|
|
2802
|
-
continue;
|
|
2803
|
-
}
|
|
2804
|
-
if (ch === "[") {
|
|
2805
|
-
inClass = true;
|
|
2806
|
-
continue;
|
|
2807
|
-
}
|
|
2808
|
-
if (ch === "(") depth++;
|
|
2809
|
-
else if (ch === ")") {
|
|
2810
|
-
depth--;
|
|
2811
|
-
if (depth === 0) break;
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
if (j >= pattern.length) return false;
|
|
2815
|
-
const next = pattern[j + 1];
|
|
2816
|
-
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
2817
|
-
let inner = pattern.slice(i + 1, j);
|
|
2818
|
-
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
2819
|
-
const branches = [];
|
|
2820
|
-
let current = "";
|
|
2821
|
-
let d = 0;
|
|
2822
|
-
let cls = false;
|
|
2823
|
-
for (let k = 0; k < inner.length; k++) {
|
|
2824
|
-
const ch = inner[k];
|
|
2825
|
-
if (ch === "\\") {
|
|
2826
|
-
current += ch + (inner[k + 1] ?? "");
|
|
2827
|
-
k++;
|
|
2828
|
-
continue;
|
|
2829
|
-
}
|
|
2830
|
-
if (cls) {
|
|
2831
|
-
if (ch === "]") cls = false;
|
|
2832
|
-
current += ch;
|
|
2833
|
-
continue;
|
|
2834
|
-
}
|
|
2835
|
-
if (ch === "[") {
|
|
2836
|
-
cls = true;
|
|
2837
|
-
current += ch;
|
|
2838
|
-
continue;
|
|
2839
|
-
}
|
|
2840
|
-
if (ch === "(") d++;
|
|
2841
|
-
if (ch === ")") d--;
|
|
2842
|
-
if (ch === "|" && d === 0) {
|
|
2843
|
-
branches.push(current);
|
|
2844
|
-
current = "";
|
|
2845
|
-
continue;
|
|
2846
|
-
}
|
|
2847
|
-
current += ch;
|
|
2848
|
-
}
|
|
2849
|
-
branches.push(current);
|
|
2850
|
-
if (branches.length < 2) continue;
|
|
2851
|
-
for (let a = 0; a < branches.length; a++) {
|
|
2852
|
-
for (let b = a + 1; b < branches.length; b++) {
|
|
2853
|
-
const x = branches[a];
|
|
2854
|
-
const y = branches[b];
|
|
2855
|
-
if (x === "" || y === "") return true;
|
|
2856
|
-
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
return false;
|
|
2861
|
-
}
|
|
2862
|
-
function compileUserRegex(pattern, flags) {
|
|
2863
|
-
if (typeof pattern !== "string") {
|
|
2864
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
2865
|
-
}
|
|
2866
|
-
if (pattern.length === 0) {
|
|
2867
|
-
return { ok: false, reason: "pattern is empty" };
|
|
2868
|
-
}
|
|
2869
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
2870
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
2871
|
-
}
|
|
2872
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
2873
|
-
if (rx.test(pattern)) {
|
|
2874
|
-
return {
|
|
2875
|
-
ok: false,
|
|
2876
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
2877
|
-
};
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
2881
|
-
return {
|
|
2882
|
-
ok: false,
|
|
2883
|
-
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
2884
|
-
};
|
|
2885
|
-
}
|
|
2886
|
-
try {
|
|
2887
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
2888
|
-
} catch (err) {
|
|
2889
|
-
return {
|
|
2890
|
-
ok: false,
|
|
2891
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
2892
|
-
};
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
4042
|
+
import {
|
|
4043
|
+
capSubject,
|
|
4044
|
+
compileUserRegex,
|
|
4045
|
+
MAX_SUBJECT_LEN
|
|
4046
|
+
} from "@wrongstack/primitives";
|
|
2896
4047
|
|
|
2897
4048
|
// src/_util.ts
|
|
2898
4049
|
import { createHash } from "node:crypto";
|
|
@@ -2992,7 +4143,7 @@ function takeHeadBytes(s, maxBytes) {
|
|
|
2992
4143
|
}
|
|
2993
4144
|
|
|
2994
4145
|
// src/codebase-index/background-indexer.ts
|
|
2995
|
-
import * as
|
|
4146
|
+
import * as fs14 from "node:fs";
|
|
2996
4147
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
2997
4148
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
2998
4149
|
|
|
@@ -3080,9 +4231,9 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
3080
4231
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
3081
4232
|
import { execFile } from "node:child_process";
|
|
3082
4233
|
import { createHash as createHash2 } from "node:crypto";
|
|
3083
|
-
import * as
|
|
4234
|
+
import * as fs10 from "node:fs/promises";
|
|
3084
4235
|
import { availableParallelism } from "node:os";
|
|
3085
|
-
import * as
|
|
4236
|
+
import * as path14 from "node:path";
|
|
3086
4237
|
import {
|
|
3087
4238
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
3088
4239
|
indexParallelBatchSize,
|
|
@@ -3752,281 +4903,94 @@ var ModuleResolver = class {
|
|
|
3752
4903
|
const head = segments[0];
|
|
3753
4904
|
if (head === "self" || head === "super") {
|
|
3754
4905
|
let base = path5.posix.dirname(fromFile);
|
|
3755
|
-
for (const segment of segments) {
|
|
3756
|
-
if (segment === "super") base = path5.posix.dirname(base);
|
|
3757
|
-
else if (segment !== "self") break;
|
|
3758
|
-
}
|
|
3759
|
-
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
3760
|
-
return this.lookupWithExtensions(path5.posix.join(base, ...rest2), "rs");
|
|
3761
|
-
}
|
|
3762
|
-
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
3763
|
-
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
3764
|
-
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
3765
|
-
);
|
|
3766
|
-
if (!crate) {
|
|
3767
|
-
return this.lookupWithExtensions(
|
|
3768
|
-
path5.posix.join(path5.posix.dirname(fromFile), ...segments),
|
|
3769
|
-
"rs"
|
|
3770
|
-
);
|
|
3771
|
-
}
|
|
3772
|
-
const rest = segments.slice(1);
|
|
3773
|
-
for (const base of crate.sourceRoots) {
|
|
3774
|
-
const parent = rest.length > 1 ? this.lookupWithExtensions(path5.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
3775
|
-
const exact = this.lookupWithExtensions(path5.posix.join(base, ...rest), "rs");
|
|
3776
|
-
const hit = exact ?? parent ?? this.lookupWithExtensions(path5.posix.join(base, "lib"), "rs");
|
|
3777
|
-
if (hit) return hit;
|
|
3778
|
-
}
|
|
3779
|
-
return void 0;
|
|
3780
|
-
}
|
|
3781
|
-
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
3782
|
-
resolveJvm(spec) {
|
|
3783
|
-
const segments = spec.split(".").filter(Boolean);
|
|
3784
|
-
if (segments.length === 0) return void 0;
|
|
3785
|
-
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
3786
|
-
const wildcard = segments[segments.length - 1] === "*";
|
|
3787
|
-
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
3788
|
-
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
3789
|
-
const target = path5.posix.join(base, ...parts);
|
|
3790
|
-
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
3791
|
-
if (hit) return hit;
|
|
3792
|
-
}
|
|
3793
|
-
return void 0;
|
|
3794
|
-
}
|
|
3795
|
-
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
3796
|
-
resolveInclude(fromFile, spec) {
|
|
3797
|
-
const relative4 = this.lookupWithExtensions(
|
|
3798
|
-
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
3799
|
-
"c"
|
|
3800
|
-
);
|
|
3801
|
-
if (relative4) return relative4;
|
|
3802
|
-
for (const base of [
|
|
3803
|
-
path5.posix.join(this.structure.projectRoot, "include"),
|
|
3804
|
-
this.structure.projectRoot
|
|
3805
|
-
]) {
|
|
3806
|
-
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
|
|
3807
|
-
if (hit) return hit;
|
|
3808
|
-
}
|
|
3809
|
-
return void 0;
|
|
3810
|
-
}
|
|
3811
|
-
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
3812
|
-
resolveRuby(fromFile, spec) {
|
|
3813
|
-
const relative4 = this.lookupWithExtensions(
|
|
3814
|
-
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
3815
|
-
"ruby"
|
|
3816
|
-
);
|
|
3817
|
-
if (relative4) return relative4;
|
|
3818
|
-
for (const base of [
|
|
3819
|
-
path5.posix.join(this.structure.projectRoot, "lib"),
|
|
3820
|
-
this.structure.projectRoot
|
|
3821
|
-
]) {
|
|
3822
|
-
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
|
|
3823
|
-
if (hit) return hit;
|
|
3824
|
-
}
|
|
3825
|
-
return void 0;
|
|
3826
|
-
}
|
|
3827
|
-
};
|
|
3828
|
-
|
|
3829
|
-
// src/codebase-index/import-extractor.ts
|
|
3830
|
-
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
3831
|
-
var IMPORT_MAX_PER_FILE = 400;
|
|
3832
|
-
var DOTTED_IMPORT = [
|
|
3833
|
-
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
3834
|
-
];
|
|
3835
|
-
var LANG_IMPORTS = {
|
|
3836
|
-
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
3837
|
-
// machines with no Go toolchain or Python interpreter installed, where the
|
|
3838
|
-
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
3839
|
-
go: [
|
|
3840
|
-
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
3841
|
-
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
3842
|
-
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
3843
|
-
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
3844
|
-
],
|
|
3845
|
-
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
3846
|
-
rs: [
|
|
3847
|
-
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
3848
|
-
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
3849
|
-
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
3850
|
-
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
3851
|
-
],
|
|
3852
|
-
java: DOTTED_IMPORT,
|
|
3853
|
-
kotlin: DOTTED_IMPORT,
|
|
3854
|
-
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
3855
|
-
csharp: [
|
|
3856
|
-
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
3857
|
-
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
3858
|
-
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
3859
|
-
],
|
|
3860
|
-
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
3861
|
-
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3862
|
-
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3863
|
-
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
3864
|
-
php: [
|
|
3865
|
-
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
3866
|
-
// for — the namespace symbol only covers the `A\B` prefix.
|
|
3867
|
-
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
3868
|
-
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
3869
|
-
],
|
|
3870
|
-
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
3871
|
-
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
3872
|
-
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
3873
|
-
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
3874
|
-
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
3875
|
-
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
3876
|
-
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
3877
|
-
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
3878
|
-
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
3879
|
-
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
3880
|
-
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3881
|
-
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3882
|
-
html: [
|
|
3883
|
-
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
3884
|
-
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
3885
|
-
],
|
|
3886
|
-
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
3887
|
-
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
3888
|
-
};
|
|
3889
|
-
function lastSegment(specifier) {
|
|
3890
|
-
const pathLike = /[/\\]|::/.test(specifier);
|
|
3891
|
-
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
3892
|
-
let last = segments[segments.length - 1] ?? specifier;
|
|
3893
|
-
if (last === "*" || last === "_") {
|
|
3894
|
-
last = segments[segments.length - 2] ?? specifier;
|
|
3895
|
-
}
|
|
3896
|
-
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
3897
|
-
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
3898
|
-
return dotted[dotted.length - 1] ?? last;
|
|
3899
|
-
}
|
|
3900
|
-
function newlineOffsets(content) {
|
|
3901
|
-
const offsets = [];
|
|
3902
|
-
for (let i = 0; i < content.length; i++) {
|
|
3903
|
-
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
3904
|
-
}
|
|
3905
|
-
return offsets;
|
|
3906
|
-
}
|
|
3907
|
-
function lineAt(offsets, index) {
|
|
3908
|
-
let low = 0;
|
|
3909
|
-
let high = offsets.length;
|
|
3910
|
-
while (low < high) {
|
|
3911
|
-
const mid = low + high >>> 1;
|
|
3912
|
-
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
3913
|
-
else high = mid;
|
|
3914
|
-
}
|
|
3915
|
-
return low + 1;
|
|
3916
|
-
}
|
|
3917
|
-
function hasImportPatterns(lang) {
|
|
3918
|
-
return LANG_IMPORTS[lang] !== void 0;
|
|
3919
|
-
}
|
|
3920
|
-
function extractImports(opts) {
|
|
3921
|
-
const patterns = LANG_IMPORTS[opts.lang];
|
|
3922
|
-
if (!patterns || !opts.content) return [];
|
|
3923
|
-
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
3924
|
-
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
3925
|
-
const refs = [];
|
|
3926
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3927
|
-
const offsets = newlineOffsets(content);
|
|
3928
|
-
for (const pattern of patterns) {
|
|
3929
|
-
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
3930
|
-
for (const match of content.matchAll(re)) {
|
|
3931
|
-
if (refs.length >= limit) return refs;
|
|
3932
|
-
const specifier = match[1]?.trim();
|
|
3933
|
-
if (!specifier) continue;
|
|
3934
|
-
const module = specifier;
|
|
3935
|
-
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
3936
|
-
if (!toName) continue;
|
|
3937
|
-
const key = `${module}\0${toName}`;
|
|
3938
|
-
if (seen.has(key)) continue;
|
|
3939
|
-
seen.add(key);
|
|
3940
|
-
refs.push({
|
|
3941
|
-
fromId: 0,
|
|
3942
|
-
toName,
|
|
3943
|
-
callType: "import",
|
|
3944
|
-
line: lineAt(offsets, match.index ?? 0),
|
|
3945
|
-
lang: opts.lang,
|
|
3946
|
-
module
|
|
3947
|
-
});
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
|
-
return refs;
|
|
3951
|
-
}
|
|
3952
|
-
|
|
3953
|
-
// src/codebase-index/parser-dispatch.ts
|
|
3954
|
-
async function parseFileContent(file, content, lang) {
|
|
3955
|
-
const parsed = await dispatch(file, content, lang);
|
|
3956
|
-
return withRelations(parsed, content, lang);
|
|
3957
|
-
}
|
|
3958
|
-
async function dispatch(file, content, lang) {
|
|
3959
|
-
switch (lang) {
|
|
3960
|
-
case "ts":
|
|
3961
|
-
case "tsx":
|
|
3962
|
-
case "js":
|
|
3963
|
-
case "jsx": {
|
|
3964
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3965
|
-
return parseSymbols9({ file, content, lang });
|
|
3966
|
-
}
|
|
3967
|
-
case "go": {
|
|
3968
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3969
|
-
return parseSymbols9({ file, content, lang: "go" });
|
|
3970
|
-
}
|
|
3971
|
-
case "py": {
|
|
3972
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3973
|
-
return parseSymbols9({ file, content, lang: "py" });
|
|
3974
|
-
}
|
|
3975
|
-
case "rs": {
|
|
3976
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3977
|
-
return parseSymbols9({ file, content, lang: "rs" });
|
|
4906
|
+
for (const segment of segments) {
|
|
4907
|
+
if (segment === "super") base = path5.posix.dirname(base);
|
|
4908
|
+
else if (segment !== "self") break;
|
|
4909
|
+
}
|
|
4910
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
4911
|
+
return this.lookupWithExtensions(path5.posix.join(base, ...rest2), "rs");
|
|
3978
4912
|
}
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
4913
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
4914
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
4915
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
4916
|
+
);
|
|
4917
|
+
if (!crate) {
|
|
4918
|
+
return this.lookupWithExtensions(
|
|
4919
|
+
path5.posix.join(path5.posix.dirname(fromFile), ...segments),
|
|
4920
|
+
"rs"
|
|
4921
|
+
);
|
|
3982
4922
|
}
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
4923
|
+
const rest = segments.slice(1);
|
|
4924
|
+
for (const base of crate.sourceRoots) {
|
|
4925
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path5.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
4926
|
+
const exact = this.lookupWithExtensions(path5.posix.join(base, ...rest), "rs");
|
|
4927
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path5.posix.join(base, "lib"), "rs");
|
|
4928
|
+
if (hit) return hit;
|
|
3986
4929
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
case "shell":
|
|
4001
|
-
case "elixir": {
|
|
4002
|
-
try {
|
|
4003
|
-
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
4004
|
-
const parsed = await parseSymbols10({ file, content, lang });
|
|
4005
|
-
if (parsed.symbols.length > 0) return parsed;
|
|
4006
|
-
} catch {
|
|
4007
|
-
}
|
|
4008
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4009
|
-
return parseSymbols9({ file, content, lang });
|
|
4930
|
+
return void 0;
|
|
4931
|
+
}
|
|
4932
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
4933
|
+
resolveJvm(spec) {
|
|
4934
|
+
const segments = spec.split(".").filter(Boolean);
|
|
4935
|
+
if (segments.length === 0) return void 0;
|
|
4936
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
4937
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
4938
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
4939
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
4940
|
+
const target = path5.posix.join(base, ...parts);
|
|
4941
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
4942
|
+
if (hit) return hit;
|
|
4010
4943
|
}
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4944
|
+
return void 0;
|
|
4945
|
+
}
|
|
4946
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
4947
|
+
resolveInclude(fromFile, spec) {
|
|
4948
|
+
const relative4 = this.lookupWithExtensions(
|
|
4949
|
+
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
4950
|
+
"c"
|
|
4951
|
+
);
|
|
4952
|
+
if (relative4) return relative4;
|
|
4953
|
+
for (const base of [
|
|
4954
|
+
path5.posix.join(this.structure.projectRoot, "include"),
|
|
4955
|
+
this.structure.projectRoot
|
|
4956
|
+
]) {
|
|
4957
|
+
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
|
|
4958
|
+
if (hit) return hit;
|
|
4014
4959
|
}
|
|
4960
|
+
return void 0;
|
|
4015
4961
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4962
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
4963
|
+
resolveRuby(fromFile, spec) {
|
|
4964
|
+
const relative4 = this.lookupWithExtensions(
|
|
4965
|
+
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
4966
|
+
"ruby"
|
|
4967
|
+
);
|
|
4968
|
+
if (relative4) return relative4;
|
|
4969
|
+
for (const base of [
|
|
4970
|
+
path5.posix.join(this.structure.projectRoot, "lib"),
|
|
4971
|
+
this.structure.projectRoot
|
|
4972
|
+
]) {
|
|
4973
|
+
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
|
|
4974
|
+
if (hit) return hit;
|
|
4975
|
+
}
|
|
4976
|
+
return void 0;
|
|
4021
4977
|
}
|
|
4022
|
-
|
|
4023
|
-
|
|
4978
|
+
};
|
|
4979
|
+
|
|
4980
|
+
// src/codebase-index/indexer.ts
|
|
4981
|
+
init_parser_dispatch();
|
|
4024
4982
|
|
|
4025
4983
|
// src/codebase-index/parser-worker-pool.ts
|
|
4984
|
+
import * as fs7 from "node:fs";
|
|
4985
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
4026
4986
|
import { Worker } from "node:worker_threads";
|
|
4027
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4028
|
-
import * as fs6 from "node:fs";
|
|
4029
4987
|
var WORKER_POOL_THRESHOLD = 500;
|
|
4988
|
+
function resolveWorkerPoolThreshold() {
|
|
4989
|
+
const raw = process.env["WRONGSTACK_INDEX_WORKER_THRESHOLD"];
|
|
4990
|
+
if (raw === void 0) return WORKER_POOL_THRESHOLD;
|
|
4991
|
+
if (!/^\d+$/.test(raw)) return WORKER_POOL_THRESHOLD;
|
|
4992
|
+
return Number.parseInt(raw, 10);
|
|
4993
|
+
}
|
|
4030
4994
|
var ParserWorkerPool = class {
|
|
4031
4995
|
constructor(maxWorkers = defaultWorkerCount()) {
|
|
4032
4996
|
this.maxWorkers = maxWorkers;
|
|
@@ -4070,7 +5034,8 @@ var ParserWorkerPool = class {
|
|
|
4070
5034
|
w.unref();
|
|
4071
5035
|
w.on("message", (msg) => this.handleMessage(msg));
|
|
4072
5036
|
w.on("error", (err) => this.handleError(err, w));
|
|
4073
|
-
|
|
5037
|
+
w.on("exit", () => this.retireByReference(w));
|
|
5038
|
+
this.workers.push({ worker: w, workerId: w.threadId, busy: false });
|
|
4074
5039
|
} catch {
|
|
4075
5040
|
if (this.workers.length === 0) {
|
|
4076
5041
|
this.unavailable = true;
|
|
@@ -4086,7 +5051,7 @@ var ParserWorkerPool = class {
|
|
|
4086
5051
|
}
|
|
4087
5052
|
/**
|
|
4088
5053
|
* Parse files in parallel across the worker pool. Returns a flat
|
|
4089
|
-
* `FileSymbols[]` in completion order (caller
|
|
5054
|
+
* `FileSymbols[]` in completion order (caller matches by file path).
|
|
4090
5055
|
*
|
|
4091
5056
|
* Content is pre-read by the main thread (for the content-hash check)
|
|
4092
5057
|
* and passed to workers to avoid a second disk read. Files are
|
|
@@ -4107,21 +5072,19 @@ var ParserWorkerPool = class {
|
|
|
4107
5072
|
chunks[i % workerCount].push(files[i]);
|
|
4108
5073
|
}
|
|
4109
5074
|
return new Promise((resolve4, reject) => {
|
|
5075
|
+
const pendingChunks = /* @__PURE__ */ new Map();
|
|
4110
5076
|
this.pending.set(batchId, {
|
|
4111
5077
|
resolve: resolve4,
|
|
4112
5078
|
reject,
|
|
4113
5079
|
accumulated: [],
|
|
4114
|
-
|
|
4115
|
-
|
|
5080
|
+
pendingChunks,
|
|
5081
|
+
settled: false
|
|
4116
5082
|
});
|
|
4117
5083
|
for (let i = 0; i < workerCount; i++) {
|
|
4118
5084
|
const pw = this.workers[i];
|
|
4119
5085
|
pw.busy = true;
|
|
4120
|
-
pw.
|
|
4121
|
-
|
|
4122
|
-
id: batchId,
|
|
4123
|
-
files: chunks[i]
|
|
4124
|
-
});
|
|
5086
|
+
pendingChunks.set(pw.workerId, chunks[i]);
|
|
5087
|
+
pw.worker.postMessage({ type: "parse", id: batchId, files: chunks[i] });
|
|
4125
5088
|
}
|
|
4126
5089
|
});
|
|
4127
5090
|
}
|
|
@@ -4130,6 +5093,12 @@ var ParserWorkerPool = class {
|
|
|
4130
5093
|
const workers = this.workers.map((w) => w.worker);
|
|
4131
5094
|
this.workers = [];
|
|
4132
5095
|
this.unavailable = false;
|
|
5096
|
+
for (const [, p] of this.pending) {
|
|
5097
|
+
if (p.settled) continue;
|
|
5098
|
+
p.settled = true;
|
|
5099
|
+
p.reject(new Error("ParserWorkerPool shut down"));
|
|
5100
|
+
}
|
|
5101
|
+
this.pending.clear();
|
|
4133
5102
|
for (const w of workers) {
|
|
4134
5103
|
try {
|
|
4135
5104
|
w.postMessage({ type: "shutdown" });
|
|
@@ -4150,39 +5119,117 @@ var ParserWorkerPool = class {
|
|
|
4150
5119
|
})
|
|
4151
5120
|
)
|
|
4152
5121
|
);
|
|
4153
|
-
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
4154
|
-
this.pending.clear();
|
|
4155
5122
|
}
|
|
4156
5123
|
handleMessage(msg) {
|
|
4157
5124
|
const batch = this.pending.get(msg.id);
|
|
4158
5125
|
if (!batch) return;
|
|
5126
|
+
const worker2 = this.workers.find((w) => w.workerId === msg.workerId);
|
|
5127
|
+
if (worker2) worker2.busy = false;
|
|
5128
|
+
batch.pendingChunks.delete(msg.workerId);
|
|
4159
5129
|
batch.accumulated.push(...msg.results);
|
|
4160
|
-
batch.
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
5130
|
+
if (batch.pendingChunks.size === 0) {
|
|
5131
|
+
if (batch.settled) return;
|
|
5132
|
+
batch.settled = true;
|
|
4164
5133
|
this.pending.delete(msg.id);
|
|
4165
5134
|
batch.resolve(batch.accumulated);
|
|
4166
5135
|
}
|
|
4167
5136
|
}
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
5137
|
+
/**
|
|
5138
|
+
* Remove a worker from the pool and salvage any chunk it still owed.
|
|
5139
|
+
*
|
|
5140
|
+
* Idempotent by workerId — `error` and `exit` can both fire for one
|
|
5141
|
+
* death, and a worker may die while no batch references it. When the
|
|
5142
|
+
* dead worker owed files to an in-flight batch and other workers remain,
|
|
5143
|
+
* those files are re-parsed inline on this thread (one fewer worker
|
|
5144
|
+
* should cost latency, not correctness). When it was the last worker,
|
|
5145
|
+
* every remaining batch rejects so the indexer's existing inline
|
|
5146
|
+
* fallback takes over the whole batch.
|
|
5147
|
+
*/
|
|
5148
|
+
retireWorker(workerId) {
|
|
5149
|
+
const entry = this.workers.find((w) => w.workerId === workerId);
|
|
5150
|
+
if (!entry) return;
|
|
5151
|
+
this.workers = this.workers.filter((w) => w.workerId !== workerId);
|
|
5152
|
+
for (const [batchId, batch] of [...this.pending]) {
|
|
5153
|
+
const orphaned = batch.pendingChunks.get(workerId);
|
|
5154
|
+
if (!orphaned) continue;
|
|
5155
|
+
if (this.workers.length === 0) {
|
|
5156
|
+
this.pending.delete(batchId);
|
|
5157
|
+
this.unavailable = true;
|
|
5158
|
+
if (!batch.settled) {
|
|
5159
|
+
batch.settled = true;
|
|
5160
|
+
batch.reject(new Error("ParserWorkerPool: all workers died mid-batch"));
|
|
5161
|
+
}
|
|
5162
|
+
continue;
|
|
5163
|
+
}
|
|
5164
|
+
void this.reparseInline(batch, orphaned, workerId);
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
/**
|
|
5168
|
+
* Salvage path: re-parse an orphaned chunk on this thread. Files that
|
|
5169
|
+
* fail here stay absent from the results — same contract as a per-file
|
|
5170
|
+
* error inside a live worker (see handleMessage).
|
|
5171
|
+
*/
|
|
5172
|
+
async reparseInline(batch, orphaned, workerId) {
|
|
5173
|
+
try {
|
|
5174
|
+
const { parseFileContent: parseFileContent2 } = await Promise.resolve().then(() => (init_parser_dispatch(), parser_dispatch_exports));
|
|
5175
|
+
for (const item of orphaned) {
|
|
5176
|
+
if (batch.settled) return;
|
|
5177
|
+
try {
|
|
5178
|
+
const parsed = await parseFileContent2(item.file, item.content, item.lang);
|
|
5179
|
+
batch.accumulated.push(parsed);
|
|
5180
|
+
} catch {
|
|
5181
|
+
}
|
|
5182
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
5183
|
+
}
|
|
5184
|
+
} finally {
|
|
5185
|
+
this.finishSalvage(batch, workerId);
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
/**
|
|
5189
|
+
* Terminal tail of a salvage — runs on every exit path. Kept free of
|
|
5190
|
+
* control flow inside a `finally` (noUnsafeFinally): releases the
|
|
5191
|
+
* pending-marker and resolves the batch if this was its last chunk.
|
|
5192
|
+
*/
|
|
5193
|
+
finishSalvage(batch, workerId) {
|
|
5194
|
+
batch.pendingChunks.delete(workerId);
|
|
5195
|
+
if (batch.pendingChunks.size === 0) {
|
|
5196
|
+
for (const [batchId, tracked] of this.pending) {
|
|
5197
|
+
if (tracked === batch) {
|
|
5198
|
+
if (batch.settled) return;
|
|
5199
|
+
batch.settled = true;
|
|
5200
|
+
this.pending.delete(batchId);
|
|
5201
|
+
batch.resolve(batch.accumulated);
|
|
5202
|
+
return;
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
4174
5205
|
}
|
|
4175
5206
|
}
|
|
5207
|
+
/**
|
|
5208
|
+
* Retire by worker object rather than threadId. `threadId` is -1 before
|
|
5209
|
+
* the worker emits `online`, so a death during script load would make a
|
|
5210
|
+
* threadId-keyed lookup silently no-op and leak the entry (with its
|
|
5211
|
+
* pending chunk) — reference identity is correct in every case.
|
|
5212
|
+
*/
|
|
5213
|
+
retireByReference(source) {
|
|
5214
|
+
const entry = this.workers.find((w) => w.worker === source);
|
|
5215
|
+
if (entry) this.retireWorker(entry.workerId);
|
|
5216
|
+
}
|
|
5217
|
+
handleError(err, source) {
|
|
5218
|
+
void err;
|
|
5219
|
+
this.retireByReference(source);
|
|
5220
|
+
}
|
|
4176
5221
|
};
|
|
4177
5222
|
function defaultWorkerCount() {
|
|
4178
5223
|
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
4179
5224
|
return Math.max(1, Math.min(4, cores - 1));
|
|
4180
5225
|
}
|
|
4181
5226
|
function resolveWorkerScriptUrl() {
|
|
5227
|
+
const override = process.env["WRONGSTACK_PARSER_WORKER_SCRIPT"];
|
|
5228
|
+
if (override) return pathToFileURL(override);
|
|
4182
5229
|
for (const rel of ["./parser-worker-script.js", "./codebase-index/parser-worker-script.js"]) {
|
|
4183
5230
|
try {
|
|
4184
5231
|
const url = new URL(rel, import.meta.url);
|
|
4185
|
-
if (url.protocol === "file:" &&
|
|
5232
|
+
if (url.protocol === "file:" && fs7.existsSync(fileURLToPath2(url))) return url;
|
|
4186
5233
|
} catch {
|
|
4187
5234
|
}
|
|
4188
5235
|
}
|
|
@@ -4195,8 +5242,8 @@ function getParserPool() {
|
|
|
4195
5242
|
}
|
|
4196
5243
|
|
|
4197
5244
|
// src/codebase-index/writer.ts
|
|
4198
|
-
import * as
|
|
4199
|
-
import * as
|
|
5245
|
+
import * as fs9 from "node:fs";
|
|
5246
|
+
import * as path13 from "node:path";
|
|
4200
5247
|
|
|
4201
5248
|
// src/codebase-index/bm25.ts
|
|
4202
5249
|
var K1 = 1.5;
|
|
@@ -4291,11 +5338,11 @@ var Bm25Index = class {
|
|
|
4291
5338
|
init_languages();
|
|
4292
5339
|
|
|
4293
5340
|
// src/codebase-index/schema.ts
|
|
4294
|
-
var SCHEMA_VERSION =
|
|
5341
|
+
var SCHEMA_VERSION = 5;
|
|
4295
5342
|
|
|
4296
5343
|
// src/codebase-index/sqlite-runtime.ts
|
|
4297
|
-
import { createRequire } from "node:module";
|
|
4298
5344
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
5345
|
+
import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
|
|
4299
5346
|
var warningSilenced = false;
|
|
4300
5347
|
function silenceSqliteExperimentalWarning() {
|
|
4301
5348
|
if (warningSilenced) return;
|
|
@@ -4313,11 +5360,10 @@ function loadDatabaseSync() {
|
|
|
4313
5360
|
if (DatabaseSyncCtor) return DatabaseSyncCtor;
|
|
4314
5361
|
silenceSqliteExperimentalWarning();
|
|
4315
5362
|
try {
|
|
4316
|
-
|
|
4317
|
-
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
5363
|
+
DatabaseSyncCtor = loadRuntimeDatabaseSync();
|
|
4318
5364
|
} catch (err) {
|
|
4319
5365
|
throw new Error(
|
|
4320
|
-
`The codebase index needs
|
|
5366
|
+
`The codebase index needs node:sqlite (Node >= 22.5) or bun:sqlite. This runtime doesn't provide it: ${toErrorMessage(err)}`
|
|
4321
5367
|
);
|
|
4322
5368
|
}
|
|
4323
5369
|
return DatabaseSyncCtor;
|
|
@@ -4360,9 +5406,87 @@ function runSqliteWithRetry(fn) {
|
|
|
4360
5406
|
throw lastError;
|
|
4361
5407
|
}
|
|
4362
5408
|
|
|
5409
|
+
// src/codebase-index/vector-search.ts
|
|
5410
|
+
var RRF_K = 60;
|
|
5411
|
+
function vectorEmbeddingEnabled() {
|
|
5412
|
+
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
5413
|
+
}
|
|
5414
|
+
var VECTOR_DIMENSIONS = 384;
|
|
5415
|
+
var NGRAM_SIZE = 3;
|
|
5416
|
+
function embedText(text) {
|
|
5417
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5418
|
+
const normalized = text.toLowerCase().trim();
|
|
5419
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
5420
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5421
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5422
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5423
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5424
|
+
vec[bucket] += 1;
|
|
5425
|
+
}
|
|
5426
|
+
} else {
|
|
5427
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5428
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5429
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5430
|
+
vec[bucket] += 1;
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
let norm = 0;
|
|
5434
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5435
|
+
norm += vec[i] * vec[i];
|
|
5436
|
+
}
|
|
5437
|
+
norm = Math.sqrt(norm);
|
|
5438
|
+
if (norm > 0) {
|
|
5439
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5440
|
+
vec[i] /= norm;
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
return vec;
|
|
5444
|
+
}
|
|
5445
|
+
function hashNgram(str) {
|
|
5446
|
+
let hash = 2166136261;
|
|
5447
|
+
for (let i = 0; i < str.length; i++) {
|
|
5448
|
+
hash ^= str.charCodeAt(i);
|
|
5449
|
+
hash = Math.imul(hash, 16777619);
|
|
5450
|
+
}
|
|
5451
|
+
return hash >>> 0;
|
|
5452
|
+
}
|
|
5453
|
+
function cosineSimilarity(a, b) {
|
|
5454
|
+
let dot = 0;
|
|
5455
|
+
const len = Math.min(a.length, b.length);
|
|
5456
|
+
for (let i = 0; i < len; i++) {
|
|
5457
|
+
dot += a[i] * b[i];
|
|
5458
|
+
}
|
|
5459
|
+
return dot;
|
|
5460
|
+
}
|
|
5461
|
+
function encodeVector(vec) {
|
|
5462
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5463
|
+
}
|
|
5464
|
+
function decodeVector(buf) {
|
|
5465
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5466
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
5467
|
+
for (let i = 0; i < copy.length; i++) {
|
|
5468
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
5469
|
+
}
|
|
5470
|
+
return copy;
|
|
5471
|
+
}
|
|
5472
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5473
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5474
|
+
const scored = [];
|
|
5475
|
+
for (const id of allIds) {
|
|
5476
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
5477
|
+
const vecRank = vectorRanks.get(id);
|
|
5478
|
+
let score = 0;
|
|
5479
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5480
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5481
|
+
scored.push([id, score]);
|
|
5482
|
+
}
|
|
5483
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
5484
|
+
return scored;
|
|
5485
|
+
}
|
|
5486
|
+
|
|
4363
5487
|
// src/codebase-index/writer-admin.ts
|
|
4364
|
-
import * as
|
|
4365
|
-
import * as
|
|
5488
|
+
import * as fs8 from "node:fs";
|
|
5489
|
+
import * as path12 from "node:path";
|
|
4366
5490
|
var DB_FILE = "index.db";
|
|
4367
5491
|
function getAllIndexableWithStatement(stmt) {
|
|
4368
5492
|
return stmt("SELECT id, text FROM symbols").all().map(
|
|
@@ -4398,6 +5522,14 @@ function getMetadataWithStatement(stmt, key) {
|
|
|
4398
5522
|
const rows = stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
4399
5523
|
return rows[0]?.value;
|
|
4400
5524
|
}
|
|
5525
|
+
function getIndexSummaryWithStatement(stmt) {
|
|
5526
|
+
const fileRows = stmt("SELECT COUNT(*) AS n FROM files").all();
|
|
5527
|
+
const lastRows = stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
5528
|
+
return {
|
|
5529
|
+
totalFiles: fileRows[0] ? Number(fileRows[0].n) : 0,
|
|
5530
|
+
lastIndexed: lastRows.length ? Number(lastRows[0]?.value) : null
|
|
5531
|
+
};
|
|
5532
|
+
}
|
|
4401
5533
|
function getFileMetaWithStatement(stmt, file) {
|
|
4402
5534
|
const rows = stmt(
|
|
4403
5535
|
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
|
|
@@ -4427,22 +5559,102 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
4427
5559
|
}
|
|
4428
5560
|
function getIndexDbSizeBytes(indexDir) {
|
|
4429
5561
|
try {
|
|
4430
|
-
return
|
|
5562
|
+
return fs8.statSync(path12.join(indexDir, DB_FILE)).size;
|
|
4431
5563
|
} catch {
|
|
4432
5564
|
return 0;
|
|
4433
5565
|
}
|
|
4434
5566
|
}
|
|
4435
5567
|
|
|
5568
|
+
// src/codebase-index/writer-helpers.ts
|
|
5569
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
5570
|
+
function escapeLike(value) {
|
|
5571
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
5572
|
+
}
|
|
5573
|
+
function ladderChunkSizes(total, max) {
|
|
5574
|
+
if (total <= 0) return [];
|
|
5575
|
+
const sizes = [];
|
|
5576
|
+
let remaining = total;
|
|
5577
|
+
while (remaining > 0) {
|
|
5578
|
+
const cap = Math.min(remaining, max);
|
|
5579
|
+
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
5580
|
+
const take = Math.max(1, Math.min(pow, remaining));
|
|
5581
|
+
sizes.push(take);
|
|
5582
|
+
remaining -= take;
|
|
5583
|
+
}
|
|
5584
|
+
return sizes;
|
|
5585
|
+
}
|
|
5586
|
+
function nextPow2(count) {
|
|
5587
|
+
return count <= 1 ? 1 : 2 ** Math.ceil(Math.log2(count));
|
|
5588
|
+
}
|
|
5589
|
+
function padToInBucket(values) {
|
|
5590
|
+
if (values.length <= 1) return values.slice();
|
|
5591
|
+
const target = nextPow2(values.length);
|
|
5592
|
+
const padded = values.slice();
|
|
5593
|
+
while (padded.length < target) padded.push(padded[0]);
|
|
5594
|
+
return padded;
|
|
5595
|
+
}
|
|
5596
|
+
function placeholders(count) {
|
|
5597
|
+
return Array.from({ length: count }, () => "?").join(",");
|
|
5598
|
+
}
|
|
5599
|
+
function inListChunks(total, max) {
|
|
5600
|
+
if (total <= 0) return [];
|
|
5601
|
+
if (nextPow2(total) <= max) return [total];
|
|
5602
|
+
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
5603
|
+
return ladderChunkSizes(total, powMax);
|
|
5604
|
+
}
|
|
5605
|
+
function posixIndexPath(file) {
|
|
5606
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5607
|
+
}
|
|
5608
|
+
function indexedFileMatchSql(column = "file") {
|
|
5609
|
+
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
5610
|
+
}
|
|
5611
|
+
function indexedFileMatchArgs(file) {
|
|
5612
|
+
const posix4 = posixIndexPath(file.trim());
|
|
5613
|
+
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
5614
|
+
}
|
|
5615
|
+
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
5616
|
+
if (packageLabel === filter) return true;
|
|
5617
|
+
const posixFile = posixIndexPath(storedFile);
|
|
5618
|
+
const posixFilter = posixIndexPath(filter.trim());
|
|
5619
|
+
if (!posixFilter) return false;
|
|
5620
|
+
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
5621
|
+
}
|
|
5622
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
5623
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
5624
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
5625
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5626
|
+
const assigned = [];
|
|
5627
|
+
for (const ref of refs) {
|
|
5628
|
+
let owner;
|
|
5629
|
+
for (const symbol of ordered) {
|
|
5630
|
+
if (symbol.line > ref.line) break;
|
|
5631
|
+
owner = symbol;
|
|
5632
|
+
}
|
|
5633
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
5634
|
+
if (!owner || owner.id <= 0) continue;
|
|
5635
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
5636
|
+
if (seen.has(key)) continue;
|
|
5637
|
+
seen.add(key);
|
|
5638
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
5639
|
+
}
|
|
5640
|
+
return assigned;
|
|
5641
|
+
}
|
|
5642
|
+
function resolveIndexDir(projectRoot, override) {
|
|
5643
|
+
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
5644
|
+
}
|
|
5645
|
+
|
|
4436
5646
|
// src/codebase-index/writer-bulk-insert.ts
|
|
4437
5647
|
function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
4438
5648
|
if (rows.length === 0) return;
|
|
4439
|
-
const
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
const
|
|
5649
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 12)));
|
|
5650
|
+
let cursor = 0;
|
|
5651
|
+
for (const take of ladder) {
|
|
5652
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5653
|
+
cursor += take;
|
|
5654
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4443
5655
|
const insert = stmt(
|
|
4444
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text
|
|
4445
|
-
VALUES ${
|
|
5656
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text)
|
|
5657
|
+
VALUES ${placeholders2}`
|
|
4446
5658
|
);
|
|
4447
5659
|
const binds = [];
|
|
4448
5660
|
for (const r of chunk) {
|
|
@@ -4457,8 +5669,7 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4457
5669
|
r.signature,
|
|
4458
5670
|
r.docComment,
|
|
4459
5671
|
r.scope,
|
|
4460
|
-
r.text
|
|
4461
|
-
r.file
|
|
5672
|
+
r.text
|
|
4462
5673
|
);
|
|
4463
5674
|
}
|
|
4464
5675
|
insert.run(...binds);
|
|
@@ -4466,11 +5677,13 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4466
5677
|
}
|
|
4467
5678
|
function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
4468
5679
|
if (!ftsAvailable || rows.length === 0) return;
|
|
4469
|
-
const
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
const
|
|
4473
|
-
|
|
5680
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5681
|
+
let cursor = 0;
|
|
5682
|
+
for (const take of ladder) {
|
|
5683
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5684
|
+
cursor += take;
|
|
5685
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5686
|
+
const insert = stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders2}`);
|
|
4474
5687
|
const binds = [];
|
|
4475
5688
|
for (const r of chunk) binds.push(r.id, r.text);
|
|
4476
5689
|
insert.run(...binds);
|
|
@@ -4478,11 +5691,13 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
4478
5691
|
}
|
|
4479
5692
|
function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
4480
5693
|
if (rows.length === 0) return;
|
|
4481
|
-
const
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
const
|
|
4485
|
-
|
|
5694
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5695
|
+
let cursor = 0;
|
|
5696
|
+
for (const take of ladder) {
|
|
5697
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5698
|
+
cursor += take;
|
|
5699
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5700
|
+
const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders2}`);
|
|
4486
5701
|
const binds = [];
|
|
4487
5702
|
for (const r of chunk) binds.push(r.id, r.vector);
|
|
4488
5703
|
insert.run(...binds);
|
|
@@ -4490,13 +5705,15 @@ function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4490
5705
|
}
|
|
4491
5706
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
4492
5707
|
if (refs.length === 0) return;
|
|
4493
|
-
const
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
const
|
|
5708
|
+
const ladder = ladderChunkSizes(refs.length, Math.max(1, Math.floor(maxSqlVars / 8)));
|
|
5709
|
+
let cursor = 0;
|
|
5710
|
+
for (const take of ladder) {
|
|
5711
|
+
const chunk = refs.slice(cursor, cursor + take);
|
|
5712
|
+
cursor += take;
|
|
5713
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4497
5714
|
const insert = stmt(
|
|
4498
5715
|
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
4499
|
-
VALUES ${
|
|
5716
|
+
VALUES ${placeholders2}`
|
|
4500
5717
|
);
|
|
4501
5718
|
const binds = [];
|
|
4502
5719
|
for (const ref of chunk) {
|
|
@@ -4650,52 +5867,6 @@ function materializeWeightedEdges(edgeMap, idPrefix) {
|
|
|
4650
5867
|
return edges;
|
|
4651
5868
|
}
|
|
4652
5869
|
|
|
4653
|
-
// src/codebase-index/writer-helpers.ts
|
|
4654
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
4655
|
-
function escapeLike(value) {
|
|
4656
|
-
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
4657
|
-
}
|
|
4658
|
-
function posixIndexPath(file) {
|
|
4659
|
-
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4660
|
-
}
|
|
4661
|
-
function indexedFileMatchSql(column = "file") {
|
|
4662
|
-
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
4663
|
-
}
|
|
4664
|
-
function indexedFileMatchArgs(file) {
|
|
4665
|
-
const posix4 = posixIndexPath(file.trim());
|
|
4666
|
-
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
4667
|
-
}
|
|
4668
|
-
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
4669
|
-
if (packageLabel === filter) return true;
|
|
4670
|
-
const posixFile = posixIndexPath(storedFile);
|
|
4671
|
-
const posixFilter = posixIndexPath(filter.trim());
|
|
4672
|
-
if (!posixFilter) return false;
|
|
4673
|
-
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
4674
|
-
}
|
|
4675
|
-
function assignRefsToSymbols(refs, symbols) {
|
|
4676
|
-
if (refs.length === 0 || symbols.length === 0) return [];
|
|
4677
|
-
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
4678
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4679
|
-
const assigned = [];
|
|
4680
|
-
for (const ref of refs) {
|
|
4681
|
-
let owner;
|
|
4682
|
-
for (const symbol of ordered) {
|
|
4683
|
-
if (symbol.line > ref.line) break;
|
|
4684
|
-
owner = symbol;
|
|
4685
|
-
}
|
|
4686
|
-
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
4687
|
-
if (!owner || owner.id <= 0) continue;
|
|
4688
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
4689
|
-
if (seen.has(key)) continue;
|
|
4690
|
-
seen.add(key);
|
|
4691
|
-
assigned.push({ ...ref, fromId: owner.id });
|
|
4692
|
-
}
|
|
4693
|
-
return assigned;
|
|
4694
|
-
}
|
|
4695
|
-
function resolveIndexDir(projectRoot, override) {
|
|
4696
|
-
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
4697
|
-
}
|
|
4698
|
-
|
|
4699
5870
|
// src/codebase-index/writer-ref-mapper.ts
|
|
4700
5871
|
function mapWriterRefRow(row) {
|
|
4701
5872
|
return {
|
|
@@ -4717,20 +5888,20 @@ function mapWriterRefRow(row) {
|
|
|
4717
5888
|
var MAX_SQL_VARS = 900;
|
|
4718
5889
|
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
4719
5890
|
const results = [];
|
|
4720
|
-
for (
|
|
4721
|
-
const chunk = ids.slice(
|
|
4722
|
-
|
|
4723
|
-
const sql = buildSql(placeholders);
|
|
5891
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5892
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5893
|
+
ids = ids.slice(take);
|
|
5894
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4724
5895
|
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
4725
5896
|
}
|
|
4726
5897
|
return results;
|
|
4727
5898
|
}
|
|
4728
5899
|
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
4729
5900
|
let total = 0;
|
|
4730
|
-
for (
|
|
4731
|
-
const chunk = ids.slice(
|
|
4732
|
-
|
|
4733
|
-
const sql = buildSql(placeholders);
|
|
5901
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5902
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5903
|
+
ids = ids.slice(take);
|
|
5904
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4734
5905
|
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
4735
5906
|
total += rows[0]?.n ?? 0;
|
|
4736
5907
|
}
|
|
@@ -4759,16 +5930,24 @@ function resolveIndexedFiles(stmt, file) {
|
|
|
4759
5930
|
}
|
|
4760
5931
|
function resolveSymbolIds(stmt, symbolName, file) {
|
|
4761
5932
|
if (!file) {
|
|
4762
|
-
const
|
|
4763
|
-
|
|
5933
|
+
const rows = stmt("SELECT id FROM symbols WHERE name = ? ORDER BY id").all(
|
|
5934
|
+
symbolName
|
|
5935
|
+
);
|
|
5936
|
+
return rows.map((r) => r.id);
|
|
4764
5937
|
}
|
|
4765
5938
|
const indexedFiles = resolveIndexedFiles(stmt, file);
|
|
4766
5939
|
if (indexedFiles.length === 0) return [];
|
|
4767
|
-
const
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
5940
|
+
const ids = [];
|
|
5941
|
+
let cursor = 0;
|
|
5942
|
+
for (const take of inListChunks(indexedFiles.length, MAX_SQL_VARS)) {
|
|
5943
|
+
const files = padToInBucket(indexedFiles.slice(cursor, cursor + take));
|
|
5944
|
+
cursor += take;
|
|
5945
|
+
const rows = stmt(
|
|
5946
|
+
`SELECT id FROM symbols WHERE name = ? AND file IN (${placeholders(files.length)}) ORDER BY id`
|
|
5947
|
+
).all(symbolName, ...files);
|
|
5948
|
+
ids.push(...rows.map((r) => r.id));
|
|
5949
|
+
}
|
|
5950
|
+
return ids;
|
|
4772
5951
|
}
|
|
4773
5952
|
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
4774
5953
|
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
@@ -5202,9 +6381,9 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5202
6381
|
const loadedIds = new Set(syms.map((s) => s.id));
|
|
5203
6382
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
5204
6383
|
if (missingIds.length > 0) {
|
|
5205
|
-
const
|
|
6384
|
+
const placeholders2 = missingIds.map(() => "?").join(",");
|
|
5206
6385
|
const extras = stmt(
|
|
5207
|
-
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${
|
|
6386
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders2})`
|
|
5208
6387
|
).all(...missingIds);
|
|
5209
6388
|
for (const s of extras) symById.set(s.id, s);
|
|
5210
6389
|
}
|
|
@@ -5217,81 +6396,6 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5217
6396
|
return { nodes, edges };
|
|
5218
6397
|
}
|
|
5219
6398
|
|
|
5220
|
-
// src/codebase-index/vector-search.ts
|
|
5221
|
-
var RRF_K = 60;
|
|
5222
|
-
var VECTOR_DIMENSIONS = 384;
|
|
5223
|
-
var NGRAM_SIZE = 3;
|
|
5224
|
-
function embedText(text) {
|
|
5225
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5226
|
-
const normalized = text.toLowerCase().trim();
|
|
5227
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
5228
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5229
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5230
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5231
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5232
|
-
vec[bucket] += 1;
|
|
5233
|
-
}
|
|
5234
|
-
} else {
|
|
5235
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5236
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5237
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5238
|
-
vec[bucket] += 1;
|
|
5239
|
-
}
|
|
5240
|
-
}
|
|
5241
|
-
let norm = 0;
|
|
5242
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5243
|
-
norm += vec[i] * vec[i];
|
|
5244
|
-
}
|
|
5245
|
-
norm = Math.sqrt(norm);
|
|
5246
|
-
if (norm > 0) {
|
|
5247
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5248
|
-
vec[i] /= norm;
|
|
5249
|
-
}
|
|
5250
|
-
}
|
|
5251
|
-
return vec;
|
|
5252
|
-
}
|
|
5253
|
-
function hashNgram(str) {
|
|
5254
|
-
let hash = 2166136261;
|
|
5255
|
-
for (let i = 0; i < str.length; i++) {
|
|
5256
|
-
hash ^= str.charCodeAt(i);
|
|
5257
|
-
hash = Math.imul(hash, 16777619);
|
|
5258
|
-
}
|
|
5259
|
-
return hash >>> 0;
|
|
5260
|
-
}
|
|
5261
|
-
function cosineSimilarity(a, b) {
|
|
5262
|
-
let dot = 0;
|
|
5263
|
-
const len = Math.min(a.length, b.length);
|
|
5264
|
-
for (let i = 0; i < len; i++) {
|
|
5265
|
-
dot += a[i] * b[i];
|
|
5266
|
-
}
|
|
5267
|
-
return dot;
|
|
5268
|
-
}
|
|
5269
|
-
function encodeVector(vec) {
|
|
5270
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5271
|
-
}
|
|
5272
|
-
function decodeVector(buf) {
|
|
5273
|
-
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5274
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
5275
|
-
for (let i = 0; i < copy.length; i++) {
|
|
5276
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
5277
|
-
}
|
|
5278
|
-
return copy;
|
|
5279
|
-
}
|
|
5280
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5281
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5282
|
-
const scored = [];
|
|
5283
|
-
for (const id of allIds) {
|
|
5284
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
5285
|
-
const vecRank = vectorRanks.get(id);
|
|
5286
|
-
let score = 0;
|
|
5287
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5288
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5289
|
-
scored.push([id, score]);
|
|
5290
|
-
}
|
|
5291
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
5292
|
-
return scored;
|
|
5293
|
-
}
|
|
5294
|
-
|
|
5295
6399
|
// src/codebase-index/writer-mutations.ts
|
|
5296
6400
|
function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvailable, allocateSymbolIds, invalidateIncomingRefsForFiles, resolveRefsForNamesUnsafe2, entries, options = {}) {
|
|
5297
6401
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
@@ -5303,24 +6407,29 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5303
6407
|
for (const ref of entry.refs) affectedNames.add(ref.toName);
|
|
5304
6408
|
}
|
|
5305
6409
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
5306
|
-
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
5307
6410
|
for (const name of invalidateIncomingRefsForFiles(options.deleteForFiles)) {
|
|
5308
6411
|
affectedNames.add(name);
|
|
5309
6412
|
}
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
6413
|
+
let cursor = 0;
|
|
6414
|
+
for (const take of inListChunks(options.deleteForFiles.length, Math.floor(maxSqlVars / 4))) {
|
|
6415
|
+
const bucket = padToInBucket(options.deleteForFiles.slice(cursor, cursor + take));
|
|
6416
|
+
cursor += take;
|
|
6417
|
+
const ph = placeholders(bucket.length);
|
|
6418
|
+
if (ftsAvailable) {
|
|
6419
|
+
stmtFn(
|
|
6420
|
+
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6421
|
+
).run(...bucket);
|
|
6422
|
+
}
|
|
6423
|
+
if (vectorsAvailable) {
|
|
6424
|
+
stmtFn(
|
|
6425
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6426
|
+
).run(...bucket);
|
|
6427
|
+
}
|
|
5316
6428
|
stmtFn(
|
|
5317
|
-
`DELETE FROM
|
|
5318
|
-
).run(...
|
|
6429
|
+
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6430
|
+
).run(...bucket);
|
|
6431
|
+
stmtFn(`DELETE FROM symbols WHERE file IN (${ph})`).run(...bucket);
|
|
5319
6432
|
}
|
|
5320
|
-
stmtFn(
|
|
5321
|
-
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
5322
|
-
).run(...options.deleteForFiles);
|
|
5323
|
-
stmtFn(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
5324
6433
|
}
|
|
5325
6434
|
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
5326
6435
|
let nextId = allocateSymbolIds(totalSymbols);
|
|
@@ -5352,12 +6461,14 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5352
6461
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
5353
6462
|
});
|
|
5354
6463
|
}
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
6464
|
+
if (vectorsAvailable) {
|
|
6465
|
+
vectorRows.push({
|
|
6466
|
+
id,
|
|
6467
|
+
vector: encodeVector(
|
|
6468
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
6469
|
+
)
|
|
6470
|
+
});
|
|
6471
|
+
}
|
|
5361
6472
|
const inserted = { ...s, id };
|
|
5362
6473
|
allInserted.push(inserted);
|
|
5363
6474
|
insertedForEntry.push(inserted);
|
|
@@ -5449,9 +6560,8 @@ var CORE_TABLES_SQL = `
|
|
|
5449
6560
|
signature TEXT NOT NULL DEFAULT '',
|
|
5450
6561
|
doc_comment TEXT NOT NULL DEFAULT '',
|
|
5451
6562
|
scope TEXT NOT NULL DEFAULT '',
|
|
5452
|
-
text TEXT NOT NULL DEFAULT ''
|
|
5453
|
-
|
|
5454
|
-
);
|
|
6563
|
+
text TEXT NOT NULL DEFAULT ''
|
|
6564
|
+
);
|
|
5455
6565
|
`;
|
|
5456
6566
|
var FILE_INDEX_SQL = [
|
|
5457
6567
|
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
@@ -5462,7 +6572,6 @@ var SYMBOL_INDEX_SQL = [
|
|
|
5462
6572
|
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
5463
6573
|
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
5464
6574
|
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
5465
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
5466
6575
|
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
5467
6576
|
];
|
|
5468
6577
|
var REFS_TABLE_SQL = `
|
|
@@ -5537,11 +6646,12 @@ function getUnresolvedImportsWithStatement(stmtFn, maxSqlVars, onlyFiles) {
|
|
|
5537
6646
|
return stmtFn(base).all();
|
|
5538
6647
|
}
|
|
5539
6648
|
const out = [];
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
const
|
|
6649
|
+
let cursor = 0;
|
|
6650
|
+
for (const take of inListChunks(onlyFiles.length, maxSqlVars)) {
|
|
6651
|
+
const chunk = padToInBucket(onlyFiles.slice(cursor, cursor + take));
|
|
6652
|
+
cursor += take;
|
|
5543
6653
|
out.push(
|
|
5544
|
-
...stmtFn(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
6654
|
+
...stmtFn(`${base} AND s.file IN (${placeholders(chunk.length)})`).all(...chunk)
|
|
5545
6655
|
);
|
|
5546
6656
|
}
|
|
5547
6657
|
return out;
|
|
@@ -5612,16 +6722,18 @@ function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVar
|
|
|
5612
6722
|
)`
|
|
5613
6723
|
);
|
|
5614
6724
|
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 4));
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
const
|
|
6725
|
+
let cursor = 0;
|
|
6726
|
+
for (const take of ladderChunkSizes(resolutions.length, chunkSize)) {
|
|
6727
|
+
const chunk = resolutions.slice(cursor, cursor + take);
|
|
6728
|
+
cursor += take;
|
|
6729
|
+
const valuesPh = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
5618
6730
|
const binds = [];
|
|
5619
6731
|
for (const entry of chunk) {
|
|
5620
6732
|
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
5621
6733
|
}
|
|
5622
6734
|
stmtFn(
|
|
5623
6735
|
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
5624
|
-
VALUES ${
|
|
6736
|
+
VALUES ${valuesPh}`
|
|
5625
6737
|
).run(...binds);
|
|
5626
6738
|
}
|
|
5627
6739
|
db.exec(
|
|
@@ -5658,9 +6770,11 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5658
6770
|
const list = [...names].filter((name) => name.length > 0);
|
|
5659
6771
|
if (list.length === 0) return 0;
|
|
5660
6772
|
let total = 0;
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
const
|
|
6773
|
+
let cursor = 0;
|
|
6774
|
+
for (const take of inListChunks(list.length, maxSqlVars)) {
|
|
6775
|
+
const chunk = padToInBucket(list.slice(cursor, cursor + take));
|
|
6776
|
+
cursor += take;
|
|
6777
|
+
const ph = placeholders(chunk.length);
|
|
5664
6778
|
try {
|
|
5665
6779
|
const result = stmtFn(
|
|
5666
6780
|
`UPDATE refs
|
|
@@ -5669,16 +6783,16 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5669
6783
|
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
5670
6784
|
FROM symbols sym
|
|
5671
6785
|
JOIN lang_family lf ON lf.lang = sym.lang
|
|
5672
|
-
WHERE sym.name IN (${
|
|
6786
|
+
WHERE sym.name IN (${ph})
|
|
5673
6787
|
GROUP BY sym.name, lf.family
|
|
5674
6788
|
UNION ALL
|
|
5675
6789
|
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
5676
6790
|
FROM symbols sym
|
|
5677
|
-
WHERE sym.name IN (${
|
|
6791
|
+
WHERE sym.name IN (${ph})
|
|
5678
6792
|
GROUP BY sym.name
|
|
5679
6793
|
) AS s,
|
|
5680
6794
|
lang_family AS rf
|
|
5681
|
-
WHERE refs.to_name IN (${
|
|
6795
|
+
WHERE refs.to_name IN (${ph})
|
|
5682
6796
|
AND rf.lang = refs.lang
|
|
5683
6797
|
AND s.name = refs.to_name
|
|
5684
6798
|
AND s.family = rf.family`
|
|
@@ -5690,7 +6804,7 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5690
6804
|
SELECT sym.id FROM symbols sym
|
|
5691
6805
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
5692
6806
|
ORDER BY sym.id LIMIT 1
|
|
5693
|
-
) WHERE refs.to_name IN (${
|
|
6807
|
+
) WHERE refs.to_name IN (${ph})
|
|
5694
6808
|
AND EXISTS (
|
|
5695
6809
|
SELECT 1 FROM symbols sym
|
|
5696
6810
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
@@ -5747,7 +6861,7 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
5747
6861
|
const conditions = [];
|
|
5748
6862
|
const values = [];
|
|
5749
6863
|
let effectiveKind = filter?.kind;
|
|
5750
|
-
if (filter?.lspKind
|
|
6864
|
+
if (filter?.lspKind != null) {
|
|
5751
6865
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5752
6866
|
if (mapped !== null) {
|
|
5753
6867
|
effectiveKind = mapped;
|
|
@@ -5826,7 +6940,7 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5826
6940
|
);
|
|
5827
6941
|
}
|
|
5828
6942
|
let effectiveKind = filter?.kind;
|
|
5829
|
-
if (filter?.lspKind
|
|
6943
|
+
if (filter?.lspKind != null) {
|
|
5830
6944
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5831
6945
|
if (mapped === null) return { results: [], total: 0 };
|
|
5832
6946
|
effectiveKind = mapped;
|
|
@@ -5863,15 +6977,14 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5863
6977
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
5864
6978
|
}
|
|
5865
6979
|
const where = conditions.join(" AND ");
|
|
5866
|
-
const countRows = stmtFn(
|
|
5867
|
-
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
5868
|
-
).all(...values);
|
|
5869
|
-
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
5870
|
-
if (total === 0) return { results: [], total: 0 };
|
|
5871
6980
|
const bm25Rows = stmtFn(
|
|
5872
6981
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
5873
6982
|
-bm25(symbols_fts) AS score,
|
|
5874
|
-
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
6983
|
+
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet,
|
|
6984
|
+
-- Keep this uncorrelated: referencing outer columns turns it into
|
|
6985
|
+
-- a per-row subquery and defeats the one-count-per-statement win.
|
|
6986
|
+
(SELECT COUNT(*) FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
6987
|
+
WHERE ${where}) AS total_count
|
|
5875
6988
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
5876
6989
|
WHERE ${where}
|
|
5877
6990
|
ORDER BY
|
|
@@ -5880,13 +6993,15 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5880
6993
|
ELSE 2 END,
|
|
5881
6994
|
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
5882
6995
|
LIMIT ?`
|
|
5883
|
-
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
5884
|
-
if (
|
|
6996
|
+
).all(...values, ...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
6997
|
+
if (bm25Rows.length === 0) return { results: [], total: 0 };
|
|
6998
|
+
const total = Number(bm25Rows[0]?.total_count ?? 0);
|
|
6999
|
+
if (vectorsAvailable) {
|
|
5885
7000
|
const queryVec = embedText(query);
|
|
5886
7001
|
const candidateIds = bm25Rows.map((r) => r.id);
|
|
5887
|
-
const
|
|
7002
|
+
const placeholders2 = candidateIds.map(() => "?").join(",");
|
|
5888
7003
|
const vecRows = stmtFn(
|
|
5889
|
-
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${
|
|
7004
|
+
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders2})`
|
|
5890
7005
|
).all(...candidateIds);
|
|
5891
7006
|
const vecScores = vecRows.map((r) => ({
|
|
5892
7007
|
id: r.symbol_id,
|
|
@@ -6074,9 +7189,9 @@ var IndexStore = class _IndexStore {
|
|
|
6074
7189
|
}
|
|
6075
7190
|
constructor(projectRoot, opts = {}) {
|
|
6076
7191
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
6077
|
-
|
|
7192
|
+
fs9.mkdirSync(this.indexDir, { recursive: true });
|
|
6078
7193
|
const Database = loadDatabaseSync();
|
|
6079
|
-
this.db = new Database(
|
|
7194
|
+
this.db = new Database(path13.join(this.indexDir, DB_FILE2));
|
|
6080
7195
|
applyIndexStorePragmas(this.db);
|
|
6081
7196
|
this.initSchema();
|
|
6082
7197
|
}
|
|
@@ -6205,7 +7320,11 @@ var IndexStore = class _IndexStore {
|
|
|
6205
7320
|
);
|
|
6206
7321
|
if (symbolCount !== ftsCount) {
|
|
6207
7322
|
this.db.exec("DELETE FROM symbols_fts");
|
|
6208
|
-
if (
|
|
7323
|
+
if (vectorEmbeddingEnabled() && this.stmt(
|
|
7324
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='symbol_vectors'"
|
|
7325
|
+
).get() !== void 0) {
|
|
7326
|
+
this.db.exec("DELETE FROM symbol_vectors");
|
|
7327
|
+
}
|
|
6209
7328
|
const rows = this.stmt(
|
|
6210
7329
|
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
6211
7330
|
).all();
|
|
@@ -6224,8 +7343,13 @@ var IndexStore = class _IndexStore {
|
|
|
6224
7343
|
this.ftsAvailable = false;
|
|
6225
7344
|
}
|
|
6226
7345
|
try {
|
|
6227
|
-
|
|
6228
|
-
|
|
7346
|
+
if (vectorEmbeddingEnabled()) {
|
|
7347
|
+
this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
|
|
7348
|
+
this.vectorsAvailable = true;
|
|
7349
|
+
} else {
|
|
7350
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
7351
|
+
this.vectorsAvailable = false;
|
|
7352
|
+
}
|
|
6229
7353
|
} catch {
|
|
6230
7354
|
this.vectorsAvailable = false;
|
|
6231
7355
|
}
|
|
@@ -6260,14 +7384,22 @@ var IndexStore = class _IndexStore {
|
|
|
6260
7384
|
}
|
|
6261
7385
|
invalidateIncomingRefsForFiles(files) {
|
|
6262
7386
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
6263
|
-
const
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
7387
|
+
const names = [];
|
|
7388
|
+
let cursor = 0;
|
|
7389
|
+
for (const take of inListChunks(files.length, _IndexStore.MAX_SQL_VARS)) {
|
|
7390
|
+
const bucket = padToInBucket(files.slice(cursor, cursor + take));
|
|
7391
|
+
cursor += take;
|
|
7392
|
+
const ph = placeholders(bucket.length);
|
|
7393
|
+
for (const row of this.stmt(`SELECT DISTINCT name FROM symbols WHERE file IN (${ph})`).all(
|
|
7394
|
+
...bucket
|
|
7395
|
+
)) {
|
|
7396
|
+
names.push(row.name);
|
|
7397
|
+
}
|
|
7398
|
+
this.stmt(
|
|
7399
|
+
`UPDATE refs SET to_id = NULL
|
|
7400
|
+
WHERE to_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
7401
|
+
).run(...bucket);
|
|
7402
|
+
}
|
|
6271
7403
|
return new Set(names);
|
|
6272
7404
|
}
|
|
6273
7405
|
resolveRefsForNamesUnsafe(names) {
|
|
@@ -6301,6 +7433,14 @@ var IndexStore = class _IndexStore {
|
|
|
6301
7433
|
if (this.ftsAvailable) {
|
|
6302
7434
|
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
6303
7435
|
}
|
|
7436
|
+
if (this.vectorsAvailable) {
|
|
7437
|
+
vectorRows.push({
|
|
7438
|
+
id,
|
|
7439
|
+
vector: encodeVector(
|
|
7440
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
7441
|
+
)
|
|
7442
|
+
});
|
|
7443
|
+
}
|
|
6304
7444
|
result.push({ ...s, id });
|
|
6305
7445
|
}
|
|
6306
7446
|
bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
|
|
@@ -6333,15 +7473,15 @@ var IndexStore = class _IndexStore {
|
|
|
6333
7473
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6334
7474
|
if (this.ftsAvailable) {
|
|
6335
7475
|
this.stmt(
|
|
6336
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7476
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6337
7477
|
).run(file);
|
|
6338
7478
|
}
|
|
6339
7479
|
if (this.vectorsAvailable) {
|
|
6340
7480
|
this.stmt(
|
|
6341
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7481
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6342
7482
|
).run(file);
|
|
6343
7483
|
}
|
|
6344
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7484
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6345
7485
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6346
7486
|
this.commitWriteTransaction(ownsTransaction);
|
|
6347
7487
|
} catch (error) {
|
|
@@ -6358,18 +7498,18 @@ var IndexStore = class _IndexStore {
|
|
|
6358
7498
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6359
7499
|
if (this.ftsAvailable) {
|
|
6360
7500
|
this.stmt(
|
|
6361
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7501
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6362
7502
|
).run(file);
|
|
6363
7503
|
}
|
|
6364
7504
|
if (this.vectorsAvailable) {
|
|
6365
7505
|
this.stmt(
|
|
6366
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7506
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6367
7507
|
).run(file);
|
|
6368
7508
|
}
|
|
6369
|
-
this.stmt(
|
|
6370
|
-
|
|
6371
|
-
)
|
|
6372
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7509
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7510
|
+
file
|
|
7511
|
+
);
|
|
7512
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6373
7513
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
6374
7514
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6375
7515
|
this.commitWriteTransaction(ownsTransaction);
|
|
@@ -6473,6 +7613,10 @@ var IndexStore = class _IndexStore {
|
|
|
6473
7613
|
getStats() {
|
|
6474
7614
|
return getStatsWithStatement((sql) => this.stmt(sql), this.indexDir);
|
|
6475
7615
|
}
|
|
7616
|
+
/** P2.5: minimal summary for search-response piggyback (see writer-admin). */
|
|
7617
|
+
getIndexSummary() {
|
|
7618
|
+
return getIndexSummaryWithStatement((sql) => this.stmt(sql));
|
|
7619
|
+
}
|
|
6476
7620
|
setLastIndexed(ts2) {
|
|
6477
7621
|
this.runWithRetry(() => {
|
|
6478
7622
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
@@ -6574,18 +7718,18 @@ var IndexStore = class _IndexStore {
|
|
|
6574
7718
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
6575
7719
|
if (this.ftsAvailable) {
|
|
6576
7720
|
this.stmt(
|
|
6577
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7721
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6578
7722
|
).run(meta.file);
|
|
6579
7723
|
}
|
|
6580
7724
|
if (this.vectorsAvailable) {
|
|
6581
7725
|
this.stmt(
|
|
6582
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7726
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6583
7727
|
).run(meta.file);
|
|
6584
7728
|
}
|
|
6585
|
-
this.stmt(
|
|
6586
|
-
|
|
6587
|
-
)
|
|
6588
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7729
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7730
|
+
meta.file
|
|
7731
|
+
);
|
|
7732
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(meta.file);
|
|
6589
7733
|
this.stmt(
|
|
6590
7734
|
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6591
7735
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -6617,6 +7761,27 @@ var IndexStore = class _IndexStore {
|
|
|
6617
7761
|
} catch {
|
|
6618
7762
|
}
|
|
6619
7763
|
}
|
|
7764
|
+
/**
|
|
7765
|
+
* P4.14: best-effort WAL checkpoint for idle-time maintenance.
|
|
7766
|
+
*
|
|
7767
|
+
* `wal_autocheckpoint` is PASSIVE and only attempts work after a COMMIT —
|
|
7768
|
+
* once writes stop, nothing fires again, so the WAL keeps whatever frames
|
|
7769
|
+
* the last burst left. This probes with PASSIVE first (never blocks; busy=1
|
|
7770
|
+
* means readers still hold WAL snapshots) and only issues the TRUNCATE —
|
|
7771
|
+
* which resets index.db-wal to zero bytes — when the checkpointer can
|
|
7772
|
+
* proceed immediately. Callers run this on the daemon's single thread, so
|
|
7773
|
+
* never wait on readers here: busy means "retry at the next idle window".
|
|
7774
|
+
*/
|
|
7775
|
+
checkpointWal() {
|
|
7776
|
+
try {
|
|
7777
|
+
const probe = this.db.prepare("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
7778
|
+
if (Number(probe?.busy ?? 1) !== 0) return false;
|
|
7779
|
+
const done = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
7780
|
+
return Number(done?.busy ?? 1) === 0;
|
|
7781
|
+
} catch {
|
|
7782
|
+
return false;
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
6620
7785
|
compactIfNeeded(options = {}) {
|
|
6621
7786
|
const minBytes = options.minBytes ?? 256 * 1024 * 1024;
|
|
6622
7787
|
const minFreeRatio = options.minFreeRatio ?? 0.35;
|
|
@@ -6702,7 +7867,9 @@ function resolveParallelBatch() {
|
|
|
6702
7867
|
return indexParallelBatchSize(availableParallelism());
|
|
6703
7868
|
}
|
|
6704
7869
|
function shouldUseParserWorkerPool(candidateFileCount, parseBatchCount) {
|
|
6705
|
-
|
|
7870
|
+
const threshold = resolveWorkerPoolThreshold();
|
|
7871
|
+
if (threshold === 0) return false;
|
|
7872
|
+
return !isFrugalPerf() && candidateFileCount >= threshold && parseBatchCount > 1;
|
|
6706
7873
|
}
|
|
6707
7874
|
function yieldEventLoop() {
|
|
6708
7875
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -6725,15 +7892,15 @@ var IndexSourceChangedError = class extends Error {
|
|
|
6725
7892
|
name = "IndexSourceChangedError";
|
|
6726
7893
|
};
|
|
6727
7894
|
function isWithinProject(projectRoot, file) {
|
|
6728
|
-
const rel =
|
|
6729
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
7895
|
+
const rel = path14.relative(projectRoot, file);
|
|
7896
|
+
return rel !== "" && !rel.startsWith(`..${path14.sep}`) && rel !== ".." && !path14.isAbsolute(rel);
|
|
6730
7897
|
}
|
|
6731
7898
|
function isMissingPathError(err) {
|
|
6732
7899
|
const code = err?.code;
|
|
6733
7900
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
6734
7901
|
}
|
|
6735
7902
|
function normalizeComparablePath(value) {
|
|
6736
|
-
const resolved =
|
|
7903
|
+
const resolved = path14.resolve(value);
|
|
6737
7904
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
6738
7905
|
}
|
|
6739
7906
|
function gitOutput(projectRoot, args) {
|
|
@@ -6779,24 +7946,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6779
7946
|
const record = statusRecords[i];
|
|
6780
7947
|
if (!record) continue;
|
|
6781
7948
|
const status = record.slice(0, 2);
|
|
6782
|
-
const changedPath =
|
|
7949
|
+
const changedPath = path14.resolve(projectRoot, record.slice(3));
|
|
6783
7950
|
dirty.add(changedPath);
|
|
6784
7951
|
if (status.includes("D")) deleted.add(changedPath);
|
|
6785
7952
|
if (status.includes("R") || status.includes("C")) {
|
|
6786
7953
|
const source = statusRecords[++i];
|
|
6787
|
-
if (source) dirty.add(
|
|
7954
|
+
if (source) dirty.add(path14.resolve(projectRoot, source));
|
|
6788
7955
|
}
|
|
6789
7956
|
}
|
|
6790
7957
|
const files = [];
|
|
6791
7958
|
for (const relative4 of output.toString("utf8").split("\0")) {
|
|
6792
7959
|
if (!relative4) continue;
|
|
6793
7960
|
const portable = relative4.replace(/\\/g, "/");
|
|
6794
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
7961
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path14.posix.basename(portable))) {
|
|
6795
7962
|
continue;
|
|
6796
7963
|
}
|
|
6797
|
-
const full =
|
|
7964
|
+
const full = path14.resolve(projectRoot, relative4);
|
|
6798
7965
|
if (deleted.has(full)) continue;
|
|
6799
|
-
const ext =
|
|
7966
|
+
const ext = path14.extname(relative4).toLowerCase();
|
|
6800
7967
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6801
7968
|
}
|
|
6802
7969
|
const snapshot = createHash2("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
@@ -6804,7 +7971,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6804
7971
|
for (const dirtyFile of [...dirty].sort()) {
|
|
6805
7972
|
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
6806
7973
|
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
6807
|
-
snapshot.update(xxhash64String(await
|
|
7974
|
+
snapshot.update(xxhash64String(await fs10.readFile(dirtyFile, "utf8")));
|
|
6808
7975
|
}
|
|
6809
7976
|
return {
|
|
6810
7977
|
files,
|
|
@@ -6840,7 +8007,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6840
8007
|
}
|
|
6841
8008
|
let entries;
|
|
6842
8009
|
try {
|
|
6843
|
-
entries = await
|
|
8010
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
6844
8011
|
} catch (err) {
|
|
6845
8012
|
complete = false;
|
|
6846
8013
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6849,14 +8016,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6849
8016
|
dirCount++;
|
|
6850
8017
|
for (const e of entries) {
|
|
6851
8018
|
if (ignoreSet.has(e.name)) continue;
|
|
6852
|
-
const full =
|
|
6853
|
-
const rel =
|
|
8019
|
+
const full = path14.join(dir, e.name);
|
|
8020
|
+
const rel = path14.relative(projectRoot, full).replace(/\\/g, "/");
|
|
6854
8021
|
if (e.isDirectory()) {
|
|
6855
8022
|
if (isGitIgnored(rel, true)) continue;
|
|
6856
8023
|
await walk(full);
|
|
6857
8024
|
} else if (e.isFile()) {
|
|
6858
8025
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
6859
|
-
const ext =
|
|
8026
|
+
const ext = path14.extname(e.name).toLowerCase();
|
|
6860
8027
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
6861
8028
|
results.push(full);
|
|
6862
8029
|
}
|
|
@@ -6942,10 +8109,10 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6942
8109
|
let trustedUnchanged;
|
|
6943
8110
|
let discoverySnapshotKey;
|
|
6944
8111
|
if (opts.files && opts.files.length > 0) {
|
|
6945
|
-
files = opts.files.map((f) =>
|
|
8112
|
+
files = opts.files.map((f) => path14.resolve(projectRoot, f)).filter((f) => {
|
|
6946
8113
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
6947
|
-
const rel =
|
|
6948
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
8114
|
+
const rel = path14.relative(projectRoot, f).replace(/\\/g, "/");
|
|
8115
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path14.basename(f)) && !isGitIgnored(rel, false);
|
|
6949
8116
|
});
|
|
6950
8117
|
} else {
|
|
6951
8118
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -6978,7 +8145,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6978
8145
|
if (!meta || !trustedUnchanged.has(file)) return true;
|
|
6979
8146
|
langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
|
|
6980
8147
|
symbolsIndexed += meta.symbolCount;
|
|
6981
|
-
filesIndexed++;
|
|
6982
8148
|
filesSkipped++;
|
|
6983
8149
|
filesPreSkipped++;
|
|
6984
8150
|
return false;
|
|
@@ -7007,7 +8173,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7007
8173
|
async (file) => {
|
|
7008
8174
|
let stat3;
|
|
7009
8175
|
try {
|
|
7010
|
-
stat3 = await
|
|
8176
|
+
stat3 = await fs10.stat(file, statOpts);
|
|
7011
8177
|
} catch (e) {
|
|
7012
8178
|
if (isAbortError(e)) throw e;
|
|
7013
8179
|
return {
|
|
@@ -7034,7 +8200,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7034
8200
|
const meta = existingMeta.get(file);
|
|
7035
8201
|
let content;
|
|
7036
8202
|
try {
|
|
7037
|
-
content = await
|
|
8203
|
+
content = await fs10.readFile(file, { encoding: "utf8", signal });
|
|
7038
8204
|
} catch (e) {
|
|
7039
8205
|
if (isAbortError(e)) throw e;
|
|
7040
8206
|
return {
|
|
@@ -7099,22 +8265,19 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7099
8265
|
}
|
|
7100
8266
|
}
|
|
7101
8267
|
if (!pool) {
|
|
7102
|
-
await
|
|
7103
|
-
toParse.map(
|
|
7104
|
-
try {
|
|
7105
|
-
const parsed = await parseFileContent(item.file, item.content, item.lang);
|
|
7106
|
-
const settled = statReadParse[item.index];
|
|
7107
|
-
if (settled.status === "fulfilled") {
|
|
7108
|
-
settled.value.parsed = parsed;
|
|
7109
|
-
}
|
|
7110
|
-
} catch (e) {
|
|
7111
|
-
const settled = statReadParse[item.index];
|
|
7112
|
-
if (settled.status === "fulfilled") {
|
|
7113
|
-
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
7114
|
-
}
|
|
7115
|
-
}
|
|
7116
|
-
})
|
|
8268
|
+
const parsedAll = await parseFilesContent(
|
|
8269
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
7117
8270
|
);
|
|
8271
|
+
for (let pi2 = 0; pi2 < parsedAll.length && pi2 < toParse.length; pi2++) {
|
|
8272
|
+
const settled = statReadParse[toParse[pi2].index];
|
|
8273
|
+
if (settled.status !== "fulfilled") continue;
|
|
8274
|
+
const slot = parsedAll[pi2];
|
|
8275
|
+
if (slot.result) {
|
|
8276
|
+
settled.value.parsed = slot.result;
|
|
8277
|
+
} else {
|
|
8278
|
+
settled.value.error = `parse error: ${slot.error ?? `no result for ${toParse[pi2].file}`}`;
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
7118
8281
|
}
|
|
7119
8282
|
}
|
|
7120
8283
|
const batchEntries = [];
|
|
@@ -7140,7 +8303,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7140
8303
|
if (result.skippedMeta) {
|
|
7141
8304
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
7142
8305
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
7143
|
-
filesIndexed++;
|
|
7144
8306
|
filesSkipped++;
|
|
7145
8307
|
const stored = existingMeta.get(file);
|
|
7146
8308
|
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
@@ -7165,7 +8327,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7165
8327
|
lastIndexed: Date.now(),
|
|
7166
8328
|
contentHash: result.contentHash ?? ""
|
|
7167
8329
|
});
|
|
7168
|
-
filesIndexed++;
|
|
7169
8330
|
filesEmpty++;
|
|
7170
8331
|
}
|
|
7171
8332
|
continue;
|
|
@@ -7179,7 +8340,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7179
8340
|
lastIndexed: Date.now(),
|
|
7180
8341
|
contentHash: result.contentHash ?? ""
|
|
7181
8342
|
});
|
|
7182
|
-
filesIndexed++;
|
|
7183
8343
|
filesEmpty++;
|
|
7184
8344
|
continue;
|
|
7185
8345
|
}
|
|
@@ -7309,7 +8469,7 @@ async function indexService(args, hooks = {}) {
|
|
|
7309
8469
|
function searchService(args) {
|
|
7310
8470
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
7311
8471
|
try {
|
|
7312
|
-
|
|
8472
|
+
const result = store.searchRanked(
|
|
7313
8473
|
args.query,
|
|
7314
8474
|
{
|
|
7315
8475
|
kind: args.kind,
|
|
@@ -7319,6 +8479,10 @@ function searchService(args) {
|
|
|
7319
8479
|
},
|
|
7320
8480
|
args.limit
|
|
7321
8481
|
);
|
|
8482
|
+
if (result.total === 0) {
|
|
8483
|
+
return { ...result, indexSummary: store.getIndexSummary() };
|
|
8484
|
+
}
|
|
8485
|
+
return result;
|
|
7322
8486
|
} finally {
|
|
7323
8487
|
indexStorePool.release(store);
|
|
7324
8488
|
}
|
|
@@ -7382,34 +8546,71 @@ function outgoingCallsService(args) {
|
|
|
7382
8546
|
init_languages();
|
|
7383
8547
|
|
|
7384
8548
|
// src/codebase-index/project-server-client.ts
|
|
7385
|
-
import { spawn as
|
|
7386
|
-
import * as
|
|
8549
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8550
|
+
import * as fs13 from "node:fs";
|
|
7387
8551
|
import * as net from "node:net";
|
|
7388
|
-
import { StringDecoder } from "node:string_decoder";
|
|
7389
8552
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7390
8553
|
|
|
7391
8554
|
// src/codebase-index/binary-frame.ts
|
|
7392
8555
|
import { decode, encode } from "@msgpack/msgpack";
|
|
7393
8556
|
var BINARY_FRAME_MAGIC = 87;
|
|
8557
|
+
var MAX_BINARY_FRAME_BYTES = 256 * 1024 * 1024;
|
|
8558
|
+
var MAX_INBOUND_BINARY_FRAME_BYTES = 64 * 1024 * 1024;
|
|
7394
8559
|
function isBinaryFrame(firstByte) {
|
|
7395
8560
|
return firstByte === BINARY_FRAME_MAGIC;
|
|
7396
8561
|
}
|
|
7397
8562
|
function encodeBinaryFrame(message) {
|
|
7398
|
-
const payload = encode(message);
|
|
8563
|
+
const payload = encode(normalizeUndefined(message));
|
|
7399
8564
|
const header = Buffer.allocUnsafe(5);
|
|
7400
8565
|
header[0] = BINARY_FRAME_MAGIC;
|
|
7401
8566
|
header.writeUInt32BE(payload.length, 1);
|
|
7402
8567
|
return Buffer.concat([header, payload], 5 + payload.length);
|
|
7403
8568
|
}
|
|
8569
|
+
function normalizeUndefined(value) {
|
|
8570
|
+
if (value instanceof Date) {
|
|
8571
|
+
const time = value.getTime();
|
|
8572
|
+
return Number.isNaN(time) ? null : value.toISOString();
|
|
8573
|
+
}
|
|
8574
|
+
if (value instanceof Map) return normalizeUndefined(Object.fromEntries(value));
|
|
8575
|
+
if (value instanceof Set) return normalizeUndefined([...value]);
|
|
8576
|
+
if (value instanceof Error) {
|
|
8577
|
+
return normalizeUndefined({ name: value.name, message: value.message, stack: value.stack });
|
|
8578
|
+
}
|
|
8579
|
+
if (value instanceof RegExp) return String(value);
|
|
8580
|
+
if (value instanceof URL) return value.toJSON();
|
|
8581
|
+
if (Buffer.isBuffer(value)) return { type: "Buffer", data: [...value] };
|
|
8582
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeUndefined(entry));
|
|
8583
|
+
if (!isPlainObject(value)) return value;
|
|
8584
|
+
const out = {};
|
|
8585
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
8586
|
+
if (entry === void 0) continue;
|
|
8587
|
+
out[key] = normalizeUndefined(entry);
|
|
8588
|
+
}
|
|
8589
|
+
return out;
|
|
8590
|
+
}
|
|
8591
|
+
function isPlainObject(value) {
|
|
8592
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
8593
|
+
const proto = Object.getPrototypeOf(value);
|
|
8594
|
+
return proto === Object.prototype || proto === null;
|
|
8595
|
+
}
|
|
7404
8596
|
function decodeBinaryFrame(payload) {
|
|
7405
8597
|
return decode(payload);
|
|
7406
8598
|
}
|
|
8599
|
+
function encodeJsonFrame(message) {
|
|
8600
|
+
return `${JSON.stringify(normalizeUndefined(message))}
|
|
8601
|
+
`;
|
|
8602
|
+
}
|
|
8603
|
+
|
|
8604
|
+
// src/codebase-index/project-server-client-state.ts
|
|
8605
|
+
import * as fs12 from "node:fs";
|
|
8606
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8607
|
+
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7407
8608
|
|
|
7408
8609
|
// src/codebase-index/project-server-endpoint.ts
|
|
7409
8610
|
import { createHash as createHash3 } from "node:crypto";
|
|
7410
|
-
import * as
|
|
7411
|
-
import * as
|
|
7412
|
-
import * as
|
|
8611
|
+
import * as fs11 from "node:fs";
|
|
8612
|
+
import * as os4 from "node:os";
|
|
8613
|
+
import * as path15 from "node:path";
|
|
7413
8614
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7414
8615
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
7415
8616
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
@@ -7418,21 +8619,21 @@ var buildIdCache;
|
|
|
7418
8619
|
function projectIndexServerBuildId(entrypoint) {
|
|
7419
8620
|
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
7420
8621
|
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
7421
|
-
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) :
|
|
8622
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) : path15.resolve(cleanHref);
|
|
7422
8623
|
try {
|
|
7423
|
-
const stat3 =
|
|
8624
|
+
const stat3 = fs11.statSync(file);
|
|
7424
8625
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat3.mtimeMs && buildIdCache.size === stat3.size) {
|
|
7425
8626
|
return buildIdCache.buildId;
|
|
7426
8627
|
}
|
|
7427
|
-
const buildId = createHash3("sha256").update(
|
|
8628
|
+
const buildId = createHash3("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
|
|
7428
8629
|
buildIdCache = { file, mtimeMs: stat3.mtimeMs, size: stat3.size, buildId };
|
|
7429
8630
|
return buildId;
|
|
7430
8631
|
} catch {
|
|
7431
|
-
return `unreadable:${
|
|
8632
|
+
return `unreadable:${path15.basename(file)}`;
|
|
7432
8633
|
}
|
|
7433
8634
|
}
|
|
7434
8635
|
function normalizeLocalPath(value) {
|
|
7435
|
-
const resolved =
|
|
8636
|
+
const resolved = path15.resolve(value);
|
|
7436
8637
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7437
8638
|
}
|
|
7438
8639
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -7444,26 +8645,16 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
7444
8645
|
if (process.platform === "win32") {
|
|
7445
8646
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
7446
8647
|
}
|
|
7447
|
-
return
|
|
8648
|
+
return path15.join(os4.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
7448
8649
|
}
|
|
7449
8650
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
7450
|
-
return
|
|
7451
|
-
|
|
8651
|
+
return path15.join(
|
|
8652
|
+
path15.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
7452
8653
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
7453
8654
|
);
|
|
7454
8655
|
}
|
|
7455
8656
|
|
|
7456
|
-
// src/codebase-index/project-server-protocol.ts
|
|
7457
|
-
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
7458
|
-
function encodeProjectServerMessage(message) {
|
|
7459
|
-
return `${JSON.stringify(message)}
|
|
7460
|
-
`;
|
|
7461
|
-
}
|
|
7462
|
-
|
|
7463
8657
|
// src/codebase-index/project-server-client-state.ts
|
|
7464
|
-
import * as fs11 from "node:fs";
|
|
7465
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7466
|
-
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7467
8658
|
var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
|
|
7468
8659
|
var SERVER_START_TIMEOUT_MS = 1e4;
|
|
7469
8660
|
var SERVER_CONTROL_TIMEOUT_MS = 5e3;
|
|
@@ -7494,7 +8685,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
7494
8685
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
7495
8686
|
try {
|
|
7496
8687
|
const url = new URL(rel, import.meta.url);
|
|
7497
|
-
if (url.protocol === "file:" &&
|
|
8688
|
+
if (url.protocol === "file:" && fs12.existsSync(fileURLToPath4(url))) {
|
|
7498
8689
|
builtUrl = url;
|
|
7499
8690
|
break;
|
|
7500
8691
|
}
|
|
@@ -7582,6 +8773,12 @@ function cancellationError(signal) {
|
|
|
7582
8773
|
return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
|
|
7583
8774
|
}
|
|
7584
8775
|
|
|
8776
|
+
// src/codebase-index/project-server-protocol.ts
|
|
8777
|
+
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
8778
|
+
function encodeProjectServerMessage(message) {
|
|
8779
|
+
return encodeJsonFrame(message);
|
|
8780
|
+
}
|
|
8781
|
+
|
|
7585
8782
|
// src/codebase-index/project-server-client.ts
|
|
7586
8783
|
var ProjectServerConnection = class {
|
|
7587
8784
|
constructor(projectRoot, indexDir, endpoint) {
|
|
@@ -7594,11 +8791,13 @@ var ProjectServerConnection = class {
|
|
|
7594
8791
|
indexDir;
|
|
7595
8792
|
endpoint;
|
|
7596
8793
|
socket = null;
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
8794
|
+
/**
|
|
8795
|
+
* Raw inbound bytes for the unified per-frame reader. Frames are sniffed
|
|
8796
|
+
* individually — JSON text (newline-terminated) or binary (magic 0x57) —
|
|
8797
|
+
* instead of latching a read mode, so a JSON broadcast between binary
|
|
8798
|
+
* frames cannot desynchronize the reader.
|
|
8799
|
+
*/
|
|
8800
|
+
readBuffer = Buffer.alloc(0);
|
|
7602
8801
|
/** P6: true once the server advertises binary support and client accepts. */
|
|
7603
8802
|
useBinary = false;
|
|
7604
8803
|
info = null;
|
|
@@ -7740,7 +8939,7 @@ var ProjectServerConnection = class {
|
|
|
7740
8939
|
this.activity = null;
|
|
7741
8940
|
this.health = null;
|
|
7742
8941
|
this.useBinary = false;
|
|
7743
|
-
this.
|
|
8942
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7744
8943
|
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
7745
8944
|
this.connectResolve = null;
|
|
7746
8945
|
this.connectReject = null;
|
|
@@ -7761,7 +8960,7 @@ var ProjectServerConnection = class {
|
|
|
7761
8960
|
currentAuthToken() {
|
|
7762
8961
|
if (this.authToken === void 0) {
|
|
7763
8962
|
try {
|
|
7764
|
-
const raw =
|
|
8963
|
+
const raw = fs13.readFileSync(
|
|
7765
8964
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
7766
8965
|
"utf8"
|
|
7767
8966
|
);
|
|
@@ -7868,10 +9067,8 @@ var ProjectServerConnection = class {
|
|
|
7868
9067
|
this.info = null;
|
|
7869
9068
|
this.activity = null;
|
|
7870
9069
|
this.health = null;
|
|
7871
|
-
this.
|
|
7872
|
-
this.binaryBuffer = [];
|
|
9070
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7873
9071
|
this.useBinary = false;
|
|
7874
|
-
this.textDecoder = null;
|
|
7875
9072
|
return new Promise((resolve4, reject) => {
|
|
7876
9073
|
const socket = net.createConnection(this.endpoint);
|
|
7877
9074
|
this.socket = socket;
|
|
@@ -7901,18 +9098,47 @@ var ProjectServerConnection = class {
|
|
|
7901
9098
|
socket.on("close", () => this.onClose(socket));
|
|
7902
9099
|
});
|
|
7903
9100
|
}
|
|
9101
|
+
/**
|
|
9102
|
+
* Unified per-frame reader. Each frame is sniffed by its first byte:
|
|
9103
|
+
* `0x57` ('W') → length-prefixed MessagePack binary, anything else →
|
|
9104
|
+
* newline-delimited JSON text. Sniffing per frame (instead of latching a
|
|
9105
|
+
* mode) is what makes mixed streams work: the server may interleave a JSON
|
|
9106
|
+
* `index-state` broadcast between binary responses, and an old JSON-only
|
|
9107
|
+
* server stays readable while `useBinary` is armed.
|
|
9108
|
+
*
|
|
9109
|
+
* Multibyte UTF-8 in JSON frames is safe: raw `0x0a` only occurs as the
|
|
9110
|
+
* JSON delimiter (inside JSON strings `\n` is escaped), so a complete line
|
|
9111
|
+
* is always complete UTF-8.
|
|
9112
|
+
*/
|
|
7904
9113
|
onData(socket, chunk) {
|
|
7905
9114
|
if (socket !== this.socket) return;
|
|
7906
|
-
|
|
7907
|
-
this.onBinaryData(socket, chunk);
|
|
7908
|
-
return;
|
|
7909
|
-
}
|
|
7910
|
-
if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
|
|
7911
|
-
this.buffer += this.textDecoder.write(chunk);
|
|
9115
|
+
this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.concat([this.readBuffer, chunk]);
|
|
7912
9116
|
while (true) {
|
|
7913
|
-
|
|
9117
|
+
if (this.readBuffer.length === 0) return;
|
|
9118
|
+
if (this.useBinary && isBinaryFrame(this.readBuffer[0])) {
|
|
9119
|
+
if (this.readBuffer.length < 5) return;
|
|
9120
|
+
const frameLen = this.readBuffer.readUInt32BE(1);
|
|
9121
|
+
if (frameLen > MAX_BINARY_FRAME_BYTES) {
|
|
9122
|
+
socket.destroy();
|
|
9123
|
+
this.transition("offline", { error: "binary frame length exceeds the IPC limit" });
|
|
9124
|
+
return;
|
|
9125
|
+
}
|
|
9126
|
+
if (this.readBuffer.length < 5 + frameLen) return;
|
|
9127
|
+
const payload = this.readBuffer.subarray(5, 5 + frameLen);
|
|
9128
|
+
this.readBuffer = this.readBuffer.subarray(5 + frameLen);
|
|
9129
|
+
let message2;
|
|
9130
|
+
try {
|
|
9131
|
+
message2 = decodeBinaryFrame(payload);
|
|
9132
|
+
} catch {
|
|
9133
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
9134
|
+
return;
|
|
9135
|
+
}
|
|
9136
|
+
this.onMessage(message2);
|
|
9137
|
+
continue;
|
|
9138
|
+
}
|
|
9139
|
+
const newline = this.readBuffer.indexOf(10);
|
|
7914
9140
|
if (newline < 0) {
|
|
7915
|
-
if (this.
|
|
9141
|
+
if (this.readBuffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
7916
9142
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7917
9143
|
}
|
|
7918
9144
|
return;
|
|
@@ -7921,8 +9147,8 @@ var ProjectServerConnection = class {
|
|
|
7921
9147
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7922
9148
|
return;
|
|
7923
9149
|
}
|
|
7924
|
-
const line = this.
|
|
7925
|
-
this.
|
|
9150
|
+
const line = this.readBuffer.subarray(0, newline).toString("utf8");
|
|
9151
|
+
this.readBuffer = this.readBuffer.subarray(newline + 1);
|
|
7926
9152
|
if (!line) continue;
|
|
7927
9153
|
let message;
|
|
7928
9154
|
try {
|
|
@@ -7934,44 +9160,6 @@ var ProjectServerConnection = class {
|
|
|
7934
9160
|
this.onMessage(message);
|
|
7935
9161
|
}
|
|
7936
9162
|
}
|
|
7937
|
-
/**
|
|
7938
|
-
* P6: Parse binary frames from the raw buffer.
|
|
7939
|
-
*
|
|
7940
|
-
* Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
|
|
7941
|
-
* The magic byte distinguishes binary from JSON — a JSON frame's first byte
|
|
7942
|
-
* is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
|
|
7943
|
-
*/
|
|
7944
|
-
onBinaryData(socket, chunk) {
|
|
7945
|
-
this.binaryBuffer.push(chunk);
|
|
7946
|
-
const all = Buffer.concat(this.binaryBuffer);
|
|
7947
|
-
let offset = 0;
|
|
7948
|
-
while (offset + 5 <= all.length) {
|
|
7949
|
-
if (!isBinaryFrame(all[offset])) {
|
|
7950
|
-
this.useBinary = false;
|
|
7951
|
-
this.buffer += all.subarray(offset).toString("utf8");
|
|
7952
|
-
this.binaryBuffer = [];
|
|
7953
|
-
return;
|
|
7954
|
-
}
|
|
7955
|
-
const frameLen = all.readUInt32BE(offset + 1);
|
|
7956
|
-
if (frameLen > 256 * 1024 * 1024) {
|
|
7957
|
-
socket.destroy();
|
|
7958
|
-
this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
|
|
7959
|
-
return;
|
|
7960
|
-
}
|
|
7961
|
-
const totalLen = 5 + frameLen;
|
|
7962
|
-
if (offset + totalLen > all.length) break;
|
|
7963
|
-
const payload = all.subarray(offset + 5, offset + 5 + frameLen);
|
|
7964
|
-
try {
|
|
7965
|
-
const message = decodeBinaryFrame(payload);
|
|
7966
|
-
this.onMessage(message);
|
|
7967
|
-
} catch {
|
|
7968
|
-
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
7969
|
-
return;
|
|
7970
|
-
}
|
|
7971
|
-
offset += totalLen;
|
|
7972
|
-
}
|
|
7973
|
-
this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
|
|
7974
|
-
}
|
|
7975
9163
|
onMessage(message) {
|
|
7976
9164
|
if (message.type === "hello") {
|
|
7977
9165
|
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
@@ -7991,7 +9179,7 @@ var ProjectServerConnection = class {
|
|
|
7991
9179
|
}
|
|
7992
9180
|
this.info = message;
|
|
7993
9181
|
this.markResponsive();
|
|
7994
|
-
if (message.binarySupported) this.useBinary = true;
|
|
9182
|
+
if (message.binarySupported && binaryFramingEnabled()) this.useBinary = true;
|
|
7995
9183
|
this.transition("connected", { pid: message.pid });
|
|
7996
9184
|
ensureHeartbeatLoop();
|
|
7997
9185
|
this.connectResolve?.();
|
|
@@ -8077,13 +9265,13 @@ var ProjectServerConnection = class {
|
|
|
8077
9265
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
8078
9266
|
if (process.platform !== "win32") {
|
|
8079
9267
|
try {
|
|
8080
|
-
|
|
9268
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
8081
9269
|
} catch {
|
|
8082
9270
|
}
|
|
8083
9271
|
}
|
|
8084
9272
|
const args = [fileURLToPath5(url), "--project-root", this.projectRoot];
|
|
8085
9273
|
if (this.indexDir) args.push("--index-dir", this.indexDir);
|
|
8086
|
-
const child =
|
|
9274
|
+
const child = spawn4(process.execPath, args, {
|
|
8087
9275
|
detached: true,
|
|
8088
9276
|
stdio: "ignore",
|
|
8089
9277
|
windowsHide: true,
|
|
@@ -8101,8 +9289,8 @@ var ProjectServerConnection = class {
|
|
|
8101
9289
|
process.kill(pid);
|
|
8102
9290
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
8103
9291
|
try {
|
|
8104
|
-
const metadata = JSON.parse(
|
|
8105
|
-
if (metadata.pid === pid)
|
|
9292
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
9293
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
8106
9294
|
} catch {
|
|
8107
9295
|
}
|
|
8108
9296
|
return true;
|
|
@@ -8114,6 +9302,10 @@ var ProjectServerConnection = class {
|
|
|
8114
9302
|
var connections = /* @__PURE__ */ new Map();
|
|
8115
9303
|
var MAX_CACHED_CONNECTIONS = 8;
|
|
8116
9304
|
var heartbeatTimer;
|
|
9305
|
+
function binaryFramingEnabled() {
|
|
9306
|
+
const flag = process.env["WRONGSTACK_INDEX_BINARY"];
|
|
9307
|
+
return flag === "1" || flag === "true";
|
|
9308
|
+
}
|
|
8117
9309
|
function forgetConnection(endpoint, connection) {
|
|
8118
9310
|
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
8119
9311
|
connection.close();
|
|
@@ -8203,7 +9395,7 @@ function resolveWorkerUrl() {
|
|
|
8203
9395
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
8204
9396
|
try {
|
|
8205
9397
|
const url = new URL(rel, import.meta.url);
|
|
8206
|
-
if (url.protocol === "file:" &&
|
|
9398
|
+
if (url.protocol === "file:" && fs14.existsSync(fileURLToPath6(url))) return url;
|
|
8207
9399
|
} catch {
|
|
8208
9400
|
}
|
|
8209
9401
|
}
|
|
@@ -8566,14 +9758,14 @@ var replaceTool = {
|
|
|
8566
9758
|
const dryRun = input.dry_run ?? true;
|
|
8567
9759
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
8568
9760
|
const fileList = await resolveFiles(filesInput, ctx, globRe);
|
|
8569
|
-
const realRoot = await
|
|
9761
|
+
const realRoot = await fs15.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
8570
9762
|
const results = [];
|
|
8571
9763
|
let totalReplacements = 0;
|
|
8572
9764
|
let diffBytesUsed = 0;
|
|
8573
9765
|
let diffsOmitted = 0;
|
|
8574
9766
|
let diffsTruncated = 0;
|
|
8575
9767
|
for (const absPath of fileList) {
|
|
8576
|
-
const lstat2 = await
|
|
9768
|
+
const lstat2 = await fs15.lstat(absPath).catch((err) => {
|
|
8577
9769
|
if (err.code === "ENOENT") return null;
|
|
8578
9770
|
throw err;
|
|
8579
9771
|
});
|
|
@@ -8581,17 +9773,17 @@ var replaceTool = {
|
|
|
8581
9773
|
if (lstat2.isSymbolicLink()) continue;
|
|
8582
9774
|
let realPath;
|
|
8583
9775
|
try {
|
|
8584
|
-
realPath = await
|
|
9776
|
+
realPath = await fs15.realpath(absPath);
|
|
8585
9777
|
} catch {
|
|
8586
9778
|
continue;
|
|
8587
9779
|
}
|
|
8588
|
-
const rel =
|
|
8589
|
-
if (rel.startsWith("..") ||
|
|
8590
|
-
const stat3 = await
|
|
9780
|
+
const rel = path16.relative(realRoot, realPath);
|
|
9781
|
+
if (rel.startsWith("..") || path16.isAbsolute(rel)) continue;
|
|
9782
|
+
const stat3 = await fs15.stat(realPath).catch(() => null);
|
|
8591
9783
|
if (!stat3?.isFile()) continue;
|
|
8592
9784
|
let content;
|
|
8593
9785
|
try {
|
|
8594
|
-
const buf = await
|
|
9786
|
+
const buf = await fs15.readFile(realPath);
|
|
8595
9787
|
if (isBinaryBuffer(buf)) continue;
|
|
8596
9788
|
content = buf.toString("utf8");
|
|
8597
9789
|
} catch {
|
|
@@ -8614,7 +9806,7 @@ var replaceTool = {
|
|
|
8614
9806
|
if (!dryRun) {
|
|
8615
9807
|
const newContent = toStyle(newContentLf, style);
|
|
8616
9808
|
await atomicWrite(realPath, newContent, { mode: stat3.mode & 511 });
|
|
8617
|
-
const written = await
|
|
9809
|
+
const written = await fs15.stat(realPath).catch(() => null);
|
|
8618
9810
|
if (written) {
|
|
8619
9811
|
ctx.recordRead?.(realPath, written.mtimeMs, "write", sha256hex(newContent));
|
|
8620
9812
|
}
|
|
@@ -8714,8 +9906,8 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
|
8714
9906
|
const resolved = [];
|
|
8715
9907
|
for (const p of parts) {
|
|
8716
9908
|
const absPath = await safeResolveReal(p, ctx);
|
|
8717
|
-
if (extraGlob && !passesExtraGlob(extraGlob,
|
|
8718
|
-
const stat3 = await
|
|
9909
|
+
if (extraGlob && !passesExtraGlob(extraGlob, path16.basename(absPath), absPath)) continue;
|
|
9910
|
+
const stat3 = await fs15.stat(absPath).catch(() => null);
|
|
8719
9911
|
if (stat3?.isFile()) {
|
|
8720
9912
|
resolved.push(absPath);
|
|
8721
9913
|
}
|
|
@@ -8729,7 +9921,7 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
8729
9921
|
const { promise } = spawnRgFind(pattern, base);
|
|
8730
9922
|
const files = await promise;
|
|
8731
9923
|
if (extraGlob) {
|
|
8732
|
-
return files.filter((f) => passesExtraGlob(extraGlob,
|
|
9924
|
+
return files.filter((f) => passesExtraGlob(extraGlob, path16.basename(f), f));
|
|
8733
9925
|
}
|
|
8734
9926
|
return files;
|
|
8735
9927
|
} catch {
|
|
@@ -8744,7 +9936,7 @@ function __resetRgDetectionForTests() {
|
|
|
8744
9936
|
function checkRg() {
|
|
8745
9937
|
rgAvailabilityCache ??= new Promise((resolve4) => {
|
|
8746
9938
|
try {
|
|
8747
|
-
const p =
|
|
9939
|
+
const p = spawn5("rg", ["--version"], {
|
|
8748
9940
|
env: buildChildEnv(),
|
|
8749
9941
|
stdio: "ignore",
|
|
8750
9942
|
windowsHide: true
|
|
@@ -8759,7 +9951,7 @@ function checkRg() {
|
|
|
8759
9951
|
}
|
|
8760
9952
|
function spawnRgFind(pattern, base) {
|
|
8761
9953
|
const args = ["--files", "--glob", pattern, base];
|
|
8762
|
-
const child =
|
|
9954
|
+
const child = spawn5("rg", args, {
|
|
8763
9955
|
signal: AbortSignal.timeout(3e4),
|
|
8764
9956
|
env: buildChildEnv(),
|
|
8765
9957
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -8792,15 +9984,15 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
8792
9984
|
const walk = async (dir) => {
|
|
8793
9985
|
let entries;
|
|
8794
9986
|
try {
|
|
8795
|
-
entries = await
|
|
9987
|
+
entries = await fs15.readdir(dir, { withFileTypes: true });
|
|
8796
9988
|
} catch {
|
|
8797
9989
|
return;
|
|
8798
9990
|
}
|
|
8799
9991
|
for (const e of entries) {
|
|
8800
9992
|
if (DEFAULT_IGNORE2.includes(e.name)) continue;
|
|
8801
|
-
const full =
|
|
9993
|
+
const full = path16.join(dir, e.name);
|
|
8802
9994
|
try {
|
|
8803
|
-
const stat3 = await
|
|
9995
|
+
const stat3 = await fs15.lstat(full);
|
|
8804
9996
|
if (stat3.isSymbolicLink()) continue;
|
|
8805
9997
|
} catch {
|
|
8806
9998
|
continue;
|