@wrongstack/tools 0.299.0 → 0.300.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/audit.js +6 -2
- package/dist/bash.js +6 -2
- package/dist/builtin.js +1473 -295
- package/dist/codebase-index/import-extractor.d.ts +39 -0
- package/dist/codebase-index/index.js +1396 -255
- package/dist/codebase-index/languages.d.ts +24 -0
- package/dist/codebase-index/module-resolver.d.ts +78 -0
- package/dist/codebase-index/module-roots.d.ts +81 -0
- package/dist/codebase-index/parser-output.d.ts +29 -0
- package/dist/codebase-index/project-server.js +1379 -236
- package/dist/codebase-index/rs-parser.d.ts +22 -0
- package/dist/codebase-index/schema.d.ts +24 -1
- package/dist/codebase-index/worker.js +1381 -238
- package/dist/codebase-index/writer-graph-helpers.d.ts +17 -5
- package/dist/codebase-index/writer-ref-mapper.d.ts +3 -0
- package/dist/codebase-index/writer-schema.d.ts +15 -3
- package/dist/codebase-index/writer.d.ts +76 -3
- package/dist/exec.js +35 -2
- package/dist/format.js +6 -2
- package/dist/index.js +1473 -295
- package/dist/install.js +6 -2
- package/dist/json.js +51 -2
- package/dist/languages/index.js +6 -2
- package/dist/lint.js +6 -2
- package/dist/outdated.js +6 -2
- package/dist/pack.js +1473 -295
- package/dist/process-registry.d.ts +6 -0
- package/dist/process-registry.js +6 -2
- package/dist/ps-slash.js +6 -2
- package/dist/read.js +1387 -244
- package/dist/test.js +6 -2
- package/dist/tool-tier.js +1473 -295
- package/dist/typecheck.js +6 -2
- package/package.json +3 -3
- package/dist/codebase-index/refs-extractor.d.ts +0 -11
package/dist/tool-tier.js
CHANGED
|
@@ -721,7 +721,7 @@ var init_process_registry = __esm({
|
|
|
721
721
|
const p = this.processes.get(pid);
|
|
722
722
|
if (!p) return false;
|
|
723
723
|
if (p.killed) return true;
|
|
724
|
-
if (p.protected) return false;
|
|
724
|
+
if (p.protected && opts.includeProtected !== true) return false;
|
|
725
725
|
if (opts.preserveBackground && p.background) return false;
|
|
726
726
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
727
727
|
const isWin5 = os.platform() === "win32";
|
|
@@ -772,9 +772,13 @@ var init_process_registry = __esm({
|
|
|
772
772
|
killAll(opts = {}) {
|
|
773
773
|
const pids = Array.from(this.processes.keys());
|
|
774
774
|
const killed = [];
|
|
775
|
+
const includeProtected = opts.includeProtected === true;
|
|
775
776
|
for (const pid of pids) {
|
|
776
777
|
const p = this.processes.get(pid);
|
|
777
|
-
if (
|
|
778
|
+
if (!p) continue;
|
|
779
|
+
if (p.protected && !includeProtected) continue;
|
|
780
|
+
if (opts.preserveBackground && p.background) continue;
|
|
781
|
+
if (this.kill(pid, opts)) killed.push(pid);
|
|
778
782
|
}
|
|
779
783
|
return killed;
|
|
780
784
|
}
|
|
@@ -1138,18 +1142,18 @@ async function resolveRealInsideRoot(absPath, ctx) {
|
|
|
1138
1142
|
const realRoots = await Promise.all(
|
|
1139
1143
|
allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
|
|
1140
1144
|
);
|
|
1141
|
-
let
|
|
1145
|
+
let probe = absPath;
|
|
1142
1146
|
const pendingTail = [];
|
|
1143
1147
|
for (; ; ) {
|
|
1144
1148
|
let real;
|
|
1145
1149
|
try {
|
|
1146
|
-
real = await fsp2.realpath(
|
|
1150
|
+
real = await fsp2.realpath(probe);
|
|
1147
1151
|
} catch (err) {
|
|
1148
1152
|
if (err.code === "ENOENT") {
|
|
1149
|
-
const parent = path3.dirname(
|
|
1150
|
-
if (parent ===
|
|
1151
|
-
pendingTail.unshift(path3.basename(
|
|
1152
|
-
|
|
1153
|
+
const parent = path3.dirname(probe);
|
|
1154
|
+
if (parent === probe) return absPath;
|
|
1155
|
+
pendingTail.unshift(path3.basename(probe));
|
|
1156
|
+
probe = parent;
|
|
1153
1157
|
continue;
|
|
1154
1158
|
}
|
|
1155
1159
|
throw err;
|
|
@@ -4968,20 +4972,23 @@ var init_legacy_bridge = __esm({
|
|
|
4968
4972
|
});
|
|
4969
4973
|
|
|
4970
4974
|
// src/codebase-index/languages.ts
|
|
4971
|
-
import * as
|
|
4975
|
+
import * as path13 from "node:path";
|
|
4972
4976
|
function detectLang(file) {
|
|
4973
|
-
const base =
|
|
4977
|
+
const base = path13.basename(file);
|
|
4974
4978
|
const lowerBase = base.toLowerCase();
|
|
4975
4979
|
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
4976
4980
|
return "ts";
|
|
4977
4981
|
}
|
|
4978
4982
|
const special = SPECIAL_FILENAMES[lowerBase];
|
|
4979
4983
|
if (special) return special;
|
|
4980
|
-
const ext =
|
|
4984
|
+
const ext = path13.extname(base).toLowerCase();
|
|
4981
4985
|
if (!ext) return null;
|
|
4982
4986
|
return EXT_TO_LANG[ext] ?? null;
|
|
4983
4987
|
}
|
|
4984
|
-
|
|
4988
|
+
function languageFamily(lang) {
|
|
4989
|
+
return LANG_FAMILY[lang] ?? "other";
|
|
4990
|
+
}
|
|
4991
|
+
var EXT_TO_LANG, INDEXABLE_EXTENSIONS, SPECIAL_FILENAMES, LANG_FAMILY, LANG_FAMILY_ENTRIES;
|
|
4985
4992
|
var init_languages2 = __esm({
|
|
4986
4993
|
"src/codebase-index/languages.ts"() {
|
|
4987
4994
|
"use strict";
|
|
@@ -5072,6 +5079,52 @@ var init_languages2 = __esm({
|
|
|
5072
5079
|
procfile: "other",
|
|
5073
5080
|
justfile: "other"
|
|
5074
5081
|
};
|
|
5082
|
+
LANG_FAMILY = {
|
|
5083
|
+
// Single-compilation-unit family: a .vue/.svelte script block is JS/TS and
|
|
5084
|
+
// imports from — and is imported by — plain .ts files.
|
|
5085
|
+
ts: "js",
|
|
5086
|
+
tsx: "js",
|
|
5087
|
+
js: "js",
|
|
5088
|
+
jsx: "js",
|
|
5089
|
+
vue: "js",
|
|
5090
|
+
svelte: "js",
|
|
5091
|
+
go: "go",
|
|
5092
|
+
py: "py",
|
|
5093
|
+
rs: "rs",
|
|
5094
|
+
// The JVM resolves across languages: Kotlin and Scala call Java directly.
|
|
5095
|
+
java: "jvm",
|
|
5096
|
+
kotlin: "jvm",
|
|
5097
|
+
scala: "jvm",
|
|
5098
|
+
csharp: "dotnet",
|
|
5099
|
+
// A .h header is consumed by both C and C++ translation units.
|
|
5100
|
+
c: "c",
|
|
5101
|
+
cpp: "c",
|
|
5102
|
+
ruby: "ruby",
|
|
5103
|
+
php: "php",
|
|
5104
|
+
swift: "swift",
|
|
5105
|
+
dart: "dart",
|
|
5106
|
+
elixir: "elixir",
|
|
5107
|
+
haskell: "haskell",
|
|
5108
|
+
zig: "zig",
|
|
5109
|
+
lua: "lua",
|
|
5110
|
+
r: "r",
|
|
5111
|
+
shell: "shell",
|
|
5112
|
+
sql: "sql",
|
|
5113
|
+
json: "data",
|
|
5114
|
+
yaml: "data",
|
|
5115
|
+
toml: "data",
|
|
5116
|
+
html: "web",
|
|
5117
|
+
css: "web",
|
|
5118
|
+
proto: "proto",
|
|
5119
|
+
graphql: "graphql",
|
|
5120
|
+
md: "other",
|
|
5121
|
+
other: "other"
|
|
5122
|
+
};
|
|
5123
|
+
LANG_FAMILY_ENTRIES = Object.freeze(
|
|
5124
|
+
Object.entries(LANG_FAMILY).map(
|
|
5125
|
+
([lang, family]) => Object.freeze([lang, family])
|
|
5126
|
+
)
|
|
5127
|
+
);
|
|
5075
5128
|
}
|
|
5076
5129
|
});
|
|
5077
5130
|
|
|
@@ -5236,7 +5289,7 @@ function getTypeName(name) {
|
|
|
5236
5289
|
function deduplicateRefs(refs) {
|
|
5237
5290
|
const seen = /* @__PURE__ */ new Set();
|
|
5238
5291
|
return refs.filter((r) => {
|
|
5239
|
-
const key = `${r.toName}:${r.callType}:${r.line}`;
|
|
5292
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
5240
5293
|
if (seen.has(key)) return false;
|
|
5241
5294
|
seen.add(key);
|
|
5242
5295
|
return true;
|
|
@@ -5246,10 +5299,16 @@ function getImportSpecifierName(spec) {
|
|
|
5246
5299
|
return spec.propertyName?.text ?? spec.name.text;
|
|
5247
5300
|
}
|
|
5248
5301
|
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
5302
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
5249
5303
|
const clause = node.importClause;
|
|
5250
|
-
if (!clause)
|
|
5304
|
+
if (!clause) {
|
|
5305
|
+
if (module) {
|
|
5306
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
5307
|
+
}
|
|
5308
|
+
return;
|
|
5309
|
+
}
|
|
5251
5310
|
if (clause.name) {
|
|
5252
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5311
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
5253
5312
|
}
|
|
5254
5313
|
const bindings = clause.namedBindings;
|
|
5255
5314
|
if (!bindings) return;
|
|
@@ -5259,26 +5318,40 @@ function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
|
5259
5318
|
fromId: 0,
|
|
5260
5319
|
toName: getImportSpecifierName(element),
|
|
5261
5320
|
callType: "import",
|
|
5262
|
-
line: lineNum
|
|
5321
|
+
line: lineNum,
|
|
5322
|
+
module
|
|
5263
5323
|
});
|
|
5264
5324
|
}
|
|
5265
5325
|
} else if (ts.isNamespaceImport(bindings)) {
|
|
5266
|
-
refs.push({
|
|
5326
|
+
refs.push({
|
|
5327
|
+
fromId: 0,
|
|
5328
|
+
toName: bindings.name.text,
|
|
5329
|
+
callType: "import",
|
|
5330
|
+
line: lineNum,
|
|
5331
|
+
module
|
|
5332
|
+
});
|
|
5267
5333
|
}
|
|
5268
5334
|
}
|
|
5335
|
+
function moduleSpecifierOf(node) {
|
|
5336
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
5337
|
+
}
|
|
5269
5338
|
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
5339
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
5270
5340
|
const clause = node.exportClause;
|
|
5271
5341
|
if (clause && ts.isNamespaceExport(clause)) {
|
|
5272
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5342
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
5273
5343
|
return;
|
|
5274
5344
|
}
|
|
5275
5345
|
if (clause && ts.isNamedExports(clause)) {
|
|
5276
5346
|
for (const element of clause.elements) {
|
|
5277
5347
|
const originalName = element.propertyName?.text ?? element.name.text;
|
|
5278
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
|
|
5348
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
5279
5349
|
}
|
|
5280
5350
|
return;
|
|
5281
5351
|
}
|
|
5352
|
+
if (module) {
|
|
5353
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
5354
|
+
}
|
|
5282
5355
|
}
|
|
5283
5356
|
var ts, tsLoad, kindMapCache;
|
|
5284
5357
|
var init_ts_parser = __esm({
|
|
@@ -5290,6 +5363,82 @@ var init_ts_parser = __esm({
|
|
|
5290
5363
|
}
|
|
5291
5364
|
});
|
|
5292
5365
|
|
|
5366
|
+
// src/codebase-index/parser-output.ts
|
|
5367
|
+
function coerceSymbols(value) {
|
|
5368
|
+
if (!Array.isArray(value)) return [];
|
|
5369
|
+
return value.flatMap((entry) => {
|
|
5370
|
+
const candidate = entry;
|
|
5371
|
+
if (typeof candidate.name !== "string" || typeof candidate.kind !== "string") return [];
|
|
5372
|
+
return [
|
|
5373
|
+
{
|
|
5374
|
+
name: candidate.name,
|
|
5375
|
+
kind: candidate.kind,
|
|
5376
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
5377
|
+
col: typeof candidate.col === "number" ? candidate.col : 0,
|
|
5378
|
+
signature: typeof candidate.signature === "string" ? candidate.signature : "",
|
|
5379
|
+
scope: typeof candidate.scope === "string" ? candidate.scope : ""
|
|
5380
|
+
}
|
|
5381
|
+
];
|
|
5382
|
+
});
|
|
5383
|
+
}
|
|
5384
|
+
function coerceRefs(value, lang) {
|
|
5385
|
+
if (!Array.isArray(value)) return [];
|
|
5386
|
+
return value.flatMap((entry) => {
|
|
5387
|
+
const candidate = entry;
|
|
5388
|
+
if (typeof candidate.toName !== "string" || !candidate.toName) return [];
|
|
5389
|
+
if (typeof candidate.callType !== "string" || !CALL_TYPES.has(candidate.callType)) return [];
|
|
5390
|
+
const module = typeof candidate.module === "string" && candidate.module ? candidate.module : void 0;
|
|
5391
|
+
return [
|
|
5392
|
+
{
|
|
5393
|
+
fromId: 0,
|
|
5394
|
+
toName: candidate.toName,
|
|
5395
|
+
callType: candidate.callType,
|
|
5396
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
5397
|
+
lang,
|
|
5398
|
+
module
|
|
5399
|
+
}
|
|
5400
|
+
];
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
function parseParserOutput(stdout, lang) {
|
|
5404
|
+
const trimmed = stdout.trim();
|
|
5405
|
+
if (!trimmed) return { symbols: [], refs: [] };
|
|
5406
|
+
let parsed;
|
|
5407
|
+
try {
|
|
5408
|
+
parsed = JSON.parse(trimmed);
|
|
5409
|
+
} catch {
|
|
5410
|
+
return { symbols: [], refs: [] };
|
|
5411
|
+
}
|
|
5412
|
+
if (Array.isArray(parsed)) return { symbols: coerceSymbols(parsed), refs: [] };
|
|
5413
|
+
const record = parsed;
|
|
5414
|
+
return {
|
|
5415
|
+
symbols: coerceSymbols(record.symbols),
|
|
5416
|
+
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
function dedupeRefs(refs) {
|
|
5420
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5421
|
+
return refs.filter((ref) => {
|
|
5422
|
+
const key = `${ref.toName}:${ref.callType}:${ref.line}:${ref.module ?? ""}`;
|
|
5423
|
+
if (seen.has(key)) return false;
|
|
5424
|
+
seen.add(key);
|
|
5425
|
+
return true;
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
var CALL_TYPES;
|
|
5429
|
+
var init_parser_output = __esm({
|
|
5430
|
+
"src/codebase-index/parser-output.ts"() {
|
|
5431
|
+
"use strict";
|
|
5432
|
+
CALL_TYPES = /* @__PURE__ */ new Set([
|
|
5433
|
+
"call",
|
|
5434
|
+
"type_ref",
|
|
5435
|
+
"inherit",
|
|
5436
|
+
"implement",
|
|
5437
|
+
"import"
|
|
5438
|
+
]);
|
|
5439
|
+
}
|
|
5440
|
+
});
|
|
5441
|
+
|
|
5293
5442
|
// src/codebase-index/spawn-gate.ts
|
|
5294
5443
|
function withSpawnGate(fn) {
|
|
5295
5444
|
const run = chain.then(fn, fn);
|
|
@@ -5315,8 +5464,8 @@ __export(go_parser_exports, {
|
|
|
5315
5464
|
});
|
|
5316
5465
|
import { spawn as spawn5 } from "node:child_process";
|
|
5317
5466
|
import * as os6 from "node:os";
|
|
5318
|
-
import * as
|
|
5319
|
-
import * as
|
|
5467
|
+
import * as path20 from "node:path";
|
|
5468
|
+
import * as fs15 from "node:fs/promises";
|
|
5320
5469
|
async function parseSymbols2(opts) {
|
|
5321
5470
|
const { file, content, lang } = opts;
|
|
5322
5471
|
try {
|
|
@@ -5324,7 +5473,8 @@ async function parseSymbols2(opts) {
|
|
|
5324
5473
|
if (parsed.symbols.length > 0) {
|
|
5325
5474
|
return parsed;
|
|
5326
5475
|
}
|
|
5327
|
-
|
|
5476
|
+
const fallback = fallbackParse(file, content, lang);
|
|
5477
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
5328
5478
|
} catch {
|
|
5329
5479
|
return fallbackParse(file, content, lang);
|
|
5330
5480
|
}
|
|
@@ -5388,9 +5538,9 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5388
5538
|
try {
|
|
5389
5539
|
let scriptPath = _cachedGoScriptPath;
|
|
5390
5540
|
if (!scriptPath) {
|
|
5391
|
-
const tmpDir = await
|
|
5392
|
-
scriptPath =
|
|
5393
|
-
await
|
|
5541
|
+
const tmpDir = await fs15.mkdtemp(path20.join(os6.tmpdir(), "ws-go-parse-"));
|
|
5542
|
+
scriptPath = path20.join(tmpDir, "parse.go");
|
|
5543
|
+
await fs15.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
5394
5544
|
_cachedGoScriptPath = scriptPath;
|
|
5395
5545
|
}
|
|
5396
5546
|
const goBinary = resolveWin32Command("go");
|
|
@@ -5432,8 +5582,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5432
5582
|
if (code !== 0 || !stdout.trim()) {
|
|
5433
5583
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
5434
5584
|
}
|
|
5435
|
-
const
|
|
5436
|
-
const symbols =
|
|
5585
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
5586
|
+
const symbols = rawSymbols.map((s) => ({
|
|
5437
5587
|
id: 0,
|
|
5438
5588
|
lang,
|
|
5439
5589
|
kind: s.kind,
|
|
@@ -5446,7 +5596,7 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5446
5596
|
scope: s.scope ?? "",
|
|
5447
5597
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
5448
5598
|
}));
|
|
5449
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
5599
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
5450
5600
|
} catch {
|
|
5451
5601
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
5452
5602
|
}
|
|
@@ -5456,6 +5606,7 @@ var init_go_parser = __esm({
|
|
|
5456
5606
|
"src/codebase-index/go-parser.ts"() {
|
|
5457
5607
|
"use strict";
|
|
5458
5608
|
init_win32_resolve();
|
|
5609
|
+
init_parser_output();
|
|
5459
5610
|
init_spawn_gate();
|
|
5460
5611
|
init_languages2();
|
|
5461
5612
|
GO_PARSE_SCRIPT = `
|
|
@@ -5469,6 +5620,7 @@ import (
|
|
|
5469
5620
|
"go/token"
|
|
5470
5621
|
"io"
|
|
5471
5622
|
"os"
|
|
5623
|
+
"strconv"
|
|
5472
5624
|
"strings"
|
|
5473
5625
|
)
|
|
5474
5626
|
|
|
@@ -5481,16 +5633,34 @@ type Sym struct {
|
|
|
5481
5633
|
Scope string \`json:"scope"\`
|
|
5482
5634
|
}
|
|
5483
5635
|
|
|
5636
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
5637
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
5638
|
+
type Ref struct {
|
|
5639
|
+
ToName string \`json:"toName"\`
|
|
5640
|
+
CallType string \`json:"callType"\`
|
|
5641
|
+
Line int \`json:"line"\`
|
|
5642
|
+
Module string \`json:"module"\`
|
|
5643
|
+
}
|
|
5644
|
+
|
|
5645
|
+
type Result struct {
|
|
5646
|
+
Symbols []Sym \`json:"symbols"\`
|
|
5647
|
+
Refs []Ref \`json:"refs"\`
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5650
|
+
func emptyResult() string {
|
|
5651
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
5652
|
+
}
|
|
5653
|
+
|
|
5484
5654
|
func main() {
|
|
5485
5655
|
src, err := io.ReadAll(os.Stdin)
|
|
5486
5656
|
if err != nil {
|
|
5487
|
-
fmt.Print(
|
|
5657
|
+
fmt.Print(emptyResult())
|
|
5488
5658
|
return
|
|
5489
5659
|
}
|
|
5490
5660
|
fset := token.NewFileSet()
|
|
5491
5661
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
5492
5662
|
if err != nil {
|
|
5493
|
-
fmt.Print(
|
|
5663
|
+
fmt.Print(emptyResult())
|
|
5494
5664
|
return
|
|
5495
5665
|
}
|
|
5496
5666
|
|
|
@@ -5554,9 +5724,43 @@ func main() {
|
|
|
5554
5724
|
}
|
|
5555
5725
|
}
|
|
5556
5726
|
|
|
5557
|
-
|
|
5727
|
+
refs := []Ref{}
|
|
5728
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
5729
|
+
switch expr := n.(type) {
|
|
5730
|
+
case *ast.CallExpr:
|
|
5731
|
+
line := fset.Position(expr.Pos()).Line
|
|
5732
|
+
switch fun := expr.Fun.(type) {
|
|
5733
|
+
case *ast.Ident:
|
|
5734
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
5735
|
+
case *ast.SelectorExpr:
|
|
5736
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
5737
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
5738
|
+
// and Python extractors' call refs do.
|
|
5739
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
5740
|
+
}
|
|
5741
|
+
case *ast.ImportSpec:
|
|
5742
|
+
if expr.Path != nil {
|
|
5743
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
5744
|
+
line := fset.Position(expr.Pos()).Line
|
|
5745
|
+
// A Go import names a package, not a symbol; the package's
|
|
5746
|
+
// last path segment is the name it is referenced by.
|
|
5747
|
+
name := importPath
|
|
5748
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
5749
|
+
name = importPath[idx+1:]
|
|
5750
|
+
}
|
|
5751
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
return true
|
|
5756
|
+
})
|
|
5757
|
+
|
|
5758
|
+
if syms == nil {
|
|
5759
|
+
syms = []Sym{}
|
|
5760
|
+
}
|
|
5761
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
5558
5762
|
if err != nil {
|
|
5559
|
-
fmt.Print(
|
|
5763
|
+
fmt.Print(emptyResult())
|
|
5560
5764
|
return
|
|
5561
5765
|
}
|
|
5562
5766
|
fmt.Print(string(data))
|
|
@@ -5908,9 +6112,13 @@ var init_generic_parser = __esm({
|
|
|
5908
6112
|
],
|
|
5909
6113
|
elixir: [
|
|
5910
6114
|
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
5911
|
-
|
|
6115
|
+
// Dotted module names must be captured whole: `alias Foo.Bar` resolves
|
|
6116
|
+
// against this symbol, and a `Foo`-only capture never matches it.
|
|
6117
|
+
{ re: /\bdefmodule\s+([A-Z][\w.]*)/g, kind: "namespace" }
|
|
5912
6118
|
],
|
|
5913
6119
|
haskell: [
|
|
6120
|
+
// Target of `import Data.List`.
|
|
6121
|
+
{ re: /^module\s+([A-Z][\w.]*)/gm, kind: "namespace" },
|
|
5914
6122
|
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
5915
6123
|
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
5916
6124
|
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
@@ -6001,9 +6209,9 @@ __export(py_parser_exports, {
|
|
|
6001
6209
|
parseSymbols: () => parseSymbols4
|
|
6002
6210
|
});
|
|
6003
6211
|
import { spawn as spawn6 } from "node:child_process";
|
|
6004
|
-
import * as
|
|
6212
|
+
import * as fs16 from "node:fs/promises";
|
|
6005
6213
|
import * as os7 from "node:os";
|
|
6006
|
-
import * as
|
|
6214
|
+
import * as path21 from "node:path";
|
|
6007
6215
|
async function parseSymbols4(opts) {
|
|
6008
6216
|
const { file, content, lang } = opts;
|
|
6009
6217
|
try {
|
|
@@ -6081,10 +6289,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
6081
6289
|
async function syncPyParse(filePath, content, lang) {
|
|
6082
6290
|
try {
|
|
6083
6291
|
if (!_cachedScriptPath) {
|
|
6084
|
-
const tmpDir =
|
|
6085
|
-
await
|
|
6086
|
-
_cachedScriptPath =
|
|
6087
|
-
await
|
|
6292
|
+
const tmpDir = path21.join(os7.tmpdir(), "ws-py-parse");
|
|
6293
|
+
await fs16.mkdir(tmpDir, { recursive: true });
|
|
6294
|
+
_cachedScriptPath = path21.join(tmpDir, "parse.py");
|
|
6295
|
+
await fs16.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
6088
6296
|
}
|
|
6089
6297
|
cachedPyBinary ??= resolvePython();
|
|
6090
6298
|
const pyBinary = await cachedPyBinary;
|
|
@@ -6098,7 +6306,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
6098
6306
|
if (code !== 0 || !stdout.trim()) {
|
|
6099
6307
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
6100
6308
|
}
|
|
6101
|
-
const raw =
|
|
6309
|
+
const { symbols: raw, refs } = parseParserOutput(stdout, lang);
|
|
6102
6310
|
const symbols = raw.map((s) => ({
|
|
6103
6311
|
id: 0,
|
|
6104
6312
|
lang,
|
|
@@ -6112,7 +6320,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
6112
6320
|
scope: s.scope ?? "",
|
|
6113
6321
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
6114
6322
|
}));
|
|
6115
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
6323
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
6116
6324
|
} catch {
|
|
6117
6325
|
return null;
|
|
6118
6326
|
}
|
|
@@ -6123,6 +6331,7 @@ var init_py_parser = __esm({
|
|
|
6123
6331
|
"use strict";
|
|
6124
6332
|
init_win32_resolve();
|
|
6125
6333
|
init_generic_parser();
|
|
6334
|
+
init_parser_output();
|
|
6126
6335
|
init_spawn_gate();
|
|
6127
6336
|
init_languages2();
|
|
6128
6337
|
PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
@@ -6184,7 +6393,18 @@ class Sym:
|
|
|
6184
6393
|
def is_private(name):
|
|
6185
6394
|
return name.startswith("__") and not name.endswith("__")
|
|
6186
6395
|
|
|
6396
|
+
def leaf_name(node):
|
|
6397
|
+
# Declared name of the callee: \`join\` of \`os.path.join\`. Matches how the
|
|
6398
|
+
# TypeScript and Go extractors record call refs, so resolution behaves the
|
|
6399
|
+
# same across languages.
|
|
6400
|
+
if isinstance(node, ast.Attribute):
|
|
6401
|
+
return node.attr
|
|
6402
|
+
if isinstance(node, ast.Name):
|
|
6403
|
+
return node.id
|
|
6404
|
+
return get_name(node).split(".")[-1]
|
|
6405
|
+
|
|
6187
6406
|
syms = []
|
|
6407
|
+
refs = []
|
|
6188
6408
|
errors = []
|
|
6189
6409
|
|
|
6190
6410
|
try:
|
|
@@ -6192,7 +6412,7 @@ try:
|
|
|
6192
6412
|
tree = ast.parse(source, filename=sys.argv[1])
|
|
6193
6413
|
except Exception as e:
|
|
6194
6414
|
errors.append(str(e))
|
|
6195
|
-
print("[]")
|
|
6415
|
+
print(json.dumps({"symbols": [], "refs": []}))
|
|
6196
6416
|
sys.exit(0)
|
|
6197
6417
|
|
|
6198
6418
|
# Module-level scope
|
|
@@ -6326,7 +6546,42 @@ class ModuleVisitor(ast.NodeVisitor):
|
|
|
6326
6546
|
visitor = ModuleVisitor()
|
|
6327
6547
|
visitor.visit(tree)
|
|
6328
6548
|
|
|
6329
|
-
|
|
6549
|
+
# Refs need a separate full walk: ModuleVisitor deliberately does not descend
|
|
6550
|
+
# into function bodies (it would index locals as symbols), but that is exactly
|
|
6551
|
+
# where the calls are.
|
|
6552
|
+
for node in ast.walk(tree):
|
|
6553
|
+
if isinstance(node, ast.Call):
|
|
6554
|
+
name = leaf_name(node.func)
|
|
6555
|
+
if name:
|
|
6556
|
+
refs.append({"toName": name, "callType": "call", "line": node.lineno})
|
|
6557
|
+
elif isinstance(node, ast.Import):
|
|
6558
|
+
for alias in node.names:
|
|
6559
|
+
refs.append({
|
|
6560
|
+
"toName": alias.name.split(".")[-1],
|
|
6561
|
+
"callType": "import",
|
|
6562
|
+
"line": node.lineno,
|
|
6563
|
+
"module": alias.name,
|
|
6564
|
+
})
|
|
6565
|
+
elif isinstance(node, ast.ImportFrom):
|
|
6566
|
+
# PEP 328: node.level is the number of leading dots. Preserving them is
|
|
6567
|
+
# what lets the resolver walk up from the importing file's package \u2014
|
|
6568
|
+
# dropping them made \`from .foo import X\` indistinguishable from an
|
|
6569
|
+
# absolute \`foo\`.
|
|
6570
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
6571
|
+
for alias in node.names:
|
|
6572
|
+
refs.append({
|
|
6573
|
+
"toName": alias.name,
|
|
6574
|
+
"callType": "import",
|
|
6575
|
+
"line": node.lineno,
|
|
6576
|
+
"module": module,
|
|
6577
|
+
})
|
|
6578
|
+
elif isinstance(node, ast.ClassDef):
|
|
6579
|
+
for base in node.bases:
|
|
6580
|
+
name = leaf_name(base)
|
|
6581
|
+
if name:
|
|
6582
|
+
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
6583
|
+
|
|
6584
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
6330
6585
|
`;
|
|
6331
6586
|
_cachedScriptPath = null;
|
|
6332
6587
|
}
|
|
@@ -6339,107 +6594,10 @@ __export(rs_parser_exports, {
|
|
|
6339
6594
|
parseSymbols: () => parseSymbols5
|
|
6340
6595
|
});
|
|
6341
6596
|
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
6342
|
-
import { execFile, spawn as spawn7 } from "node:child_process";
|
|
6343
|
-
import * as fs16 from "node:fs/promises";
|
|
6344
|
-
import * as path21 from "node:path";
|
|
6345
6597
|
async function parseSymbols5(opts) {
|
|
6346
6598
|
const { file, content, lang } = opts;
|
|
6347
|
-
const nativeAvailable = await checkNativeParser();
|
|
6348
|
-
if (nativeAvailable) {
|
|
6349
|
-
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
6350
|
-
if (result) return result;
|
|
6351
|
-
}
|
|
6352
6599
|
return regexParse({ file, content, lang });
|
|
6353
6600
|
}
|
|
6354
|
-
function probe(command, args) {
|
|
6355
|
-
return new Promise((resolve16, reject) => {
|
|
6356
|
-
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
6357
|
-
if (error) reject(error);
|
|
6358
|
-
else resolve16();
|
|
6359
|
-
});
|
|
6360
|
-
});
|
|
6361
|
-
}
|
|
6362
|
-
function checkNativeParser() {
|
|
6363
|
-
nativeParserAvailability ??= (async () => {
|
|
6364
|
-
try {
|
|
6365
|
-
await probe("rustc", ["--version"]);
|
|
6366
|
-
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6367
|
-
await probe(
|
|
6368
|
-
"cargo",
|
|
6369
|
-
[
|
|
6370
|
-
"metadata",
|
|
6371
|
-
"--no-deps",
|
|
6372
|
-
"--format-version",
|
|
6373
|
-
"1",
|
|
6374
|
-
"--manifest-path",
|
|
6375
|
-
path21.join(toolsDir, "Cargo.toml")
|
|
6376
|
-
]
|
|
6377
|
-
);
|
|
6378
|
-
return true;
|
|
6379
|
-
} catch {
|
|
6380
|
-
return false;
|
|
6381
|
-
}
|
|
6382
|
-
})();
|
|
6383
|
-
return nativeParserAvailability;
|
|
6384
|
-
}
|
|
6385
|
-
async function tryNativeParse(file, content) {
|
|
6386
|
-
try {
|
|
6387
|
-
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6388
|
-
const crateDir = path21.join(toolsDir, "syn-parser");
|
|
6389
|
-
const tmpFile = path21.join(crateDir, "src", "input.rs");
|
|
6390
|
-
await fs16.writeFile(tmpFile, content, "utf8");
|
|
6391
|
-
const cargoBinary = resolveWin32Command("cargo");
|
|
6392
|
-
const result = await new Promise(
|
|
6393
|
-
(resolve16, reject) => {
|
|
6394
|
-
let settled = false;
|
|
6395
|
-
const proc = spawn7(
|
|
6396
|
-
cargoBinary,
|
|
6397
|
-
["run", "--manifest-path", path21.join(toolsDir, "Cargo.toml")],
|
|
6398
|
-
{
|
|
6399
|
-
cwd: process.cwd(),
|
|
6400
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
6401
|
-
windowsHide: true
|
|
6402
|
-
}
|
|
6403
|
-
);
|
|
6404
|
-
proc.on("error", (err) => {
|
|
6405
|
-
if (settled) return;
|
|
6406
|
-
settled = true;
|
|
6407
|
-
reject(err);
|
|
6408
|
-
});
|
|
6409
|
-
let stdout2 = "";
|
|
6410
|
-
proc.stdout?.on("data", (chunk) => {
|
|
6411
|
-
stdout2 += chunk.toString();
|
|
6412
|
-
});
|
|
6413
|
-
proc.stderr?.resume();
|
|
6414
|
-
const timer = setTimeout(() => {
|
|
6415
|
-
if (settled) return;
|
|
6416
|
-
settled = true;
|
|
6417
|
-
proc.kill("SIGKILL");
|
|
6418
|
-
reject(new Error("timeout"));
|
|
6419
|
-
}, 15e3);
|
|
6420
|
-
timer.unref?.();
|
|
6421
|
-
proc.on("close", (c) => {
|
|
6422
|
-
if (settled) return;
|
|
6423
|
-
settled = true;
|
|
6424
|
-
clearTimeout(timer);
|
|
6425
|
-
resolve16({ code: c, stdout: stdout2 });
|
|
6426
|
-
});
|
|
6427
|
-
}
|
|
6428
|
-
);
|
|
6429
|
-
const { code, stdout } = result;
|
|
6430
|
-
if (code === 0 && stdout.trim()) {
|
|
6431
|
-
const symbols = JSON.parse(stdout.trim());
|
|
6432
|
-
return {
|
|
6433
|
-
file,
|
|
6434
|
-
lang: "rs",
|
|
6435
|
-
symbols: symbols.map((s) => ({ ...s, id: 0, lang: "rs" })),
|
|
6436
|
-
mtimeMs: Date.now()
|
|
6437
|
-
};
|
|
6438
|
-
}
|
|
6439
|
-
} catch {
|
|
6440
|
-
}
|
|
6441
|
-
return null;
|
|
6442
|
-
}
|
|
6443
6601
|
function regexParse(opts) {
|
|
6444
6602
|
const { file, content, lang } = opts;
|
|
6445
6603
|
const symbols = [];
|
|
@@ -6495,12 +6653,10 @@ function regexParse(opts) {
|
|
|
6495
6653
|
});
|
|
6496
6654
|
return { file, lang, symbols: deduped, mtimeMs: Date.now() };
|
|
6497
6655
|
}
|
|
6498
|
-
var
|
|
6656
|
+
var RS_PATTERNS;
|
|
6499
6657
|
var init_rs_parser = __esm({
|
|
6500
6658
|
"src/codebase-index/rs-parser.ts"() {
|
|
6501
6659
|
"use strict";
|
|
6502
|
-
init_win32_resolve();
|
|
6503
|
-
init_spawn_gate();
|
|
6504
6660
|
init_languages2();
|
|
6505
6661
|
RS_PATTERNS = [
|
|
6506
6662
|
{ regex: /fn\s+(\w+)\s*\([^)]*\)/g, kind: "function" },
|
|
@@ -9954,7 +10110,7 @@ for (const tool of browserTools) tool.timeoutMs ??= 6e4;
|
|
|
9954
10110
|
|
|
9955
10111
|
// src/codebase-index/project-server-client.ts
|
|
9956
10112
|
import { spawn as spawn4 } from "node:child_process";
|
|
9957
|
-
import * as
|
|
10113
|
+
import * as fs13 from "node:fs";
|
|
9958
10114
|
import * as net3 from "node:net";
|
|
9959
10115
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9960
10116
|
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
@@ -10041,16 +10197,16 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
10041
10197
|
|
|
10042
10198
|
// src/codebase-index/project-server-endpoint.ts
|
|
10043
10199
|
import { createHash as createHash4 } from "node:crypto";
|
|
10044
|
-
import * as
|
|
10200
|
+
import * as fs12 from "node:fs";
|
|
10045
10201
|
import * as os5 from "node:os";
|
|
10046
|
-
import * as
|
|
10202
|
+
import * as path17 from "node:path";
|
|
10047
10203
|
import { fileURLToPath } from "node:url";
|
|
10048
10204
|
import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
|
|
10049
10205
|
|
|
10050
10206
|
// src/codebase-index/writer.ts
|
|
10051
10207
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
10052
|
-
import * as
|
|
10053
|
-
import * as
|
|
10208
|
+
import * as fs11 from "node:fs";
|
|
10209
|
+
import * as path16 from "node:path";
|
|
10054
10210
|
|
|
10055
10211
|
// src/codebase-index/bm25.ts
|
|
10056
10212
|
var K1 = 1.5;
|
|
@@ -10141,6 +10297,9 @@ var Bm25Index = class {
|
|
|
10141
10297
|
}
|
|
10142
10298
|
};
|
|
10143
10299
|
|
|
10300
|
+
// src/codebase-index/writer.ts
|
|
10301
|
+
init_languages2();
|
|
10302
|
+
|
|
10144
10303
|
// src/codebase-index/lsp-kind.ts
|
|
10145
10304
|
function lspKindToInternalKind(k) {
|
|
10146
10305
|
switch (k) {
|
|
@@ -10175,7 +10334,7 @@ function lspKindToInternalKind(k) {
|
|
|
10175
10334
|
}
|
|
10176
10335
|
|
|
10177
10336
|
// src/codebase-index/schema.ts
|
|
10178
|
-
var SCHEMA_VERSION =
|
|
10337
|
+
var SCHEMA_VERSION = 4;
|
|
10179
10338
|
|
|
10180
10339
|
// src/codebase-index/sqlite-runtime.ts
|
|
10181
10340
|
import { createRequire } from "node:module";
|
|
@@ -10246,7 +10405,7 @@ function runSqliteWithRetry(fn) {
|
|
|
10246
10405
|
|
|
10247
10406
|
// src/codebase-index/writer-admin.ts
|
|
10248
10407
|
import * as fs9 from "node:fs";
|
|
10249
|
-
import * as
|
|
10408
|
+
import * as path14 from "node:path";
|
|
10250
10409
|
var DB_FILE = "index.db";
|
|
10251
10410
|
function getAllIndexableWithStatement(stmt) {
|
|
10252
10411
|
return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
@@ -10305,7 +10464,7 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
10305
10464
|
}
|
|
10306
10465
|
function getIndexDbSizeBytes(indexDir) {
|
|
10307
10466
|
try {
|
|
10308
|
-
return fs9.statSync(
|
|
10467
|
+
return fs9.statSync(path14.join(indexDir, DB_FILE)).size;
|
|
10309
10468
|
} catch {
|
|
10310
10469
|
return 0;
|
|
10311
10470
|
}
|
|
@@ -10356,49 +10515,345 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
10356
10515
|
}
|
|
10357
10516
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
10358
10517
|
if (refs.length === 0) return;
|
|
10359
|
-
const chunkSize = Math.max(1, Math.floor(maxSqlVars /
|
|
10518
|
+
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 8));
|
|
10360
10519
|
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
10361
10520
|
const chunk = refs.slice(i, i + chunkSize);
|
|
10362
|
-
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
10521
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
10363
10522
|
const insert = stmt(
|
|
10364
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line
|
|
10523
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
10524
|
+
VALUES ${placeholders}`
|
|
10365
10525
|
);
|
|
10366
10526
|
const binds = [];
|
|
10367
10527
|
for (const ref of chunk) {
|
|
10368
|
-
binds.push(
|
|
10528
|
+
binds.push(
|
|
10529
|
+
ref.fromId,
|
|
10530
|
+
ref.toName,
|
|
10531
|
+
ref.toId ?? null,
|
|
10532
|
+
ref.callType,
|
|
10533
|
+
ref.line,
|
|
10534
|
+
ref.lang ?? "",
|
|
10535
|
+
ref.module ?? null,
|
|
10536
|
+
ref.toFile ?? null
|
|
10537
|
+
);
|
|
10369
10538
|
}
|
|
10370
10539
|
insert.run(...binds);
|
|
10371
10540
|
}
|
|
10372
10541
|
}
|
|
10373
10542
|
|
|
10374
|
-
// src/codebase-index/writer-graph-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10543
|
+
// src/codebase-index/writer-graph-reader.ts
|
|
10544
|
+
init_languages2();
|
|
10545
|
+
|
|
10546
|
+
// src/codebase-index/module-roots.ts
|
|
10547
|
+
init_languages2();
|
|
10548
|
+
import * as fs10 from "node:fs/promises";
|
|
10549
|
+
import * as path15 from "node:path";
|
|
10550
|
+
function toPortablePath(file) {
|
|
10551
|
+
return file.replace(/\\/g, "/");
|
|
10552
|
+
}
|
|
10553
|
+
async function readTextIfPresent(file) {
|
|
10554
|
+
try {
|
|
10555
|
+
return await fs10.readFile(file, "utf8");
|
|
10556
|
+
} catch {
|
|
10557
|
+
return void 0;
|
|
10558
|
+
}
|
|
10559
|
+
}
|
|
10560
|
+
function parsePackageJsonName(source) {
|
|
10561
|
+
try {
|
|
10562
|
+
const parsed = JSON.parse(source);
|
|
10563
|
+
return typeof parsed.name === "string" && parsed.name ? parsed.name : void 0;
|
|
10564
|
+
} catch {
|
|
10565
|
+
return void 0;
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
function parseGoModulePath(source) {
|
|
10569
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10570
|
+
const line = rawLine.replace(/\/\/.*$/, "").trim();
|
|
10571
|
+
const match = /^module\s+(\S+)/.exec(line);
|
|
10572
|
+
if (match?.[1]) return match[1].replace(/^["']|["']$/g, "");
|
|
10573
|
+
}
|
|
10574
|
+
return void 0;
|
|
10575
|
+
}
|
|
10576
|
+
function parseTomlTableName(source, tables) {
|
|
10577
|
+
let current = "";
|
|
10578
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10579
|
+
const line = rawLine.replace(/#.*$/, "").trim();
|
|
10580
|
+
if (line.startsWith("[[")) {
|
|
10581
|
+
current = "\0";
|
|
10582
|
+
continue;
|
|
10583
|
+
}
|
|
10584
|
+
const table = /^\[([^\]]+)\]$/.exec(line);
|
|
10585
|
+
if (table?.[1]) {
|
|
10586
|
+
current = table[1].trim();
|
|
10587
|
+
continue;
|
|
10588
|
+
}
|
|
10589
|
+
if (!tables.includes(current)) continue;
|
|
10590
|
+
const match = /^name\s*=\s*["']([^"']+)["']/.exec(line);
|
|
10591
|
+
if (match?.[1]) return match[1];
|
|
10592
|
+
}
|
|
10593
|
+
return void 0;
|
|
10594
|
+
}
|
|
10595
|
+
function parsePomArtifactId(source) {
|
|
10596
|
+
const withoutParent = source.replace(/<parent>[\s\S]*?<\/parent>/g, "");
|
|
10597
|
+
return /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(withoutParent)?.[1];
|
|
10598
|
+
}
|
|
10599
|
+
var LANGS_BY_KIND = {
|
|
10600
|
+
npm: ["ts", "tsx", "js", "jsx", "vue", "svelte"],
|
|
10601
|
+
cargo: ["rs"],
|
|
10602
|
+
go: ["go"],
|
|
10603
|
+
python: ["py"],
|
|
10604
|
+
maven: ["java", "kotlin", "scala"],
|
|
10605
|
+
gradle: ["java", "kotlin", "scala"],
|
|
10606
|
+
dotnet: ["csharp"]
|
|
10607
|
+
};
|
|
10608
|
+
function ancestorsOf(dir, stopAt) {
|
|
10609
|
+
const out = [];
|
|
10610
|
+
let current = dir;
|
|
10611
|
+
for (; ; ) {
|
|
10612
|
+
out.push(current);
|
|
10613
|
+
if (current === stopAt || current.length <= stopAt.length) break;
|
|
10614
|
+
const parent = path15.posix.dirname(current);
|
|
10615
|
+
if (parent === current) break;
|
|
10616
|
+
current = parent;
|
|
10617
|
+
}
|
|
10618
|
+
return out;
|
|
10619
|
+
}
|
|
10620
|
+
var MARKER_PROBES = [
|
|
10621
|
+
{
|
|
10622
|
+
kind: "npm",
|
|
10623
|
+
file: "package.json",
|
|
10624
|
+
build: (dir, source) => {
|
|
10625
|
+
const name = parsePackageJsonName(source) ?? path15.posix.basename(dir);
|
|
10626
|
+
return { name, importPath: name, sourceRoots: [dir] };
|
|
10627
|
+
}
|
|
10628
|
+
},
|
|
10629
|
+
{
|
|
10630
|
+
kind: "cargo",
|
|
10631
|
+
file: "Cargo.toml",
|
|
10632
|
+
build: (dir, source) => {
|
|
10633
|
+
const name = parseTomlTableName(source, ["package"]);
|
|
10634
|
+
if (!name) return void 0;
|
|
10635
|
+
return {
|
|
10636
|
+
name: `crate:${name}`,
|
|
10637
|
+
// Rust paths use underscores where crate names often use dashes.
|
|
10638
|
+
importPath: name.replace(/-/g, "_"),
|
|
10639
|
+
sourceRoots: [path15.posix.join(dir, "src")]
|
|
10640
|
+
};
|
|
10641
|
+
}
|
|
10642
|
+
},
|
|
10643
|
+
{
|
|
10644
|
+
kind: "go",
|
|
10645
|
+
file: "go.mod",
|
|
10646
|
+
build: (dir, source) => {
|
|
10647
|
+
const modulePath = parseGoModulePath(source);
|
|
10648
|
+
if (!modulePath) return void 0;
|
|
10649
|
+
return { name: `go:${modulePath}`, importPath: modulePath, sourceRoots: [dir] };
|
|
10650
|
+
}
|
|
10651
|
+
},
|
|
10652
|
+
{
|
|
10653
|
+
kind: "python",
|
|
10654
|
+
file: "pyproject.toml",
|
|
10655
|
+
build: (dir, source) => {
|
|
10656
|
+
const name = parseTomlTableName(source, ["project", "tool.poetry"]) ?? path15.posix.basename(dir);
|
|
10657
|
+
return {
|
|
10658
|
+
name: `py:${name}`,
|
|
10659
|
+
importPath: void 0,
|
|
10660
|
+
// `src/` layout is the packaging-guide default; the root itself covers
|
|
10661
|
+
// the flat layout. Both are probed, missing ones simply never match.
|
|
10662
|
+
sourceRoots: [path15.posix.join(dir, "src"), dir]
|
|
10663
|
+
};
|
|
10664
|
+
}
|
|
10665
|
+
},
|
|
10666
|
+
{
|
|
10667
|
+
kind: "python",
|
|
10668
|
+
file: "setup.py",
|
|
10669
|
+
build: (dir) => ({
|
|
10670
|
+
name: `py:${path15.posix.basename(dir)}`,
|
|
10671
|
+
importPath: void 0,
|
|
10672
|
+
sourceRoots: [path15.posix.join(dir, "src"), dir]
|
|
10673
|
+
})
|
|
10674
|
+
},
|
|
10675
|
+
{
|
|
10676
|
+
kind: "maven",
|
|
10677
|
+
file: "pom.xml",
|
|
10678
|
+
build: (dir, source) => {
|
|
10679
|
+
const artifactId = parsePomArtifactId(source) ?? path15.posix.basename(dir);
|
|
10680
|
+
return {
|
|
10681
|
+
name: `mvn:${artifactId}`,
|
|
10682
|
+
importPath: void 0,
|
|
10683
|
+
sourceRoots: [
|
|
10684
|
+
path15.posix.join(dir, "src/main/java"),
|
|
10685
|
+
path15.posix.join(dir, "src/main/kotlin"),
|
|
10686
|
+
path15.posix.join(dir, "src/main/scala"),
|
|
10687
|
+
path15.posix.join(dir, "src/test/java")
|
|
10688
|
+
]
|
|
10689
|
+
};
|
|
10690
|
+
}
|
|
10691
|
+
},
|
|
10692
|
+
{
|
|
10693
|
+
kind: "gradle",
|
|
10694
|
+
file: "build.gradle",
|
|
10695
|
+
build: (dir) => buildGradleRoot(dir)
|
|
10696
|
+
},
|
|
10697
|
+
{
|
|
10698
|
+
kind: "gradle",
|
|
10699
|
+
file: "build.gradle.kts",
|
|
10700
|
+
build: (dir) => buildGradleRoot(dir)
|
|
10701
|
+
}
|
|
10702
|
+
];
|
|
10703
|
+
function buildGradleRoot(dir) {
|
|
10704
|
+
return {
|
|
10705
|
+
name: `gradle:${path15.posix.basename(dir)}`,
|
|
10706
|
+
importPath: void 0,
|
|
10707
|
+
sourceRoots: [
|
|
10708
|
+
path15.posix.join(dir, "src/main/java"),
|
|
10709
|
+
path15.posix.join(dir, "src/main/kotlin"),
|
|
10710
|
+
path15.posix.join(dir, "src/main/scala")
|
|
10711
|
+
]
|
|
10712
|
+
};
|
|
10713
|
+
}
|
|
10714
|
+
async function probeDotnetRoot(dir) {
|
|
10715
|
+
let entries;
|
|
10716
|
+
try {
|
|
10717
|
+
entries = await fs10.readdir(dir);
|
|
10718
|
+
} catch {
|
|
10719
|
+
return void 0;
|
|
10720
|
+
}
|
|
10721
|
+
const project = entries.find((entry) => entry.toLowerCase().endsWith(".csproj"));
|
|
10722
|
+
if (!project) return void 0;
|
|
10723
|
+
const name = project.slice(0, -".csproj".length);
|
|
10724
|
+
return {
|
|
10725
|
+
dir,
|
|
10726
|
+
kind: "dotnet",
|
|
10727
|
+
name: `csproj:${name}`,
|
|
10728
|
+
importPath: void 0,
|
|
10729
|
+
sourceRoots: [dir]
|
|
10730
|
+
};
|
|
10731
|
+
}
|
|
10732
|
+
async function detectModuleRoots(projectRoot, files) {
|
|
10733
|
+
const root = toPortablePath(projectRoot).replace(/\/+$/, "");
|
|
10734
|
+
const langsByDir = /* @__PURE__ */ new Map();
|
|
10735
|
+
for (const file of files) {
|
|
10736
|
+
const portable = toPortablePath(file);
|
|
10737
|
+
const lang = detectLang(portable);
|
|
10738
|
+
if (!lang) continue;
|
|
10739
|
+
const dir = path15.posix.dirname(portable);
|
|
10740
|
+
let langs = langsByDir.get(dir);
|
|
10741
|
+
if (!langs) {
|
|
10742
|
+
langs = /* @__PURE__ */ new Set();
|
|
10743
|
+
langsByDir.set(dir, langs);
|
|
10744
|
+
}
|
|
10745
|
+
langs.add(lang);
|
|
10746
|
+
}
|
|
10747
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
10748
|
+
for (const [dir, langs] of langsByDir) {
|
|
10749
|
+
for (const ancestor of ancestorsOf(dir, root)) {
|
|
10750
|
+
let merged = candidates.get(ancestor);
|
|
10751
|
+
if (!merged) {
|
|
10752
|
+
merged = /* @__PURE__ */ new Set();
|
|
10753
|
+
candidates.set(ancestor, merged);
|
|
10754
|
+
}
|
|
10755
|
+
for (const lang of langs) merged.add(lang);
|
|
10756
|
+
}
|
|
10757
|
+
}
|
|
10758
|
+
const roots = [];
|
|
10759
|
+
await Promise.all(
|
|
10760
|
+
[...candidates].map(async ([dir, langs]) => {
|
|
10761
|
+
for (const probe of MARKER_PROBES) {
|
|
10762
|
+
if (!LANGS_BY_KIND[probe.kind].some((lang) => langs.has(lang))) continue;
|
|
10763
|
+
const source = await readTextIfPresent(path15.posix.join(dir, probe.file));
|
|
10764
|
+
if (source === void 0) continue;
|
|
10765
|
+
const built = probe.build(dir, source);
|
|
10766
|
+
if (built) roots.push({ dir, kind: probe.kind, ...built });
|
|
10767
|
+
}
|
|
10768
|
+
if (LANGS_BY_KIND.dotnet.some((lang) => langs.has(lang))) {
|
|
10769
|
+
const dotnet = await probeDotnetRoot(dir);
|
|
10770
|
+
if (dotnet) roots.push(dotnet);
|
|
10771
|
+
}
|
|
10772
|
+
})
|
|
10773
|
+
);
|
|
10774
|
+
roots.sort((a, b) => b.dir.length - a.dir.length || a.dir.localeCompare(b.dir));
|
|
10775
|
+
return { projectRoot: root, roots };
|
|
10776
|
+
}
|
|
10777
|
+
function findOwningRoot(structure, file, kinds) {
|
|
10778
|
+
const portable = toPortablePath(file);
|
|
10779
|
+
for (const root of structure.roots) {
|
|
10780
|
+
if (kinds && !kinds.includes(root.kind)) continue;
|
|
10781
|
+
if (portable === root.dir || portable.startsWith(`${root.dir}/`)) return root;
|
|
10782
|
+
}
|
|
10783
|
+
return void 0;
|
|
10784
|
+
}
|
|
10785
|
+
function derivePackageFromLayout(filePath) {
|
|
10786
|
+
const portable = toPortablePath(filePath);
|
|
10787
|
+
const packagesIdx = portable.indexOf("/packages/");
|
|
10788
|
+
if (packagesIdx !== -1) {
|
|
10789
|
+
const segment = portable.slice(packagesIdx + "/packages/".length).split("/")[0];
|
|
10790
|
+
if (segment) return `@wrongstack/${segment}`;
|
|
10791
|
+
}
|
|
10792
|
+
const appsIdx = portable.indexOf("/apps/");
|
|
10385
10793
|
if (appsIdx !== -1) {
|
|
10386
|
-
const
|
|
10387
|
-
|
|
10388
|
-
return seg ? `app:${seg}` : void 0;
|
|
10794
|
+
const segment = portable.slice(appsIdx + "/apps/".length).split("/")[0];
|
|
10795
|
+
if (segment) return `app:${segment}`;
|
|
10389
10796
|
}
|
|
10390
10797
|
return void 0;
|
|
10391
10798
|
}
|
|
10392
|
-
function
|
|
10393
|
-
|
|
10394
|
-
const
|
|
10395
|
-
|
|
10799
|
+
function pythonPackageLabel(structure, file, initDirs) {
|
|
10800
|
+
const portable = toPortablePath(file);
|
|
10801
|
+
const dir = path15.posix.dirname(portable);
|
|
10802
|
+
if (!initDirs.has(dir)) return void 0;
|
|
10803
|
+
const segments = [];
|
|
10804
|
+
let current = dir;
|
|
10805
|
+
while (initDirs.has(current) && current.length > structure.projectRoot.length) {
|
|
10806
|
+
segments.unshift(path15.posix.basename(current));
|
|
10807
|
+
current = path15.posix.dirname(current);
|
|
10808
|
+
}
|
|
10809
|
+
return segments.length > 0 ? `py:${segments.join(".")}` : void 0;
|
|
10396
10810
|
}
|
|
10397
|
-
function
|
|
10811
|
+
function assignPackageLabels(structure, files) {
|
|
10812
|
+
const initDirs = /* @__PURE__ */ new Set();
|
|
10813
|
+
for (const file of files) {
|
|
10814
|
+
const portable = toPortablePath(file);
|
|
10815
|
+
if (path15.posix.basename(portable) === "__init__.py") {
|
|
10816
|
+
initDirs.add(path15.posix.dirname(portable));
|
|
10817
|
+
}
|
|
10818
|
+
}
|
|
10819
|
+
const labels = /* @__PURE__ */ new Map();
|
|
10820
|
+
for (const file of files) {
|
|
10821
|
+
const portable = toPortablePath(file);
|
|
10822
|
+
const lang = detectLang(portable);
|
|
10823
|
+
if (lang === "go") {
|
|
10824
|
+
const owner3 = findOwningRoot(structure, portable, ["go"]);
|
|
10825
|
+
const dir = path15.posix.dirname(portable);
|
|
10826
|
+
if (owner3?.importPath) {
|
|
10827
|
+
const relative12 = path15.posix.relative(owner3.dir, dir);
|
|
10828
|
+
labels.set(file, relative12 ? `${owner3.importPath}/${relative12}` : owner3.importPath);
|
|
10829
|
+
} else {
|
|
10830
|
+
labels.set(file, `go:${path15.posix.relative(structure.projectRoot, dir) || "."}`);
|
|
10831
|
+
}
|
|
10832
|
+
continue;
|
|
10833
|
+
}
|
|
10834
|
+
if (lang === "py") {
|
|
10835
|
+
const dotted = pythonPackageLabel(structure, portable, initDirs);
|
|
10836
|
+
if (dotted) {
|
|
10837
|
+
labels.set(file, dotted);
|
|
10838
|
+
continue;
|
|
10839
|
+
}
|
|
10840
|
+
}
|
|
10841
|
+
const owner2 = findOwningRoot(structure, portable);
|
|
10842
|
+
const label = owner2?.name ?? derivePackageFromLayout(portable) ?? "(root)";
|
|
10843
|
+
labels.set(file, label);
|
|
10844
|
+
}
|
|
10845
|
+
return labels;
|
|
10846
|
+
}
|
|
10847
|
+
|
|
10848
|
+
// src/codebase-index/writer-graph-helpers.ts
|
|
10849
|
+
function createPackageLabeller(stored) {
|
|
10850
|
+
return (file) => stored.get(file) ?? derivePackageFromLayout(file) ?? "(root)";
|
|
10851
|
+
}
|
|
10852
|
+
function buildPackageGraphNodes(fileCounts, files, packageOf) {
|
|
10398
10853
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
10399
10854
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
10400
10855
|
for (const { file, n } of fileCounts) {
|
|
10401
|
-
const pkg =
|
|
10856
|
+
const pkg = packageOf(file);
|
|
10402
10857
|
fileToPkg.set(file, pkg);
|
|
10403
10858
|
const node = pkgNodes.get(pkg);
|
|
10404
10859
|
if (node) {
|
|
@@ -10415,7 +10870,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
10415
10870
|
}
|
|
10416
10871
|
}
|
|
10417
10872
|
for (const { file } of files) {
|
|
10418
|
-
const pkg =
|
|
10873
|
+
const pkg = packageOf(file);
|
|
10419
10874
|
fileToPkg.set(file, pkg);
|
|
10420
10875
|
const node = pkgNodes.get(pkg);
|
|
10421
10876
|
if (node) {
|
|
@@ -10433,7 +10888,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
10433
10888
|
}
|
|
10434
10889
|
return { pkgNodes, fileToPkg };
|
|
10435
10890
|
}
|
|
10436
|
-
function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
10891
|
+
function buildFileGraphNodeState(pkgSyms, localFiles, packageOf) {
|
|
10437
10892
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
10438
10893
|
const symToFile = /* @__PURE__ */ new Map();
|
|
10439
10894
|
const fileStats = /* @__PURE__ */ new Map();
|
|
@@ -10452,7 +10907,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
10452
10907
|
id: `file:${file}`,
|
|
10453
10908
|
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
10454
10909
|
kind: "file",
|
|
10455
|
-
package:
|
|
10910
|
+
package: packageOf(file),
|
|
10456
10911
|
file,
|
|
10457
10912
|
symbolCount: stats?.count ?? 0,
|
|
10458
10913
|
lang: stats?.lang,
|
|
@@ -10464,7 +10919,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
10464
10919
|
}
|
|
10465
10920
|
return { fileNodes, symToFile, fileStats, ensureFileNode };
|
|
10466
10921
|
}
|
|
10467
|
-
function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
10922
|
+
function buildSymbolGraphNodes(symById, relatedIds, fileFilter, packageOf) {
|
|
10468
10923
|
return [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
10469
10924
|
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
10470
10925
|
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
@@ -10476,7 +10931,7 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10476
10931
|
symbolId: s.id,
|
|
10477
10932
|
symbolKind: s.kind,
|
|
10478
10933
|
file: s.file,
|
|
10479
|
-
package:
|
|
10934
|
+
package: packageOf(s.file),
|
|
10480
10935
|
lang: s.lang,
|
|
10481
10936
|
line: s.line,
|
|
10482
10937
|
signature: s.signature,
|
|
@@ -10484,29 +10939,6 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10484
10939
|
external: s.file !== fileFilter
|
|
10485
10940
|
}));
|
|
10486
10941
|
}
|
|
10487
|
-
function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
10488
|
-
if (!moduleName.startsWith(".")) return void 0;
|
|
10489
|
-
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
10490
|
-
const absolute = path14.posix.normalize(
|
|
10491
|
-
path14.posix.join(path14.posix.dirname(normalizedFrom), moduleName)
|
|
10492
|
-
);
|
|
10493
|
-
const extension = path14.posix.extname(absolute);
|
|
10494
|
-
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
10495
|
-
const candidates = [
|
|
10496
|
-
absolute,
|
|
10497
|
-
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
10498
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(absolute, `index${ext}`)),
|
|
10499
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(base, `index${ext}`))
|
|
10500
|
-
];
|
|
10501
|
-
const indexedByPortablePath = new Map(
|
|
10502
|
-
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
10503
|
-
);
|
|
10504
|
-
for (const candidate of candidates) {
|
|
10505
|
-
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
10506
|
-
if (indexed) return indexed;
|
|
10507
|
-
}
|
|
10508
|
-
return void 0;
|
|
10509
|
-
}
|
|
10510
10942
|
function addWeightedEdge(edgeMap, source, target, callType, weight) {
|
|
10511
10943
|
const key = `${source}\0${target}`;
|
|
10512
10944
|
let edge = edgeMap.get(key);
|
|
@@ -10547,7 +10979,12 @@ function mapWriterRefRow(row) {
|
|
|
10547
10979
|
toName: row.to_name,
|
|
10548
10980
|
toId: row.to_id ?? void 0,
|
|
10549
10981
|
callType: row.call_type,
|
|
10550
|
-
line: row.line
|
|
10982
|
+
line: row.line,
|
|
10983
|
+
// `lang`/`module`/`to_file` are absent from the narrower column lists some
|
|
10984
|
+
// queries select; `undefined` keeps those rows valid Refs.
|
|
10985
|
+
lang: row.lang || void 0,
|
|
10986
|
+
module: row.module ?? void 0,
|
|
10987
|
+
toFile: row.to_file ?? void 0
|
|
10551
10988
|
};
|
|
10552
10989
|
}
|
|
10553
10990
|
|
|
@@ -10695,7 +11132,8 @@ function findRefsFromWithStatement(stmt, symbolId) {
|
|
|
10695
11132
|
function getPackageGraphWithStatement(stmt) {
|
|
10696
11133
|
const fileCounts = stmt("SELECT file, COUNT(*) AS n FROM symbols GROUP BY file").all();
|
|
10697
11134
|
const files = stmt("SELECT DISTINCT file FROM files").all();
|
|
10698
|
-
const
|
|
11135
|
+
const packageOf = readPackageLabeller(stmt);
|
|
11136
|
+
const { pkgNodes, fileToPkg } = buildPackageGraphNodes(fileCounts, files, packageOf);
|
|
10699
11137
|
const refRows = stmt(
|
|
10700
11138
|
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
10701
11139
|
FROM refs r
|
|
@@ -10706,32 +11144,42 @@ function getPackageGraphWithStatement(stmt) {
|
|
|
10706
11144
|
).all();
|
|
10707
11145
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
10708
11146
|
for (const r of refRows) {
|
|
10709
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
10710
|
-
const toPkg = fileToPkg.get(r.to_file) ??
|
|
11147
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
11148
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
10711
11149
|
if (fromPkg === toPkg) continue;
|
|
10712
11150
|
const n = Number(r.n) || 0;
|
|
10713
11151
|
addWeightedEdge(edgeMap, fromPkg, toPkg, r.call_type, n);
|
|
10714
11152
|
}
|
|
10715
11153
|
const importRows = stmt(
|
|
10716
|
-
`SELECT
|
|
11154
|
+
`SELECT s.file AS from_file,
|
|
11155
|
+
COALESCE(r.to_file, st.file) AS to_file,
|
|
11156
|
+
COUNT(*) AS n
|
|
10717
11157
|
FROM refs r
|
|
10718
11158
|
JOIN symbols s ON s.id = r.from_id
|
|
11159
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
10719
11160
|
WHERE r.call_type = 'import'
|
|
10720
|
-
|
|
11161
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
11162
|
+
GROUP BY s.file, COALESCE(r.to_file, st.file)`
|
|
10721
11163
|
).all();
|
|
10722
11164
|
for (const r of importRows) {
|
|
10723
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
10724
|
-
const toPkg =
|
|
10725
|
-
if (
|
|
11165
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
11166
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
11167
|
+
if (fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
10726
11168
|
const n = Number(r.n) || 0;
|
|
10727
11169
|
addWeightedEdge(edgeMap, fromPkg, toPkg, "import", n);
|
|
10728
11170
|
}
|
|
10729
11171
|
const edges = materializeWeightedEdges(edgeMap, "pkg");
|
|
10730
11172
|
return { nodes: [...pkgNodes.values()], edges };
|
|
10731
11173
|
}
|
|
11174
|
+
function readPackageLabeller(stmt) {
|
|
11175
|
+
const rows = stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
11176
|
+
return createPackageLabeller(new Map(rows.map((row) => [row.file, row.package])));
|
|
11177
|
+
}
|
|
10732
11178
|
function getFileGraphWithStatement(stmt, packageFilter) {
|
|
10733
11179
|
const allFiles = stmt("SELECT DISTINCT file FROM symbols").all();
|
|
10734
|
-
const
|
|
11180
|
+
const packageOf = readPackageLabeller(stmt);
|
|
11181
|
+
const langOf = (file) => detectLang(file) ?? "other";
|
|
11182
|
+
const pkgFilePaths = allFiles.filter((f) => packageOf(f.file) === packageFilter).map((f) => f.file);
|
|
10735
11183
|
const localFiles = new Set(pkgFilePaths);
|
|
10736
11184
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
10737
11185
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
@@ -10740,9 +11188,9 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
10740
11188
|
).all(...pkgFilePaths);
|
|
10741
11189
|
const { fileNodes, symToFile, fileStats, ensureFileNode } = buildFileGraphNodeState(
|
|
10742
11190
|
pkgSyms,
|
|
10743
|
-
localFiles
|
|
11191
|
+
localFiles,
|
|
11192
|
+
packageOf
|
|
10744
11193
|
);
|
|
10745
|
-
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
10746
11194
|
const refRows = stmt(
|
|
10747
11195
|
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
10748
11196
|
FROM refs r
|
|
@@ -10765,7 +11213,7 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
10765
11213
|
for (const x of extras) {
|
|
10766
11214
|
symToFile.set(x.id, x.file);
|
|
10767
11215
|
if (!fileStats.has(x.file)) {
|
|
10768
|
-
fileStats.set(x.file, { count: 0, lang:
|
|
11216
|
+
fileStats.set(x.file, { count: 0, lang: langOf(x.file) });
|
|
10769
11217
|
}
|
|
10770
11218
|
}
|
|
10771
11219
|
}
|
|
@@ -10782,17 +11230,22 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
10782
11230
|
addWeightedEdge(edgeMap, fromFile, toFile, r.call_type, n);
|
|
10783
11231
|
}
|
|
10784
11232
|
const importRows = stmt(
|
|
10785
|
-
`SELECT r.from_id, r.
|
|
11233
|
+
`SELECT r.from_id, COALESCE(r.to_file, st.file) AS to_file, COUNT(*) AS n
|
|
10786
11234
|
FROM refs r
|
|
11235
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
10787
11236
|
WHERE r.call_type = 'import'
|
|
11237
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
10788
11238
|
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
10789
|
-
GROUP BY r.from_id, r.
|
|
11239
|
+
GROUP BY r.from_id, COALESCE(r.to_file, st.file)`
|
|
10790
11240
|
).all(...pkgFilePaths);
|
|
10791
11241
|
for (const r of importRows) {
|
|
10792
11242
|
const fromFile = symToFile.get(r.from_id);
|
|
10793
11243
|
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
10794
|
-
const toFile =
|
|
11244
|
+
const toFile = r.to_file;
|
|
10795
11245
|
if (!toFile || fromFile === toFile) continue;
|
|
11246
|
+
if (!fileStats.has(toFile)) {
|
|
11247
|
+
fileStats.set(toFile, { count: 0, lang: langOf(toFile) });
|
|
11248
|
+
}
|
|
10796
11249
|
ensureFileNode(fromFile);
|
|
10797
11250
|
ensureFileNode(toFile);
|
|
10798
11251
|
const n = Number(r.n) || 0;
|
|
@@ -10842,7 +11295,7 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
10842
11295
|
).all(...missingIds);
|
|
10843
11296
|
for (const s of extras) symById.set(s.id, s);
|
|
10844
11297
|
}
|
|
10845
|
-
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter);
|
|
11298
|
+
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter, readPackageLabeller(stmt));
|
|
10846
11299
|
return { nodes, edges };
|
|
10847
11300
|
}
|
|
10848
11301
|
|
|
@@ -10864,7 +11317,7 @@ function assignRefsToSymbols(refs, symbols) {
|
|
|
10864
11317
|
}
|
|
10865
11318
|
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10866
11319
|
if (!owner2 || owner2.id <= 0) continue;
|
|
10867
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
11320
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
10868
11321
|
if (seen.has(key)) continue;
|
|
10869
11322
|
seen.add(key);
|
|
10870
11323
|
assigned.push({ ...ref, fromId: owner2.id });
|
|
@@ -10910,7 +11363,11 @@ var CORE_TABLES_SQL = `
|
|
|
10910
11363
|
lang TEXT NOT NULL,
|
|
10911
11364
|
mtime_ms INTEGER NOT NULL,
|
|
10912
11365
|
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
10913
|
-
last_indexed INTEGER NOT NULL
|
|
11366
|
+
last_indexed INTEGER NOT NULL,
|
|
11367
|
+
-- Code Atlas grouping label, computed at index time from the ecosystem's
|
|
11368
|
+
-- own manifests (package.json, go.mod, Cargo.toml, \u2026). Stored rather than
|
|
11369
|
+
-- re-derived per query because the evidence lives on disk, not in the DB.
|
|
11370
|
+
package TEXT NOT NULL DEFAULT ''
|
|
10914
11371
|
);
|
|
10915
11372
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
10916
11373
|
id INTEGER PRIMARY KEY,
|
|
@@ -10927,6 +11384,9 @@ var CORE_TABLES_SQL = `
|
|
|
10927
11384
|
file_fk TEXT NOT NULL
|
|
10928
11385
|
);
|
|
10929
11386
|
`;
|
|
11387
|
+
var FILE_INDEX_SQL = [
|
|
11388
|
+
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
11389
|
+
];
|
|
10930
11390
|
var SYMBOL_INDEX_SQL = [
|
|
10931
11391
|
"CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
|
|
10932
11392
|
"CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
|
|
@@ -10943,15 +11403,32 @@ var REFS_TABLE_SQL = `
|
|
|
10943
11403
|
to_name TEXT NOT NULL,
|
|
10944
11404
|
to_id INTEGER,
|
|
10945
11405
|
call_type TEXT NOT NULL,
|
|
10946
|
-
line INTEGER NOT NULL
|
|
11406
|
+
line INTEGER NOT NULL,
|
|
11407
|
+
lang TEXT NOT NULL DEFAULT '',
|
|
11408
|
+
module TEXT,
|
|
11409
|
+
to_file TEXT
|
|
10947
11410
|
);
|
|
10948
11411
|
`;
|
|
10949
11412
|
var REFS_INDEX_SQL = [
|
|
10950
11413
|
"CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
|
|
10951
11414
|
"CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
|
|
10952
11415
|
"CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
|
|
10953
|
-
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
|
|
11416
|
+
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)",
|
|
11417
|
+
// Name resolution matches (to_name, lang) pairs; the composite keeps the
|
|
11418
|
+
// language-scoped UPDATE from degrading into a scan of every same-named row.
|
|
11419
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_name_lang ON refs(to_name, lang)",
|
|
11420
|
+
// The post-index module resolution pass groups unresolved import refs by
|
|
11421
|
+
// (module, lang); graph readers then read to_file back.
|
|
11422
|
+
"CREATE INDEX IF NOT EXISTS idx_r_module ON refs(module)",
|
|
11423
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_file ON refs(to_file)"
|
|
10954
11424
|
];
|
|
11425
|
+
var LANG_FAMILY_TABLE_SQL = `
|
|
11426
|
+
CREATE TABLE IF NOT EXISTS lang_family (
|
|
11427
|
+
lang TEXT PRIMARY KEY,
|
|
11428
|
+
family TEXT NOT NULL
|
|
11429
|
+
);
|
|
11430
|
+
`;
|
|
11431
|
+
var LANG_FAMILY_WILDCARD = "*";
|
|
10955
11432
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
10956
11433
|
|
|
10957
11434
|
// src/codebase-index/writer-search-helpers.ts
|
|
@@ -11163,15 +11640,69 @@ var IndexStore = class _IndexStore {
|
|
|
11163
11640
|
}
|
|
11164
11641
|
constructor(projectRoot, opts = {}) {
|
|
11165
11642
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
11166
|
-
|
|
11643
|
+
fs11.mkdirSync(this.indexDir, { recursive: true });
|
|
11167
11644
|
const Database = loadDatabaseSync();
|
|
11168
|
-
this.db = new Database(
|
|
11645
|
+
this.db = new Database(path16.join(this.indexDir, DB_FILE2));
|
|
11169
11646
|
applyIndexStorePragmas(this.db);
|
|
11170
11647
|
this.initSchema();
|
|
11171
11648
|
}
|
|
11172
11649
|
runWithRetry(fn) {
|
|
11173
11650
|
return runSqliteWithRetry(fn);
|
|
11174
11651
|
}
|
|
11652
|
+
/**
|
|
11653
|
+
* Mirror the in-process language→family map into SQLite.
|
|
11654
|
+
*
|
|
11655
|
+
* Rewritten on every open rather than only on schema bumps: the mapping is
|
|
11656
|
+
* static lookup data, so a code-side change (a new language, a language
|
|
11657
|
+
* moving families) must take effect without forcing a full reindex.
|
|
11658
|
+
*/
|
|
11659
|
+
seedLangFamilies() {
|
|
11660
|
+
const insert = this.stmt("INSERT OR REPLACE INTO lang_family(lang, family) VALUES (?, ?)");
|
|
11661
|
+
for (const [lang, family] of LANG_FAMILY_ENTRIES) insert.run(lang, family);
|
|
11662
|
+
insert.run("", LANG_FAMILY_WILDCARD);
|
|
11663
|
+
}
|
|
11664
|
+
/**
|
|
11665
|
+
* Add any column the current schema expects but the on-disk table lacks.
|
|
11666
|
+
*
|
|
11667
|
+
* `CREATE TABLE IF NOT EXISTS` silently keeps an existing table's old shape,
|
|
11668
|
+
* and the version check above only rebuilds on a version *mismatch*. That
|
|
11669
|
+
* leaves a real gap: several wstack processes share this database, and while
|
|
11670
|
+
* a version upgrade is rolling out one of them may still be running the
|
|
11671
|
+
* previous build. That older process sees the newer version number, drops the
|
|
11672
|
+
* tables, and recreates them from *its* DDL — without the newer columns —
|
|
11673
|
+
* while the metadata row still reads the new version. Every later query for
|
|
11674
|
+
* one of those columns then fails with `no such column`, and no amount of
|
|
11675
|
+
* reindexing fixes it, because the version numbers already agree.
|
|
11676
|
+
*
|
|
11677
|
+
* Repairing column-by-column makes the schema self-healing from any of those
|
|
11678
|
+
* states. Table and column names are compile-time literals from this module,
|
|
11679
|
+
* never user input.
|
|
11680
|
+
*/
|
|
11681
|
+
repairMissingColumns() {
|
|
11682
|
+
const expected = [
|
|
11683
|
+
{ table: "files", columns: [["package", "TEXT NOT NULL DEFAULT ''"]] },
|
|
11684
|
+
{
|
|
11685
|
+
table: "refs",
|
|
11686
|
+
columns: [
|
|
11687
|
+
["lang", "TEXT NOT NULL DEFAULT ''"],
|
|
11688
|
+
["module", "TEXT"],
|
|
11689
|
+
["to_file", "TEXT"]
|
|
11690
|
+
]
|
|
11691
|
+
}
|
|
11692
|
+
];
|
|
11693
|
+
for (const { table, columns } of expected) {
|
|
11694
|
+
const present = new Set(
|
|
11695
|
+
this.db.prepare(`PRAGMA table_info(${table})`).all().flatMap(
|
|
11696
|
+
(row) => typeof row.name === "string" ? [row.name] : []
|
|
11697
|
+
)
|
|
11698
|
+
);
|
|
11699
|
+
if (present.size === 0) continue;
|
|
11700
|
+
for (const [name, type] of columns) {
|
|
11701
|
+
if (present.has(name)) continue;
|
|
11702
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${type}`);
|
|
11703
|
+
}
|
|
11704
|
+
}
|
|
11705
|
+
}
|
|
11175
11706
|
initSchema() {
|
|
11176
11707
|
this.db.exec(METADATA_TABLE_SQL);
|
|
11177
11708
|
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
@@ -11194,9 +11725,13 @@ var IndexStore = class _IndexStore {
|
|
|
11194
11725
|
);
|
|
11195
11726
|
}
|
|
11196
11727
|
this.db.exec(CORE_TABLES_SQL);
|
|
11197
|
-
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
11198
11728
|
this.db.exec(REFS_TABLE_SQL);
|
|
11729
|
+
this.repairMissingColumns();
|
|
11730
|
+
for (const sql of FILE_INDEX_SQL) this.db.exec(sql);
|
|
11731
|
+
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
11199
11732
|
for (const sql of REFS_INDEX_SQL) this.db.exec(sql);
|
|
11733
|
+
this.db.exec(LANG_FAMILY_TABLE_SQL);
|
|
11734
|
+
this.seedLangFamilies();
|
|
11200
11735
|
try {
|
|
11201
11736
|
this.db.exec(SYMBOLS_FTS_SQL);
|
|
11202
11737
|
this.ftsAvailable = true;
|
|
@@ -11231,6 +11766,18 @@ var IndexStore = class _IndexStore {
|
|
|
11231
11766
|
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
11232
11767
|
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
11233
11768
|
static MAX_SQL_VARS = 900;
|
|
11769
|
+
/**
|
|
11770
|
+
* Correlated predicate: the ref in `refs` and the candidate symbol aliased
|
|
11771
|
+
* `sym` belong to the same language family — or the ref carries no language,
|
|
11772
|
+
* in which case the wildcard bind matches everything.
|
|
11773
|
+
*
|
|
11774
|
+
* Each textual occurrence consumes one `?` bind of {@link LANG_FAMILY_WILDCARD}.
|
|
11775
|
+
*/
|
|
11776
|
+
static FAMILY_MATCH_SQL = `(
|
|
11777
|
+
(SELECT family FROM lang_family WHERE lang = refs.lang) = ?
|
|
11778
|
+
OR (SELECT family FROM lang_family WHERE lang = sym.lang)
|
|
11779
|
+
= (SELECT family FROM lang_family WHERE lang = refs.lang)
|
|
11780
|
+
)`;
|
|
11234
11781
|
/**
|
|
11235
11782
|
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
11236
11783
|
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
@@ -11294,9 +11841,12 @@ var IndexStore = class _IndexStore {
|
|
|
11294
11841
|
const placeholders = chunk.map(() => "?").join(",");
|
|
11295
11842
|
const result = this.stmt(
|
|
11296
11843
|
`UPDATE refs
|
|
11297
|
-
SET to_id = (
|
|
11844
|
+
SET to_id = (
|
|
11845
|
+
SELECT MIN(sym.id) FROM symbols sym
|
|
11846
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
11847
|
+
)
|
|
11298
11848
|
WHERE to_name IN (${placeholders})`
|
|
11299
|
-
).run(...chunk);
|
|
11849
|
+
).run(LANG_FAMILY_WILDCARD, ...chunk);
|
|
11300
11850
|
changes += result.changes ?? 0;
|
|
11301
11851
|
}
|
|
11302
11852
|
return changes;
|
|
@@ -11423,6 +11973,115 @@ var IndexStore = class _IndexStore {
|
|
|
11423
11973
|
getAllFileMetas() {
|
|
11424
11974
|
return getAllFileMetasWithStatement((sql) => this.stmt(sql));
|
|
11425
11975
|
}
|
|
11976
|
+
// ─── Project structure & module resolution ──────────────────────────────────
|
|
11977
|
+
/** Store the Code Atlas grouping label for each indexed file. */
|
|
11978
|
+
setFilePackages(entries) {
|
|
11979
|
+
if (entries.size === 0) return;
|
|
11980
|
+
this.runWithRetry(() => {
|
|
11981
|
+
const update = this.stmt("UPDATE files SET package = ? WHERE file = ?");
|
|
11982
|
+
for (const [file, label] of entries) update.run(label, file);
|
|
11983
|
+
});
|
|
11984
|
+
}
|
|
11985
|
+
/**
|
|
11986
|
+
* Every indexed `namespace`/`module` declaration, for ecosystems whose import
|
|
11987
|
+
* specifiers name a namespace rather than a path (C#, PHP, Elixir, Haskell).
|
|
11988
|
+
* Ordered so the resolver's choice among duplicate declarations is stable.
|
|
11989
|
+
*/
|
|
11990
|
+
getNamespaceDeclarations() {
|
|
11991
|
+
return this.stmt(
|
|
11992
|
+
`SELECT name, file FROM symbols WHERE kind = 'namespace' ORDER BY file, id`
|
|
11993
|
+
).all();
|
|
11994
|
+
}
|
|
11995
|
+
/** `file → package` for every indexed file that has a label. */
|
|
11996
|
+
getFilePackages() {
|
|
11997
|
+
const rows = this.stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
11998
|
+
return new Map(rows.map((row) => [row.file, row.package]));
|
|
11999
|
+
}
|
|
12000
|
+
/**
|
|
12001
|
+
* Distinct `(fromFile, lang, module)` triples needing module resolution.
|
|
12002
|
+
*
|
|
12003
|
+
* Distinct rather than per-ref because resolution depends only on these three
|
|
12004
|
+
* values: a file importing the same module twenty times resolves it once.
|
|
12005
|
+
*/
|
|
12006
|
+
getUnresolvedImports(onlyFiles) {
|
|
12007
|
+
const base = `SELECT DISTINCT s.file AS fromFile, r.lang AS lang, r.module AS module
|
|
12008
|
+
FROM refs r
|
|
12009
|
+
JOIN symbols s ON s.id = r.from_id
|
|
12010
|
+
WHERE r.call_type = 'import' AND r.module IS NOT NULL`;
|
|
12011
|
+
if (!onlyFiles?.length) {
|
|
12012
|
+
return this.stmt(base).all();
|
|
12013
|
+
}
|
|
12014
|
+
const out = [];
|
|
12015
|
+
for (let i = 0; i < onlyFiles.length; i += _IndexStore.MAX_SQL_VARS) {
|
|
12016
|
+
const chunk = onlyFiles.slice(i, i + _IndexStore.MAX_SQL_VARS);
|
|
12017
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
12018
|
+
out.push(
|
|
12019
|
+
...this.stmt(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
12020
|
+
);
|
|
12021
|
+
}
|
|
12022
|
+
return out;
|
|
12023
|
+
}
|
|
12024
|
+
/**
|
|
12025
|
+
* Write resolved import targets back onto `refs.to_file`.
|
|
12026
|
+
*
|
|
12027
|
+
* Applied through a temp table and a single UPDATE: one statement per
|
|
12028
|
+
* resolution would mean thousands of round-trips on a first index.
|
|
12029
|
+
*/
|
|
12030
|
+
applyImportResolutions(resolutions) {
|
|
12031
|
+
if (resolutions.length === 0) return 0;
|
|
12032
|
+
return this.runWithRetry(() => {
|
|
12033
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
12034
|
+
this.db.exec(
|
|
12035
|
+
`CREATE TEMP TABLE import_resolution (
|
|
12036
|
+
from_file TEXT NOT NULL,
|
|
12037
|
+
lang TEXT NOT NULL,
|
|
12038
|
+
module TEXT NOT NULL,
|
|
12039
|
+
to_file TEXT NOT NULL
|
|
12040
|
+
)`
|
|
12041
|
+
);
|
|
12042
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / 4));
|
|
12043
|
+
for (let i = 0; i < resolutions.length; i += chunkSize) {
|
|
12044
|
+
const chunk = resolutions.slice(i, i + chunkSize);
|
|
12045
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
12046
|
+
const binds = [];
|
|
12047
|
+
for (const entry of chunk) {
|
|
12048
|
+
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
12049
|
+
}
|
|
12050
|
+
this.stmt(
|
|
12051
|
+
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
12052
|
+
VALUES ${placeholders}`
|
|
12053
|
+
).run(...binds);
|
|
12054
|
+
}
|
|
12055
|
+
this.db.exec(
|
|
12056
|
+
`CREATE INDEX IF NOT EXISTS temp.idx_ir
|
|
12057
|
+
ON import_resolution(module, lang, from_file)`
|
|
12058
|
+
);
|
|
12059
|
+
const result = this.stmt(
|
|
12060
|
+
`UPDATE refs
|
|
12061
|
+
SET to_file = (
|
|
12062
|
+
SELECT ir.to_file
|
|
12063
|
+
FROM temp.import_resolution ir
|
|
12064
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
12065
|
+
WHERE ir.module = refs.module
|
|
12066
|
+
AND ir.lang = refs.lang
|
|
12067
|
+
AND ir.from_file = s.file
|
|
12068
|
+
LIMIT 1
|
|
12069
|
+
)
|
|
12070
|
+
WHERE refs.call_type = 'import'
|
|
12071
|
+
AND refs.module IS NOT NULL
|
|
12072
|
+
AND EXISTS (
|
|
12073
|
+
SELECT 1
|
|
12074
|
+
FROM temp.import_resolution ir
|
|
12075
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
12076
|
+
WHERE ir.module = refs.module
|
|
12077
|
+
AND ir.lang = refs.lang
|
|
12078
|
+
AND ir.from_file = s.file
|
|
12079
|
+
)`
|
|
12080
|
+
).run();
|
|
12081
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
12082
|
+
return result.changes ?? 0;
|
|
12083
|
+
});
|
|
12084
|
+
}
|
|
11426
12085
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
11427
12086
|
search(query, filter, opts) {
|
|
11428
12087
|
const built = this.buildSearchWhere(query, filter);
|
|
@@ -11809,9 +12468,12 @@ var IndexStore = class _IndexStore {
|
|
|
11809
12468
|
* Resolve `to_name` → `to_id` for all refs that have a name but no id.
|
|
11810
12469
|
* Call this after all symbols have been inserted to fill in cross-references.
|
|
11811
12470
|
*
|
|
11812
|
-
*
|
|
11813
|
-
* the
|
|
11814
|
-
*
|
|
12471
|
+
* A match additionally requires the referencing ref and the target symbol to
|
|
12472
|
+
* be in the same {@link LangFamily}. Without that guard a name match is a
|
|
12473
|
+
* cross-language accident waiting to happen — `main`, `New`, `Parse` and
|
|
12474
|
+
* `Config` are declared in most languages at once, and each collision draws a
|
|
12475
|
+
* Code Atlas edge between files that never reference each other. Refs stored
|
|
12476
|
+
* without a language keep the old global behaviour via the `'*'` wildcard row.
|
|
11815
12477
|
*/
|
|
11816
12478
|
resolveRefs() {
|
|
11817
12479
|
return this.runWithRetry(() => {
|
|
@@ -11820,20 +12482,35 @@ var IndexStore = class _IndexStore {
|
|
|
11820
12482
|
`UPDATE refs
|
|
11821
12483
|
SET to_id = s.id
|
|
11822
12484
|
FROM (
|
|
11823
|
-
|
|
11824
|
-
|
|
12485
|
+
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
12486
|
+
FROM symbols sym
|
|
12487
|
+
JOIN lang_family lf ON lf.lang = sym.lang
|
|
12488
|
+
GROUP BY sym.name, lf.family
|
|
12489
|
+
UNION ALL
|
|
12490
|
+
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
12491
|
+
FROM symbols sym
|
|
12492
|
+
GROUP BY sym.name
|
|
12493
|
+
) AS s,
|
|
12494
|
+
lang_family AS rf
|
|
11825
12495
|
WHERE refs.to_id IS NULL
|
|
11826
12496
|
AND refs.to_name IS NOT NULL
|
|
11827
|
-
AND
|
|
12497
|
+
AND rf.lang = refs.lang
|
|
12498
|
+
AND s.name = refs.to_name
|
|
12499
|
+
AND s.family = rf.family`
|
|
11828
12500
|
).run();
|
|
11829
12501
|
return result.changes ?? 0;
|
|
11830
12502
|
} catch {
|
|
11831
12503
|
const result = this.stmt(
|
|
11832
12504
|
`UPDATE refs SET to_id = (
|
|
11833
|
-
SELECT id FROM symbols
|
|
12505
|
+
SELECT sym.id FROM symbols sym
|
|
12506
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12507
|
+
ORDER BY sym.id LIMIT 1
|
|
11834
12508
|
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
11835
|
-
AND
|
|
11836
|
-
|
|
12509
|
+
AND EXISTS (
|
|
12510
|
+
SELECT 1 FROM symbols sym
|
|
12511
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12512
|
+
)`
|
|
12513
|
+
).run(LANG_FAMILY_WILDCARD, LANG_FAMILY_WILDCARD);
|
|
11837
12514
|
return result.changes ?? 0;
|
|
11838
12515
|
}
|
|
11839
12516
|
});
|
|
@@ -12022,21 +12699,21 @@ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
|
12022
12699
|
var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
|
|
12023
12700
|
var buildIdCache;
|
|
12024
12701
|
function projectIndexServerBuildId(entrypoint) {
|
|
12025
|
-
const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) :
|
|
12702
|
+
const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path17.resolve(entrypoint);
|
|
12026
12703
|
try {
|
|
12027
|
-
const stat18 =
|
|
12704
|
+
const stat18 = fs12.statSync(file);
|
|
12028
12705
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat18.mtimeMs && buildIdCache.size === stat18.size) {
|
|
12029
12706
|
return buildIdCache.buildId;
|
|
12030
12707
|
}
|
|
12031
|
-
const buildId = createHash4("sha256").update(
|
|
12708
|
+
const buildId = createHash4("sha256").update(fs12.readFileSync(file)).digest("hex").slice(0, 24);
|
|
12032
12709
|
buildIdCache = { file, mtimeMs: stat18.mtimeMs, size: stat18.size, buildId };
|
|
12033
12710
|
return buildId;
|
|
12034
12711
|
} catch {
|
|
12035
|
-
return `unreadable:${
|
|
12712
|
+
return `unreadable:${path17.basename(file)}`;
|
|
12036
12713
|
}
|
|
12037
12714
|
}
|
|
12038
12715
|
function normalizeLocalPath(value) {
|
|
12039
|
-
const resolved =
|
|
12716
|
+
const resolved = path17.resolve(value);
|
|
12040
12717
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12041
12718
|
}
|
|
12042
12719
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -12048,11 +12725,11 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
12048
12725
|
if (process.platform === "win32") {
|
|
12049
12726
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
12050
12727
|
}
|
|
12051
|
-
return
|
|
12728
|
+
return path17.join(os5.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
12052
12729
|
}
|
|
12053
12730
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
12054
|
-
return
|
|
12055
|
-
|
|
12731
|
+
return path17.join(
|
|
12732
|
+
path17.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
12056
12733
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
12057
12734
|
);
|
|
12058
12735
|
}
|
|
@@ -12092,7 +12769,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
12092
12769
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
12093
12770
|
try {
|
|
12094
12771
|
const url = new URL(rel, import.meta.url);
|
|
12095
|
-
if (url.protocol === "file:" &&
|
|
12772
|
+
if (url.protocol === "file:" && fs13.existsSync(fileURLToPath2(url))) {
|
|
12096
12773
|
builtUrl = url;
|
|
12097
12774
|
break;
|
|
12098
12775
|
}
|
|
@@ -12349,7 +13026,7 @@ var ProjectServerConnection = class {
|
|
|
12349
13026
|
currentAuthToken() {
|
|
12350
13027
|
if (this.authToken === void 0) {
|
|
12351
13028
|
try {
|
|
12352
|
-
const raw =
|
|
13029
|
+
const raw = fs13.readFileSync(
|
|
12353
13030
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
12354
13031
|
"utf8"
|
|
12355
13032
|
);
|
|
@@ -12614,7 +13291,7 @@ var ProjectServerConnection = class {
|
|
|
12614
13291
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
12615
13292
|
if (process.platform !== "win32") {
|
|
12616
13293
|
try {
|
|
12617
|
-
|
|
13294
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
12618
13295
|
} catch {
|
|
12619
13296
|
}
|
|
12620
13297
|
}
|
|
@@ -12638,8 +13315,8 @@ var ProjectServerConnection = class {
|
|
|
12638
13315
|
process.kill(pid);
|
|
12639
13316
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
12640
13317
|
try {
|
|
12641
|
-
const metadata = JSON.parse(
|
|
12642
|
-
if (metadata.pid === pid)
|
|
13318
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
13319
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
12643
13320
|
} catch {
|
|
12644
13321
|
}
|
|
12645
13322
|
return true;
|
|
@@ -12709,7 +13386,7 @@ import { Worker } from "node:worker_threads";
|
|
|
12709
13386
|
|
|
12710
13387
|
// src/codebase-index/indexer.ts
|
|
12711
13388
|
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
12712
|
-
import { execFile
|
|
13389
|
+
import { execFile } from "node:child_process";
|
|
12713
13390
|
import * as fs17 from "node:fs/promises";
|
|
12714
13391
|
import { availableParallelism } from "node:os";
|
|
12715
13392
|
import * as path23 from "node:path";
|
|
@@ -12720,8 +13397,8 @@ import {
|
|
|
12720
13397
|
} from "@wrongstack/core/utils";
|
|
12721
13398
|
|
|
12722
13399
|
// src/codebase-index/gitignore.ts
|
|
12723
|
-
import * as
|
|
12724
|
-
import * as
|
|
13400
|
+
import * as fs14 from "node:fs/promises";
|
|
13401
|
+
import * as path18 from "node:path";
|
|
12725
13402
|
import { compileGlob } from "@wrongstack/core/utils";
|
|
12726
13403
|
function globBody(glob) {
|
|
12727
13404
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
@@ -12767,7 +13444,7 @@ function compileGitignore(lines) {
|
|
|
12767
13444
|
async function loadGitignoreMatcher(projectRoot) {
|
|
12768
13445
|
let lines = [];
|
|
12769
13446
|
try {
|
|
12770
|
-
const raw = await
|
|
13447
|
+
const raw = await fs14.readFile(path18.join(projectRoot, ".gitignore"), "utf8");
|
|
12771
13448
|
lines = raw.split("\n");
|
|
12772
13449
|
} catch {
|
|
12773
13450
|
}
|
|
@@ -12777,8 +13454,434 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
12777
13454
|
// src/codebase-index/indexer.ts
|
|
12778
13455
|
init_languages2();
|
|
12779
13456
|
|
|
13457
|
+
// src/codebase-index/module-resolver.ts
|
|
13458
|
+
init_languages2();
|
|
13459
|
+
import * as path19 from "node:path";
|
|
13460
|
+
var EXTENSIONS = {
|
|
13461
|
+
js: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
|
|
13462
|
+
py: [".py", ".pyi"],
|
|
13463
|
+
rs: [".rs"],
|
|
13464
|
+
jvm: [".java", ".kt", ".scala"],
|
|
13465
|
+
c: [".h", ".hpp", ".hh", ".hxx", ".c", ".cpp", ".cc", ".cxx"],
|
|
13466
|
+
ruby: [".rb"],
|
|
13467
|
+
go: [".go"]
|
|
13468
|
+
};
|
|
13469
|
+
var DIRECTORY_ENTRIES = {
|
|
13470
|
+
js: ["index"],
|
|
13471
|
+
py: ["__init__"],
|
|
13472
|
+
rs: ["mod"],
|
|
13473
|
+
ruby: ["index"]
|
|
13474
|
+
};
|
|
13475
|
+
function normalizeNamespace(value) {
|
|
13476
|
+
return value.replace(/::|[\\/]/g, ".").replace(/^\.+|\.+$/g, "").toLowerCase();
|
|
13477
|
+
}
|
|
13478
|
+
var ModuleResolver = class {
|
|
13479
|
+
structure;
|
|
13480
|
+
/** Lowercased portable path → the path as indexed (case is preserved). */
|
|
13481
|
+
byPath;
|
|
13482
|
+
/** Lowercased portable directory → files directly inside it, as indexed. */
|
|
13483
|
+
byDir;
|
|
13484
|
+
/** Normalized namespace → the file declaring it (first by path, stable). */
|
|
13485
|
+
byNamespace;
|
|
13486
|
+
constructor(structure, files, namespaces = []) {
|
|
13487
|
+
this.structure = structure;
|
|
13488
|
+
this.byPath = /* @__PURE__ */ new Map();
|
|
13489
|
+
this.byDir = /* @__PURE__ */ new Map();
|
|
13490
|
+
this.byNamespace = /* @__PURE__ */ new Map();
|
|
13491
|
+
const dirsByKey = /* @__PURE__ */ new Map();
|
|
13492
|
+
for (const file of files) {
|
|
13493
|
+
const portable = toPortablePath(file);
|
|
13494
|
+
const pathKey = portable.toLowerCase();
|
|
13495
|
+
const priorPath = this.byPath.get(pathKey);
|
|
13496
|
+
if (priorPath !== void 0 && priorPath !== file) this.byPath.delete(pathKey);
|
|
13497
|
+
else this.byPath.set(pathKey, file);
|
|
13498
|
+
const dir = path19.posix.dirname(portable);
|
|
13499
|
+
const dirKey = dir.toLowerCase();
|
|
13500
|
+
const knownDir = dirsByKey.get(dirKey);
|
|
13501
|
+
if (knownDir === void 0) {
|
|
13502
|
+
dirsByKey.set(dirKey, dir);
|
|
13503
|
+
this.byDir.set(dirKey, [file]);
|
|
13504
|
+
} else if (knownDir === dir) {
|
|
13505
|
+
this.byDir.get(dirKey)?.push(file);
|
|
13506
|
+
} else {
|
|
13507
|
+
dirsByKey.delete(dirKey);
|
|
13508
|
+
this.byDir.delete(dirKey);
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
for (const { name, file } of namespaces) {
|
|
13512
|
+
const lang = detectLang(file);
|
|
13513
|
+
if (!lang) continue;
|
|
13514
|
+
const key = `${languageFamily(lang)}:${normalizeNamespace(name)}`;
|
|
13515
|
+
if (normalizeNamespace(name) && !this.byNamespace.has(key)) {
|
|
13516
|
+
this.byNamespace.set(key, file);
|
|
13517
|
+
}
|
|
13518
|
+
}
|
|
13519
|
+
}
|
|
13520
|
+
/**
|
|
13521
|
+
* Resolve `specifier` as written in `fromFile`.
|
|
13522
|
+
* Returns the indexed target path, or `undefined` when it is external or
|
|
13523
|
+
* cannot be located.
|
|
13524
|
+
*/
|
|
13525
|
+
resolve(fromFile, lang, specifier) {
|
|
13526
|
+
const spec = specifier.trim().replace(/\\/g, "/");
|
|
13527
|
+
if (!spec) return void 0;
|
|
13528
|
+
const from = toPortablePath(fromFile);
|
|
13529
|
+
switch (languageFamily(lang)) {
|
|
13530
|
+
case "js":
|
|
13531
|
+
return this.resolveJs(from, spec);
|
|
13532
|
+
case "go":
|
|
13533
|
+
return this.resolveGo(spec);
|
|
13534
|
+
case "py":
|
|
13535
|
+
return this.resolvePython(from, spec);
|
|
13536
|
+
case "rs":
|
|
13537
|
+
return this.resolveRust(from, spec);
|
|
13538
|
+
case "jvm":
|
|
13539
|
+
return this.resolveJvm(spec);
|
|
13540
|
+
case "c":
|
|
13541
|
+
return this.resolveInclude(from, spec);
|
|
13542
|
+
case "ruby":
|
|
13543
|
+
return this.resolveRuby(from, spec);
|
|
13544
|
+
case "dotnet":
|
|
13545
|
+
case "php":
|
|
13546
|
+
case "elixir":
|
|
13547
|
+
case "haskell":
|
|
13548
|
+
return this.resolveNamespace(lang, spec);
|
|
13549
|
+
default:
|
|
13550
|
+
return void 0;
|
|
13551
|
+
}
|
|
13552
|
+
}
|
|
13553
|
+
/**
|
|
13554
|
+
* Resolve a namespace specifier to the file declaring it.
|
|
13555
|
+
*
|
|
13556
|
+
* Tried whole first, then with the trailing segment dropped: `using Foo.Bar`
|
|
13557
|
+
* names a namespace outright, while PHP's `use App\Models\User` names a
|
|
13558
|
+
* *class* inside `App\Models`, so the prefix is what was declared.
|
|
13559
|
+
*/
|
|
13560
|
+
resolveNamespace(lang, spec) {
|
|
13561
|
+
const family = languageFamily(lang);
|
|
13562
|
+
const normalized = normalizeNamespace(spec);
|
|
13563
|
+
const exact = this.byNamespace.get(`${family}:${normalized}`);
|
|
13564
|
+
if (exact) return exact;
|
|
13565
|
+
const segments = normalized.split(".").filter(Boolean);
|
|
13566
|
+
if (segments.length < 2) return void 0;
|
|
13567
|
+
return this.byNamespace.get(`${family}:${segments.slice(0, -1).join(".")}`);
|
|
13568
|
+
}
|
|
13569
|
+
// ─── Lookup primitives ──────────────────────────────────────────────────────
|
|
13570
|
+
lookup(candidate) {
|
|
13571
|
+
return this.byPath.get(path19.posix.normalize(candidate).toLowerCase());
|
|
13572
|
+
}
|
|
13573
|
+
/**
|
|
13574
|
+
* Try `base` verbatim, then `base` + each extension, then each directory
|
|
13575
|
+
* entry point inside `base`.
|
|
13576
|
+
*/
|
|
13577
|
+
lookupWithExtensions(base, family) {
|
|
13578
|
+
const direct = this.lookup(base);
|
|
13579
|
+
if (direct) return direct;
|
|
13580
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
13581
|
+
const suffix = path19.posix.extname(base);
|
|
13582
|
+
const stem = suffix && extensions.includes(suffix) ? base.slice(0, -suffix.length) : base;
|
|
13583
|
+
for (const ext of extensions) {
|
|
13584
|
+
const hit = this.lookup(`${stem}${ext}`);
|
|
13585
|
+
if (hit) return hit;
|
|
13586
|
+
}
|
|
13587
|
+
for (const entry of DIRECTORY_ENTRIES[family] ?? []) {
|
|
13588
|
+
for (const ext of extensions) {
|
|
13589
|
+
const hit = this.lookup(path19.posix.join(base, `${entry}${ext}`));
|
|
13590
|
+
if (hit) return hit;
|
|
13591
|
+
}
|
|
13592
|
+
}
|
|
13593
|
+
return void 0;
|
|
13594
|
+
}
|
|
13595
|
+
/**
|
|
13596
|
+
* A representative indexed file inside `dir`, for ecosystems whose import
|
|
13597
|
+
* unit is a directory rather than a file (Go packages, JVM wildcard imports).
|
|
13598
|
+
*
|
|
13599
|
+
* The choice is deterministic — a file named after the directory, else the
|
|
13600
|
+
* first by name — so the same import always produces the same edge. Package
|
|
13601
|
+
* grouping is unaffected either way: every file in the directory carries the
|
|
13602
|
+
* same package label, so the package-level edge is exact regardless of which
|
|
13603
|
+
* member represents it.
|
|
13604
|
+
*/
|
|
13605
|
+
representativeIn(dir, family) {
|
|
13606
|
+
const members = this.byDir.get(path19.posix.normalize(dir).toLowerCase());
|
|
13607
|
+
if (!members?.length) return void 0;
|
|
13608
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
13609
|
+
const eligible = members.filter((file) => extensions.includes(path19.posix.extname(toPortablePath(file)).toLowerCase())).sort((a, b) => toPortablePath(a).localeCompare(toPortablePath(b)));
|
|
13610
|
+
if (eligible.length === 0) return void 0;
|
|
13611
|
+
const base = path19.posix.basename(path19.posix.normalize(dir)).toLowerCase();
|
|
13612
|
+
const named = eligible.find(
|
|
13613
|
+
(file) => path19.posix.basename(toPortablePath(file)).split(".")[0]?.toLowerCase() === base
|
|
13614
|
+
);
|
|
13615
|
+
return named ?? eligible[0];
|
|
13616
|
+
}
|
|
13617
|
+
// ─── Per-family resolution ──────────────────────────────────────────────────
|
|
13618
|
+
/** Relative specifiers, then workspace package names and their subpaths. */
|
|
13619
|
+
resolveJs(fromFile, spec) {
|
|
13620
|
+
if (spec.startsWith(".")) {
|
|
13621
|
+
const absolute = path19.posix.join(path19.posix.dirname(fromFile), spec);
|
|
13622
|
+
return this.lookupWithExtensions(absolute, "js");
|
|
13623
|
+
}
|
|
13624
|
+
const owner2 = this.structure.roots.find(
|
|
13625
|
+
(root) => root.kind === "npm" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
13626
|
+
);
|
|
13627
|
+
if (!owner2?.importPath) return void 0;
|
|
13628
|
+
const subpath = spec.slice(owner2.importPath.length).replace(/^\//, "");
|
|
13629
|
+
if (!subpath) {
|
|
13630
|
+
return this.lookupWithExtensions(path19.posix.join(owner2.dir, "src/index"), "js") ?? this.lookupWithExtensions(path19.posix.join(owner2.dir, "index"), "js");
|
|
13631
|
+
}
|
|
13632
|
+
return this.lookupWithExtensions(path19.posix.join(owner2.dir, subpath), "js") ?? this.lookupWithExtensions(path19.posix.join(owner2.dir, "src", subpath), "js");
|
|
13633
|
+
}
|
|
13634
|
+
/** Go import paths are absolute module paths; a package is a directory. */
|
|
13635
|
+
resolveGo(spec) {
|
|
13636
|
+
const owner2 = this.structure.roots.find(
|
|
13637
|
+
(root) => root.kind === "go" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
13638
|
+
);
|
|
13639
|
+
if (!owner2?.importPath) return void 0;
|
|
13640
|
+
const subpath = spec.slice(owner2.importPath.length).replace(/^\//, "");
|
|
13641
|
+
return this.representativeIn(path19.posix.join(owner2.dir, subpath), "go");
|
|
13642
|
+
}
|
|
13643
|
+
/**
|
|
13644
|
+
* `import a.b.c` / `from a.b import c`, plus PEP 328 relative imports whose
|
|
13645
|
+
* leading dots the extractor preserves (`.sibling`, `..parent.mod`).
|
|
13646
|
+
*/
|
|
13647
|
+
resolvePython(fromFile, spec) {
|
|
13648
|
+
const leadingDots = /^\.*/.exec(spec)?.[0].length ?? 0;
|
|
13649
|
+
if (leadingDots > 0) {
|
|
13650
|
+
let base = path19.posix.dirname(fromFile);
|
|
13651
|
+
for (let i = 1; i < leadingDots; i++) base = path19.posix.dirname(base);
|
|
13652
|
+
const rest = spec.slice(leadingDots).split(".").filter(Boolean);
|
|
13653
|
+
return this.lookupWithExtensions(path19.posix.join(base, ...rest), "py");
|
|
13654
|
+
}
|
|
13655
|
+
const segments = spec.split(".").filter(Boolean);
|
|
13656
|
+
if (segments.length === 0) return void 0;
|
|
13657
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "python").flatMap((root) => root.sourceRoots);
|
|
13658
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
13659
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, ...segments), "py");
|
|
13660
|
+
if (hit) return hit;
|
|
13661
|
+
if (segments.length > 1) {
|
|
13662
|
+
const parent = this.lookupWithExtensions(
|
|
13663
|
+
path19.posix.join(base, ...segments.slice(0, -1)),
|
|
13664
|
+
"py"
|
|
13665
|
+
);
|
|
13666
|
+
if (parent) return parent;
|
|
13667
|
+
}
|
|
13668
|
+
}
|
|
13669
|
+
return void 0;
|
|
13670
|
+
}
|
|
13671
|
+
/** `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`. */
|
|
13672
|
+
resolveRust(fromFile, spec) {
|
|
13673
|
+
const segments = spec.split("::").filter(Boolean);
|
|
13674
|
+
if (segments.length === 0) return void 0;
|
|
13675
|
+
const head = segments[0];
|
|
13676
|
+
if (head === "self" || head === "super") {
|
|
13677
|
+
let base = path19.posix.dirname(fromFile);
|
|
13678
|
+
for (const segment of segments) {
|
|
13679
|
+
if (segment === "super") base = path19.posix.dirname(base);
|
|
13680
|
+
else if (segment !== "self") break;
|
|
13681
|
+
}
|
|
13682
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
13683
|
+
return this.lookupWithExtensions(path19.posix.join(base, ...rest2), "rs");
|
|
13684
|
+
}
|
|
13685
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
13686
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
13687
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
13688
|
+
);
|
|
13689
|
+
if (!crate) {
|
|
13690
|
+
return this.lookupWithExtensions(
|
|
13691
|
+
path19.posix.join(path19.posix.dirname(fromFile), ...segments),
|
|
13692
|
+
"rs"
|
|
13693
|
+
);
|
|
13694
|
+
}
|
|
13695
|
+
const rest = segments.slice(1);
|
|
13696
|
+
for (const base of crate.sourceRoots) {
|
|
13697
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path19.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
13698
|
+
const exact = this.lookupWithExtensions(path19.posix.join(base, ...rest), "rs");
|
|
13699
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path19.posix.join(base, "lib"), "rs");
|
|
13700
|
+
if (hit) return hit;
|
|
13701
|
+
}
|
|
13702
|
+
return void 0;
|
|
13703
|
+
}
|
|
13704
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
13705
|
+
resolveJvm(spec) {
|
|
13706
|
+
const segments = spec.split(".").filter(Boolean);
|
|
13707
|
+
if (segments.length === 0) return void 0;
|
|
13708
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
13709
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
13710
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
13711
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
13712
|
+
const target = path19.posix.join(base, ...parts);
|
|
13713
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
13714
|
+
if (hit) return hit;
|
|
13715
|
+
}
|
|
13716
|
+
return void 0;
|
|
13717
|
+
}
|
|
13718
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
13719
|
+
resolveInclude(fromFile, spec) {
|
|
13720
|
+
const relative12 = this.lookupWithExtensions(
|
|
13721
|
+
path19.posix.join(path19.posix.dirname(fromFile), spec),
|
|
13722
|
+
"c"
|
|
13723
|
+
);
|
|
13724
|
+
if (relative12) return relative12;
|
|
13725
|
+
for (const base of [
|
|
13726
|
+
path19.posix.join(this.structure.projectRoot, "include"),
|
|
13727
|
+
this.structure.projectRoot
|
|
13728
|
+
]) {
|
|
13729
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, spec), "c");
|
|
13730
|
+
if (hit) return hit;
|
|
13731
|
+
}
|
|
13732
|
+
return void 0;
|
|
13733
|
+
}
|
|
13734
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
13735
|
+
resolveRuby(fromFile, spec) {
|
|
13736
|
+
const relative12 = this.lookupWithExtensions(
|
|
13737
|
+
path19.posix.join(path19.posix.dirname(fromFile), spec),
|
|
13738
|
+
"ruby"
|
|
13739
|
+
);
|
|
13740
|
+
if (relative12) return relative12;
|
|
13741
|
+
for (const base of [
|
|
13742
|
+
path19.posix.join(this.structure.projectRoot, "lib"),
|
|
13743
|
+
this.structure.projectRoot
|
|
13744
|
+
]) {
|
|
13745
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, spec), "ruby");
|
|
13746
|
+
if (hit) return hit;
|
|
13747
|
+
}
|
|
13748
|
+
return void 0;
|
|
13749
|
+
}
|
|
13750
|
+
};
|
|
13751
|
+
|
|
13752
|
+
// src/codebase-index/import-extractor.ts
|
|
13753
|
+
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
13754
|
+
var IMPORT_MAX_PER_FILE = 400;
|
|
13755
|
+
var DOTTED_IMPORT = [
|
|
13756
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
13757
|
+
];
|
|
13758
|
+
var LANG_IMPORTS = {
|
|
13759
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
13760
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
13761
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
13762
|
+
go: [
|
|
13763
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
13764
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
13765
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
13766
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
13767
|
+
],
|
|
13768
|
+
py: [
|
|
13769
|
+
{ re: /^[ \t]*import\s+([\w.]+)/gm },
|
|
13770
|
+
{ re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }
|
|
13771
|
+
],
|
|
13772
|
+
rs: [
|
|
13773
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
13774
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
13775
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
13776
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
13777
|
+
],
|
|
13778
|
+
java: DOTTED_IMPORT,
|
|
13779
|
+
kotlin: DOTTED_IMPORT,
|
|
13780
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
13781
|
+
csharp: [
|
|
13782
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
13783
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
13784
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
13785
|
+
],
|
|
13786
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
13787
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
13788
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
13789
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
13790
|
+
php: [
|
|
13791
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
13792
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
13793
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
13794
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
13795
|
+
],
|
|
13796
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
13797
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
13798
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
13799
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
13800
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
13801
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
13802
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
13803
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
13804
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
13805
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
13806
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
13807
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
13808
|
+
html: [
|
|
13809
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
13810
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
13811
|
+
],
|
|
13812
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
13813
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
13814
|
+
};
|
|
13815
|
+
function lastSegment(specifier) {
|
|
13816
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
13817
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
13818
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
13819
|
+
if (last === "*" || last === "_") {
|
|
13820
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
13821
|
+
}
|
|
13822
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
13823
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
13824
|
+
return dotted[dotted.length - 1] ?? last;
|
|
13825
|
+
}
|
|
13826
|
+
function newlineOffsets(content) {
|
|
13827
|
+
const offsets = [];
|
|
13828
|
+
for (let i = 0; i < content.length; i++) {
|
|
13829
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
13830
|
+
}
|
|
13831
|
+
return offsets;
|
|
13832
|
+
}
|
|
13833
|
+
function lineAt(offsets, index) {
|
|
13834
|
+
let low = 0;
|
|
13835
|
+
let high = offsets.length;
|
|
13836
|
+
while (low < high) {
|
|
13837
|
+
const mid = low + high >>> 1;
|
|
13838
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
13839
|
+
else high = mid;
|
|
13840
|
+
}
|
|
13841
|
+
return low + 1;
|
|
13842
|
+
}
|
|
13843
|
+
function hasImportPatterns(lang) {
|
|
13844
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
13845
|
+
}
|
|
13846
|
+
function extractImports(opts) {
|
|
13847
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
13848
|
+
if (!patterns || !opts.content) return [];
|
|
13849
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
13850
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
13851
|
+
const refs = [];
|
|
13852
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13853
|
+
const offsets = newlineOffsets(content);
|
|
13854
|
+
for (const pattern of patterns) {
|
|
13855
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
13856
|
+
for (const match of content.matchAll(re)) {
|
|
13857
|
+
if (refs.length >= limit) return refs;
|
|
13858
|
+
const specifier = match[1]?.trim();
|
|
13859
|
+
if (!specifier) continue;
|
|
13860
|
+
const module = specifier;
|
|
13861
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
13862
|
+
if (!toName) continue;
|
|
13863
|
+
const key = `${module}\0${toName}`;
|
|
13864
|
+
if (seen.has(key)) continue;
|
|
13865
|
+
seen.add(key);
|
|
13866
|
+
refs.push({
|
|
13867
|
+
fromId: 0,
|
|
13868
|
+
toName,
|
|
13869
|
+
callType: "import",
|
|
13870
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
13871
|
+
lang: opts.lang,
|
|
13872
|
+
module
|
|
13873
|
+
});
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
return refs;
|
|
13877
|
+
}
|
|
13878
|
+
|
|
12780
13879
|
// src/codebase-index/parser-dispatch.ts
|
|
12781
13880
|
async function parseFileContent(file, content, lang) {
|
|
13881
|
+
const parsed = await dispatch(file, content, lang);
|
|
13882
|
+
return withRelations(parsed, content, lang);
|
|
13883
|
+
}
|
|
13884
|
+
async function dispatch(file, content, lang) {
|
|
12782
13885
|
switch (lang) {
|
|
12783
13886
|
case "ts":
|
|
12784
13887
|
case "tsx":
|
|
@@ -12813,6 +13916,13 @@ async function parseFileContent(file, content, lang) {
|
|
|
12813
13916
|
}
|
|
12814
13917
|
}
|
|
12815
13918
|
}
|
|
13919
|
+
function withRelations(parsed, content, lang) {
|
|
13920
|
+
let refs = parsed.refs ?? [];
|
|
13921
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
13922
|
+
refs = extractImports({ content, lang });
|
|
13923
|
+
}
|
|
13924
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
13925
|
+
}
|
|
12816
13926
|
|
|
12817
13927
|
// src/codebase-index/indexer.ts
|
|
12818
13928
|
var YIELD_EVERY_N = 50;
|
|
@@ -12849,7 +13959,7 @@ function normalizeComparablePath(value) {
|
|
|
12849
13959
|
}
|
|
12850
13960
|
function gitOutput(projectRoot, args) {
|
|
12851
13961
|
return new Promise((resolve16, reject) => {
|
|
12852
|
-
|
|
13962
|
+
execFile(
|
|
12853
13963
|
"git",
|
|
12854
13964
|
["-C", projectRoot, ...args],
|
|
12855
13965
|
{
|
|
@@ -12980,13 +14090,40 @@ function assignRefsToSymbols2(refs, symbols) {
|
|
|
12980
14090
|
}
|
|
12981
14091
|
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
12982
14092
|
if (!owner2 || owner2.id <= 0) continue;
|
|
12983
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
14093
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
12984
14094
|
if (seen.has(key)) continue;
|
|
12985
14095
|
seen.add(key);
|
|
12986
14096
|
assigned.push({ ...ref, fromId: owner2.id });
|
|
12987
14097
|
}
|
|
12988
14098
|
return assigned;
|
|
12989
14099
|
}
|
|
14100
|
+
async function resolveProjectRelations(store, projectRoot, opts) {
|
|
14101
|
+
if (opts.signal?.aborted) return;
|
|
14102
|
+
try {
|
|
14103
|
+
const indexedFiles = store.getAllFileMetas().map((meta) => meta.file);
|
|
14104
|
+
if (indexedFiles.length === 0) return;
|
|
14105
|
+
const structure = await detectModuleRoots(projectRoot, indexedFiles);
|
|
14106
|
+
if (opts.signal?.aborted) return;
|
|
14107
|
+
store.setFilePackages(assignPackageLabels(structure, indexedFiles));
|
|
14108
|
+
const resolver = new ModuleResolver(
|
|
14109
|
+
structure,
|
|
14110
|
+
indexedFiles,
|
|
14111
|
+
store.getNamespaceDeclarations()
|
|
14112
|
+
);
|
|
14113
|
+
const pending2 = store.getUnresolvedImports(opts.onlyFiles);
|
|
14114
|
+
const resolutions = [];
|
|
14115
|
+
for (const entry of pending2) {
|
|
14116
|
+
const toFile = resolver.resolve(entry.fromFile, entry.lang, entry.module);
|
|
14117
|
+
if (toFile && toFile !== entry.fromFile) {
|
|
14118
|
+
resolutions.push({ ...entry, toFile });
|
|
14119
|
+
}
|
|
14120
|
+
}
|
|
14121
|
+
if (opts.signal?.aborted) return;
|
|
14122
|
+
store.applyImportResolutions(resolutions);
|
|
14123
|
+
} catch (err) {
|
|
14124
|
+
opts.errors.push(`relation resolution: ${err instanceof Error ? err.message : String(err)}`);
|
|
14125
|
+
}
|
|
14126
|
+
}
|
|
12990
14127
|
async function runIndexerWithStore(store, opts) {
|
|
12991
14128
|
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
12992
14129
|
const relationGraphVersion = "2";
|
|
@@ -13231,6 +14368,14 @@ async function runIndexerWithStore(store, opts) {
|
|
|
13231
14368
|
}
|
|
13232
14369
|
}
|
|
13233
14370
|
if (needsFullRefResolution) store.resolveRefs();
|
|
14371
|
+
await resolveProjectRelations(store, projectRoot, {
|
|
14372
|
+
// A watcher run re-resolves only what it touched; a full run (or a contract
|
|
14373
|
+
// bump) re-resolves everything, because a newly indexed file can be the
|
|
14374
|
+
// target of imports written long before it.
|
|
14375
|
+
onlyFiles: needsFullRefResolution ? void 0 : opts.files,
|
|
14376
|
+
errors,
|
|
14377
|
+
signal
|
|
14378
|
+
});
|
|
13234
14379
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
13235
14380
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
13236
14381
|
if (!opts.files || filesIndexed >= 50) store.optimize();
|
|
@@ -14637,17 +15782,17 @@ import {
|
|
|
14637
15782
|
} from "@wrongstack/core/design";
|
|
14638
15783
|
async function resolveReal(p) {
|
|
14639
15784
|
const resolved = path25.resolve(p);
|
|
14640
|
-
let
|
|
15785
|
+
let probe = resolved;
|
|
14641
15786
|
const missing = [];
|
|
14642
15787
|
for (; ; ) {
|
|
14643
15788
|
try {
|
|
14644
|
-
return path25.resolve(await fs20.realpath(
|
|
15789
|
+
return path25.resolve(await fs20.realpath(probe), ...missing);
|
|
14645
15790
|
} catch (err) {
|
|
14646
15791
|
if (err.code === "ENOENT") {
|
|
14647
|
-
const parent = path25.dirname(
|
|
14648
|
-
if (parent ===
|
|
14649
|
-
missing.unshift(path25.basename(
|
|
14650
|
-
|
|
15792
|
+
const parent = path25.dirname(probe);
|
|
15793
|
+
if (parent === probe) return resolved;
|
|
15794
|
+
missing.unshift(path25.basename(probe));
|
|
15795
|
+
probe = parent;
|
|
14651
15796
|
continue;
|
|
14652
15797
|
}
|
|
14653
15798
|
return resolved;
|
|
@@ -14922,7 +16067,7 @@ Replace off-palette colors with kit tokens (or the materialized CSS vars / token
|
|
|
14922
16067
|
|
|
14923
16068
|
// src/diff.ts
|
|
14924
16069
|
init_util();
|
|
14925
|
-
import { spawn as
|
|
16070
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
14926
16071
|
import { statSync as statSync3 } from "node:fs";
|
|
14927
16072
|
import * as fs21 from "node:fs/promises";
|
|
14928
16073
|
import * as path26 from "node:path";
|
|
@@ -15025,7 +16170,7 @@ function runGit(args, cwd, signal) {
|
|
|
15025
16170
|
return new Promise((resolve16) => {
|
|
15026
16171
|
let stdout = "";
|
|
15027
16172
|
let stderr = "";
|
|
15028
|
-
const child =
|
|
16173
|
+
const child = spawn7("git", args, {
|
|
15029
16174
|
cwd,
|
|
15030
16175
|
signal,
|
|
15031
16176
|
env: buildChildEnv3(),
|
|
@@ -15236,7 +16381,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
15236
16381
|
|
|
15237
16382
|
// src/e2e.ts
|
|
15238
16383
|
init_util();
|
|
15239
|
-
import { open, readdir as
|
|
16384
|
+
import { open, readdir as readdir7 } from "node:fs/promises";
|
|
15240
16385
|
import * as path27 from "node:path";
|
|
15241
16386
|
async function readBoundedText(filePath, maxBytes) {
|
|
15242
16387
|
let handle;
|
|
@@ -15354,7 +16499,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
15354
16499
|
}
|
|
15355
16500
|
let entries;
|
|
15356
16501
|
try {
|
|
15357
|
-
entries = await
|
|
16502
|
+
entries = await readdir7(current.directory, { withFileTypes: true });
|
|
15358
16503
|
} catch {
|
|
15359
16504
|
continue;
|
|
15360
16505
|
}
|
|
@@ -15413,7 +16558,7 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
15413
16558
|
while (true) {
|
|
15414
16559
|
const names = /* @__PURE__ */ new Set();
|
|
15415
16560
|
try {
|
|
15416
|
-
for (const entry of await
|
|
16561
|
+
for (const entry of await readdir7(directory)) names.add(entry);
|
|
15417
16562
|
} catch {
|
|
15418
16563
|
}
|
|
15419
16564
|
if (names.has("pnpm-lock.yaml")) return "pnpm";
|
|
@@ -15481,7 +16626,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
15481
16626
|
if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
|
|
15482
16627
|
let entries;
|
|
15483
16628
|
try {
|
|
15484
|
-
entries = await
|
|
16629
|
+
entries = await readdir7(directory, { withFileTypes: true });
|
|
15485
16630
|
} catch {
|
|
15486
16631
|
continue;
|
|
15487
16632
|
}
|
|
@@ -15682,7 +16827,7 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
15682
16827
|
const exact = [];
|
|
15683
16828
|
let idx = fileLf.indexOf(oldLf);
|
|
15684
16829
|
while (idx !== -1) {
|
|
15685
|
-
exact.push({ start: idx, end: idx + oldLf.length, startLine:
|
|
16830
|
+
exact.push({ start: idx, end: idx + oldLf.length, startLine: lineAt2(fileLf, idx) });
|
|
15686
16831
|
idx = fileLf.indexOf(oldLf, idx + 1);
|
|
15687
16832
|
}
|
|
15688
16833
|
if (exact.length > 0) return { tier: "exact", matches: exact };
|
|
@@ -15714,7 +16859,7 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
15714
16859
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
15715
16860
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
15716
16861
|
}
|
|
15717
|
-
function
|
|
16862
|
+
function lineAt2(text, pos) {
|
|
15718
16863
|
if (pos < 512) {
|
|
15719
16864
|
let line2 = 1;
|
|
15720
16865
|
for (let i = 0; i < pos; i++) {
|
|
@@ -16176,7 +17321,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
16176
17321
|
};
|
|
16177
17322
|
|
|
16178
17323
|
// src/exec.ts
|
|
16179
|
-
import { spawn as
|
|
17324
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
16180
17325
|
import {
|
|
16181
17326
|
emitProcessCompleted as emitProcessCompleted3,
|
|
16182
17327
|
emitProcessOutput as emitProcessOutput3,
|
|
@@ -17393,6 +18538,26 @@ var BLOCKED_ARG_PATTERNS = {
|
|
|
17393
18538
|
pnpm: [],
|
|
17394
18539
|
npx: []
|
|
17395
18540
|
};
|
|
18541
|
+
var BLOCKED_OPTION_NAMES = {
|
|
18542
|
+
git: /* @__PURE__ */ new Set([
|
|
18543
|
+
"--exec",
|
|
18544
|
+
"--upload-pack",
|
|
18545
|
+
"--receive-pack",
|
|
18546
|
+
"--exec-path",
|
|
18547
|
+
"--git-dir",
|
|
18548
|
+
"--work-tree",
|
|
18549
|
+
"--namespace",
|
|
18550
|
+
"-c",
|
|
18551
|
+
"--config",
|
|
18552
|
+
"--config-env",
|
|
18553
|
+
"-C"
|
|
18554
|
+
]),
|
|
18555
|
+
find: /* @__PURE__ */ new Set(["-exec", "-ok", "-execdir"])
|
|
18556
|
+
};
|
|
18557
|
+
function optionName(arg) {
|
|
18558
|
+
const eq = arg.indexOf("=");
|
|
18559
|
+
return eq > 0 ? arg.slice(0, eq) : arg;
|
|
18560
|
+
}
|
|
17396
18561
|
var BLOCKED_SUBCOMMANDS = {
|
|
17397
18562
|
docker: /* @__PURE__ */ new Set(["push"]),
|
|
17398
18563
|
podman: /* @__PURE__ */ new Set(["push"]),
|
|
@@ -17430,6 +18595,15 @@ function validateArgs(cmd, args) {
|
|
|
17430
18595
|
const blocked2 = blockedSequences.find((seq) => seq.every((part, idx) => actual[idx] === part));
|
|
17431
18596
|
if (blocked2) return `Blocked subcommand "${blocked2.join(" ")}" for command "${cmd}"`;
|
|
17432
18597
|
}
|
|
18598
|
+
const blockedOptions = BLOCKED_OPTION_NAMES[cmd];
|
|
18599
|
+
if (blockedOptions) {
|
|
18600
|
+
for (const arg of args) {
|
|
18601
|
+
if (arg === "--") break;
|
|
18602
|
+
if (blockedOptions.has(optionName(arg))) {
|
|
18603
|
+
return `Blocked option "${optionName(arg)}" for command "${cmd}"`;
|
|
18604
|
+
}
|
|
18605
|
+
}
|
|
18606
|
+
}
|
|
17433
18607
|
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
17434
18608
|
if (!blocked) return null;
|
|
17435
18609
|
for (const arg of args) {
|
|
@@ -17612,7 +18786,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
17612
18786
|
};
|
|
17613
18787
|
let child;
|
|
17614
18788
|
try {
|
|
17615
|
-
child =
|
|
18789
|
+
child = spawn8(spawnCmd, spawnArgs, {
|
|
17616
18790
|
cwd,
|
|
17617
18791
|
env: buildChildEnv2(sessionId),
|
|
17618
18792
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -18246,7 +19420,7 @@ async function detectFixer(cwd) {
|
|
|
18246
19420
|
|
|
18247
19421
|
// src/git.ts
|
|
18248
19422
|
init_util();
|
|
18249
|
-
import { spawn as
|
|
19423
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
18250
19424
|
import { statSync as statSync4 } from "node:fs";
|
|
18251
19425
|
import { dirname as dirname14, resolve as resolve13, sep as sep6 } from "node:path";
|
|
18252
19426
|
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
@@ -18510,7 +19684,7 @@ function runGit2(args, cwd, signal) {
|
|
|
18510
19684
|
return new Promise((resolve16) => {
|
|
18511
19685
|
let stdout = "";
|
|
18512
19686
|
let stderr = "";
|
|
18513
|
-
const child =
|
|
19687
|
+
const child = spawn9("git", args, {
|
|
18514
19688
|
cwd,
|
|
18515
19689
|
signal,
|
|
18516
19690
|
env: buildChildEnv4(),
|
|
@@ -18701,7 +19875,7 @@ var globTool = {
|
|
|
18701
19875
|
};
|
|
18702
19876
|
|
|
18703
19877
|
// src/grep.ts
|
|
18704
|
-
import { spawn as
|
|
19878
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
18705
19879
|
import * as fs25 from "node:fs/promises";
|
|
18706
19880
|
import * as path31 from "node:path";
|
|
18707
19881
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
@@ -18858,7 +20032,7 @@ var grepTool = {
|
|
|
18858
20032
|
async function detectRg(signal) {
|
|
18859
20033
|
return new Promise((resolve16) => {
|
|
18860
20034
|
try {
|
|
18861
|
-
const p =
|
|
20035
|
+
const p = spawn10("rg", ["--version"], { env: buildChildEnv5(), stdio: "ignore", signal, windowsHide: true });
|
|
18862
20036
|
p.on("error", () => resolve16(false));
|
|
18863
20037
|
p.on("close", (code) => resolve16(code === 0));
|
|
18864
20038
|
} catch {
|
|
@@ -18892,7 +20066,7 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
18892
20066
|
const FLUSH_AT = 16;
|
|
18893
20067
|
const MAX_BUF_BYTES = 1e6;
|
|
18894
20068
|
let bufOverflow = false;
|
|
18895
|
-
const child =
|
|
20069
|
+
const child = spawn10("rg", args, {
|
|
18896
20070
|
signal,
|
|
18897
20071
|
env: buildChildEnv5(),
|
|
18898
20072
|
// rg diagnostics are not part of the tool result. Ignoring stderr avoids
|
|
@@ -19189,7 +20363,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
19189
20363
|
init_spawn_stream();
|
|
19190
20364
|
init_util();
|
|
19191
20365
|
init_legacy_bridge();
|
|
19192
|
-
import { join as
|
|
20366
|
+
import { join as join24 } from "node:path";
|
|
19193
20367
|
import {
|
|
19194
20368
|
detectEcosystem as detectPackageEcosystem,
|
|
19195
20369
|
recordPackageAction
|
|
@@ -19373,17 +20547,17 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
19373
20547
|
case "pnpm":
|
|
19374
20548
|
case "yarn":
|
|
19375
20549
|
case "npm":
|
|
19376
|
-
return
|
|
20550
|
+
return join24(cwd, "package.json");
|
|
19377
20551
|
/* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
|
|
19378
20552
|
default:
|
|
19379
|
-
return
|
|
20553
|
+
return join24(cwd, "package.json");
|
|
19380
20554
|
}
|
|
19381
20555
|
}
|
|
19382
20556
|
|
|
19383
20557
|
// src/json.ts
|
|
19384
|
-
init_util();
|
|
19385
20558
|
import * as fs26 from "node:fs/promises";
|
|
19386
20559
|
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
20560
|
+
init_util();
|
|
19387
20561
|
var MAX_JSON_FILE_BYTES = 16 * 1024 * 1024;
|
|
19388
20562
|
var MAX_JSON_FILE_BYTES_HUMAN = "16 MiB";
|
|
19389
20563
|
var JsonFileTooLargeError = class extends Error {
|
|
@@ -19822,8 +20996,12 @@ function validateJsonSchema(data, schema) {
|
|
|
19822
20996
|
}
|
|
19823
20997
|
}
|
|
19824
20998
|
if (typeof value === "string" && s["pattern"]) {
|
|
19825
|
-
const
|
|
19826
|
-
if (!
|
|
20999
|
+
const compiled = compileUserRegex(s["pattern"], "");
|
|
21000
|
+
if (!compiled.ok) {
|
|
21001
|
+
errors.push(`${path38}: invalid schema pattern \u2014 ${compiled.reason}`);
|
|
21002
|
+
} else if (!compiled.regex.test(capSubject(value))) {
|
|
21003
|
+
errors.push(`${path38}: does not match pattern ${s["pattern"]}`);
|
|
21004
|
+
}
|
|
19827
21005
|
}
|
|
19828
21006
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
19829
21007
|
errors.push(`${path38}: string too short (min ${s["minLength"]})`);
|
|
@@ -21808,7 +22986,7 @@ async function detectLinter(cwd) {
|
|
|
21808
22986
|
}
|
|
21809
22987
|
|
|
21810
22988
|
// src/logs.ts
|
|
21811
|
-
import { spawn as
|
|
22989
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
21812
22990
|
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
21813
22991
|
init_util();
|
|
21814
22992
|
var logsTool = {
|
|
@@ -21915,7 +23093,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
21915
23093
|
clearTimeout(timer);
|
|
21916
23094
|
resolve16(result);
|
|
21917
23095
|
};
|
|
21918
|
-
const child =
|
|
23096
|
+
const child = spawn11("docker", args, { cwd, signal, env: buildChildEnv6(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
21919
23097
|
const timer = setTimeout(() => {
|
|
21920
23098
|
child.kill("SIGTERM");
|
|
21921
23099
|
finish(empty());
|
|
@@ -22024,7 +23202,7 @@ function parseLine(line) {
|
|
|
22024
23202
|
// src/outdated.ts
|
|
22025
23203
|
init_util();
|
|
22026
23204
|
init_win32_resolve();
|
|
22027
|
-
import { spawn as
|
|
23205
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
22028
23206
|
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
22029
23207
|
var outdatedTool = {
|
|
22030
23208
|
name: "outdated",
|
|
@@ -22144,7 +23322,7 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
22144
23322
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
22145
23323
|
const spawnCmd = shim?.command ?? resolved;
|
|
22146
23324
|
const spawnArgs = shim?.args ?? args;
|
|
22147
|
-
const child =
|
|
23325
|
+
const child = spawn12(spawnCmd, spawnArgs, {
|
|
22148
23326
|
cwd,
|
|
22149
23327
|
signal,
|
|
22150
23328
|
env: buildChildEnv7(),
|
|
@@ -22210,7 +23388,7 @@ function parseOutdatedOutput(json2, exitCode) {
|
|
|
22210
23388
|
|
|
22211
23389
|
// src/patch.ts
|
|
22212
23390
|
init_util();
|
|
22213
|
-
import { spawn as
|
|
23391
|
+
import { spawn as spawn13 } from "node:child_process";
|
|
22214
23392
|
import * as fs27 from "node:fs/promises";
|
|
22215
23393
|
import * as os9 from "node:os";
|
|
22216
23394
|
import * as path32 from "node:path";
|
|
@@ -22352,7 +23530,7 @@ function runPatch(args, cwd, signal) {
|
|
|
22352
23530
|
let stdout = "";
|
|
22353
23531
|
let stderr = "";
|
|
22354
23532
|
const env = { ...buildChildEnv8(), LANG: "C", LC_ALL: "C" };
|
|
22355
|
-
const child =
|
|
23533
|
+
const child = spawn13("patch", args, {
|
|
22356
23534
|
cwd,
|
|
22357
23535
|
signal,
|
|
22358
23536
|
env,
|
|
@@ -22949,7 +24127,7 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
22949
24127
|
}
|
|
22950
24128
|
|
|
22951
24129
|
// src/replace.ts
|
|
22952
|
-
import { spawn as
|
|
24130
|
+
import { spawn as spawn14 } from "node:child_process";
|
|
22953
24131
|
import * as fs29 from "node:fs/promises";
|
|
22954
24132
|
import * as path33 from "node:path";
|
|
22955
24133
|
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
@@ -23134,7 +24312,7 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
23134
24312
|
function checkRg() {
|
|
23135
24313
|
return new Promise((resolve16) => {
|
|
23136
24314
|
try {
|
|
23137
|
-
const p =
|
|
24315
|
+
const p = spawn14("rg", ["--version"], {
|
|
23138
24316
|
env: buildChildEnv9(),
|
|
23139
24317
|
stdio: "ignore",
|
|
23140
24318
|
windowsHide: true
|
|
@@ -23148,7 +24326,7 @@ function checkRg() {
|
|
|
23148
24326
|
}
|
|
23149
24327
|
function spawnRgFind(pattern, base) {
|
|
23150
24328
|
const args = ["--files", "--glob", pattern, base];
|
|
23151
|
-
const child =
|
|
24329
|
+
const child = spawn14("rg", args, {
|
|
23152
24330
|
signal: AbortSignal.timeout(3e4),
|
|
23153
24331
|
env: buildChildEnv9(),
|
|
23154
24332
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -25187,7 +26365,7 @@ var writeTool = {
|
|
|
25187
26365
|
required: ["path", "content"]
|
|
25188
26366
|
},
|
|
25189
26367
|
async execute(input, ctx, opts) {
|
|
25190
|
-
return
|
|
26368
|
+
return writeFile6(input, ctx, opts?.signal);
|
|
25191
26369
|
},
|
|
25192
26370
|
async *executeStream(input, ctx, opts) {
|
|
25193
26371
|
const prepared = await prepareWrite(input, ctx);
|
|
@@ -25200,7 +26378,7 @@ var writeTool = {
|
|
|
25200
26378
|
yield { type: "final", output: await finishWrite(input, ctx, prepared, opts?.signal) };
|
|
25201
26379
|
}
|
|
25202
26380
|
};
|
|
25203
|
-
async function
|
|
26381
|
+
async function writeFile6(input, ctx, signal) {
|
|
25204
26382
|
return finishWrite(input, ctx, await prepareWrite(input, ctx), signal);
|
|
25205
26383
|
}
|
|
25206
26384
|
async function prepareWrite(input, ctx) {
|