scip-query 0.3.5 → 0.4.1
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/README.md +2 -1
- package/dist/{chunk-VMM4SYV4.js → chunk-24LF6IZB.js} +17 -4
- package/dist/{chunk-EPWLXXBL.js → chunk-3NJSJ7TE.js} +28 -30
- package/dist/{chunk-SMDCNPMK.js → chunk-43A4UCS7.js} +3 -3
- package/dist/{chunk-CHDJXYBG.js → chunk-5GCORUNV.js} +3 -3
- package/dist/{chunk-OIDHN6GD.js → chunk-6CH23IAS.js} +148 -7
- package/dist/chunk-6ECR2FLR.js +60 -0
- package/dist/{chunk-C7H5WBTJ.js → chunk-6UZU7DFL.js} +3 -3
- package/dist/chunk-7BS4CPJX.js +162 -0
- package/dist/{chunk-UGQKAVCD.js → chunk-A6XLXV6W.js} +3 -3
- package/dist/chunk-ALUFWH3U.js +2695 -0
- package/dist/{chunk-F7XU27LU.js → chunk-CBIWNZZZ.js} +27 -3
- package/dist/{chunk-26DOJ63W.js → chunk-DUJNJQPO.js} +14 -3
- package/dist/{chunk-GSH2FPKV.js → chunk-EAGKJFDX.js} +3 -3
- package/dist/{chunk-VIYSWZCO.js → chunk-ELFGD5EW.js} +32 -4
- package/dist/chunk-FVJE4MQL.js +37 -0
- package/dist/{chunk-NFS5W3PP.js → chunk-GNAMV3JC.js} +3 -3
- package/dist/{chunk-TRESG7OB.js → chunk-J47VSL6I.js} +3 -3
- package/dist/chunk-J6QXMYAQ.js +115 -0
- package/dist/{chunk-6FKIA6EI.js → chunk-JHVQB4Y5.js} +12 -12
- package/dist/{chunk-YY4QGUQ5.js → chunk-JKXHHV4B.js} +2 -2
- package/dist/{chunk-EN2Z2CLO.js → chunk-KG4OFQEN.js} +17 -20
- package/dist/chunk-KLNKDX6A.js +110 -0
- package/dist/{chunk-5OMVSV6E.js → chunk-KYPXKV64.js} +3 -3
- package/dist/chunk-NXUIWD6K.js +87 -0
- package/dist/{chunk-LFJQVJYJ.js → chunk-OXX3QF24.js} +2 -2
- package/dist/{chunk-NG5F43OU.js → chunk-P3VCDYMJ.js} +70 -1
- package/dist/{chunk-7HK5ZLOE.js → chunk-PCU455MX.js} +2 -2
- package/dist/{chunk-O7Q7FDUJ.js → chunk-POLELLNM.js} +3 -3
- package/dist/chunk-PU2254N2.js +115 -0
- package/dist/{chunk-WGAD3GNR.js → chunk-QMXSLHZP.js} +5 -5
- package/dist/{chunk-YDBXNPYU.js → chunk-R7HPHMRZ.js} +3 -3
- package/dist/{chunk-HLUS2HEB.js → chunk-RE7POFGI.js} +5 -4
- package/dist/{chunk-VT4JBH6L.js → chunk-RJ5GULL6.js} +2 -2
- package/dist/{chunk-7KIMF5PV.js → chunk-RL74LF47.js} +2 -2
- package/dist/chunk-SVLUJSY7.js +75 -0
- package/dist/chunk-SYQR4QGK.js +38 -0
- package/dist/{chunk-QIXNAB5K.js → chunk-TO3L4YNK.js} +1 -2
- package/dist/{chunk-P3E6L7KW.js → chunk-TWVXFKJA.js} +84 -4
- package/dist/chunk-UJWI5CBB.js +75 -0
- package/dist/{chunk-J3JSOSUO.js → chunk-VKBOLNYN.js} +3 -3
- package/dist/{chunk-KBOQX573.js → chunk-VY2L4TP6.js} +20 -3
- package/dist/{chunk-KKCHYLVI.js → chunk-W46L2BXT.js} +2 -2
- package/dist/chunk-XUVPQDXW.js +34 -0
- package/dist/{chunk-DH7G3DDV.js → chunk-Z5VSUOEE.js} +2 -2
- package/dist/{chunk-GEXE2T6I.js → chunk-ZVZAIIB5.js} +23 -15
- package/dist/cli.js +2838 -870
- package/dist/{db-viWlyVtv.d.ts → db-C4rPbKkI.d.ts} +6 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +376 -142
- package/dist/postinstall.js +9 -3
- package/dist/queries/affected.d.ts +1 -1
- package/dist/queries/affected.js +3 -3
- package/dist/queries/bottlenecks.d.ts +1 -1
- package/dist/queries/bottlenecks.js +3 -2
- package/dist/queries/by-kind.d.ts +1 -1
- package/dist/queries/by-kind.js +3 -2
- package/dist/queries/call-graph.d.ts +1 -1
- package/dist/queries/call-graph.js +3 -3
- package/dist/queries/change-surface.d.ts +1 -1
- package/dist/queries/change-surface.js +3 -3
- package/dist/queries/code.d.ts +1 -1
- package/dist/queries/code.js +3 -3
- package/dist/queries/complexity-hotspots.d.ts +1 -1
- package/dist/queries/complexity-hotspots.js +3 -3
- package/dist/queries/complexity.d.ts +1 -1
- package/dist/queries/complexity.js +3 -3
- package/dist/queries/convergence.d.ts +1 -1
- package/dist/queries/convergence.js +3 -3
- package/dist/queries/coupling.d.ts +1 -1
- package/dist/queries/coupling.js +3 -3
- package/dist/queries/cycles.d.ts +1 -1
- package/dist/queries/cycles.js +3 -3
- package/dist/queries/dataflow.d.ts +1 -1
- package/dist/queries/dataflow.js +3 -3
- package/dist/queries/dead.d.ts +1 -1
- package/dist/queries/dead.js +4 -4
- package/dist/queries/deep-chains.d.ts +1 -1
- package/dist/queries/deep-chains.js +3 -3
- package/dist/queries/deps.d.ts +1 -1
- package/dist/queries/deps.js +3 -3
- package/dist/queries/diff-impact.d.ts +1 -1
- package/dist/queries/diff-impact.js +2 -2
- package/dist/queries/drift.d.ts +1 -1
- package/dist/queries/drift.js +3 -3
- package/dist/queries/extract-candidates.d.ts +1 -1
- package/dist/queries/extract-candidates.js +3 -3
- package/dist/queries/fan.d.ts +1 -1
- package/dist/queries/fan.js +3 -3
- package/dist/queries/files.d.ts +1 -1
- package/dist/queries/health.d.ts +1 -1
- package/dist/queries/health.js +14 -14
- package/dist/queries/hierarchy.d.ts +1 -1
- package/dist/queries/hierarchy.js +3 -3
- package/dist/queries/hotspots.d.ts +1 -1
- package/dist/queries/hotspots.js +3 -2
- package/dist/queries/imports.d.ts +1 -1
- package/dist/queries/imports.js +3 -3
- package/dist/queries/index.d.ts +1 -1
- package/dist/queries/index.js +44 -44
- package/dist/queries/isolated.d.ts +3 -4
- package/dist/queries/isolated.js +4 -4
- package/dist/queries/members.d.ts +1 -1
- package/dist/queries/members.js +3 -3
- package/dist/queries/methods.d.ts +1 -1
- package/dist/queries/methods.js +3 -2
- package/dist/queries/outline.d.ts +1 -1
- package/dist/queries/outline.js +3 -3
- package/dist/queries/passthrough-candidates.d.ts +1 -1
- package/dist/queries/passthrough-candidates.js +3 -3
- package/dist/queries/redundant-reexports.d.ts +1 -1
- package/dist/queries/redundant-reexports.js +4 -4
- package/dist/queries/refs.d.ts +1 -1
- package/dist/queries/refs.js +3 -3
- package/dist/queries/similar-chains.d.ts +1 -1
- package/dist/queries/similar-chains.js +3 -3
- package/dist/queries/similar-files.d.ts +1 -1
- package/dist/queries/similar-files.js +3 -3
- package/dist/queries/similar-signatures.d.ts +5 -3
- package/dist/queries/similar-signatures.js +3 -2
- package/dist/queries/similar.d.ts +1 -1
- package/dist/queries/similar.js +3 -3
- package/dist/queries/slice.d.ts +1 -1
- package/dist/queries/slice.js +3 -3
- package/dist/queries/stale-abstractions.d.ts +1 -1
- package/dist/queries/stale-abstractions.js +3 -3
- package/dist/queries/stats.d.ts +1 -1
- package/dist/queries/surface.d.ts +1 -1
- package/dist/queries/surface.js +3 -3
- package/dist/queries/symbols.d.ts +1 -1
- package/dist/queries/symbols.js +3 -3
- package/dist/queries/system.d.ts +1 -1
- package/dist/queries/system.js +3 -3
- package/dist/queries/trace.d.ts +1 -1
- package/dist/queries/trace.js +3 -3
- package/dist/queries/wrapper-candidates.d.ts +1 -1
- package/dist/queries/wrapper-candidates.js +3 -3
- package/dist/reindex-worker.js +216 -64
- package/package.json +5 -1
- package/skills/scip-language-playbook/SKILL.md +371 -0
- package/dist/chunk-4JCSOF2O.js +0 -97
- package/dist/chunk-AXQKUYKF.js +0 -1442
- package/dist/chunk-CPVAQJEC.js +0 -46
- package/dist/chunk-EOROMIFO.js +0 -41
- package/dist/chunk-GU2H5QRN.js +0 -28
- package/dist/chunk-LQJUPXQY.js +0 -109
- package/dist/chunk-MPGIHELS.js +0 -39
- package/dist/chunk-TOIEB3LG.js +0 -78
- package/dist/chunk-UQEQ6AHX.js +0 -60
- package/dist/chunk-VJJKSGIX.js +0 -121
- package/dist/chunk-YGGFLMTM.js +0 -83
- package/dist/chunk-ZEUCXQBN.js +0 -71
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
|
-
import { existsSync as
|
|
7
|
-
import { join as
|
|
6
|
+
import { existsSync as existsSync10, realpathSync } from "fs";
|
|
7
|
+
import { join as join12 } from "path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9
9
|
|
|
10
10
|
// src/db.ts
|
|
@@ -42,12 +42,23 @@ var ScipDatabase = class {
|
|
|
42
42
|
* that need SQL-level filtering, use excludedPathPatterns().
|
|
43
43
|
*/
|
|
44
44
|
get localSymbolPredicate() {
|
|
45
|
-
return `
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
return `(
|
|
46
|
+
EXISTS (
|
|
47
|
+
SELECT 1
|
|
48
|
+
FROM defn_enclosing_ranges local_der
|
|
49
|
+
JOIN documents local_d ON local_der.document_id = local_d.id
|
|
50
|
+
WHERE local_der.symbol_id = gs.id
|
|
51
|
+
${this.pathExclusionsFor("local_d").trimStart()}
|
|
52
|
+
)
|
|
53
|
+
OR EXISTS (
|
|
54
|
+
SELECT 1
|
|
55
|
+
FROM mentions local_m
|
|
56
|
+
JOIN chunks local_c ON local_m.chunk_id = local_c.id
|
|
57
|
+
JOIN documents local_d ON local_c.document_id = local_d.id
|
|
58
|
+
WHERE local_m.symbol_id = gs.id
|
|
59
|
+
AND local_m.role = 1
|
|
60
|
+
${this.pathExclusionsFor("local_d").trimStart()}
|
|
61
|
+
)
|
|
51
62
|
)`;
|
|
52
63
|
}
|
|
53
64
|
/**
|
|
@@ -275,8 +286,8 @@ function ensureDir(dir) {
|
|
|
275
286
|
|
|
276
287
|
// src/reindex/index.ts
|
|
277
288
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
278
|
-
import { existsSync as
|
|
279
|
-
import { join as
|
|
289
|
+
import { existsSync as existsSync6, renameSync } from "fs";
|
|
290
|
+
import { join as join6 } from "path";
|
|
280
291
|
|
|
281
292
|
// src/scip-cli.ts
|
|
282
293
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -284,7 +295,9 @@ import { platform as platform2, arch } from "os";
|
|
|
284
295
|
|
|
285
296
|
// src/reindex/install.ts
|
|
286
297
|
import { execFileSync } from "child_process";
|
|
298
|
+
import { existsSync as existsSync3 } from "fs";
|
|
287
299
|
import { platform } from "os";
|
|
300
|
+
import { join as join3 } from "path";
|
|
288
301
|
var IS_WINDOWS = platform() === "win32";
|
|
289
302
|
function isBinaryAvailable(name) {
|
|
290
303
|
const cmd = IS_WINDOWS ? "where" : "which";
|
|
@@ -313,6 +326,67 @@ function resolveIndexerBinary(config) {
|
|
|
313
326
|
function isIndexerInstalled(config) {
|
|
314
327
|
return resolveIndexerBinary(config) !== null;
|
|
315
328
|
}
|
|
329
|
+
function resolveProjectLocalIndexerBinary(config, projectRoot) {
|
|
330
|
+
for (const relativePath of config.projectLocalBinaries ?? []) {
|
|
331
|
+
const candidate = join3(projectRoot, relativePath);
|
|
332
|
+
if (existsSync3(candidate)) {
|
|
333
|
+
return candidate;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
function resolveIndexerBinaryForProject(config, projectRoot) {
|
|
339
|
+
return resolveProjectLocalIndexerBinary(config, projectRoot) ?? resolveIndexerBinary(config);
|
|
340
|
+
}
|
|
341
|
+
function getIndexerExecutionEnv(config, baseEnv = process.env, binary = config.indexerBinary) {
|
|
342
|
+
if (config.indexerBinary !== "scip-dotnet") {
|
|
343
|
+
return baseEnv;
|
|
344
|
+
}
|
|
345
|
+
if (canRunDotnetIndexer(binary, baseEnv)) {
|
|
346
|
+
return baseEnv;
|
|
347
|
+
}
|
|
348
|
+
const dotnetRoot = resolveWorkingDotnetRoot(binary, baseEnv);
|
|
349
|
+
if (!dotnetRoot) {
|
|
350
|
+
return baseEnv;
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
...baseEnv,
|
|
354
|
+
DOTNET_ROOT: dotnetRoot
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function getIndexerDependencyStatus(config, projectRoot) {
|
|
358
|
+
const binaryLabel = describeIndexerBinary(config);
|
|
359
|
+
const resolvedBinary = projectRoot ? resolveIndexerBinaryForProject(config, projectRoot) : resolveIndexerBinary(config);
|
|
360
|
+
if (!resolvedBinary) {
|
|
361
|
+
return {
|
|
362
|
+
language: config.language,
|
|
363
|
+
binaryLabel,
|
|
364
|
+
installed: false,
|
|
365
|
+
runnable: false,
|
|
366
|
+
resolvedBinary: null,
|
|
367
|
+
installUrl: config.installUrl
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (config.indexerBinary !== "scip-dotnet") {
|
|
371
|
+
return {
|
|
372
|
+
language: config.language,
|
|
373
|
+
binaryLabel,
|
|
374
|
+
installed: true,
|
|
375
|
+
runnable: true,
|
|
376
|
+
resolvedBinary
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const runtimeProbe = probeDotnetRuntime(resolvedBinary);
|
|
380
|
+
return {
|
|
381
|
+
language: config.language,
|
|
382
|
+
binaryLabel,
|
|
383
|
+
installed: true,
|
|
384
|
+
runnable: runtimeProbe.runnable,
|
|
385
|
+
resolvedBinary,
|
|
386
|
+
installUrl: config.installUrl,
|
|
387
|
+
note: runtimeProbe.note
|
|
388
|
+
};
|
|
389
|
+
}
|
|
316
390
|
function tryInstallIndexer(config, onStatus) {
|
|
317
391
|
const methods2 = config.installMethods;
|
|
318
392
|
const binaryLabel = describeIndexerBinary(config);
|
|
@@ -352,6 +426,64 @@ function tryInstallIndexer(config, onStatus) {
|
|
|
352
426
|
}
|
|
353
427
|
return false;
|
|
354
428
|
}
|
|
429
|
+
function probeDotnetRuntime(binary) {
|
|
430
|
+
if (canRunDotnetIndexer(binary, process.env)) {
|
|
431
|
+
return { runnable: true };
|
|
432
|
+
}
|
|
433
|
+
const dotnetRoot = resolveWorkingDotnetRoot(binary, process.env);
|
|
434
|
+
if (dotnetRoot) {
|
|
435
|
+
return {
|
|
436
|
+
runnable: true,
|
|
437
|
+
note: `using .NET 9 runtime from ${dotnetRoot}`
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const attemptedRoots = getDotnetRootCandidates(process.env);
|
|
441
|
+
const attemptedNote = attemptedRoots.length > 0 ? `.NET 9 runtime still unavailable after checking ${attemptedRoots.join(", ")}` : "binary is present, but scip-dotnet still needs a .NET 9 runtime";
|
|
442
|
+
return {
|
|
443
|
+
runnable: false,
|
|
444
|
+
note: attemptedNote
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function resolveWorkingDotnetRoot(binary, env) {
|
|
448
|
+
for (const dotnetRoot of getDotnetRootCandidates(env)) {
|
|
449
|
+
if (canRunDotnetIndexer(binary, { ...env, DOTNET_ROOT: dotnetRoot })) {
|
|
450
|
+
return dotnetRoot;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
function getDotnetRootCandidates(env) {
|
|
456
|
+
const candidates = [];
|
|
457
|
+
const configured = env["DOTNET_ROOT"];
|
|
458
|
+
if (configured && existsSync3(configured)) {
|
|
459
|
+
candidates.push(configured);
|
|
460
|
+
}
|
|
461
|
+
if (platform() === "darwin" && isBinaryAvailable("brew")) {
|
|
462
|
+
try {
|
|
463
|
+
const prefix = execFileSync("brew", ["--prefix", "dotnet@9"], {
|
|
464
|
+
stdio: "pipe",
|
|
465
|
+
env
|
|
466
|
+
}).toString().trim();
|
|
467
|
+
const candidate = join3(prefix, "libexec");
|
|
468
|
+
if (existsSync3(candidate) && !candidates.includes(candidate)) {
|
|
469
|
+
candidates.push(candidate);
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return candidates;
|
|
475
|
+
}
|
|
476
|
+
function canRunDotnetIndexer(binary, env) {
|
|
477
|
+
try {
|
|
478
|
+
execFileSync(binary, ["--version"], {
|
|
479
|
+
stdio: "pipe",
|
|
480
|
+
env
|
|
481
|
+
});
|
|
482
|
+
return true;
|
|
483
|
+
} catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
355
487
|
|
|
356
488
|
// src/scip-cli.ts
|
|
357
489
|
var IS_WINDOWS2 = platform2() === "win32";
|
|
@@ -461,46 +593,151 @@ function tryInstallScipCli(onStatus) {
|
|
|
461
593
|
}
|
|
462
594
|
|
|
463
595
|
// src/reindex/detect.ts
|
|
464
|
-
import { existsSync as
|
|
465
|
-
import { join as
|
|
596
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
597
|
+
import { extname, join as join4 } from "path";
|
|
598
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
599
|
+
".git",
|
|
600
|
+
".hg",
|
|
601
|
+
".svn",
|
|
602
|
+
".idea",
|
|
603
|
+
".vscode",
|
|
604
|
+
"node_modules",
|
|
605
|
+
"vendor",
|
|
606
|
+
"dist",
|
|
607
|
+
"build",
|
|
608
|
+
"target",
|
|
609
|
+
"bin",
|
|
610
|
+
"obj",
|
|
611
|
+
".dart_tool",
|
|
612
|
+
".gradle",
|
|
613
|
+
".next",
|
|
614
|
+
".venv",
|
|
615
|
+
"venv",
|
|
616
|
+
"__pycache__"
|
|
617
|
+
]);
|
|
466
618
|
var LANGUAGE_MARKERS = [
|
|
467
|
-
{ language: "typescript", files: ["tsconfig.json", "tsconfig.base.json"] },
|
|
468
|
-
{ language: "rust", files: ["Cargo.toml"] },
|
|
469
|
-
{ language: "go", files: ["go.mod"] },
|
|
470
|
-
{ language: "java", files: ["pom.xml", "build.gradle", "build.gradle.kts"] },
|
|
471
|
-
{ language: "kotlin", files: ["build.gradle.kts"] },
|
|
472
|
-
{ language: "scala", files: ["build.sbt"] },
|
|
473
|
-
{ language: "python", files: ["pyproject.toml", "setup.py", "setup.cfg", "Pipfile"] },
|
|
474
|
-
{ language: "ruby", files: ["Gemfile"] },
|
|
475
|
-
{ language: "
|
|
476
|
-
{ language: "
|
|
477
|
-
{ language: "
|
|
478
|
-
{ language: "
|
|
479
|
-
|
|
619
|
+
{ language: "typescript", files: ["tsconfig.json", "tsconfig.base.json"], extensions: [".ts", ".tsx", ".mts", ".cts"] },
|
|
620
|
+
{ language: "rust", files: ["Cargo.toml"], extensions: [".rs"] },
|
|
621
|
+
{ language: "go", files: ["go.mod"], extensions: [".go"] },
|
|
622
|
+
{ language: "java", files: ["pom.xml", "build.gradle", "build.gradle.kts"], extensions: [".java"] },
|
|
623
|
+
{ language: "kotlin", files: ["build.gradle.kts"], extensions: [".kt", ".kts"] },
|
|
624
|
+
{ language: "scala", files: ["build.sbt"], extensions: [".scala"] },
|
|
625
|
+
{ language: "python", files: ["pyproject.toml", "setup.py", "setup.cfg", "Pipfile"], extensions: [".py", ".pyi"] },
|
|
626
|
+
{ language: "ruby", files: ["Gemfile"], extensions: [".rb"] },
|
|
627
|
+
{ language: "cpp", files: ["compile_commands.json", "CMakeLists.txt", "Makefile"], extensions: [".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"] },
|
|
628
|
+
{ language: "c", files: ["compile_commands.json", "CMakeLists.txt", "Makefile"], extensions: [".c", ".h"] },
|
|
629
|
+
{ language: "csharp", globs: ["*.csproj", "*.sln"], extensions: [".cs"] },
|
|
630
|
+
{ language: "vb", globs: ["*.vbproj"], extensions: [".vb"] },
|
|
631
|
+
{ language: "dart", files: ["pubspec.yaml"], extensions: [".dart"] },
|
|
632
|
+
{ language: "php", files: ["composer.json"], extensions: [".php"] },
|
|
633
|
+
{ language: "javascript", files: ["package.json"], extensions: [".js", ".jsx", ".mjs", ".cjs"] }
|
|
634
|
+
// Last — very common
|
|
480
635
|
];
|
|
481
636
|
function detectLanguages(projectRoot) {
|
|
482
637
|
const detected = [];
|
|
638
|
+
const rootEntries = safeReadDir(projectRoot);
|
|
639
|
+
const extensionSet = collectExtensions(projectRoot);
|
|
483
640
|
for (const marker of LANGUAGE_MARKERS) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
641
|
+
if (hasMarkerFile(projectRoot, marker.files)) {
|
|
642
|
+
detected.push(marker.language);
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (matchesRootGlob(rootEntries, marker.globs)) {
|
|
646
|
+
detected.push(marker.language);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (hasExtension(extensionSet, marker.extensions)) {
|
|
650
|
+
detected.push(marker.language);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (detected.includes("typescript")) {
|
|
654
|
+
removeLanguage(detected, "javascript");
|
|
655
|
+
}
|
|
656
|
+
if (detected.includes("cpp") && !extensionSet.has(".c")) {
|
|
657
|
+
removeLanguage(detected, "c");
|
|
658
|
+
}
|
|
659
|
+
return detected;
|
|
660
|
+
}
|
|
661
|
+
function safeReadDir(projectRoot) {
|
|
662
|
+
try {
|
|
663
|
+
return readdirSync(projectRoot);
|
|
664
|
+
} catch {
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function hasMarkerFile(projectRoot, files2) {
|
|
669
|
+
if (!files2?.length) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
return files2.some((file) => existsSync4(join4(projectRoot, file)));
|
|
673
|
+
}
|
|
674
|
+
function matchesRootGlob(entries, globs) {
|
|
675
|
+
if (!globs?.length) {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
return entries.some((entry) => globs.some((glob) => matchesSimpleGlob(entry, glob)));
|
|
679
|
+
}
|
|
680
|
+
function matchesSimpleGlob(entry, pattern) {
|
|
681
|
+
if (pattern === "*") {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
if (!pattern.includes("*")) {
|
|
685
|
+
return entry === pattern;
|
|
686
|
+
}
|
|
687
|
+
const [prefix, suffix] = pattern.split("*");
|
|
688
|
+
return entry.startsWith(prefix ?? "") && entry.endsWith(suffix ?? "");
|
|
689
|
+
}
|
|
690
|
+
function collectExtensions(projectRoot) {
|
|
691
|
+
const found = /* @__PURE__ */ new Set();
|
|
692
|
+
const stack = [projectRoot];
|
|
693
|
+
while (stack.length > 0) {
|
|
694
|
+
const current = stack.pop();
|
|
695
|
+
if (!current) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
let entries;
|
|
699
|
+
try {
|
|
700
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
701
|
+
} catch {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
for (const entry of entries) {
|
|
705
|
+
if (entry.name.startsWith(".") && !entry.name.endsWith("proj") && !entry.name.endsWith("sln")) {
|
|
706
|
+
if (entry.isDirectory()) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
487
709
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
710
|
+
const fullPath = join4(current, entry.name);
|
|
711
|
+
if (entry.isDirectory()) {
|
|
712
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
713
|
+
stack.push(fullPath);
|
|
491
714
|
}
|
|
492
|
-
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
const extension = extname(entry.name).toLowerCase();
|
|
718
|
+
if (extension) {
|
|
719
|
+
found.add(extension);
|
|
493
720
|
}
|
|
494
721
|
}
|
|
495
722
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
723
|
+
return found;
|
|
724
|
+
}
|
|
725
|
+
function hasExtension(extensionSet, extensions) {
|
|
726
|
+
if (!extensions?.length) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
return extensions.some((extension) => extensionSet.has(extension));
|
|
730
|
+
}
|
|
731
|
+
function removeLanguage(detected, language) {
|
|
732
|
+
const index = detected.indexOf(language);
|
|
733
|
+
if (index !== -1) {
|
|
734
|
+
detected.splice(index, 1);
|
|
499
735
|
}
|
|
500
|
-
return detected;
|
|
501
736
|
}
|
|
502
737
|
|
|
503
738
|
// src/reindex/indexers.ts
|
|
739
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
740
|
+
import { join as join5 } from "path";
|
|
504
741
|
var INDEXER_CONFIGS = {
|
|
505
742
|
typescript: {
|
|
506
743
|
language: "typescript",
|
|
@@ -540,9 +777,7 @@ var INDEXER_CONFIGS = {
|
|
|
540
777
|
args: ["index", "--output", outputPath]
|
|
541
778
|
}),
|
|
542
779
|
markerFiles: ["pom.xml", "build.gradle"],
|
|
543
|
-
installMethods: [
|
|
544
|
-
{ label: "coursier", prerequisite: "cs", binary: "cs", args: ["install", "scip-java"] }
|
|
545
|
-
],
|
|
780
|
+
installMethods: [],
|
|
546
781
|
installUrl: "https://github.com/sourcegraph/scip-java/releases"
|
|
547
782
|
},
|
|
548
783
|
scala: {
|
|
@@ -554,9 +789,7 @@ var INDEXER_CONFIGS = {
|
|
|
554
789
|
args: ["index", "--output", outputPath]
|
|
555
790
|
}),
|
|
556
791
|
markerFiles: ["build.sbt"],
|
|
557
|
-
installMethods: [
|
|
558
|
-
{ label: "coursier", prerequisite: "cs", binary: "cs", args: ["install", "scip-java"] }
|
|
559
|
-
],
|
|
792
|
+
installMethods: [],
|
|
560
793
|
installUrl: "https://github.com/sourcegraph/scip-java/releases"
|
|
561
794
|
},
|
|
562
795
|
kotlin: {
|
|
@@ -568,9 +801,7 @@ var INDEXER_CONFIGS = {
|
|
|
568
801
|
args: ["index", "--output", outputPath]
|
|
569
802
|
}),
|
|
570
803
|
markerFiles: ["build.gradle.kts"],
|
|
571
|
-
installMethods: [
|
|
572
|
-
{ label: "coursier", prerequisite: "cs", binary: "cs", args: ["install", "scip-java"] }
|
|
573
|
-
],
|
|
804
|
+
installMethods: [],
|
|
574
805
|
installUrl: "https://github.com/sourcegraph/scip-java/releases"
|
|
575
806
|
},
|
|
576
807
|
rust: {
|
|
@@ -606,10 +837,11 @@ var INDEXER_CONFIGS = {
|
|
|
606
837
|
language: "ruby",
|
|
607
838
|
indexerBinary: "scip-ruby",
|
|
608
839
|
checkCommand: "scip-ruby --version",
|
|
609
|
-
indexArgs: ({
|
|
610
|
-
binary:
|
|
611
|
-
args: ["--
|
|
840
|
+
indexArgs: ({ indexerBinary }) => ({
|
|
841
|
+
binary: indexerBinary,
|
|
842
|
+
args: ["--dir", "."]
|
|
612
843
|
}),
|
|
844
|
+
defaultOutputPath: "index.scip",
|
|
613
845
|
markerFiles: ["Gemfile"],
|
|
614
846
|
installMethods: [],
|
|
615
847
|
installUrl: "https://github.com/sourcegraph/scip-ruby/releases"
|
|
@@ -634,7 +866,7 @@ var INDEXER_CONFIGS = {
|
|
|
634
866
|
checkCommand: "scip-clang --version",
|
|
635
867
|
indexArgs: ({ outputPath }) => ({
|
|
636
868
|
binary: "scip-clang",
|
|
637
|
-
args: ["--output", outputPath]
|
|
869
|
+
args: ["--compdb-path", "compile_commands.json", "--index-output-path", outputPath]
|
|
638
870
|
}),
|
|
639
871
|
markerFiles: ["CMakeLists.txt", "Makefile"],
|
|
640
872
|
installMethods: [],
|
|
@@ -646,7 +878,7 @@ var INDEXER_CONFIGS = {
|
|
|
646
878
|
checkCommand: "scip-clang --version",
|
|
647
879
|
indexArgs: ({ outputPath }) => ({
|
|
648
880
|
binary: "scip-clang",
|
|
649
|
-
args: ["--output", outputPath]
|
|
881
|
+
args: ["--compdb-path", "compile_commands.json", "--index-output-path", outputPath]
|
|
650
882
|
}),
|
|
651
883
|
markerFiles: ["CMakeLists.txt", "Makefile"],
|
|
652
884
|
installMethods: [],
|
|
@@ -656,11 +888,25 @@ var INDEXER_CONFIGS = {
|
|
|
656
888
|
language: "csharp",
|
|
657
889
|
indexerBinary: "scip-dotnet",
|
|
658
890
|
checkCommand: "scip-dotnet --version",
|
|
659
|
-
indexArgs: ({ outputPath }) => ({
|
|
891
|
+
indexArgs: ({ projectRoot, outputPath }) => ({
|
|
660
892
|
binary: "scip-dotnet",
|
|
661
|
-
args: ["index", "--output", outputPath]
|
|
893
|
+
args: ["index", resolveDotnetProject(projectRoot, [".sln", ".csproj"]) ?? projectRoot, "--output", outputPath, "--working-directory", projectRoot]
|
|
894
|
+
}),
|
|
895
|
+
markerFiles: ["*.csproj", "*.sln"],
|
|
896
|
+
installMethods: [
|
|
897
|
+
{ label: "dotnet", prerequisite: "dotnet", binary: "dotnet", args: ["tool", "install", "--global", "scip-dotnet"] }
|
|
898
|
+
],
|
|
899
|
+
installUrl: "https://github.com/sourcegraph/scip-dotnet/releases"
|
|
900
|
+
},
|
|
901
|
+
vb: {
|
|
902
|
+
language: "vb",
|
|
903
|
+
indexerBinary: "scip-dotnet",
|
|
904
|
+
checkCommand: "scip-dotnet --version",
|
|
905
|
+
indexArgs: ({ projectRoot, outputPath }) => ({
|
|
906
|
+
binary: "scip-dotnet",
|
|
907
|
+
args: ["index", resolveDotnetProject(projectRoot, [".sln", ".vbproj"]) ?? projectRoot, "--output", outputPath, "--working-directory", projectRoot]
|
|
662
908
|
}),
|
|
663
|
-
markerFiles: [],
|
|
909
|
+
markerFiles: ["*.vbproj", "*.sln"],
|
|
664
910
|
installMethods: [
|
|
665
911
|
{ label: "dotnet", prerequisite: "dotnet", binary: "dotnet", args: ["tool", "install", "--global", "scip-dotnet"] }
|
|
666
912
|
],
|
|
@@ -670,9 +916,9 @@ var INDEXER_CONFIGS = {
|
|
|
670
916
|
language: "dart",
|
|
671
917
|
indexerBinary: "scip-dart",
|
|
672
918
|
checkCommand: "scip-dart --version",
|
|
673
|
-
indexArgs: ({ outputPath }) => ({
|
|
674
|
-
binary:
|
|
675
|
-
args: ["
|
|
919
|
+
indexArgs: ({ indexerBinary, outputPath }) => ({
|
|
920
|
+
binary: indexerBinary,
|
|
921
|
+
args: ["--output", outputPath]
|
|
676
922
|
}),
|
|
677
923
|
markerFiles: ["pubspec.yaml"],
|
|
678
924
|
installMethods: [
|
|
@@ -683,21 +929,44 @@ var INDEXER_CONFIGS = {
|
|
|
683
929
|
php: {
|
|
684
930
|
language: "php",
|
|
685
931
|
indexerBinary: "scip-php",
|
|
932
|
+
projectLocalBinaries: ["vendor/davidrjenni/scip-php/bin/scip-php", "vendor/bin/scip-php"],
|
|
686
933
|
checkCommand: "scip-php --version",
|
|
687
|
-
indexArgs: ({
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
934
|
+
indexArgs: ({ projectRoot, indexerBinary }) => {
|
|
935
|
+
const localBinary = join5(projectRoot, "vendor", "bin", "scip-php");
|
|
936
|
+
const nestedLocalBinary = join5(projectRoot, "vendor", "davidrjenni", "scip-php", "bin", "scip-php");
|
|
937
|
+
const targetBinary = existsSync5(nestedLocalBinary) ? nestedLocalBinary : existsSync5(localBinary) ? localBinary : indexerBinary;
|
|
938
|
+
return {
|
|
939
|
+
binary: "php",
|
|
940
|
+
args: [
|
|
941
|
+
"-d",
|
|
942
|
+
"error_reporting=E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED",
|
|
943
|
+
targetBinary
|
|
944
|
+
]
|
|
945
|
+
};
|
|
946
|
+
},
|
|
947
|
+
defaultOutputPath: "index.scip",
|
|
691
948
|
markerFiles: ["composer.json"],
|
|
692
|
-
installMethods: [
|
|
693
|
-
{ label: "composer", prerequisite: "composer", binary: "composer", args: ["global", "require", "davidrjenni/scip-php"] }
|
|
694
|
-
],
|
|
949
|
+
installMethods: [],
|
|
695
950
|
installUrl: "https://github.com/davidrjenni/scip-php/releases"
|
|
696
951
|
}
|
|
697
952
|
};
|
|
698
953
|
function getIndexerConfig(language) {
|
|
699
954
|
return INDEXER_CONFIGS[language];
|
|
700
955
|
}
|
|
956
|
+
function resolveDotnetProject(projectRoot, suffixes) {
|
|
957
|
+
let entries;
|
|
958
|
+
try {
|
|
959
|
+
entries = readdirSync2(projectRoot);
|
|
960
|
+
} catch {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
for (const entry of entries) {
|
|
964
|
+
if (suffixes.some((suffix) => entry.endsWith(suffix))) {
|
|
965
|
+
return join5(projectRoot, entry);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
701
970
|
|
|
702
971
|
// src/reindex/index.ts
|
|
703
972
|
async function reindex(opts) {
|
|
@@ -707,8 +976,8 @@ async function reindex(opts) {
|
|
|
707
976
|
onStatus = console.log,
|
|
708
977
|
skipAutoInstall = false
|
|
709
978
|
} = opts;
|
|
710
|
-
const outputScip = opts.outputScip ??
|
|
711
|
-
const outputDb = opts.outputDb ??
|
|
979
|
+
const outputScip = opts.outputScip ?? join6(projectRoot, "index.scip");
|
|
980
|
+
const outputDb = opts.outputDb ?? join6(projectRoot, "index.db");
|
|
712
981
|
const start = Date.now();
|
|
713
982
|
const languages = opts.languages ?? detectLanguages(projectRoot);
|
|
714
983
|
if (languages.length === 0) {
|
|
@@ -737,7 +1006,8 @@ async function reindex(opts) {
|
|
|
737
1006
|
for (const lang of languages) {
|
|
738
1007
|
const config = getIndexerConfig(lang);
|
|
739
1008
|
const binaryLabel = describeIndexerBinary(config);
|
|
740
|
-
|
|
1009
|
+
const projectLocalBinary = resolveProjectLocalIndexerBinary(config, projectRoot);
|
|
1010
|
+
if (!projectLocalBinary && !isIndexerInstalled(config)) {
|
|
741
1011
|
if (skipAutoInstall) {
|
|
742
1012
|
throw new Error(
|
|
743
1013
|
`${binaryLabel} is required to index ${lang} but not found on PATH.
|
|
@@ -752,7 +1022,7 @@ async function reindex(opts) {
|
|
|
752
1022
|
);
|
|
753
1023
|
}
|
|
754
1024
|
}
|
|
755
|
-
const resolvedBinary = resolveIndexerBinary(config);
|
|
1025
|
+
const resolvedBinary = projectLocalBinary ?? resolveIndexerBinary(config);
|
|
756
1026
|
if (!resolvedBinary) {
|
|
757
1027
|
throw new Error(
|
|
758
1028
|
`${binaryLabel} is required to index ${lang} but was not found on PATH after installation checks.
|
|
@@ -760,6 +1030,7 @@ async function reindex(opts) {
|
|
|
760
1030
|
);
|
|
761
1031
|
}
|
|
762
1032
|
onStatus(`Indexing ${lang} with ${resolvedBinary}...`);
|
|
1033
|
+
const indexerEnv = getIndexerExecutionEnv(config, env, resolvedBinary);
|
|
763
1034
|
const { binary, args } = config.indexArgs({
|
|
764
1035
|
projectRoot,
|
|
765
1036
|
outputPath: outputScip,
|
|
@@ -769,7 +1040,7 @@ async function reindex(opts) {
|
|
|
769
1040
|
try {
|
|
770
1041
|
execFileSync3(binary, args, {
|
|
771
1042
|
cwd: projectRoot,
|
|
772
|
-
env,
|
|
1043
|
+
env: indexerEnv,
|
|
773
1044
|
stdio: "pipe",
|
|
774
1045
|
maxBuffer: 50 * 1024 * 1024
|
|
775
1046
|
});
|
|
@@ -777,12 +1048,14 @@ async function reindex(opts) {
|
|
|
777
1048
|
const msg = err instanceof Error ? err.message : String(err);
|
|
778
1049
|
throw new Error(
|
|
779
1050
|
`Failed to index ${lang} with ${resolvedBinary}: ${msg}
|
|
780
|
-
Make sure ${binaryLabel} is installed and available on PATH
|
|
1051
|
+
Make sure ${binaryLabel} is installed and available on PATH.`,
|
|
1052
|
+
{ cause: err }
|
|
781
1053
|
);
|
|
782
1054
|
}
|
|
1055
|
+
moveDefaultOutputIfNeeded(config, projectRoot, outputScip);
|
|
783
1056
|
}
|
|
784
1057
|
onStatus("Converting to SQLite...");
|
|
785
|
-
if (!
|
|
1058
|
+
if (!existsSync6(outputScip)) {
|
|
786
1059
|
throw new Error(`SCIP index not found at ${outputScip} after indexing`);
|
|
787
1060
|
}
|
|
788
1061
|
try {
|
|
@@ -793,17 +1066,26 @@ Make sure ${binaryLabel} is installed and available on PATH.`
|
|
|
793
1066
|
});
|
|
794
1067
|
} catch (err) {
|
|
795
1068
|
const msg = err instanceof Error ? err.message : String(err);
|
|
796
|
-
throw new Error(`Failed to convert SCIP index to SQLite: ${msg}
|
|
1069
|
+
throw new Error(`Failed to convert SCIP index to SQLite: ${msg}`, { cause: err });
|
|
797
1070
|
}
|
|
798
1071
|
const durationMs = Date.now() - start;
|
|
799
1072
|
onStatus(`Done in ${(durationMs / 1e3).toFixed(1)}s`);
|
|
800
1073
|
return { languages, indexPath: outputScip, dbPath: outputDb, durationMs };
|
|
801
1074
|
}
|
|
1075
|
+
function moveDefaultOutputIfNeeded(config, projectRoot, outputScip) {
|
|
1076
|
+
if (!config.defaultOutputPath) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
const defaultOutputPath = join6(projectRoot, config.defaultOutputPath);
|
|
1080
|
+
if (outputScip !== defaultOutputPath && existsSync6(defaultOutputPath)) {
|
|
1081
|
+
renameSync(defaultOutputPath, outputScip);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
802
1084
|
|
|
803
1085
|
// src/watch.ts
|
|
804
1086
|
import { watch } from "fs";
|
|
805
|
-
import { existsSync as
|
|
806
|
-
import { join as
|
|
1087
|
+
import { existsSync as existsSync7, renameSync as renameSync2 } from "fs";
|
|
1088
|
+
import { join as join7, relative } from "path";
|
|
807
1089
|
import { fork } from "child_process";
|
|
808
1090
|
import ignore2 from "ignore";
|
|
809
1091
|
var Watcher = class {
|
|
@@ -881,7 +1163,7 @@ var Watcher = class {
|
|
|
881
1163
|
}
|
|
882
1164
|
// ── Internal ─────────────────────────────────────────────
|
|
883
1165
|
handleFileChange(filename) {
|
|
884
|
-
const rel = relative(this.projectRoot,
|
|
1166
|
+
const rel = relative(this.projectRoot, join7(this.projectRoot, filename));
|
|
885
1167
|
if (this.gitignoreFilter.isIgnored(rel)) return;
|
|
886
1168
|
if (this.extraIgnore.ignores(rel)) return;
|
|
887
1169
|
if (filename.endsWith("index.db") || filename.endsWith("index.scip") || filename.endsWith("index.db.tmp") || filename.endsWith(".scipquery.json")) {
|
|
@@ -984,11 +1266,11 @@ var Watcher = class {
|
|
|
984
1266
|
child.on("exit", (code2) => {
|
|
985
1267
|
if (code2 === 0) {
|
|
986
1268
|
try {
|
|
987
|
-
if (
|
|
988
|
-
|
|
1269
|
+
if (existsSync7(tmpDb)) {
|
|
1270
|
+
renameSync2(tmpDb, this.indexPaths.dbPath);
|
|
989
1271
|
}
|
|
990
|
-
if (
|
|
991
|
-
|
|
1272
|
+
if (existsSync7(tmpScip)) {
|
|
1273
|
+
renameSync2(tmpScip, this.indexPaths.indexPath);
|
|
992
1274
|
}
|
|
993
1275
|
resolve4(Date.now() - start);
|
|
994
1276
|
} catch (err) {
|
|
@@ -1042,27 +1324,36 @@ function files(db, pattern) {
|
|
|
1042
1324
|
}
|
|
1043
1325
|
|
|
1044
1326
|
// src/query-support.ts
|
|
1045
|
-
import { basename } from "path";
|
|
1327
|
+
import { basename as basename2 } from "path";
|
|
1046
1328
|
|
|
1047
1329
|
// src/source-analysis.ts
|
|
1048
1330
|
import {
|
|
1049
|
-
existsSync as
|
|
1050
|
-
readFileSync as
|
|
1331
|
+
existsSync as existsSync8,
|
|
1332
|
+
readFileSync as readFileSync3
|
|
1051
1333
|
} from "fs";
|
|
1052
1334
|
import {
|
|
1335
|
+
basename,
|
|
1053
1336
|
dirname as dirname2,
|
|
1054
|
-
extname,
|
|
1055
|
-
join as
|
|
1337
|
+
extname as extname2,
|
|
1338
|
+
join as join8,
|
|
1056
1339
|
relative as relative2,
|
|
1057
1340
|
resolve as resolve2
|
|
1058
1341
|
} from "path";
|
|
1059
1342
|
var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1343
|
+
var SOURCE_EXPORT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1060
1344
|
var SOURCE_TEXT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1061
1345
|
var SOURCE_CALL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1062
1346
|
var SOURCE_BINDING_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1063
1347
|
var INDEXED_PATH_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1064
1348
|
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1065
1349
|
var PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
|
|
1350
|
+
var JVM_SOURCE_EXTENSIONS = [".java", ".scala", ".kt", ".kts"];
|
|
1351
|
+
var RUST_SOURCE_EXTENSIONS = [".rs"];
|
|
1352
|
+
var RUBY_SOURCE_EXTENSIONS = [".rb"];
|
|
1353
|
+
var C_LIKE_SOURCE_EXTENSIONS = [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"];
|
|
1354
|
+
var DOTNET_SOURCE_EXTENSIONS = [".cs", ".vb"];
|
|
1355
|
+
var DART_SOURCE_EXTENSIONS = [".dart"];
|
|
1356
|
+
var PHP_SOURCE_EXTENSIONS = [".php"];
|
|
1066
1357
|
function getSourceImports(db, relativePath) {
|
|
1067
1358
|
const cache = getCachedMap(SOURCE_IMPORT_CACHE, db);
|
|
1068
1359
|
const normalized = normalizePath(relativePath);
|
|
@@ -1070,13 +1361,30 @@ function getSourceImports(db, relativePath) {
|
|
|
1070
1361
|
if (cached) {
|
|
1071
1362
|
return cached;
|
|
1072
1363
|
}
|
|
1073
|
-
const fullPath =
|
|
1074
|
-
if (!
|
|
1364
|
+
const fullPath = join8(db.config.projectRoot, normalized);
|
|
1365
|
+
if (!existsSync8(fullPath)) {
|
|
1366
|
+
cache.set(normalized, []);
|
|
1367
|
+
return [];
|
|
1368
|
+
}
|
|
1369
|
+
const source = readFileSync3(fullPath, "utf-8");
|
|
1370
|
+
const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : isJavaScriptSourcePath(normalized) ? parseJavaScriptImports(db, normalized, source) : isJvmSourcePath(normalized) ? parseJvmImports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustImports(db, normalized, source) : isRubySourcePath(normalized) ? parseRubyImports(db, normalized, source) : isCLikeSourcePath(normalized) ? parseCLikeImports(db, normalized, source) : isDotNetSourcePath(normalized) ? parseDotNetImports(db, normalized, source) : isDartSourcePath(normalized) ? parseDartImports(db, normalized, source) : isPhpSourcePath(normalized) ? parsePhpImports(db, normalized, source) : [];
|
|
1371
|
+
cache.set(normalized, parsed);
|
|
1372
|
+
return parsed;
|
|
1373
|
+
}
|
|
1374
|
+
function getSourceExports(db, relativePath) {
|
|
1375
|
+
const cache = getCachedMap(SOURCE_EXPORT_CACHE, db);
|
|
1376
|
+
const normalized = normalizePath(relativePath);
|
|
1377
|
+
const cached = cache.get(normalized);
|
|
1378
|
+
if (cached) {
|
|
1379
|
+
return cached;
|
|
1380
|
+
}
|
|
1381
|
+
const fullPath = join8(db.config.projectRoot, normalized);
|
|
1382
|
+
if (!existsSync8(fullPath)) {
|
|
1075
1383
|
cache.set(normalized, []);
|
|
1076
1384
|
return [];
|
|
1077
1385
|
}
|
|
1078
|
-
const source =
|
|
1079
|
-
const parsed =
|
|
1386
|
+
const source = readFileSync3(fullPath, "utf-8");
|
|
1387
|
+
const parsed = isDartSourcePath(normalized) ? parseDartExports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustExports(db, normalized, source) : [];
|
|
1080
1388
|
cache.set(normalized, parsed);
|
|
1081
1389
|
return parsed;
|
|
1082
1390
|
}
|
|
@@ -1225,97 +1533,358 @@ function parseJavaScriptImportStatement(db, importerPath, clause, specifier, sta
|
|
|
1225
1533
|
};
|
|
1226
1534
|
});
|
|
1227
1535
|
}
|
|
1228
|
-
function
|
|
1229
|
-
const
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
"
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
"elif",
|
|
1237
|
-
"return",
|
|
1238
|
-
"yield",
|
|
1239
|
-
"assert",
|
|
1240
|
-
"raise",
|
|
1241
|
-
"lambda",
|
|
1242
|
-
"class",
|
|
1243
|
-
"def"
|
|
1244
|
-
]);
|
|
1245
|
-
for (let index = 0; index < lines.length; index++) {
|
|
1246
|
-
const rawLine = lines[index] ?? "";
|
|
1247
|
-
const stripped = stripCommentsAndStrings(rawLine);
|
|
1248
|
-
if (!stripped.trim()) continue;
|
|
1249
|
-
const attributeMatches = [...stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w]*)\s*\(/g)];
|
|
1250
|
-
const attributeRanges = attributeMatches.map((match) => ({
|
|
1251
|
-
start: match.index ?? -1,
|
|
1252
|
-
end: (match.index ?? -1) + match[0].length
|
|
1253
|
-
}));
|
|
1254
|
-
for (const match of attributeMatches) {
|
|
1255
|
-
const receiverName = match[1];
|
|
1256
|
-
const calleeName = match[2];
|
|
1257
|
-
if (!receiverName || !calleeName) continue;
|
|
1258
|
-
calls.push({
|
|
1259
|
-
receiverName,
|
|
1260
|
-
calleeName,
|
|
1261
|
-
line: baseLine + index
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
|
-
for (const match of stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
|
|
1265
|
-
const calleeName = match[1];
|
|
1266
|
-
const start = match.index ?? -1;
|
|
1267
|
-
if (!calleeName || start < 0) continue;
|
|
1268
|
-
if (controlKeywords.has(calleeName)) continue;
|
|
1269
|
-
if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
|
|
1270
|
-
const prefix = stripped.slice(0, start).trimEnd();
|
|
1271
|
-
if (prefix.endsWith("def") || prefix.endsWith("class") || prefix.endsWith("async def")) {
|
|
1272
|
-
continue;
|
|
1273
|
-
}
|
|
1274
|
-
calls.push({
|
|
1275
|
-
receiverName: null,
|
|
1276
|
-
calleeName,
|
|
1277
|
-
line: baseLine + index
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1536
|
+
function parseJvmImports(db, importerPath, source) {
|
|
1537
|
+
const statements = [];
|
|
1538
|
+
for (const match of source.matchAll(/^[ \t]*import\s+(?:static\s+)?(.+?)\s*;?$/gm)) {
|
|
1539
|
+
const clause = match[1]?.trim();
|
|
1540
|
+
const full = match[0];
|
|
1541
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1542
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1543
|
+
statements.push(...parseJvmImportClause(db, importerPath, clause, body));
|
|
1280
1544
|
}
|
|
1281
|
-
return
|
|
1545
|
+
return statements;
|
|
1282
1546
|
}
|
|
1283
|
-
function
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
"
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1547
|
+
function parseJvmImportClause(db, importerPath, clause, body) {
|
|
1548
|
+
if (clause.includes("{") && clause.includes("}")) {
|
|
1549
|
+
const prefix = clause.slice(0, clause.indexOf("{")).replace(/\.$/, "").trim();
|
|
1550
|
+
const inner = clause.slice(clause.indexOf("{") + 1, clause.lastIndexOf("}")).trim();
|
|
1551
|
+
return splitTopLevel(inner).flatMap((entry) => {
|
|
1552
|
+
const cleaned = entry.trim();
|
|
1553
|
+
if (!cleaned) return [];
|
|
1554
|
+
const [importedPart, aliasPart] = cleaned.includes("=>") ? cleaned.split(/\s*=>\s*/) : cleaned.split(/\s+as\s+/);
|
|
1555
|
+
const importedName = importedPart?.trim();
|
|
1556
|
+
if (!importedName || importedName === "_") return [];
|
|
1557
|
+
const localName = (aliasPart ?? importedName.split(".").pop() ?? importedName).trim();
|
|
1558
|
+
const qualified = importedName === "_" ? prefix : `${prefix}.${importedName}`.replace(/\.\./g, ".");
|
|
1559
|
+
return [buildSimpleImport(db, importerPath, body, qualified, importedName, localName)];
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
return [buildSimpleImport(
|
|
1563
|
+
db,
|
|
1564
|
+
importerPath,
|
|
1565
|
+
body,
|
|
1566
|
+
clause,
|
|
1567
|
+
clause.split(".").pop() ?? clause,
|
|
1568
|
+
clause.split(".").pop() ?? clause
|
|
1569
|
+
)];
|
|
1570
|
+
}
|
|
1571
|
+
function parseRustImports(db, importerPath, source) {
|
|
1572
|
+
const statements = [];
|
|
1573
|
+
for (const match of source.matchAll(/^[ \t]*use\s+(.+?)\s*;$/gm)) {
|
|
1574
|
+
const clause = match[1]?.trim();
|
|
1575
|
+
const full = match[0];
|
|
1576
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1577
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1578
|
+
statements.push(...parseRustUseClause(db, importerPath, clause, body));
|
|
1579
|
+
}
|
|
1580
|
+
return statements;
|
|
1581
|
+
}
|
|
1582
|
+
function parseRustUseClause(db, importerPath, clause, body) {
|
|
1583
|
+
const trimmed = clause.trim();
|
|
1584
|
+
if (trimmed.includes("{") && trimmed.includes("}")) {
|
|
1585
|
+
const prefix = trimmed.slice(0, trimmed.indexOf("{")).replace(/::$/, "").trim();
|
|
1586
|
+
const inner = trimmed.slice(trimmed.indexOf("{") + 1, trimmed.lastIndexOf("}")).trim();
|
|
1587
|
+
return splitTopLevel(inner).flatMap((entry) => {
|
|
1588
|
+
const cleaned = entry.trim();
|
|
1589
|
+
if (!cleaned || cleaned === "self") return [];
|
|
1590
|
+
const [importedPart2, aliasPart2] = cleaned.split(/\s+as\s+/);
|
|
1591
|
+
const importedName2 = importedPart2?.trim();
|
|
1592
|
+
if (!importedName2) return [];
|
|
1593
|
+
const localName2 = (aliasPart2 ?? importedName2.split("::").pop() ?? importedName2).trim();
|
|
1594
|
+
const moduleSpecifier = `${prefix}::${importedName2}`.replace(/::::/g, "::");
|
|
1595
|
+
return [buildSimpleImport(
|
|
1596
|
+
db,
|
|
1597
|
+
importerPath,
|
|
1598
|
+
body,
|
|
1599
|
+
moduleSpecifier,
|
|
1600
|
+
importedName2.split("::").pop() ?? importedName2,
|
|
1601
|
+
localName2,
|
|
1602
|
+
resolveRustImportPath(db, importerPath, prefix)
|
|
1603
|
+
)];
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
const [importedPart, aliasPart] = trimmed.split(/\s+as\s+/);
|
|
1607
|
+
const importedName = importedPart?.trim() ?? trimmed;
|
|
1608
|
+
const localName = (aliasPart ?? importedName.split("::").pop() ?? importedName).trim();
|
|
1609
|
+
const resolved = resolveRustImportPath(db, importerPath, importedName) ?? resolveRustImportPath(db, importerPath, importedName.split("::").slice(0, -1).join("::"));
|
|
1610
|
+
return [buildSimpleImport(
|
|
1611
|
+
db,
|
|
1612
|
+
importerPath,
|
|
1613
|
+
body,
|
|
1614
|
+
importedName,
|
|
1615
|
+
importedName.split("::").pop() ?? importedName,
|
|
1616
|
+
localName,
|
|
1617
|
+
resolved
|
|
1618
|
+
)];
|
|
1619
|
+
}
|
|
1620
|
+
function parseRustExports(db, importerPath, source) {
|
|
1621
|
+
const statements = [];
|
|
1622
|
+
for (const match of source.matchAll(/^[ \t]*pub\s+use\s+(.+?)\s*;$/gm)) {
|
|
1623
|
+
const clause = match[1]?.trim();
|
|
1624
|
+
if (!clause) continue;
|
|
1625
|
+
statements.push(...parseRustExportClause(db, importerPath, clause));
|
|
1626
|
+
}
|
|
1627
|
+
return statements;
|
|
1628
|
+
}
|
|
1629
|
+
function parseRustExportClause(db, importerPath, clause) {
|
|
1630
|
+
const trimmed = clause.trim();
|
|
1631
|
+
if (trimmed.includes("{") && trimmed.includes("}")) {
|
|
1632
|
+
const prefix = trimmed.slice(0, trimmed.indexOf("{")).replace(/::$/, "").trim();
|
|
1633
|
+
const inner = trimmed.slice(trimmed.indexOf("{") + 1, trimmed.lastIndexOf("}")).trim();
|
|
1634
|
+
return splitTopLevel(inner).flatMap((entry) => {
|
|
1635
|
+
const cleaned = entry.trim();
|
|
1636
|
+
if (!cleaned || cleaned === "self") return [];
|
|
1637
|
+
const [qualifiedPart] = cleaned.split(/\s+as\s+/);
|
|
1638
|
+
const qualified = `${prefix}::${qualifiedPart?.trim() ?? cleaned}`.replace(/::::/g, "::");
|
|
1639
|
+
return [buildRustExport(db, importerPath, qualified)];
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
return [buildRustExport(db, importerPath, trimmed)];
|
|
1643
|
+
}
|
|
1644
|
+
function buildRustExport(db, importerPath, specifier) {
|
|
1645
|
+
return {
|
|
1646
|
+
specifier,
|
|
1647
|
+
sourcePath: resolveRustImportPath(db, importerPath, specifier) ?? resolveRustImportPath(db, importerPath, specifier.split("::").slice(0, -1).join("::"))
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
function parseRubyImports(db, importerPath, source) {
|
|
1651
|
+
const statements = [];
|
|
1652
|
+
for (const match of source.matchAll(/^[ \t]*(require_relative|require)\s+["']([^"']+)["']\s*$/gm)) {
|
|
1653
|
+
const kind = match[1];
|
|
1654
|
+
const specifier = match[2];
|
|
1655
|
+
const full = match[0];
|
|
1656
|
+
if (!kind || !specifier || !full || typeof match.index !== "number") continue;
|
|
1657
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1658
|
+
const sourcePath = kind === "require_relative" ? resolveRubyImportPath(db, importerPath, specifier) : null;
|
|
1659
|
+
if (sourcePath) {
|
|
1660
|
+
const localName = rubyConstantName(specifier);
|
|
1661
|
+
statements.push({
|
|
1662
|
+
importedName: localName,
|
|
1663
|
+
localName,
|
|
1664
|
+
sourcePath,
|
|
1665
|
+
kind: "named",
|
|
1666
|
+
used: hasIdentifierUsage(body, localName),
|
|
1667
|
+
usedMembers: []
|
|
1668
|
+
});
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
statements.push({
|
|
1672
|
+
importedName: specifier,
|
|
1673
|
+
localName: null,
|
|
1674
|
+
sourcePath,
|
|
1675
|
+
kind: "side-effect",
|
|
1676
|
+
used: true,
|
|
1677
|
+
usedMembers: []
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
return statements;
|
|
1681
|
+
}
|
|
1682
|
+
function rubyConstantName(specifier) {
|
|
1683
|
+
return basename(specifier).replace(/\.[^.]+$/, "").split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1684
|
+
}
|
|
1685
|
+
function parseCLikeImports(db, importerPath, source) {
|
|
1686
|
+
const statements = [];
|
|
1687
|
+
for (const match of source.matchAll(/^[ \t]*#include\s+[<"]([^">]+)[">]\s*$/gm)) {
|
|
1688
|
+
const specifier = match[1]?.trim();
|
|
1689
|
+
const full = match[0];
|
|
1690
|
+
if (!specifier || !full || typeof match.index !== "number") continue;
|
|
1691
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1692
|
+
const localName = basename(specifier).replace(/\.[^.]+$/, "");
|
|
1693
|
+
statements.push({
|
|
1694
|
+
importedName: specifier,
|
|
1695
|
+
localName,
|
|
1696
|
+
sourcePath: resolveCLikeImportPath(db, importerPath, specifier),
|
|
1697
|
+
kind: "named",
|
|
1698
|
+
used: hasIdentifierUsage(body, localName),
|
|
1699
|
+
usedMembers: []
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
return statements;
|
|
1703
|
+
}
|
|
1704
|
+
function parseDotNetImports(db, importerPath, source) {
|
|
1705
|
+
const statements = [];
|
|
1706
|
+
const lineRegex = isVisualBasicSourcePath(importerPath) ? /^[ \t]*Imports\s+(.+?)\s*$/gm : /^[ \t]*using\s+(.+?)\s*;$/gm;
|
|
1707
|
+
for (const match of source.matchAll(lineRegex)) {
|
|
1708
|
+
const clause = match[1]?.trim();
|
|
1709
|
+
const full = match[0];
|
|
1710
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1711
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1712
|
+
const [aliasPart, targetPart] = isVisualBasicSourcePath(importerPath) ? clause.split(/\s*=\s*/) : clause.split(/\s*=\s*/);
|
|
1713
|
+
const hasAlias = Boolean(targetPart);
|
|
1714
|
+
const qualified = (hasAlias ? targetPart : aliasPart)?.trim() ?? clause;
|
|
1715
|
+
const importedName = qualified.split(".").pop() ?? qualified;
|
|
1716
|
+
const localName = hasAlias ? aliasPart?.trim() ?? importedName : importedName;
|
|
1717
|
+
statements.push(buildSimpleImport(
|
|
1718
|
+
db,
|
|
1719
|
+
importerPath,
|
|
1720
|
+
body,
|
|
1721
|
+
qualified,
|
|
1722
|
+
importedName,
|
|
1723
|
+
localName,
|
|
1724
|
+
resolveQualifiedImportPath(db, qualified, DOTNET_SOURCE_EXTENSIONS)
|
|
1725
|
+
));
|
|
1726
|
+
}
|
|
1727
|
+
return statements;
|
|
1728
|
+
}
|
|
1729
|
+
function parseDartImports(db, importerPath, source) {
|
|
1730
|
+
const statements = [];
|
|
1731
|
+
for (const match of source.matchAll(/^[ \t]*import\s+['"]([^'"]+)['"](?:\s+as\s+([A-Za-z_]\w*))?[\s\S]*?;$/gm)) {
|
|
1732
|
+
const specifier = match[1]?.trim();
|
|
1733
|
+
const alias = match[2]?.trim() ?? null;
|
|
1734
|
+
const full = match[0];
|
|
1735
|
+
if (!specifier || !full || typeof match.index !== "number") continue;
|
|
1736
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1737
|
+
statements.push({
|
|
1738
|
+
importedName: specifier,
|
|
1739
|
+
localName: alias,
|
|
1740
|
+
sourcePath: resolveDartImportPath(db, importerPath, specifier),
|
|
1741
|
+
kind: alias ? "namespace" : "side-effect",
|
|
1742
|
+
used: alias ? hasIdentifierUsage(body, alias) : true,
|
|
1743
|
+
usedMembers: alias ? collectNamespaceMembers(body, alias) : []
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
return statements;
|
|
1747
|
+
}
|
|
1748
|
+
function parseDartExports(db, importerPath, source) {
|
|
1749
|
+
const statements = [];
|
|
1750
|
+
for (const match of source.matchAll(/^[ \t]*export\s+['"]([^'"]+)['"][\s\S]*?;$/gm)) {
|
|
1751
|
+
const specifier = match[1]?.trim();
|
|
1752
|
+
if (!specifier) continue;
|
|
1753
|
+
statements.push({
|
|
1754
|
+
specifier,
|
|
1755
|
+
sourcePath: resolveDartImportPath(db, importerPath, specifier)
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
return statements;
|
|
1759
|
+
}
|
|
1760
|
+
function parsePhpImports(db, importerPath, source) {
|
|
1761
|
+
const statements = [];
|
|
1762
|
+
for (const match of source.matchAll(/^[ \t]*use\s+(.+?)\s*;$/gm)) {
|
|
1763
|
+
const clause = match[1]?.trim();
|
|
1764
|
+
const full = match[0];
|
|
1765
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1766
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1767
|
+
for (const entry of splitTopLevel(clause)) {
|
|
1768
|
+
const cleaned = entry.trim();
|
|
1769
|
+
if (!cleaned) continue;
|
|
1770
|
+
const [qualifiedPart, aliasPart] = cleaned.split(/\s+as\s+/i);
|
|
1771
|
+
const qualified = qualifiedPart?.trim() ?? cleaned;
|
|
1772
|
+
const importedName = qualified.split("\\").pop() ?? qualified;
|
|
1773
|
+
const localName = (aliasPart ?? importedName).trim();
|
|
1774
|
+
statements.push(buildSimpleImport(
|
|
1775
|
+
db,
|
|
1776
|
+
importerPath,
|
|
1777
|
+
body,
|
|
1778
|
+
qualified,
|
|
1779
|
+
importedName,
|
|
1780
|
+
localName,
|
|
1781
|
+
resolveQualifiedImportPath(db, qualified.replace(/\\/g, "."), PHP_SOURCE_EXTENSIONS)
|
|
1782
|
+
));
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
return statements;
|
|
1786
|
+
}
|
|
1787
|
+
function buildSimpleImport(db, importerPath, body, qualifiedName, importedName, localName, sourcePath) {
|
|
1788
|
+
return {
|
|
1789
|
+
importedName,
|
|
1790
|
+
localName,
|
|
1791
|
+
sourcePath: sourcePath ?? resolveQualifiedImportPath(db, qualifiedName, extensionFamilyFor(importerPath)),
|
|
1792
|
+
kind: "named",
|
|
1793
|
+
used: hasIdentifierUsage(body, localName),
|
|
1794
|
+
usedMembers: []
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
function parsePythonCalls(lines, baseLine) {
|
|
1798
|
+
const calls = [];
|
|
1799
|
+
const controlKeywords = /* @__PURE__ */ new Set([
|
|
1800
|
+
"if",
|
|
1801
|
+
"for",
|
|
1802
|
+
"while",
|
|
1803
|
+
"with",
|
|
1804
|
+
"except",
|
|
1805
|
+
"elif",
|
|
1806
|
+
"return",
|
|
1807
|
+
"yield",
|
|
1808
|
+
"assert",
|
|
1809
|
+
"raise",
|
|
1810
|
+
"lambda",
|
|
1811
|
+
"class",
|
|
1812
|
+
"def"
|
|
1813
|
+
]);
|
|
1814
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1815
|
+
const rawLine = lines[index] ?? "";
|
|
1816
|
+
const stripped = stripCommentsAndStrings(rawLine);
|
|
1817
|
+
if (!stripped.trim()) continue;
|
|
1818
|
+
const attributeMatches = [...stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w]*)\s*\(/g)];
|
|
1819
|
+
const attributeRanges = attributeMatches.map((match) => ({
|
|
1820
|
+
start: match.index ?? -1,
|
|
1821
|
+
end: (match.index ?? -1) + match[0].length
|
|
1822
|
+
}));
|
|
1823
|
+
for (const match of attributeMatches) {
|
|
1824
|
+
const receiverName = match[1];
|
|
1825
|
+
const calleeName = match[2];
|
|
1826
|
+
if (!receiverName || !calleeName) continue;
|
|
1827
|
+
calls.push({
|
|
1828
|
+
receiverName,
|
|
1829
|
+
calleeName,
|
|
1830
|
+
line: baseLine + index
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
for (const match of stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
|
|
1834
|
+
const calleeName = match[1];
|
|
1835
|
+
const start = match.index ?? -1;
|
|
1836
|
+
if (!calleeName || start < 0) continue;
|
|
1837
|
+
if (controlKeywords.has(calleeName)) continue;
|
|
1838
|
+
if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
|
|
1839
|
+
const prefix = stripped.slice(0, start).trimEnd();
|
|
1840
|
+
if (prefix.endsWith("def") || prefix.endsWith("class") || prefix.endsWith("async def")) {
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
calls.push({
|
|
1844
|
+
receiverName: null,
|
|
1845
|
+
calleeName,
|
|
1846
|
+
line: baseLine + index
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
return calls;
|
|
1851
|
+
}
|
|
1852
|
+
function parseJavaScriptCalls(lines, baseLine) {
|
|
1853
|
+
const calls = [];
|
|
1854
|
+
const controlKeywords = /* @__PURE__ */ new Set([
|
|
1855
|
+
"if",
|
|
1856
|
+
"for",
|
|
1857
|
+
"while",
|
|
1858
|
+
"switch",
|
|
1859
|
+
"catch",
|
|
1860
|
+
"function",
|
|
1861
|
+
"class",
|
|
1862
|
+
"return",
|
|
1863
|
+
"typeof",
|
|
1864
|
+
"import"
|
|
1865
|
+
]);
|
|
1866
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1867
|
+
const stripped = stripCommentsAndStrings(lines[index] ?? "");
|
|
1868
|
+
if (!stripped.trim()) continue;
|
|
1869
|
+
const attributeMatches = [
|
|
1870
|
+
...stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g),
|
|
1871
|
+
...stripped.matchAll(/\bthis\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g)
|
|
1872
|
+
];
|
|
1873
|
+
const attributeRanges = attributeMatches.map((match) => ({
|
|
1874
|
+
start: match.index ?? -1,
|
|
1875
|
+
end: (match.index ?? -1) + match[0].length
|
|
1876
|
+
}));
|
|
1877
|
+
for (const match of attributeMatches) {
|
|
1878
|
+
const receiverName = match[2] ? match[1] : "this";
|
|
1879
|
+
const calleeName = match[2] ?? match[1];
|
|
1880
|
+
if (!receiverName || !calleeName) continue;
|
|
1881
|
+
calls.push({
|
|
1882
|
+
receiverName,
|
|
1883
|
+
calleeName,
|
|
1884
|
+
line: baseLine + index
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
for (const match of stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
1319
1888
|
const calleeName = match[1];
|
|
1320
1889
|
const start = match.index ?? -1;
|
|
1321
1890
|
if (!calleeName || start < 0) continue;
|
|
@@ -1574,18 +2143,33 @@ function resolveImportPath(db, importerPath, specifier) {
|
|
|
1574
2143
|
if (isPythonSourcePath(importerPath)) {
|
|
1575
2144
|
return resolvePythonImportPath(db, importerPath, specifier);
|
|
1576
2145
|
}
|
|
2146
|
+
if (isRustSourcePath(importerPath)) {
|
|
2147
|
+
return resolveRustImportPath(db, importerPath, specifier);
|
|
2148
|
+
}
|
|
2149
|
+
if (isRubySourcePath(importerPath)) {
|
|
2150
|
+
return resolveRubyImportPath(db, importerPath, specifier);
|
|
2151
|
+
}
|
|
2152
|
+
if (isCLikeSourcePath(importerPath)) {
|
|
2153
|
+
return resolveCLikeImportPath(db, importerPath, specifier);
|
|
2154
|
+
}
|
|
2155
|
+
if (isJvmSourcePath(importerPath) || isDotNetSourcePath(importerPath) || isPhpSourcePath(importerPath)) {
|
|
2156
|
+
return resolveQualifiedImportPath(db, specifier.replace(/\\/g, "."), extensionFamilyFor(importerPath));
|
|
2157
|
+
}
|
|
2158
|
+
if (isDartSourcePath(importerPath)) {
|
|
2159
|
+
return resolveDartImportPath(db, importerPath, specifier);
|
|
2160
|
+
}
|
|
1577
2161
|
return resolveJavaScriptImportPath(db, importerPath, specifier);
|
|
1578
2162
|
}
|
|
1579
2163
|
function resolveJavaScriptImportPath(db, importerPath, specifier) {
|
|
1580
2164
|
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
1581
2165
|
return null;
|
|
1582
2166
|
}
|
|
1583
|
-
const importerDir = dirname2(
|
|
2167
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
1584
2168
|
const absolute = resolve2(importerDir, specifier);
|
|
1585
2169
|
const indexedPaths = getIndexedPaths(db);
|
|
1586
2170
|
for (const candidate of candidateImportPaths(absolute)) {
|
|
1587
2171
|
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
1588
|
-
if (indexedPaths.has(relativeCandidate) ||
|
|
2172
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
1589
2173
|
return relativeCandidate;
|
|
1590
2174
|
}
|
|
1591
2175
|
}
|
|
@@ -1599,7 +2183,7 @@ function resolvePythonImportPath(db, importerPath, specifier) {
|
|
|
1599
2183
|
if (!match) return null;
|
|
1600
2184
|
const dots = match[1].length;
|
|
1601
2185
|
const remainder = match[2].replace(/^\./, "");
|
|
1602
|
-
let baseDir = dirname2(
|
|
2186
|
+
let baseDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
1603
2187
|
for (let i = 1; i < dots; i++) {
|
|
1604
2188
|
baseDir = dirname2(baseDir);
|
|
1605
2189
|
}
|
|
@@ -1609,26 +2193,145 @@ function resolvePythonImportPath(db, importerPath, specifier) {
|
|
|
1609
2193
|
}
|
|
1610
2194
|
for (const candidate of pythonCandidateImportPaths(basePath)) {
|
|
1611
2195
|
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
1612
|
-
if (indexedPaths.has(relativeCandidate) ||
|
|
2196
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
2197
|
+
return relativeCandidate;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
return null;
|
|
2201
|
+
}
|
|
2202
|
+
function resolveRustImportPath(db, importerPath, specifier) {
|
|
2203
|
+
if (!specifier) return null;
|
|
2204
|
+
const normalizedSpecifier = specifier.replace(/\s+as\s+.+$/, "").trim();
|
|
2205
|
+
if (!normalizedSpecifier.startsWith("crate::") && !normalizedSpecifier.startsWith("self::") && !normalizedSpecifier.startsWith("super::")) {
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
2208
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2209
|
+
let basePath;
|
|
2210
|
+
if (normalizedSpecifier.startsWith("crate::")) {
|
|
2211
|
+
basePath = resolve2(db.config.projectRoot, "src", normalizedSpecifier.slice("crate::".length).replace(/::/g, "/"));
|
|
2212
|
+
} else if (normalizedSpecifier.startsWith("self::")) {
|
|
2213
|
+
basePath = resolve2(importerDir, normalizedSpecifier.slice("self::".length).replace(/::/g, "/"));
|
|
2214
|
+
} else {
|
|
2215
|
+
basePath = resolve2(dirname2(importerDir), normalizedSpecifier.slice("super::".length).replace(/::/g, "/"));
|
|
2216
|
+
}
|
|
2217
|
+
for (const candidate of rustCandidateImportPaths(basePath)) {
|
|
2218
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2219
|
+
if (getIndexedPaths(db).has(relativeCandidate) || existsSync8(candidate)) {
|
|
2220
|
+
return relativeCandidate;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
return null;
|
|
2224
|
+
}
|
|
2225
|
+
function resolveRubyImportPath(db, importerPath, specifier) {
|
|
2226
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2227
|
+
const absolute = resolve2(importerDir, specifier);
|
|
2228
|
+
for (const candidate of rubyCandidateImportPaths(absolute)) {
|
|
2229
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2230
|
+
if (getIndexedPaths(db).has(relativeCandidate) || existsSync8(candidate)) {
|
|
2231
|
+
return relativeCandidate;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
function resolveCLikeImportPath(db, importerPath, specifier) {
|
|
2237
|
+
const indexedPaths = getIndexedPaths(db);
|
|
2238
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2239
|
+
const candidates = [
|
|
2240
|
+
resolve2(importerDir, specifier),
|
|
2241
|
+
resolve2(db.config.projectRoot, specifier),
|
|
2242
|
+
resolve2(db.config.projectRoot, "include", specifier),
|
|
2243
|
+
resolve2(db.config.projectRoot, "src", specifier)
|
|
2244
|
+
];
|
|
2245
|
+
for (const candidate of candidates) {
|
|
2246
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2247
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
2248
|
+
return relativeCandidate;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
function resolveQualifiedImportPath(db, specifier, extensions) {
|
|
2254
|
+
const indexedPaths = getIndexedPaths(db);
|
|
2255
|
+
const normalized = specifier.replace(/\\/g, ".").replace(/::/g, ".").replace(/^global::/, "");
|
|
2256
|
+
const pathified = normalized.replace(/\./g, "/");
|
|
2257
|
+
const basenameOnly = normalized.split(".").pop() ?? normalized;
|
|
2258
|
+
for (const ext of extensions) {
|
|
2259
|
+
const exactSuffix = `${pathified}${ext}`;
|
|
2260
|
+
const exact = [...indexedPaths].find((relativePath) => relativePath.endsWith(exactSuffix));
|
|
2261
|
+
if (exact) return exact;
|
|
2262
|
+
}
|
|
2263
|
+
for (const ext of extensions) {
|
|
2264
|
+
const basenameMatch = [...indexedPaths].find((relativePath) => basename(relativePath) === `${basenameOnly}${ext}`);
|
|
2265
|
+
if (basenameMatch) return basenameMatch;
|
|
2266
|
+
}
|
|
2267
|
+
const folderMatches = [...indexedPaths].filter((relativePath) => extensions.includes(extname2(relativePath).toLowerCase())).filter((relativePath) => relativePath.includes(`/${pathified}/`) || relativePath.includes(`/${basenameOnly}/`)).sort((left, right) => left.localeCompare(right));
|
|
2268
|
+
if (folderMatches.length === 1) {
|
|
2269
|
+
return folderMatches[0];
|
|
2270
|
+
}
|
|
2271
|
+
return null;
|
|
2272
|
+
}
|
|
2273
|
+
function resolveDartImportPath(db, importerPath, specifier) {
|
|
2274
|
+
const indexedPaths = getIndexedPaths(db);
|
|
2275
|
+
if (specifier.startsWith("package:")) {
|
|
2276
|
+
const withoutScheme = specifier.slice("package:".length);
|
|
2277
|
+
const slashIndex = withoutScheme.indexOf("/");
|
|
2278
|
+
if (slashIndex < 0) return null;
|
|
2279
|
+
const packageRelative = withoutScheme.slice(slashIndex + 1);
|
|
2280
|
+
const candidate = normalizePath(packageRelative.startsWith("lib/") ? packageRelative : `lib/${packageRelative}`);
|
|
2281
|
+
if (indexedPaths.has(candidate)) return candidate;
|
|
2282
|
+
return null;
|
|
2283
|
+
}
|
|
2284
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2285
|
+
const absolute = resolve2(importerDir, specifier);
|
|
2286
|
+
for (const candidate of dartCandidateImportPaths(absolute)) {
|
|
2287
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2288
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
1613
2289
|
return relativeCandidate;
|
|
1614
2290
|
}
|
|
1615
2291
|
}
|
|
1616
2292
|
return null;
|
|
1617
2293
|
}
|
|
1618
2294
|
function pythonCandidateImportPaths(basePath) {
|
|
1619
|
-
const ext =
|
|
2295
|
+
const ext = extname2(basePath);
|
|
1620
2296
|
if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
|
|
1621
2297
|
return [basePath];
|
|
1622
2298
|
}
|
|
1623
2299
|
return [
|
|
1624
2300
|
`${basePath}.py`,
|
|
1625
2301
|
`${basePath}.pyi`,
|
|
1626
|
-
|
|
1627
|
-
|
|
2302
|
+
join8(basePath, "__init__.py"),
|
|
2303
|
+
join8(basePath, "__init__.pyi")
|
|
2304
|
+
];
|
|
2305
|
+
}
|
|
2306
|
+
function rustCandidateImportPaths(basePath) {
|
|
2307
|
+
const ext = extname2(basePath);
|
|
2308
|
+
if (RUST_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2309
|
+
return [basePath];
|
|
2310
|
+
}
|
|
2311
|
+
return [
|
|
2312
|
+
`${basePath}.rs`,
|
|
2313
|
+
join8(basePath, "mod.rs")
|
|
2314
|
+
];
|
|
2315
|
+
}
|
|
2316
|
+
function rubyCandidateImportPaths(basePath) {
|
|
2317
|
+
const ext = extname2(basePath);
|
|
2318
|
+
if (RUBY_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2319
|
+
return [basePath];
|
|
2320
|
+
}
|
|
2321
|
+
return [
|
|
2322
|
+
`${basePath}.rb`,
|
|
2323
|
+
join8(basePath, "index.rb")
|
|
1628
2324
|
];
|
|
1629
2325
|
}
|
|
2326
|
+
function dartCandidateImportPaths(basePath) {
|
|
2327
|
+
const ext = extname2(basePath);
|
|
2328
|
+
if (DART_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2329
|
+
return [basePath];
|
|
2330
|
+
}
|
|
2331
|
+
return [`${basePath}.dart`, basePath];
|
|
2332
|
+
}
|
|
1630
2333
|
function candidateImportPaths(absolute) {
|
|
1631
|
-
const ext =
|
|
2334
|
+
const ext = extname2(absolute);
|
|
1632
2335
|
const candidates = /* @__PURE__ */ new Set();
|
|
1633
2336
|
if (ext) {
|
|
1634
2337
|
candidates.add(absolute);
|
|
@@ -1638,7 +2341,7 @@ function candidateImportPaths(absolute) {
|
|
|
1638
2341
|
} else {
|
|
1639
2342
|
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
1640
2343
|
candidates.add(`${absolute}${sourceExt}`);
|
|
1641
|
-
candidates.add(
|
|
2344
|
+
candidates.add(join8(absolute, `index${sourceExt}`));
|
|
1642
2345
|
}
|
|
1643
2346
|
}
|
|
1644
2347
|
return [...candidates];
|
|
@@ -1671,10 +2374,44 @@ function normalizePath(path2) {
|
|
|
1671
2374
|
return path2.replace(/\\/g, "/");
|
|
1672
2375
|
}
|
|
1673
2376
|
function isJavaScriptSourcePath(relativePath) {
|
|
1674
|
-
return SOURCE_EXTENSIONS.includes(
|
|
2377
|
+
return SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
1675
2378
|
}
|
|
1676
2379
|
function isPythonSourcePath(relativePath) {
|
|
1677
|
-
return PYTHON_SOURCE_EXTENSIONS.includes(
|
|
2380
|
+
return PYTHON_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2381
|
+
}
|
|
2382
|
+
function isJvmSourcePath(relativePath) {
|
|
2383
|
+
return JVM_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2384
|
+
}
|
|
2385
|
+
function isRustSourcePath(relativePath) {
|
|
2386
|
+
return RUST_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2387
|
+
}
|
|
2388
|
+
function isRubySourcePath(relativePath) {
|
|
2389
|
+
return RUBY_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2390
|
+
}
|
|
2391
|
+
function isCLikeSourcePath(relativePath) {
|
|
2392
|
+
return C_LIKE_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2393
|
+
}
|
|
2394
|
+
function isDotNetSourcePath(relativePath) {
|
|
2395
|
+
return DOTNET_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2396
|
+
}
|
|
2397
|
+
function isVisualBasicSourcePath(relativePath) {
|
|
2398
|
+
return extname2(relativePath).toLowerCase() === ".vb";
|
|
2399
|
+
}
|
|
2400
|
+
function isDartSourcePath(relativePath) {
|
|
2401
|
+
return DART_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2402
|
+
}
|
|
2403
|
+
function isPhpSourcePath(relativePath) {
|
|
2404
|
+
return PHP_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2405
|
+
}
|
|
2406
|
+
function extensionFamilyFor(relativePath) {
|
|
2407
|
+
if (isJvmSourcePath(relativePath)) return JVM_SOURCE_EXTENSIONS;
|
|
2408
|
+
if (isDotNetSourcePath(relativePath)) return DOTNET_SOURCE_EXTENSIONS;
|
|
2409
|
+
if (isPhpSourcePath(relativePath)) return PHP_SOURCE_EXTENSIONS;
|
|
2410
|
+
if (isDartSourcePath(relativePath)) return DART_SOURCE_EXTENSIONS;
|
|
2411
|
+
if (isCLikeSourcePath(relativePath)) return C_LIKE_SOURCE_EXTENSIONS;
|
|
2412
|
+
if (isRustSourcePath(relativePath)) return RUST_SOURCE_EXTENSIONS;
|
|
2413
|
+
if (isRubySourcePath(relativePath)) return RUBY_SOURCE_EXTENSIONS;
|
|
2414
|
+
return SOURCE_EXTENSIONS;
|
|
1678
2415
|
}
|
|
1679
2416
|
function getSourceText(db, relativePath) {
|
|
1680
2417
|
const cache = getCachedMap(SOURCE_TEXT_CACHE, db);
|
|
@@ -1683,12 +2420,12 @@ function getSourceText(db, relativePath) {
|
|
|
1683
2420
|
if (typeof cached === "string") {
|
|
1684
2421
|
return cached;
|
|
1685
2422
|
}
|
|
1686
|
-
const fullPath =
|
|
1687
|
-
if (!
|
|
2423
|
+
const fullPath = join8(db.config.projectRoot, normalized);
|
|
2424
|
+
if (!existsSync8(fullPath)) {
|
|
1688
2425
|
cache.set(normalized, "");
|
|
1689
2426
|
return "";
|
|
1690
2427
|
}
|
|
1691
|
-
const source =
|
|
2428
|
+
const source = readFileSync3(fullPath, "utf-8");
|
|
1692
2429
|
cache.set(normalized, source);
|
|
1693
2430
|
return source;
|
|
1694
2431
|
}
|
|
@@ -1790,7 +2527,6 @@ function parseDescriptors(input) {
|
|
|
1790
2527
|
const closingTick = input.indexOf("`", i + 1);
|
|
1791
2528
|
if (closingTick === -1) {
|
|
1792
2529
|
name = input.slice(i + 1);
|
|
1793
|
-
i = input.length;
|
|
1794
2530
|
descriptors.push({ name, suffix: "term" });
|
|
1795
2531
|
break;
|
|
1796
2532
|
}
|
|
@@ -1942,18 +2678,43 @@ function buildFileDepGraph(db, scope) {
|
|
|
1942
2678
|
${scopeFilter}`
|
|
1943
2679
|
);
|
|
1944
2680
|
const graph = /* @__PURE__ */ new Map();
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2681
|
+
const indexedFiles = new Set(
|
|
2682
|
+
db.all(
|
|
2683
|
+
`SELECT relative_path
|
|
2684
|
+
FROM documents
|
|
2685
|
+
WHERE 1 = 1
|
|
2686
|
+
${db.pathExclusionsFor("documents")}
|
|
2687
|
+
ORDER BY relative_path`
|
|
2688
|
+
).map((row) => row.relative_path).filter((relativePath) => !db.isIgnored(relativePath))
|
|
2689
|
+
);
|
|
2690
|
+
const addEdge = (fromFile, toFile) => {
|
|
2691
|
+
if (fromFile === toFile) return;
|
|
2692
|
+
if (db.isIgnored(fromFile) || db.isIgnored(toFile)) return;
|
|
2693
|
+
if (!indexedFiles.has(toFile)) return;
|
|
2694
|
+
if (!graph.has(fromFile)) graph.set(fromFile, /* @__PURE__ */ new Set());
|
|
2695
|
+
graph.get(fromFile).add(toFile);
|
|
2696
|
+
};
|
|
2697
|
+
for (const edge of edges) {
|
|
2698
|
+
addEdge(edge.from_file, edge.to_file);
|
|
2699
|
+
}
|
|
2700
|
+
for (const relativePath of indexedFiles) {
|
|
2701
|
+
if (scope && !relativePath.includes(scope)) continue;
|
|
2702
|
+
for (const entry of getSourceImports(db, relativePath)) {
|
|
2703
|
+
if (!entry.sourcePath) continue;
|
|
2704
|
+
addEdge(relativePath, entry.sourcePath);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
return graph;
|
|
2708
|
+
}
|
|
1952
2709
|
function findFirstSymbolMatch(db, symbolPattern) {
|
|
2710
|
+
const exact = findExactSymbolMatch(db, symbolPattern.trim());
|
|
2711
|
+
if (exact) {
|
|
2712
|
+
return exact;
|
|
2713
|
+
}
|
|
1953
2714
|
const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
|
|
1954
2715
|
if (fileLineMatch) {
|
|
1955
2716
|
const [, filePath, startStr, endStr] = fileLineMatch;
|
|
1956
|
-
|
|
2717
|
+
let row = db.get(
|
|
1957
2718
|
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
1958
2719
|
FROM global_symbols gs
|
|
1959
2720
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
@@ -1962,25 +2723,41 @@ function findFirstSymbolMatch(db, symbolPattern) {
|
|
|
1962
2723
|
AND der.start_line <= ? AND der.end_line >= ?
|
|
1963
2724
|
${db.pathExclusionsFor("d")}
|
|
1964
2725
|
ORDER BY (der.end_line - der.start_line) ASC
|
|
1965
|
-
|
|
2726
|
+
LIMIT 1`,
|
|
1966
2727
|
`%${filePath}%`,
|
|
1967
2728
|
parseInt(startStr, 10),
|
|
1968
2729
|
parseInt(endStr, 10)
|
|
1969
2730
|
);
|
|
2731
|
+
if (!row) {
|
|
2732
|
+
row = db.get(
|
|
2733
|
+
`SELECT gs.id, gs.symbol, c.document_id, MIN(c.start_line) AS start_line, MAX(c.end_line) AS end_line, d.relative_path
|
|
2734
|
+
FROM global_symbols gs
|
|
2735
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
2736
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2737
|
+
JOIN documents d ON c.document_id = d.id
|
|
2738
|
+
WHERE m.role = 1
|
|
2739
|
+
AND d.relative_path LIKE ?
|
|
2740
|
+
AND c.start_line <= ? AND c.end_line >= ?
|
|
2741
|
+
${db.pathExclusionsFor("d")}
|
|
2742
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path
|
|
2743
|
+
ORDER BY (MAX(c.end_line) - MIN(c.start_line)) ASC
|
|
2744
|
+
LIMIT 1`,
|
|
2745
|
+
`%${filePath}%`,
|
|
2746
|
+
parseInt(startStr, 10),
|
|
2747
|
+
parseInt(endStr, 10)
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
1970
2750
|
if (row && !db.isIgnored(row.relative_path)) {
|
|
1971
|
-
return
|
|
1972
|
-
symbolId: row.id,
|
|
1973
|
-
symbol: row.symbol,
|
|
1974
|
-
documentId: row.document_id,
|
|
1975
|
-
startLine: row.start_line,
|
|
1976
|
-
endLine: row.end_line,
|
|
1977
|
-
relativePath: row.relative_path
|
|
1978
|
-
};
|
|
2751
|
+
return hydrateSymbolMatch(db, row);
|
|
1979
2752
|
}
|
|
1980
2753
|
}
|
|
1981
2754
|
const cleaned = normalizeLookupPattern(symbolPattern);
|
|
1982
2755
|
const tokens = lookupTokens(symbolPattern);
|
|
1983
2756
|
const candidates = getSymbolLookupCandidates(db, tokens);
|
|
2757
|
+
const direct = findDirectSymbolCandidate(candidates, symbolPattern, cleaned);
|
|
2758
|
+
if (direct && !db.isIgnored(direct.relative_path)) {
|
|
2759
|
+
return hydrateSymbolMatch(db, direct);
|
|
2760
|
+
}
|
|
1984
2761
|
let best = null;
|
|
1985
2762
|
for (const row of candidates) {
|
|
1986
2763
|
if (db.isIgnored(row.relative_path)) continue;
|
|
@@ -1991,14 +2768,7 @@ function findFirstSymbolMatch(db, symbolPattern) {
|
|
|
1991
2768
|
}
|
|
1992
2769
|
}
|
|
1993
2770
|
if (best) {
|
|
1994
|
-
return
|
|
1995
|
-
symbolId: best.row.id,
|
|
1996
|
-
symbol: best.row.symbol,
|
|
1997
|
-
documentId: best.row.document_id,
|
|
1998
|
-
startLine: best.row.start_line,
|
|
1999
|
-
endLine: best.row.end_line,
|
|
2000
|
-
relativePath: best.row.relative_path
|
|
2001
|
-
};
|
|
2771
|
+
return hydrateSymbolMatch(db, best.row);
|
|
2002
2772
|
}
|
|
2003
2773
|
return null;
|
|
2004
2774
|
}
|
|
@@ -2017,14 +2787,7 @@ function findExactSymbolMatch(db, symbol) {
|
|
|
2017
2787
|
if (!row || db.isIgnored(row.relative_path)) {
|
|
2018
2788
|
return null;
|
|
2019
2789
|
}
|
|
2020
|
-
return
|
|
2021
|
-
symbolId: row.id,
|
|
2022
|
-
symbol: row.symbol,
|
|
2023
|
-
documentId: row.document_id,
|
|
2024
|
-
startLine: row.start_line,
|
|
2025
|
-
endLine: row.end_line,
|
|
2026
|
-
relativePath: row.relative_path
|
|
2027
|
-
};
|
|
2790
|
+
return hydrateSymbolMatch(db, row);
|
|
2028
2791
|
}
|
|
2029
2792
|
function resolveIndexedFile(db, filePattern) {
|
|
2030
2793
|
return resolveDocumentCandidates(db, filePattern, { allowMultiple: false })[0]?.relativePath ?? null;
|
|
@@ -2048,28 +2811,63 @@ function getSymbolLookupCandidates(db, tokens) {
|
|
|
2048
2811
|
const like = `%${token}%`;
|
|
2049
2812
|
return [like, like, like];
|
|
2050
2813
|
});
|
|
2051
|
-
|
|
2814
|
+
const primary = db.all(
|
|
2052
2815
|
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
|
|
2053
2816
|
FROM global_symbols gs
|
|
2054
2817
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2055
2818
|
JOIN documents d ON der.document_id = d.id
|
|
2056
2819
|
WHERE ${tokenClauses.join("\n AND ")}
|
|
2057
2820
|
${db.pathExclusionsFor("d")}
|
|
2821
|
+
LIMIT 200`,
|
|
2822
|
+
...params
|
|
2823
|
+
);
|
|
2824
|
+
if (primary.length > 0) {
|
|
2825
|
+
return primary;
|
|
2826
|
+
}
|
|
2827
|
+
return db.all(
|
|
2828
|
+
`SELECT
|
|
2829
|
+
gs.id,
|
|
2830
|
+
gs.symbol,
|
|
2831
|
+
c.document_id,
|
|
2832
|
+
MIN(c.start_line) AS start_line,
|
|
2833
|
+
MAX(c.end_line) AS end_line,
|
|
2834
|
+
d.relative_path,
|
|
2835
|
+
gs.display_name
|
|
2836
|
+
FROM global_symbols gs
|
|
2837
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
2838
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2839
|
+
JOIN documents d ON c.document_id = d.id
|
|
2840
|
+
WHERE m.role = 1
|
|
2841
|
+
AND ${tokenClauses.join("\n AND ")}
|
|
2842
|
+
${db.pathExclusionsFor("d")}
|
|
2843
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path, gs.display_name
|
|
2058
2844
|
LIMIT 200`,
|
|
2059
2845
|
...params
|
|
2060
2846
|
);
|
|
2061
2847
|
}
|
|
2062
2848
|
function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
|
|
2849
|
+
const originalCase = originalPattern.trim();
|
|
2850
|
+
const cleanedCase = cleanedPattern;
|
|
2851
|
+
const noParensCase = cleanedCase.replace(/\(\)$/, "");
|
|
2063
2852
|
const original = originalPattern.toLowerCase();
|
|
2064
2853
|
const cleaned = cleanedPattern.toLowerCase();
|
|
2065
2854
|
const noParens = cleaned.replace(/\(\)$/, "");
|
|
2855
|
+
const rawCase = row.symbol;
|
|
2856
|
+
const shortCase = shortenSymbol(row.symbol);
|
|
2857
|
+
const leafCase = leafName(row.symbol);
|
|
2858
|
+
const displayCase = row.display_name ?? "";
|
|
2066
2859
|
const raw = row.symbol.toLowerCase();
|
|
2067
|
-
const short =
|
|
2068
|
-
const leaf =
|
|
2069
|
-
const display =
|
|
2860
|
+
const short = shortCase.toLowerCase();
|
|
2861
|
+
const leaf = leafCase.toLowerCase();
|
|
2862
|
+
const display = displayCase.toLowerCase();
|
|
2070
2863
|
const path2 = row.relative_path.toLowerCase();
|
|
2071
2864
|
const looksPathLike = /[/:.]/.test(cleanedPattern);
|
|
2072
2865
|
let score = 0;
|
|
2866
|
+
if (rawCase === originalCase || rawCase === cleanedCase) score += 1150;
|
|
2867
|
+
if (shortCase === originalCase || shortCase === cleanedCase) score += 1100;
|
|
2868
|
+
if (displayCase === noParensCase) score += 980;
|
|
2869
|
+
if (leafCase === noParensCase) score += 960;
|
|
2870
|
+
if (`${leafCase}()` === originalCase || `${leafCase}()` === cleanedCase) score += 955;
|
|
2073
2871
|
if (raw === original || raw === cleaned) score += 1e3;
|
|
2074
2872
|
if (short === original || short === cleaned) score += 950;
|
|
2075
2873
|
if (path2 === original || path2 === cleaned) score += 925;
|
|
@@ -2098,48 +2896,42 @@ function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
|
|
|
2098
2896
|
return score;
|
|
2099
2897
|
}
|
|
2100
2898
|
function getCalleeRowsForSymbol(db, symbol, opts = {}) {
|
|
2101
|
-
const
|
|
2102
|
-
`SELECT DISTINCT
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
WHERE c.document_id = ?
|
|
2112
|
-
AND c.start_line >= ?
|
|
2113
|
-
AND c.end_line <= ?
|
|
2114
|
-
AND m.role != 1
|
|
2115
|
-
AND callee_gs.id != ?
|
|
2116
|
-
${db.symbolNoiseFor("callee_gs")}
|
|
2117
|
-
${db.pathExclusionsFor("callee_d")}
|
|
2118
|
-
ORDER BY callee_d.relative_path
|
|
2119
|
-
${opts.limit ? "LIMIT ?" : ""}`,
|
|
2899
|
+
const mentions = db.all(
|
|
2900
|
+
`SELECT DISTINCT m.symbol_id, c.id AS chunk_id
|
|
2901
|
+
FROM mentions m
|
|
2902
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2903
|
+
WHERE c.document_id = ?
|
|
2904
|
+
AND c.start_line >= ?
|
|
2905
|
+
AND c.end_line <= ?
|
|
2906
|
+
AND m.role != 1
|
|
2907
|
+
AND m.symbol_id != ?
|
|
2908
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
2120
2909
|
...calleeQueryParams(symbol, opts.limit)
|
|
2121
2910
|
);
|
|
2122
|
-
const primary =
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2911
|
+
const primary = [];
|
|
2912
|
+
const directSeen = /* @__PURE__ */ new Set();
|
|
2913
|
+
for (const mention of mentions) {
|
|
2914
|
+
const callee = getFullSymbolMatch(db, {
|
|
2915
|
+
symbolId: mention.symbol_id,
|
|
2916
|
+
documentId: symbol.documentId,
|
|
2917
|
+
startLine: symbol.startLine,
|
|
2918
|
+
endLine: symbol.endLine
|
|
2919
|
+
});
|
|
2920
|
+
if (!callee || db.isIgnored(callee.relativePath)) continue;
|
|
2921
|
+
const key = `${callee.symbol}|${callee.relativePath}|${mention.chunk_id}`;
|
|
2922
|
+
if (directSeen.has(key)) continue;
|
|
2923
|
+
directSeen.add(key);
|
|
2924
|
+
primary.push({
|
|
2925
|
+
symbol: callee.symbol,
|
|
2926
|
+
file: callee.relativePath,
|
|
2927
|
+
chunkId: mention.chunk_id
|
|
2928
|
+
});
|
|
2130
2929
|
}
|
|
2131
|
-
|
|
2930
|
+
const sourceFallback = getSourceBackedCalleeRows(db, symbol, opts.limit);
|
|
2931
|
+
if (sourceFallback.length > 0) {
|
|
2132
2932
|
return applyLimit(sourceFallback, opts.limit);
|
|
2133
2933
|
}
|
|
2134
|
-
|
|
2135
|
-
const merged = [...primary];
|
|
2136
|
-
for (const row of sourceFallback) {
|
|
2137
|
-
const key = `${row.symbol}|${row.file}`;
|
|
2138
|
-
if (seen.has(key)) continue;
|
|
2139
|
-
seen.add(key);
|
|
2140
|
-
merged.push(row);
|
|
2141
|
-
}
|
|
2142
|
-
return applyLimit(merged, opts.limit);
|
|
2934
|
+
return primary;
|
|
2143
2935
|
}
|
|
2144
2936
|
function getCallerRowsForSymbol(db, symbol, opts = {}) {
|
|
2145
2937
|
const match = getFullSymbolMatch(db, symbol);
|
|
@@ -2147,35 +2939,38 @@ function getCallerRowsForSymbol(db, symbol, opts = {}) {
|
|
|
2147
2939
|
return [];
|
|
2148
2940
|
}
|
|
2149
2941
|
const primary = db.all(
|
|
2150
|
-
`SELECT DISTINCT
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2942
|
+
`SELECT DISTINCT ref_d.relative_path AS caller_file, c.start_line AS line
|
|
2943
|
+
FROM mentions m
|
|
2944
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2945
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
2946
|
+
WHERE m.symbol_id = ?
|
|
2947
|
+
AND m.role != 1
|
|
2948
|
+
${db.pathExclusionsFor("ref_d")}
|
|
2949
|
+
ORDER BY ref_d.relative_path
|
|
2950
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
2951
|
+
...referenceQueryParams(match, opts.limit)
|
|
2952
|
+
).filter((row) => !db.isIgnored(row.caller_file)).flatMap((row) => {
|
|
2953
|
+
const enclosing = findEnclosingDefinition(getDefinitionsForFile(db, row.caller_file), row.line);
|
|
2954
|
+
if (!enclosing || enclosing.symbolId === match.symbolId) {
|
|
2955
|
+
return [];
|
|
2956
|
+
}
|
|
2957
|
+
return [{
|
|
2958
|
+
symbol: enclosing.symbol,
|
|
2959
|
+
file: row.caller_file
|
|
2960
|
+
}];
|
|
2961
|
+
});
|
|
2962
|
+
const sourceFallback = dedupeCallerRows([
|
|
2963
|
+
...getPythonSourceCallerRows(db, match, opts.limit),
|
|
2964
|
+
...getGenericSourceCallerRows(db, match, opts.limit)
|
|
2965
|
+
]);
|
|
2173
2966
|
if (sourceFallback.length === 0) {
|
|
2174
|
-
return primary;
|
|
2967
|
+
return dedupeCallerRows(primary);
|
|
2175
2968
|
}
|
|
2176
|
-
const merged =
|
|
2969
|
+
const merged = [...sourceFallback];
|
|
2970
|
+
const fallbackFiles = new Set(sourceFallback.map((row) => row.file));
|
|
2177
2971
|
const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
|
|
2178
2972
|
for (const row of primary) {
|
|
2973
|
+
if (fallbackFiles.has(row.file)) continue;
|
|
2179
2974
|
const key = `${row.symbol}|${row.file}`;
|
|
2180
2975
|
if (seen.has(key)) continue;
|
|
2181
2976
|
if (isFunctionLikeSymbol(row.symbol) || merged.length === 0) {
|
|
@@ -2185,6 +2980,26 @@ function getCallerRowsForSymbol(db, symbol, opts = {}) {
|
|
|
2185
2980
|
}
|
|
2186
2981
|
return applyLimit(merged, opts.limit);
|
|
2187
2982
|
}
|
|
2983
|
+
function getGenericSourceCallerRows(db, symbol, limit) {
|
|
2984
|
+
return applyLimit(
|
|
2985
|
+
getSourceReferenceSites(db, symbol).filter((site) => site.enclosingSymbol && site.enclosingSymbol !== symbol.symbol).map((site) => ({
|
|
2986
|
+
symbol: site.enclosingSymbol,
|
|
2987
|
+
file: site.file
|
|
2988
|
+
})),
|
|
2989
|
+
limit
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
function dedupeCallerRows(rows) {
|
|
2993
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2994
|
+
const unique = [];
|
|
2995
|
+
for (const row of rows) {
|
|
2996
|
+
const key = `${row.symbol}|${row.file}`;
|
|
2997
|
+
if (seen.has(key)) continue;
|
|
2998
|
+
seen.add(key);
|
|
2999
|
+
unique.push(row);
|
|
3000
|
+
}
|
|
3001
|
+
return unique;
|
|
3002
|
+
}
|
|
2188
3003
|
function getSourceReferenceSites(db, symbol) {
|
|
2189
3004
|
const match = getFullSymbolMatch(db, symbol);
|
|
2190
3005
|
if (!match) {
|
|
@@ -2206,21 +3021,9 @@ function getSourceReferenceSites(db, symbol) {
|
|
|
2206
3021
|
for (const document of documents) {
|
|
2207
3022
|
if (db.isIgnored(document.relative_path)) continue;
|
|
2208
3023
|
const lines = findIdentifierLines(db, document.relative_path, identifier, document.relative_path === match.relativePath ? { excludeStartLine: match.startLine, excludeEndLine: match.endLine } : {});
|
|
3024
|
+
const definitions = getDefinitionsForFile(db, document.relative_path);
|
|
2209
3025
|
for (const line of lines) {
|
|
2210
|
-
const enclosing =
|
|
2211
|
-
`SELECT gs.symbol
|
|
2212
|
-
FROM defn_enclosing_ranges der
|
|
2213
|
-
JOIN global_symbols gs ON der.symbol_id = gs.id
|
|
2214
|
-
JOIN documents d ON der.document_id = d.id
|
|
2215
|
-
WHERE d.relative_path = ?
|
|
2216
|
-
AND der.start_line <= ?
|
|
2217
|
-
AND der.end_line >= ?
|
|
2218
|
-
ORDER BY (der.end_line - der.start_line) ASC
|
|
2219
|
-
LIMIT 1`,
|
|
2220
|
-
document.relative_path,
|
|
2221
|
-
line,
|
|
2222
|
-
line
|
|
2223
|
-
);
|
|
3026
|
+
const enclosing = findEnclosingDefinition(definitions, line);
|
|
2224
3027
|
const key = `${document.relative_path}|${line}|${enclosing?.symbol ?? ""}`;
|
|
2225
3028
|
if (seen.has(key)) continue;
|
|
2226
3029
|
seen.add(key);
|
|
@@ -2245,13 +3048,23 @@ function calleeQueryParams(symbol, limit) {
|
|
|
2245
3048
|
}
|
|
2246
3049
|
return params;
|
|
2247
3050
|
}
|
|
2248
|
-
function
|
|
2249
|
-
const params = [symbol.symbolId
|
|
3051
|
+
function referenceQueryParams(symbol, limit) {
|
|
3052
|
+
const params = [symbol.symbolId];
|
|
2250
3053
|
if (typeof limit === "number") {
|
|
2251
3054
|
params.push(limit);
|
|
2252
3055
|
}
|
|
2253
3056
|
return params;
|
|
2254
3057
|
}
|
|
3058
|
+
function findEnclosingDefinition(definitions, line) {
|
|
3059
|
+
let best = null;
|
|
3060
|
+
for (const definition of definitions) {
|
|
3061
|
+
if (definition.startLine > line || definition.endLine < line) continue;
|
|
3062
|
+
if (!best || definition.endLine - definition.startLine < best.endLine - best.startLine) {
|
|
3063
|
+
best = definition;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return best;
|
|
3067
|
+
}
|
|
2255
3068
|
function getPythonSourceCalleeRows(db, symbol, limit) {
|
|
2256
3069
|
const match = getFullSymbolMatch(db, symbol);
|
|
2257
3070
|
if (!match || !isPythonDocument(db, match.relativePath)) {
|
|
@@ -2342,6 +3155,94 @@ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
|
|
|
2342
3155
|
}
|
|
2343
3156
|
return applyLimit(rows, limit);
|
|
2344
3157
|
}
|
|
3158
|
+
function getJavaSourceCalleeRows(db, symbol, limit) {
|
|
3159
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3160
|
+
if (!match || !isJavaDocument(db, match.relativePath)) {
|
|
3161
|
+
return [];
|
|
3162
|
+
}
|
|
3163
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3164
|
+
const bindings = parseJavaFieldBindings(getSourceText(db, match.relativePath));
|
|
3165
|
+
return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
|
|
3166
|
+
}
|
|
3167
|
+
function getKotlinSourceCalleeRows(db, symbol, limit) {
|
|
3168
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3169
|
+
if (!match || !isKotlinDocument(db, match.relativePath)) {
|
|
3170
|
+
return [];
|
|
3171
|
+
}
|
|
3172
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3173
|
+
const bindings = parseKotlinFieldBindings(getSourceText(db, match.relativePath));
|
|
3174
|
+
return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
|
|
3175
|
+
}
|
|
3176
|
+
function getScalaSourceCalleeRows(db, symbol, limit) {
|
|
3177
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3178
|
+
if (!match || !isScalaDocument(db, match.relativePath)) {
|
|
3179
|
+
return [];
|
|
3180
|
+
}
|
|
3181
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3182
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3183
|
+
}
|
|
3184
|
+
function getCSharpSourceCalleeRows(db, symbol, limit) {
|
|
3185
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3186
|
+
if (!match || !isCSharpDocument(db, match.relativePath)) {
|
|
3187
|
+
return [];
|
|
3188
|
+
}
|
|
3189
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3190
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3191
|
+
}
|
|
3192
|
+
function getVisualBasicSourceCalleeRows(db, symbol, limit) {
|
|
3193
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3194
|
+
if (!match || !isVisualBasicDocument(db, match.relativePath)) {
|
|
3195
|
+
return [];
|
|
3196
|
+
}
|
|
3197
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3198
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3199
|
+
}
|
|
3200
|
+
function getCppSourceCalleeRows(db, symbol, limit) {
|
|
3201
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3202
|
+
if (!match || !isCppDocument(db, match.relativePath)) {
|
|
3203
|
+
return [];
|
|
3204
|
+
}
|
|
3205
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3206
|
+
const bindings = parseCppReceiverBindings(getSourceText(db, match.relativePath));
|
|
3207
|
+
return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
|
|
3208
|
+
}
|
|
3209
|
+
function getRustSourceCalleeRows(db, symbol, limit) {
|
|
3210
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3211
|
+
if (!match || !isRustDocument(db, match.relativePath)) {
|
|
3212
|
+
return [];
|
|
3213
|
+
}
|
|
3214
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3215
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3216
|
+
}
|
|
3217
|
+
function getRubySourceCalleeRows(db, symbol, limit) {
|
|
3218
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3219
|
+
if (!match || !isRubyDocument(db, match.relativePath)) {
|
|
3220
|
+
return [];
|
|
3221
|
+
}
|
|
3222
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, { allowInstanceVariables: true });
|
|
3223
|
+
const rubyCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, {
|
|
3224
|
+
allowInstanceVariables: true,
|
|
3225
|
+
allowBareMemberCalls: true
|
|
3226
|
+
});
|
|
3227
|
+
const bindings = parseRubyReceiverBindings(db, getSourceText(db, match.relativePath));
|
|
3228
|
+
return resolveSimpleSourceCallees(db, match, rubyCalls.length > 0 ? rubyCalls : calls, bindings, limit);
|
|
3229
|
+
}
|
|
3230
|
+
function getDartSourceCalleeRows(db, symbol, limit) {
|
|
3231
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3232
|
+
if (!match || !isDartDocument(db, match.relativePath)) {
|
|
3233
|
+
return [];
|
|
3234
|
+
}
|
|
3235
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3236
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3237
|
+
}
|
|
3238
|
+
function getPhpSourceCalleeRows(db, symbol, limit) {
|
|
3239
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3240
|
+
if (!match || !isPhpDocument(db, match.relativePath)) {
|
|
3241
|
+
return [];
|
|
3242
|
+
}
|
|
3243
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3244
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3245
|
+
}
|
|
2345
3246
|
function getSourceBackedCalleeRows(db, symbol, limit) {
|
|
2346
3247
|
const match = getFullSymbolMatch(db, symbol);
|
|
2347
3248
|
if (!match) {
|
|
@@ -2353,6 +3254,36 @@ function getSourceBackedCalleeRows(db, symbol, limit) {
|
|
|
2353
3254
|
if (isJavaScriptDocument(db, match.relativePath)) {
|
|
2354
3255
|
return getJavaScriptSourceCalleeRows(db, match, limit);
|
|
2355
3256
|
}
|
|
3257
|
+
if (isJavaDocument(db, match.relativePath)) {
|
|
3258
|
+
return getJavaSourceCalleeRows(db, match, limit);
|
|
3259
|
+
}
|
|
3260
|
+
if (isScalaDocument(db, match.relativePath)) {
|
|
3261
|
+
return getScalaSourceCalleeRows(db, match, limit);
|
|
3262
|
+
}
|
|
3263
|
+
if (isKotlinDocument(db, match.relativePath)) {
|
|
3264
|
+
return getKotlinSourceCalleeRows(db, match, limit);
|
|
3265
|
+
}
|
|
3266
|
+
if (isCSharpDocument(db, match.relativePath)) {
|
|
3267
|
+
return getCSharpSourceCalleeRows(db, match, limit);
|
|
3268
|
+
}
|
|
3269
|
+
if (isVisualBasicDocument(db, match.relativePath)) {
|
|
3270
|
+
return getVisualBasicSourceCalleeRows(db, match, limit);
|
|
3271
|
+
}
|
|
3272
|
+
if (isCppDocument(db, match.relativePath)) {
|
|
3273
|
+
return getCppSourceCalleeRows(db, match, limit);
|
|
3274
|
+
}
|
|
3275
|
+
if (isRustDocument(db, match.relativePath)) {
|
|
3276
|
+
return getRustSourceCalleeRows(db, match, limit);
|
|
3277
|
+
}
|
|
3278
|
+
if (isRubyDocument(db, match.relativePath)) {
|
|
3279
|
+
return getRubySourceCalleeRows(db, match, limit);
|
|
3280
|
+
}
|
|
3281
|
+
if (isDartDocument(db, match.relativePath)) {
|
|
3282
|
+
return getDartSourceCalleeRows(db, match, limit);
|
|
3283
|
+
}
|
|
3284
|
+
if (isPhpDocument(db, match.relativePath)) {
|
|
3285
|
+
return getPhpSourceCalleeRows(db, match, limit);
|
|
3286
|
+
}
|
|
2356
3287
|
return [];
|
|
2357
3288
|
}
|
|
2358
3289
|
function getPythonSourceCallerRows(db, target, limit) {
|
|
@@ -2378,27 +3309,49 @@ function getPythonSourceCallerRows(db, target, limit) {
|
|
|
2378
3309
|
}
|
|
2379
3310
|
return rows;
|
|
2380
3311
|
}
|
|
2381
|
-
function
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
}
|
|
2385
|
-
const row = db.get(
|
|
2386
|
-
`SELECT gs.symbol, d.relative_path
|
|
3312
|
+
function getDefinitionRowsForSymbolId(db, symbolId) {
|
|
3313
|
+
const primary = db.all(
|
|
3314
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
|
|
2387
3315
|
FROM global_symbols gs
|
|
2388
3316
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2389
3317
|
JOIN documents d ON der.document_id = d.id
|
|
2390
3318
|
WHERE gs.id = ?
|
|
2391
|
-
|
|
2392
|
-
|
|
3319
|
+
ORDER BY der.start_line, der.end_line`,
|
|
3320
|
+
symbolId
|
|
3321
|
+
);
|
|
3322
|
+
if (primary.length > 0) {
|
|
3323
|
+
return primary;
|
|
3324
|
+
}
|
|
3325
|
+
return db.all(
|
|
3326
|
+
`SELECT
|
|
3327
|
+
gs.id,
|
|
3328
|
+
gs.symbol,
|
|
3329
|
+
c.document_id,
|
|
3330
|
+
MIN(c.start_line) AS start_line,
|
|
3331
|
+
MAX(c.end_line) AS end_line,
|
|
3332
|
+
d.relative_path,
|
|
3333
|
+
gs.display_name
|
|
3334
|
+
FROM global_symbols gs
|
|
3335
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
3336
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
3337
|
+
JOIN documents d ON c.document_id = d.id
|
|
3338
|
+
WHERE gs.id = ?
|
|
3339
|
+
AND m.role = 1
|
|
3340
|
+
${db.pathExclusionsFor("d")}
|
|
3341
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path, gs.display_name
|
|
3342
|
+
ORDER BY start_line, end_line`,
|
|
3343
|
+
symbolId
|
|
2393
3344
|
);
|
|
3345
|
+
}
|
|
3346
|
+
function getFullSymbolMatch(db, symbol) {
|
|
3347
|
+
if ("symbol" in symbol && "relativePath" in symbol) {
|
|
3348
|
+
return symbol;
|
|
3349
|
+
}
|
|
3350
|
+
const row = getDefinitionRowsForSymbolId(db, symbol.symbolId)[0];
|
|
2394
3351
|
if (!row) {
|
|
2395
3352
|
return null;
|
|
2396
3353
|
}
|
|
2397
|
-
return
|
|
2398
|
-
...symbol,
|
|
2399
|
-
symbol: row.symbol,
|
|
2400
|
-
relativePath: row.relative_path
|
|
2401
|
-
};
|
|
3354
|
+
return hydrateSymbolMatch(db, row);
|
|
2402
3355
|
}
|
|
2403
3356
|
function getDefinitionsForFile(db, relativePath) {
|
|
2404
3357
|
let cache = FILE_DEFINITION_CACHE.get(db);
|
|
@@ -2410,8 +3363,18 @@ function getDefinitionsForFile(db, relativePath) {
|
|
|
2410
3363
|
if (cached) {
|
|
2411
3364
|
return cached;
|
|
2412
3365
|
}
|
|
2413
|
-
const
|
|
2414
|
-
`SELECT
|
|
3366
|
+
const primary = db.all(
|
|
3367
|
+
`SELECT
|
|
3368
|
+
gs.id,
|
|
3369
|
+
gs.symbol,
|
|
3370
|
+
der.document_id,
|
|
3371
|
+
der.start_line,
|
|
3372
|
+
der.end_line,
|
|
3373
|
+
d.relative_path,
|
|
3374
|
+
gs.display_name,
|
|
3375
|
+
gs.kind,
|
|
3376
|
+
gs.documentation,
|
|
3377
|
+
gs.enclosing_symbol
|
|
2415
3378
|
FROM global_symbols gs
|
|
2416
3379
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2417
3380
|
JOIN documents d ON der.document_id = d.id
|
|
@@ -2419,54 +3382,272 @@ function getDefinitionsForFile(db, relativePath) {
|
|
|
2419
3382
|
${db.symbolNoiseFor("gs")}
|
|
2420
3383
|
ORDER BY der.start_line, der.end_line`,
|
|
2421
3384
|
relativePath
|
|
2422
|
-
)
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
return definitions;
|
|
2436
|
-
}
|
|
2437
|
-
function getAllFunctionLikeDefinitions(db) {
|
|
2438
|
-
const rows = db.all(
|
|
2439
|
-
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
3385
|
+
);
|
|
3386
|
+
const fallback = primary.length > 0 ? [] : db.all(
|
|
3387
|
+
`SELECT
|
|
3388
|
+
gs.id,
|
|
3389
|
+
gs.symbol,
|
|
3390
|
+
c.document_id,
|
|
3391
|
+
MIN(c.start_line) AS start_line,
|
|
3392
|
+
MAX(c.end_line) AS end_line,
|
|
3393
|
+
d.relative_path,
|
|
3394
|
+
gs.display_name,
|
|
3395
|
+
gs.kind,
|
|
3396
|
+
gs.documentation,
|
|
3397
|
+
gs.enclosing_symbol
|
|
2440
3398
|
FROM global_symbols gs
|
|
2441
|
-
JOIN
|
|
2442
|
-
JOIN
|
|
2443
|
-
|
|
2444
|
-
|
|
3399
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
3400
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
3401
|
+
JOIN documents d ON c.document_id = d.id
|
|
3402
|
+
WHERE d.relative_path = ?
|
|
3403
|
+
AND m.role = 1
|
|
2445
3404
|
${db.symbolNoiseFor("gs")}
|
|
2446
|
-
|
|
3405
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path
|
|
3406
|
+
ORDER BY start_line, end_line`,
|
|
3407
|
+
relativePath
|
|
3408
|
+
);
|
|
3409
|
+
const definitions = correctDefinitionRangesFromSource(
|
|
3410
|
+
db,
|
|
3411
|
+
relativePath,
|
|
3412
|
+
(primary.length > 0 ? primary : fallback).map((row) => ({
|
|
3413
|
+
symbolId: row.id,
|
|
3414
|
+
symbol: row.symbol,
|
|
3415
|
+
documentId: row.document_id,
|
|
3416
|
+
startLine: row.start_line,
|
|
3417
|
+
endLine: row.end_line,
|
|
3418
|
+
relativePath: row.relative_path,
|
|
3419
|
+
leaf: leafName(row.symbol),
|
|
3420
|
+
parentTypeName: parentTypeName(row.symbol),
|
|
3421
|
+
isFunctionLike: isFunctionLikeSymbol(row.symbol),
|
|
3422
|
+
isTypeLike: leafSuffix(row.symbol) === "type",
|
|
3423
|
+
kind: row.kind ?? null,
|
|
3424
|
+
documentation: row.documentation ?? null,
|
|
3425
|
+
enclosingSymbol: row.enclosing_symbol ?? null
|
|
3426
|
+
}))
|
|
2447
3427
|
);
|
|
2448
|
-
|
|
3428
|
+
cache.set(relativePath, definitions);
|
|
3429
|
+
return definitions;
|
|
3430
|
+
}
|
|
3431
|
+
function hydrateSymbolMatch(db, row) {
|
|
3432
|
+
const corrected = getDefinitionsForFile(db, row.relative_path).find((definition) => definition.symbolId === row.id);
|
|
3433
|
+
if (corrected) {
|
|
3434
|
+
return {
|
|
3435
|
+
symbolId: corrected.symbolId,
|
|
3436
|
+
symbol: corrected.symbol,
|
|
3437
|
+
documentId: corrected.documentId,
|
|
3438
|
+
startLine: corrected.startLine,
|
|
3439
|
+
endLine: corrected.endLine,
|
|
3440
|
+
relativePath: corrected.relativePath
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
return {
|
|
2449
3444
|
symbolId: row.id,
|
|
2450
3445
|
symbol: row.symbol,
|
|
2451
3446
|
documentId: row.document_id,
|
|
2452
3447
|
startLine: row.start_line,
|
|
2453
3448
|
endLine: row.end_line,
|
|
2454
|
-
relativePath: row.relative_path
|
|
2455
|
-
|
|
2456
|
-
parentTypeName: parentTypeName(row.symbol),
|
|
2457
|
-
isFunctionLike: true,
|
|
2458
|
-
isTypeLike: false
|
|
2459
|
-
}));
|
|
3449
|
+
relativePath: row.relative_path
|
|
3450
|
+
};
|
|
2460
3451
|
}
|
|
2461
|
-
function
|
|
2462
|
-
|
|
2463
|
-
|
|
3452
|
+
function findDirectSymbolCandidate(candidates, symbolPattern, cleanedPattern) {
|
|
3453
|
+
const trimmed = symbolPattern.trim();
|
|
3454
|
+
const directMatches = candidates.filter((row) => {
|
|
3455
|
+
const short = shortenSymbol(row.symbol);
|
|
3456
|
+
const display = (row.display_name ?? "").trim();
|
|
3457
|
+
return row.symbol === trimmed || short === trimmed || short === cleanedPattern || display === trimmed || display === cleanedPattern || `${display}()` === trimmed || row.relative_path === trimmed;
|
|
3458
|
+
});
|
|
3459
|
+
if (directMatches.length === 0) {
|
|
3460
|
+
return null;
|
|
2464
3461
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
3462
|
+
directMatches.sort(
|
|
3463
|
+
(left, right) => left.end_line - left.start_line - (right.end_line - right.start_line) || left.relative_path.localeCompare(right.relative_path) || left.symbol.localeCompare(right.symbol)
|
|
3464
|
+
);
|
|
3465
|
+
return directMatches[0] ?? null;
|
|
3466
|
+
}
|
|
3467
|
+
function correctDefinitionRangesFromSource(db, relativePath, definitions) {
|
|
3468
|
+
const source = getSourceText(db, relativePath);
|
|
3469
|
+
if (!source) {
|
|
3470
|
+
return definitions;
|
|
3471
|
+
}
|
|
3472
|
+
const lines = source.split(/\r?\n/);
|
|
3473
|
+
const correctedStarts = /* @__PURE__ */ new Map();
|
|
3474
|
+
for (const definition of definitions) {
|
|
3475
|
+
correctedStarts.set(
|
|
3476
|
+
definition.symbolId,
|
|
3477
|
+
resolveCallableDefinitionStartLine(lines, definition)
|
|
3478
|
+
);
|
|
3479
|
+
}
|
|
3480
|
+
const correctedRanges = /* @__PURE__ */ new Map();
|
|
3481
|
+
const callableDefinitions = definitions.filter((definition) => isCallableDefinition(definition.symbol)).map((definition) => ({
|
|
3482
|
+
definition,
|
|
3483
|
+
startLine: correctedStarts.get(definition.symbolId) ?? definition.startLine
|
|
3484
|
+
})).sort(
|
|
3485
|
+
(left, right) => left.startLine - right.startLine || left.definition.startLine - right.definition.startLine || left.definition.symbol.localeCompare(right.definition.symbol)
|
|
3486
|
+
);
|
|
3487
|
+
for (let index = 0; index < callableDefinitions.length; index += 1) {
|
|
3488
|
+
const current = callableDefinitions[index];
|
|
3489
|
+
const next = callableDefinitions[index + 1];
|
|
3490
|
+
const maxEndLine = next ? Math.max(current.startLine, next.startLine - 1) : lines.length - 1;
|
|
3491
|
+
correctedRanges.set(current.definition.symbolId, {
|
|
3492
|
+
startLine: current.startLine,
|
|
3493
|
+
endLine: resolveCallableDefinitionEndLine(
|
|
3494
|
+
lines,
|
|
3495
|
+
current.definition,
|
|
3496
|
+
current.startLine,
|
|
3497
|
+
maxEndLine
|
|
3498
|
+
)
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
return definitions.map((definition) => {
|
|
3502
|
+
const corrected = correctedRanges.get(definition.symbolId);
|
|
3503
|
+
if (!corrected) {
|
|
3504
|
+
return definition;
|
|
3505
|
+
}
|
|
3506
|
+
return {
|
|
3507
|
+
...definition,
|
|
3508
|
+
startLine: corrected.startLine,
|
|
3509
|
+
endLine: corrected.endLine
|
|
3510
|
+
};
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
function resolveCallableDefinitionStartLine(lines, definition) {
|
|
3514
|
+
if (!isCallableDefinition(definition.symbol)) {
|
|
3515
|
+
return definition.startLine;
|
|
3516
|
+
}
|
|
3517
|
+
const escapedLeaf = escapeRegex2(definition.leaf);
|
|
3518
|
+
const strongPatterns = [
|
|
3519
|
+
new RegExp(`\\b(?:function|def|fn)\\s+${escapedLeaf}\\b`),
|
|
3520
|
+
new RegExp(`\\b${escapedLeaf}\\b\\s*[:=]\\s*(?:async\\s*)?(?:function\\b|\\()`)
|
|
3521
|
+
];
|
|
3522
|
+
const fallbackPatterns = [
|
|
3523
|
+
new RegExp(`\\b${escapedLeaf}\\b\\s*\\(`)
|
|
3524
|
+
];
|
|
3525
|
+
return findNearestMatchingLine(
|
|
3526
|
+
lines,
|
|
3527
|
+
[...strongPatterns, ...fallbackPatterns],
|
|
3528
|
+
definition.startLine,
|
|
3529
|
+
definition.endLine
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
function findNearestMatchingLine(lines, patterns, preferredStartLine, preferredEndLine) {
|
|
3533
|
+
const windowStart = Math.max(0, preferredStartLine - 40);
|
|
3534
|
+
const windowEnd = Math.min(lines.length - 1, Math.max(preferredEndLine + 40, preferredStartLine + 5));
|
|
3535
|
+
const windowMatch = matchNearestLine(lines, patterns, preferredStartLine, windowStart, windowEnd);
|
|
3536
|
+
if (windowMatch !== null) {
|
|
3537
|
+
return windowMatch;
|
|
3538
|
+
}
|
|
3539
|
+
const fullMatch = matchNearestLine(lines, patterns, preferredStartLine, 0, lines.length - 1);
|
|
3540
|
+
return fullMatch ?? Math.max(0, Math.min(preferredStartLine, lines.length - 1));
|
|
3541
|
+
}
|
|
3542
|
+
function matchNearestLine(lines, patterns, preferredLine, startLine, endLine) {
|
|
3543
|
+
let best = null;
|
|
3544
|
+
for (let lineIndex = startLine; lineIndex <= endLine; lineIndex += 1) {
|
|
3545
|
+
const line = lines[lineIndex] ?? "";
|
|
3546
|
+
if (!patterns.some((pattern) => pattern.test(line))) continue;
|
|
3547
|
+
const distance = Math.abs(lineIndex - preferredLine);
|
|
3548
|
+
if (!best || distance < best.distance) {
|
|
3549
|
+
best = { line: lineIndex, distance };
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
return best?.line ?? null;
|
|
3553
|
+
}
|
|
3554
|
+
function resolveCallableDefinitionEndLine(lines, definition, startLine, maxEndLine) {
|
|
3555
|
+
const boundedEndLine = Math.max(startLine, Math.min(lines.length - 1, maxEndLine));
|
|
3556
|
+
const fallbackEndLine = Math.max(startLine, Math.min(boundedEndLine, definition.endLine));
|
|
3557
|
+
let braceDepth = 0;
|
|
3558
|
+
let parenDepth = 0;
|
|
3559
|
+
let sawOpeningBrace = false;
|
|
3560
|
+
for (let lineIndex = startLine; lineIndex <= boundedEndLine; lineIndex += 1) {
|
|
3561
|
+
const masked = maskStructuralLine(lines[lineIndex] ?? "");
|
|
3562
|
+
for (const char of masked) {
|
|
3563
|
+
if (char === "{") {
|
|
3564
|
+
braceDepth += 1;
|
|
3565
|
+
sawOpeningBrace = true;
|
|
3566
|
+
} else if (char === "}") {
|
|
3567
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
3568
|
+
} else if (char === "(") {
|
|
3569
|
+
parenDepth += 1;
|
|
3570
|
+
} else if (char === ")") {
|
|
3571
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
if (sawOpeningBrace && braceDepth === 0) {
|
|
3575
|
+
return lineIndex;
|
|
3576
|
+
}
|
|
3577
|
+
if (!sawOpeningBrace && parenDepth === 0 && lineIndex >= fallbackEndLine) {
|
|
3578
|
+
return lineIndex;
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
return fallbackEndLine;
|
|
3582
|
+
}
|
|
3583
|
+
function maskStructuralLine(line) {
|
|
3584
|
+
let masked = "";
|
|
3585
|
+
let quote = null;
|
|
3586
|
+
let escaping = false;
|
|
3587
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
3588
|
+
const char = line[index];
|
|
3589
|
+
const next = line[index + 1];
|
|
3590
|
+
if (!quote && char === "/" && next === "/") {
|
|
3591
|
+
masked += " ".repeat(line.length - index);
|
|
3592
|
+
break;
|
|
3593
|
+
}
|
|
3594
|
+
if (quote) {
|
|
3595
|
+
if (escaping) {
|
|
3596
|
+
escaping = false;
|
|
3597
|
+
masked += " ";
|
|
3598
|
+
continue;
|
|
3599
|
+
}
|
|
3600
|
+
if (char === "\\") {
|
|
3601
|
+
escaping = true;
|
|
3602
|
+
masked += " ";
|
|
3603
|
+
continue;
|
|
3604
|
+
}
|
|
3605
|
+
if (char === quote) {
|
|
3606
|
+
quote = null;
|
|
3607
|
+
}
|
|
3608
|
+
masked += " ";
|
|
3609
|
+
continue;
|
|
3610
|
+
}
|
|
3611
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
3612
|
+
quote = char;
|
|
3613
|
+
masked += " ";
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
3616
|
+
masked += char;
|
|
3617
|
+
}
|
|
3618
|
+
return masked;
|
|
3619
|
+
}
|
|
3620
|
+
function isCallableDefinition(symbol) {
|
|
3621
|
+
return symbol.includes("().");
|
|
3622
|
+
}
|
|
3623
|
+
function escapeRegex2(value) {
|
|
3624
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3625
|
+
}
|
|
3626
|
+
function getAllDefinitions(db, opts = {}) {
|
|
3627
|
+
const { scope } = opts;
|
|
3628
|
+
const rows = db.all(
|
|
3629
|
+
`SELECT relative_path
|
|
3630
|
+
FROM documents
|
|
3631
|
+
WHERE 1 = 1
|
|
3632
|
+
${db.pathExclusionsFor("documents")}
|
|
3633
|
+
${scope ? "AND relative_path LIKE ?" : ""}
|
|
3634
|
+
ORDER BY relative_path`,
|
|
3635
|
+
...scope ? [`%${scope}%`] : []
|
|
3636
|
+
);
|
|
3637
|
+
return rows.filter((row) => !db.isIgnored(row.relative_path)).flatMap((row) => getDefinitionsForFile(db, row.relative_path));
|
|
3638
|
+
}
|
|
3639
|
+
function getAllFunctionLikeDefinitions(db) {
|
|
3640
|
+
return getAllDefinitions(db).filter((definition) => definition.isFunctionLike);
|
|
3641
|
+
}
|
|
3642
|
+
function resolvePythonCallTarget(db, current, currentFileDefinitions, imports2, constructorBindings, receiverName, calleeName) {
|
|
3643
|
+
if (receiverName === "self" || receiverName === "cls") {
|
|
3644
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
|
|
3645
|
+
}
|
|
3646
|
+
if (receiverName) {
|
|
3647
|
+
const inferredType = constructorBindings.get(receiverName);
|
|
3648
|
+
if (inferredType) {
|
|
3649
|
+
const boundMethod = findDefinitionByName(currentFileDefinitions, calleeName, inferredType, ["function"]);
|
|
3650
|
+
if (boundMethod) {
|
|
2470
3651
|
return boundMethod;
|
|
2471
3652
|
}
|
|
2472
3653
|
for (const entry of imports2) {
|
|
@@ -2557,6 +3738,225 @@ function resolveJavaScriptCallTarget(db, current, currentFileDefinitions, import
|
|
|
2557
3738
|
}
|
|
2558
3739
|
return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
|
|
2559
3740
|
}
|
|
3741
|
+
function resolveSimpleSourceCallees(db, current, calls, bindings, limit) {
|
|
3742
|
+
const currentFileDefinitions = getDefinitionsForFile(db, current.relativePath);
|
|
3743
|
+
const currentDefinition = currentFileDefinitions.find((definition) => definition.symbolId === current.symbolId);
|
|
3744
|
+
if (!currentDefinition) {
|
|
3745
|
+
return [];
|
|
3746
|
+
}
|
|
3747
|
+
const rows = [];
|
|
3748
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3749
|
+
for (const call of calls) {
|
|
3750
|
+
const resolved = resolveSimpleSourceCallTarget(
|
|
3751
|
+
db,
|
|
3752
|
+
currentDefinition,
|
|
3753
|
+
currentFileDefinitions,
|
|
3754
|
+
bindings,
|
|
3755
|
+
call.receiverName,
|
|
3756
|
+
call.calleeName
|
|
3757
|
+
);
|
|
3758
|
+
if (!resolved || resolved.symbolId === current.symbolId || db.isIgnored(resolved.relativePath)) continue;
|
|
3759
|
+
const chunkId = 2e9 + call.line;
|
|
3760
|
+
const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
|
|
3761
|
+
if (seen.has(key)) continue;
|
|
3762
|
+
seen.add(key);
|
|
3763
|
+
rows.push({
|
|
3764
|
+
symbol: resolved.symbol,
|
|
3765
|
+
file: resolved.relativePath,
|
|
3766
|
+
chunkId
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
return applyLimit(rows, limit);
|
|
3770
|
+
}
|
|
3771
|
+
function resolveSimpleSourceCallTarget(db, current, currentFileDefinitions, bindings, receiverName, calleeName) {
|
|
3772
|
+
if (!receiverName) {
|
|
3773
|
+
const localMethod = findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
|
|
3774
|
+
if (localMethod) {
|
|
3775
|
+
return localMethod;
|
|
3776
|
+
}
|
|
3777
|
+
if (current.parentTypeName) {
|
|
3778
|
+
return findProjectDefinitionByTypeAndLeaf(db, current.parentTypeName, calleeName);
|
|
3779
|
+
}
|
|
3780
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
|
|
3781
|
+
}
|
|
3782
|
+
const normalizedReceiver = normalizeReceiverName(receiverName);
|
|
3783
|
+
const inferredType = bindings.get(normalizedReceiver) ?? inferTypeNameFromReceiver(db, normalizedReceiver);
|
|
3784
|
+
if (!inferredType) {
|
|
3785
|
+
return null;
|
|
3786
|
+
}
|
|
3787
|
+
return findProjectDefinitionByTypeAndLeaf(db, inferredType, calleeName);
|
|
3788
|
+
}
|
|
3789
|
+
function findProjectDefinitionByTypeAndLeaf(db, typeName, calleeName) {
|
|
3790
|
+
const definitions = getAllDefinitions(db).filter((definition) => definition.isFunctionLike || definition.symbol.endsWith("()."));
|
|
3791
|
+
const exact = definitions.find((definition) => definition.leaf === calleeName && (definition.parentTypeName === typeName || definition.symbol.includes(typeName)));
|
|
3792
|
+
if (exact) {
|
|
3793
|
+
return exact;
|
|
3794
|
+
}
|
|
3795
|
+
const normalizedType = normalizeLookupName(typeName);
|
|
3796
|
+
const normalizedMatch = definitions.find((definition) => definition.leaf === calleeName && normalizeLookupName(definition.parentTypeName ?? "").includes(normalizedType));
|
|
3797
|
+
if (normalizedMatch) {
|
|
3798
|
+
return normalizedMatch;
|
|
3799
|
+
}
|
|
3800
|
+
return findLooseProjectDefinitionByTypeAndLeaf(db, typeName, calleeName);
|
|
3801
|
+
}
|
|
3802
|
+
function inferTypeNameFromReceiver(db, receiverName) {
|
|
3803
|
+
const normalizedReceiver = normalizeLookupName(receiverName);
|
|
3804
|
+
if (!normalizedReceiver) {
|
|
3805
|
+
return null;
|
|
3806
|
+
}
|
|
3807
|
+
const candidates = getAllDefinitions(db).filter((definition) => definition.isTypeLike || definition.symbol.endsWith("#")).map((definition) => definition.leaf).filter((leaf) => leaf.length > 0);
|
|
3808
|
+
let best = null;
|
|
3809
|
+
for (const candidate of candidates) {
|
|
3810
|
+
const normalizedCandidate = normalizeLookupName(candidate);
|
|
3811
|
+
let score = 0;
|
|
3812
|
+
if (normalizedCandidate === normalizedReceiver) score += 100;
|
|
3813
|
+
if (normalizedCandidate.endsWith(normalizedReceiver)) score += 80;
|
|
3814
|
+
if (normalizedCandidate.includes(normalizedReceiver)) score += 40;
|
|
3815
|
+
if (normalizedReceiver.includes(normalizedCandidate)) score += 20;
|
|
3816
|
+
if (score > 0 && (!best || score > best.score || score === best.score && candidate.length < best.name.length)) {
|
|
3817
|
+
best = { name: candidate, score };
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
return best?.name ?? null;
|
|
3821
|
+
}
|
|
3822
|
+
function getSimpleSourceCalls(db, relativePath, startLine, endLine, opts = {}) {
|
|
3823
|
+
const source = getSourceText(db, relativePath);
|
|
3824
|
+
if (!source) {
|
|
3825
|
+
return [];
|
|
3826
|
+
}
|
|
3827
|
+
const lines = source.split("\n");
|
|
3828
|
+
const scoped = lines.slice(startLine, endLine + 1);
|
|
3829
|
+
const calls = [];
|
|
3830
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3831
|
+
const pattern = opts.allowInstanceVariables ? /(@?[A-Za-z_][\w]*)\s*(?:\.|::)\s*([A-Za-z_][\w!?=]*)\s*\(/g : /\b([A-Za-z_][\w]*)\s*(?:\.|::)\s*([A-Za-z_][\w]*)\s*\(/g;
|
|
3832
|
+
for (let index = 0; index < scoped.length; index++) {
|
|
3833
|
+
const rawLine = scoped[index] ?? "";
|
|
3834
|
+
for (const match of rawLine.matchAll(pattern)) {
|
|
3835
|
+
const receiverName = match[1];
|
|
3836
|
+
const calleeName = match[2];
|
|
3837
|
+
if (!receiverName || !calleeName) continue;
|
|
3838
|
+
const key = `${startLine + index}|${receiverName}|${calleeName}`;
|
|
3839
|
+
if (seen.has(key)) continue;
|
|
3840
|
+
seen.add(key);
|
|
3841
|
+
calls.push({
|
|
3842
|
+
receiverName,
|
|
3843
|
+
calleeName,
|
|
3844
|
+
line: startLine + index
|
|
3845
|
+
});
|
|
3846
|
+
}
|
|
3847
|
+
if (opts.allowBareMemberCalls) {
|
|
3848
|
+
for (const match of rawLine.matchAll(/(@?[A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w!?=]*)\b(?!\s*[:=])/g)) {
|
|
3849
|
+
const receiverName = match[1];
|
|
3850
|
+
const calleeName = match[2];
|
|
3851
|
+
if (!receiverName || !calleeName) continue;
|
|
3852
|
+
const key = `${startLine + index}|${receiverName}|${calleeName}`;
|
|
3853
|
+
if (seen.has(key)) continue;
|
|
3854
|
+
seen.add(key);
|
|
3855
|
+
calls.push({
|
|
3856
|
+
receiverName,
|
|
3857
|
+
calleeName,
|
|
3858
|
+
line: startLine + index
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
for (const match of rawLine.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
|
|
3863
|
+
const calleeName = match[1];
|
|
3864
|
+
const start = match.index ?? -1;
|
|
3865
|
+
if (!calleeName || start < 0) continue;
|
|
3866
|
+
const prefix = rawLine.slice(0, start).trimEnd();
|
|
3867
|
+
if (prefix.endsWith("def") || prefix.endsWith("fun") || prefix.endsWith("fn") || /\b(?:class|interface|trait|module|object)\s*$/.test(prefix)) {
|
|
3868
|
+
continue;
|
|
3869
|
+
}
|
|
3870
|
+
if (rawLine.slice(Math.max(0, start - 3), start).includes(".")) {
|
|
3871
|
+
continue;
|
|
3872
|
+
}
|
|
3873
|
+
calls.push({
|
|
3874
|
+
receiverName: null,
|
|
3875
|
+
calleeName,
|
|
3876
|
+
line: startLine + index
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
return calls;
|
|
3881
|
+
}
|
|
3882
|
+
function parseJavaFieldBindings(source) {
|
|
3883
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3884
|
+
for (const match of source.matchAll(/\b(?:private|protected|public)\s+(?:final\s+)?([A-Z][A-Za-z0-9_$<>?, ]*)\s+([A-Za-z_][\w$]*)\s*;/g)) {
|
|
3885
|
+
const typeName = stripGenericType(match[1]);
|
|
3886
|
+
const localName = match[2];
|
|
3887
|
+
if (typeName && localName) {
|
|
3888
|
+
bindings.set(localName, typeName);
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
return bindings;
|
|
3892
|
+
}
|
|
3893
|
+
function parseKotlinFieldBindings(source) {
|
|
3894
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3895
|
+
for (const match of source.matchAll(/\b(?:private|protected|public)?\s*(?:val|var)\s+([A-Za-z_][\w]*)\s*:\s*([A-Z][A-Za-z0-9_.<>?]*)/g)) {
|
|
3896
|
+
const localName = match[1];
|
|
3897
|
+
const typeName = stripGenericType(match[2]);
|
|
3898
|
+
if (typeName && localName) {
|
|
3899
|
+
bindings.set(localName, typeName);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
return bindings;
|
|
3903
|
+
}
|
|
3904
|
+
function parseCppReceiverBindings(source) {
|
|
3905
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3906
|
+
const constructorMatch = source.match(/RunCoordinator::RunCoordinator\s*\(([\s\S]*?)\)\s*(?::\s*([\s\S]*?))?\s*\{/);
|
|
3907
|
+
if (!constructorMatch) {
|
|
3908
|
+
return bindings;
|
|
3909
|
+
}
|
|
3910
|
+
const params = constructorMatch[1] ?? "";
|
|
3911
|
+
const initializers = constructorMatch[2] ?? "";
|
|
3912
|
+
const parameterTypes = /* @__PURE__ */ new Map();
|
|
3913
|
+
for (const param of params.split(",")) {
|
|
3914
|
+
const trimmed = param.trim();
|
|
3915
|
+
const match = trimmed.match(/(.+?)\s+([A-Za-z_][\w]*)$/);
|
|
3916
|
+
if (!match) continue;
|
|
3917
|
+
const [, rawType, rawName] = match;
|
|
3918
|
+
if (!rawType || !rawName) continue;
|
|
3919
|
+
const typeName = stripGenericType(rawType.replace(/[&*]+/g, " ").replace(/\bconst\b/g, " ").trim());
|
|
3920
|
+
if (!typeName) continue;
|
|
3921
|
+
parameterTypes.set(normalizeReceiverName(rawName), typeName);
|
|
3922
|
+
}
|
|
3923
|
+
for (const initializer of initializers.split(",")) {
|
|
3924
|
+
const trimmed = initializer.trim();
|
|
3925
|
+
const match = trimmed.match(/([A-Za-z_][\w]*)\s*\(\s*([A-Za-z_][\w]*)\s*\)$/);
|
|
3926
|
+
if (!match) continue;
|
|
3927
|
+
const [, fieldName, sourceName] = match;
|
|
3928
|
+
if (!fieldName || !sourceName) continue;
|
|
3929
|
+
const typeName = parameterTypes.get(normalizeReceiverName(sourceName));
|
|
3930
|
+
if (!typeName) continue;
|
|
3931
|
+
bindings.set(normalizeReceiverName(fieldName), typeName);
|
|
3932
|
+
}
|
|
3933
|
+
return bindings;
|
|
3934
|
+
}
|
|
3935
|
+
function parseRubyReceiverBindings(db, source) {
|
|
3936
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3937
|
+
for (const match of source.matchAll(/@([A-Za-z_][\w]*)\s*=\s*([A-Za-z_][\w]*)/g)) {
|
|
3938
|
+
const ivarName = `@${match[1]}`;
|
|
3939
|
+
const localName = match[2];
|
|
3940
|
+
const typeName = inferTypeNameFromReceiver(db, localName ?? match[1] ?? "");
|
|
3941
|
+
if (typeName) {
|
|
3942
|
+
bindings.set(ivarName, typeName);
|
|
3943
|
+
bindings.set(match[1], typeName);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
return bindings;
|
|
3947
|
+
}
|
|
3948
|
+
function stripGenericType(typeName) {
|
|
3949
|
+
return (typeName ?? "").replace(/<.*$/, "").replace(/^.*\./, "").trim();
|
|
3950
|
+
}
|
|
3951
|
+
function normalizeReceiverName(receiverName) {
|
|
3952
|
+
return receiverName.replace(/^@/, "").replace(/^this(?:\.|->)/, "").replace(/^_+/, "").replace(/_+$/, "").trim();
|
|
3953
|
+
}
|
|
3954
|
+
function normalizeLookupName(value) {
|
|
3955
|
+
return value.replace(/[^A-Za-z0-9]+/g, "").toLowerCase();
|
|
3956
|
+
}
|
|
3957
|
+
function isLikelyTestPath(relativePath) {
|
|
3958
|
+
return /(^|\/)(tests?|__tests__)\//.test(relativePath) || /\.(?:spec|test)\./.test(relativePath) || /_test\./.test(relativePath);
|
|
3959
|
+
}
|
|
2560
3960
|
function findDefinitionByName(definitions, leaf, parentType, preference) {
|
|
2561
3961
|
const candidates = definitions.filter((definition) => definition.leaf === leaf && definition.parentTypeName === parentType);
|
|
2562
3962
|
if (candidates.length === 0) {
|
|
@@ -2570,6 +3970,58 @@ function findDefinitionByName(definitions, leaf, parentType, preference) {
|
|
|
2570
3970
|
}
|
|
2571
3971
|
return candidates[0] ?? null;
|
|
2572
3972
|
}
|
|
3973
|
+
function findLooseProjectDefinitionByTypeAndLeaf(db, typeName, calleeName) {
|
|
3974
|
+
const rows = db.all(
|
|
3975
|
+
`SELECT
|
|
3976
|
+
gs.id,
|
|
3977
|
+
gs.symbol,
|
|
3978
|
+
c.document_id,
|
|
3979
|
+
MIN(c.start_line) AS start_line,
|
|
3980
|
+
MAX(c.end_line) AS end_line,
|
|
3981
|
+
d.relative_path,
|
|
3982
|
+
gs.kind,
|
|
3983
|
+
gs.documentation,
|
|
3984
|
+
gs.enclosing_symbol
|
|
3985
|
+
FROM global_symbols gs
|
|
3986
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
3987
|
+
JOIN chunks c ON c.id = m.chunk_id
|
|
3988
|
+
JOIN documents d ON d.id = c.document_id
|
|
3989
|
+
WHERE gs.symbol LIKE ?
|
|
3990
|
+
AND gs.symbol LIKE ?
|
|
3991
|
+
${db.pathExclusionsFor("d")}
|
|
3992
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path, gs.kind, gs.documentation, gs.enclosing_symbol`,
|
|
3993
|
+
`%${typeName}%`,
|
|
3994
|
+
`%${calleeName}%`
|
|
3995
|
+
);
|
|
3996
|
+
const normalizedType = normalizeLookupName(typeName);
|
|
3997
|
+
const candidates = rows.filter((row2) => leafName(row2.symbol) === calleeName).filter((row2) => {
|
|
3998
|
+
const parentType = parentTypeName(row2.symbol);
|
|
3999
|
+
return parentType === typeName || row2.symbol.includes(typeName) || normalizeLookupName(parentType ?? "").includes(normalizedType);
|
|
4000
|
+
}).sort((left, right) => {
|
|
4001
|
+
const leftTest = isLikelyTestPath(left.relative_path) ? 1 : 0;
|
|
4002
|
+
const rightTest = isLikelyTestPath(right.relative_path) ? 1 : 0;
|
|
4003
|
+
return leftTest - rightTest || left.relative_path.localeCompare(right.relative_path) || left.start_line - right.start_line;
|
|
4004
|
+
});
|
|
4005
|
+
const row = candidates[0];
|
|
4006
|
+
if (!row) {
|
|
4007
|
+
return null;
|
|
4008
|
+
}
|
|
4009
|
+
return {
|
|
4010
|
+
symbolId: row.id,
|
|
4011
|
+
symbol: row.symbol,
|
|
4012
|
+
documentId: row.document_id,
|
|
4013
|
+
startLine: row.start_line,
|
|
4014
|
+
endLine: row.end_line,
|
|
4015
|
+
relativePath: row.relative_path,
|
|
4016
|
+
leaf: leafName(row.symbol),
|
|
4017
|
+
parentTypeName: parentTypeName(row.symbol),
|
|
4018
|
+
isFunctionLike: isFunctionLikeSymbol(row.symbol),
|
|
4019
|
+
isTypeLike: leafSuffix(row.symbol) === "type",
|
|
4020
|
+
kind: row.kind,
|
|
4021
|
+
documentation: row.documentation,
|
|
4022
|
+
enclosingSymbol: row.enclosing_symbol
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
2573
4025
|
function hasUniqueLeafDefinition(db, leaf, symbolId) {
|
|
2574
4026
|
const rows = db.all(
|
|
2575
4027
|
`SELECT id, symbol
|
|
@@ -2615,6 +4067,76 @@ function isJavaScriptDocument(db, relativePath) {
|
|
|
2615
4067
|
);
|
|
2616
4068
|
return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
|
|
2617
4069
|
}
|
|
4070
|
+
function isJavaDocument(db, relativePath) {
|
|
4071
|
+
const row = db.get(
|
|
4072
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4073
|
+
relativePath
|
|
4074
|
+
);
|
|
4075
|
+
return row?.language === "java" || relativePath.endsWith(".java");
|
|
4076
|
+
}
|
|
4077
|
+
function isKotlinDocument(db, relativePath) {
|
|
4078
|
+
const row = db.get(
|
|
4079
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4080
|
+
relativePath
|
|
4081
|
+
);
|
|
4082
|
+
return row?.language === "kotlin" || relativePath.endsWith(".kt") || relativePath.endsWith(".kts");
|
|
4083
|
+
}
|
|
4084
|
+
function isScalaDocument(db, relativePath) {
|
|
4085
|
+
const row = db.get(
|
|
4086
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4087
|
+
relativePath
|
|
4088
|
+
);
|
|
4089
|
+
return row?.language === "scala" || relativePath.endsWith(".scala");
|
|
4090
|
+
}
|
|
4091
|
+
function isCSharpDocument(db, relativePath) {
|
|
4092
|
+
const row = db.get(
|
|
4093
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4094
|
+
relativePath
|
|
4095
|
+
);
|
|
4096
|
+
return row?.language === "C#" || relativePath.endsWith(".cs");
|
|
4097
|
+
}
|
|
4098
|
+
function isVisualBasicDocument(db, relativePath) {
|
|
4099
|
+
const row = db.get(
|
|
4100
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4101
|
+
relativePath
|
|
4102
|
+
);
|
|
4103
|
+
return row?.language === "Visual Basic" || relativePath.endsWith(".vb");
|
|
4104
|
+
}
|
|
4105
|
+
function isCppDocument(db, relativePath) {
|
|
4106
|
+
const row = db.get(
|
|
4107
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4108
|
+
relativePath
|
|
4109
|
+
);
|
|
4110
|
+
return row?.language === "CPP" || /\.(?:cc|cpp|cxx|hpp|hh|hxx)$/.test(relativePath);
|
|
4111
|
+
}
|
|
4112
|
+
function isRustDocument(db, relativePath) {
|
|
4113
|
+
const row = db.get(
|
|
4114
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4115
|
+
relativePath
|
|
4116
|
+
);
|
|
4117
|
+
return row?.language === "Rust" || relativePath.endsWith(".rs");
|
|
4118
|
+
}
|
|
4119
|
+
function isRubyDocument(db, relativePath) {
|
|
4120
|
+
const row = db.get(
|
|
4121
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4122
|
+
relativePath
|
|
4123
|
+
);
|
|
4124
|
+
return row?.language === "ruby" || relativePath.endsWith(".rb");
|
|
4125
|
+
}
|
|
4126
|
+
function isDartDocument(db, relativePath) {
|
|
4127
|
+
const row = db.get(
|
|
4128
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4129
|
+
relativePath
|
|
4130
|
+
);
|
|
4131
|
+
return row?.language === "Dart" || relativePath.endsWith(".dart");
|
|
4132
|
+
}
|
|
4133
|
+
function isPhpDocument(db, relativePath) {
|
|
4134
|
+
const row = db.get(
|
|
4135
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
4136
|
+
relativePath
|
|
4137
|
+
);
|
|
4138
|
+
return row?.language === "PHP" || relativePath.endsWith(".php");
|
|
4139
|
+
}
|
|
2618
4140
|
function applyLimit(values, limit) {
|
|
2619
4141
|
return typeof limit === "number" ? values.slice(0, limit) : values;
|
|
2620
4142
|
}
|
|
@@ -2635,7 +4157,14 @@ function resolveDocumentCandidates(db, filePattern, opts) {
|
|
|
2635
4157
|
score: scoreDocumentPath(row.relative_path, normalizedPattern)
|
|
2636
4158
|
})).filter((row) => row.score > 0).sort((a, b) => b.score - a.score || a.relativePath.localeCompare(b.relativePath));
|
|
2637
4159
|
if (scored.length === 0) {
|
|
2638
|
-
|
|
4160
|
+
const symbolMatch = findFirstSymbolMatch(db, filePattern);
|
|
4161
|
+
if (!symbolMatch || db.isIgnored(symbolMatch.relativePath)) {
|
|
4162
|
+
return [];
|
|
4163
|
+
}
|
|
4164
|
+
return [{
|
|
4165
|
+
relativePath: symbolMatch.relativePath,
|
|
4166
|
+
score: 700
|
|
4167
|
+
}];
|
|
2639
4168
|
}
|
|
2640
4169
|
const exactish = scored.filter((row) => row.score >= 800);
|
|
2641
4170
|
if (exactish.length > 0) {
|
|
@@ -2645,8 +4174,8 @@ function resolveDocumentCandidates(db, filePattern, opts) {
|
|
|
2645
4174
|
}
|
|
2646
4175
|
function scoreDocumentPath(relativePath, rawPattern) {
|
|
2647
4176
|
const normalizedPath = normalizeLookupPath(relativePath);
|
|
2648
|
-
const pathBase =
|
|
2649
|
-
const patternBase =
|
|
4177
|
+
const pathBase = basename2(normalizedPath);
|
|
4178
|
+
const patternBase = basename2(rawPattern);
|
|
2650
4179
|
let score = 0;
|
|
2651
4180
|
if (normalizedPath === rawPattern) score += 1200;
|
|
2652
4181
|
if (normalizedPath.endsWith(`/${rawPattern}`)) score += 1100;
|
|
@@ -2674,62 +4203,69 @@ function symbols(db, filePattern) {
|
|
|
2674
4203
|
if (resolvedPaths.length === 0) {
|
|
2675
4204
|
return [];
|
|
2676
4205
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
...resolvedPaths
|
|
2693
|
-
);
|
|
2694
|
-
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
2695
|
-
startLine: r.start_line,
|
|
2696
|
-
endLine: r.end_line,
|
|
2697
|
-
symbol: r.symbol,
|
|
2698
|
-
shortName: shortenSymbol(r.symbol),
|
|
2699
|
-
signature: cleanSignature(r.sig)
|
|
2700
|
-
}));
|
|
4206
|
+
return resolvedPaths.flatMap((relativePath) => getDefinitionsForFile(db, relativePath)).filter((row) => !db.isIgnored(row.relativePath)).map((row) => {
|
|
4207
|
+
const docRow = db.get(
|
|
4208
|
+
`SELECT REPLACE(SUBSTR(documentation, INSTR(documentation, '|') + 1), char(10), ' ') AS sig
|
|
4209
|
+
FROM global_symbols
|
|
4210
|
+
WHERE id = ?`,
|
|
4211
|
+
row.symbolId
|
|
4212
|
+
);
|
|
4213
|
+
return {
|
|
4214
|
+
startLine: row.startLine,
|
|
4215
|
+
endLine: row.endLine,
|
|
4216
|
+
symbol: row.symbol,
|
|
4217
|
+
shortName: shortenSymbol(row.symbol),
|
|
4218
|
+
signature: cleanSignature(docRow?.sig ?? null)
|
|
4219
|
+
};
|
|
4220
|
+
});
|
|
2701
4221
|
}
|
|
2702
4222
|
|
|
2703
4223
|
// src/queries/methods.ts
|
|
4224
|
+
import { basename as basename3 } from "path";
|
|
2704
4225
|
function methods(db, className) {
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
startLine: r.start_line,
|
|
2718
|
-
endLine: r.end_line,
|
|
2719
|
-
name: leafName(r.symbol)
|
|
4226
|
+
const classMatch = findFirstSymbolMatch(db, className);
|
|
4227
|
+
if (!classMatch) {
|
|
4228
|
+
return [];
|
|
4229
|
+
}
|
|
4230
|
+
const ownerName = leafName(classMatch.symbol);
|
|
4231
|
+
const definitions = getDefinitionsForFile(db, classMatch.relativePath).filter((definition) => isCallableSymbol(definition.symbol));
|
|
4232
|
+
const directMethods = definitions.filter((definition) => definition.parentTypeName === ownerName || definition.symbol.includes(ownerName));
|
|
4233
|
+
const fileScopedMethods = directMethods.length > 0 ? directMethods : stripExtension(basename3(classMatch.relativePath)) === ownerName ? definitions.filter((definition) => definition.symbol.includes("<invalid-global-code>")) : [];
|
|
4234
|
+
return fileScopedMethods.map((definition) => ({
|
|
4235
|
+
startLine: definition.startLine,
|
|
4236
|
+
endLine: definition.endLine,
|
|
4237
|
+
name: leafName(definition.symbol)
|
|
2720
4238
|
}));
|
|
2721
4239
|
}
|
|
4240
|
+
function isCallableSymbol(rawSymbol) {
|
|
4241
|
+
return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
|
|
4242
|
+
}
|
|
4243
|
+
function stripExtension(relativePath) {
|
|
4244
|
+
return relativePath.replace(/\.[^.]+$/, "");
|
|
4245
|
+
}
|
|
2722
4246
|
|
|
2723
4247
|
// src/queries/refs.ts
|
|
2724
4248
|
function refs(db, symbolPattern) {
|
|
2725
4249
|
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
2726
4250
|
if (match) {
|
|
4251
|
+
const includeDefinitionSite = !isFunctionLikeSymbol(match.symbol);
|
|
4252
|
+
const definitionRows = includeDefinitionSite ? [{
|
|
4253
|
+
relativePath: match.relativePath,
|
|
4254
|
+
line: match.startLine
|
|
4255
|
+
}] : [];
|
|
2727
4256
|
const sourceSites = getSourceReferenceSites(db, match).filter((site) => !db.isIgnored(site.file)).map((site) => ({
|
|
2728
4257
|
relativePath: site.file,
|
|
2729
4258
|
line: site.line
|
|
2730
4259
|
}));
|
|
2731
4260
|
if (sourceSites.length > 0) {
|
|
2732
|
-
|
|
4261
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
4262
|
+
const rows2 = [...definitionRows, ...sourceSites, ...getRubySemanticRefs(db, match)].filter((site) => {
|
|
4263
|
+
const key = `${site.relativePath}:${site.line}`;
|
|
4264
|
+
if (seen2.has(key)) return false;
|
|
4265
|
+
seen2.add(key);
|
|
4266
|
+
return true;
|
|
4267
|
+
});
|
|
4268
|
+
return rows2;
|
|
2733
4269
|
}
|
|
2734
4270
|
}
|
|
2735
4271
|
const rows = db.all(
|
|
@@ -2744,10 +4280,70 @@ function refs(db, symbolPattern) {
|
|
|
2744
4280
|
ORDER BY d.relative_path, c.start_line`,
|
|
2745
4281
|
match?.symbolId ?? -1
|
|
2746
4282
|
);
|
|
2747
|
-
|
|
4283
|
+
const referenceRows = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
2748
4284
|
relativePath: r.relative_path,
|
|
2749
4285
|
line: r.start_line
|
|
2750
4286
|
}));
|
|
4287
|
+
if (!match || db.isIgnored(match.relativePath) || isFunctionLikeSymbol(match.symbol)) {
|
|
4288
|
+
return referenceRows;
|
|
4289
|
+
}
|
|
4290
|
+
const seen = new Set(referenceRows.map((row) => `${row.relativePath}:${row.line}`));
|
|
4291
|
+
if (!seen.has(`${match.relativePath}:${match.startLine}`)) {
|
|
4292
|
+
referenceRows.unshift({
|
|
4293
|
+
relativePath: match.relativePath,
|
|
4294
|
+
line: match.startLine
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
for (const row of getRubySemanticRefs(db, match)) {
|
|
4298
|
+
const key = `${row.relativePath}:${row.line}`;
|
|
4299
|
+
if (seen.has(key)) continue;
|
|
4300
|
+
seen.add(key);
|
|
4301
|
+
referenceRows.push(row);
|
|
4302
|
+
}
|
|
4303
|
+
return referenceRows;
|
|
4304
|
+
}
|
|
4305
|
+
function getRubySemanticRefs(db, match) {
|
|
4306
|
+
if (!match.relativePath.endsWith(".rb")) {
|
|
4307
|
+
return [];
|
|
4308
|
+
}
|
|
4309
|
+
const tokens = rubyReferenceTokens(match.symbol);
|
|
4310
|
+
if (tokens.length === 0) {
|
|
4311
|
+
return [];
|
|
4312
|
+
}
|
|
4313
|
+
const rows = db.all(
|
|
4314
|
+
`SELECT relative_path
|
|
4315
|
+
FROM documents
|
|
4316
|
+
WHERE relative_path LIKE '%.rb'
|
|
4317
|
+
${db.pathExclusionsFor("documents")}
|
|
4318
|
+
ORDER BY relative_path`
|
|
4319
|
+
);
|
|
4320
|
+
const results = [];
|
|
4321
|
+
for (const row of rows) {
|
|
4322
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
4323
|
+
const source = getSourceText(db, row.relative_path);
|
|
4324
|
+
if (!source) continue;
|
|
4325
|
+
const lines = source.split("\n");
|
|
4326
|
+
for (let index = 0; index < lines.length; index++) {
|
|
4327
|
+
const line = lines[index] ?? "";
|
|
4328
|
+
if (tokens.some((token) => new RegExp(`@${token}\\b|\\b${token}:`).test(line))) {
|
|
4329
|
+
results.push({
|
|
4330
|
+
relativePath: row.relative_path,
|
|
4331
|
+
line: index
|
|
4332
|
+
});
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
return results;
|
|
4337
|
+
}
|
|
4338
|
+
function rubyReferenceTokens(rawSymbol) {
|
|
4339
|
+
const leaf = rawSymbol.split(":").pop() ?? rawSymbol;
|
|
4340
|
+
const snake = leaf.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase().replace(/^_+|_+$/g, "");
|
|
4341
|
+
const parts = snake.split("_").filter(Boolean);
|
|
4342
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4343
|
+
if (snake) candidates.add(snake);
|
|
4344
|
+
if (parts.length >= 1) candidates.add(parts[parts.length - 1]);
|
|
4345
|
+
if (parts.length >= 2) candidates.add(parts.slice(-2).join("_"));
|
|
4346
|
+
return [...candidates];
|
|
2751
4347
|
}
|
|
2752
4348
|
|
|
2753
4349
|
// src/queries/trace.ts
|
|
@@ -2756,24 +4352,22 @@ function trace(db, symbolPattern) {
|
|
|
2756
4352
|
if (!match) {
|
|
2757
4353
|
return { definitions: [], referencedBy: [] };
|
|
2758
4354
|
}
|
|
2759
|
-
const
|
|
2760
|
-
`SELECT
|
|
4355
|
+
const definitionMeta = db.get(
|
|
4356
|
+
`SELECT
|
|
2761
4357
|
gs.display_name,
|
|
2762
4358
|
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
2763
4359
|
FROM global_symbols gs
|
|
2764
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2765
|
-
JOIN documents d ON der.document_id = d.id
|
|
2766
4360
|
WHERE gs.id = ?
|
|
2767
|
-
ORDER BY d.relative_path, der.start_line
|
|
2768
4361
|
LIMIT 10`,
|
|
2769
4362
|
match.symbolId
|
|
2770
4363
|
);
|
|
2771
|
-
const definitions =
|
|
2772
|
-
relativePath:
|
|
2773
|
-
startLine:
|
|
2774
|
-
endLine:
|
|
2775
|
-
signature: buildTraceSignature(
|
|
2776
|
-
|
|
4364
|
+
const definitions = db.isIgnored(match.relativePath) ? [] : [{
|
|
4365
|
+
relativePath: match.relativePath,
|
|
4366
|
+
startLine: match.startLine,
|
|
4367
|
+
endLine: match.endLine,
|
|
4368
|
+
signature: buildTraceSignature(definitionMeta?.sig ?? null, definitionMeta?.display_name ?? null, match.symbol),
|
|
4369
|
+
source: definitionSource(db, match.relativePath, match.startLine, match.endLine)
|
|
4370
|
+
}];
|
|
2777
4371
|
const sourceSites = getSourceReferenceSites(db, match);
|
|
2778
4372
|
const referencedBy = sourceSites.length > 0 ? sourceSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
|
|
2779
4373
|
relativePath: site.file,
|
|
@@ -2806,6 +4400,15 @@ function trace(db, symbolPattern) {
|
|
|
2806
4400
|
}));
|
|
2807
4401
|
return { definitions, referencedBy };
|
|
2808
4402
|
}
|
|
4403
|
+
function definitionSource(db, relativePath, startLine, endLine) {
|
|
4404
|
+
const source = getSourceText(db, relativePath);
|
|
4405
|
+
if (!source) {
|
|
4406
|
+
return null;
|
|
4407
|
+
}
|
|
4408
|
+
const lines = source.split("\n");
|
|
4409
|
+
const slice2 = lines.slice(startLine, endLine + 1).join("\n").trimEnd();
|
|
4410
|
+
return slice2.length > 0 ? slice2 : null;
|
|
4411
|
+
}
|
|
2809
4412
|
function buildTraceSignature(signature, displayName, rawSymbol) {
|
|
2810
4413
|
const cleaned = cleanSignature(signature);
|
|
2811
4414
|
if (cleaned && !looksBogusSignature(cleaned)) {
|
|
@@ -2946,22 +4549,54 @@ function surface(db, modulePattern) {
|
|
|
2946
4549
|
JOIN chunks c ON m.chunk_id = c.id
|
|
2947
4550
|
JOIN documents d1 ON c.document_id = d1.id
|
|
2948
4551
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
4552
|
+
WHERE d1.relative_path NOT IN (${placeholders})
|
|
4553
|
+
AND (
|
|
4554
|
+
EXISTS (
|
|
4555
|
+
SELECT 1
|
|
4556
|
+
FROM defn_enclosing_ranges der
|
|
4557
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
4558
|
+
WHERE der.symbol_id = gs.id
|
|
4559
|
+
AND d2.relative_path IN (${placeholders})
|
|
4560
|
+
)
|
|
4561
|
+
OR EXISTS (
|
|
4562
|
+
SELECT 1
|
|
4563
|
+
FROM mentions def_m
|
|
4564
|
+
JOIN chunks def_c ON def_m.chunk_id = def_c.id
|
|
4565
|
+
JOIN documents d2 ON def_c.document_id = d2.id
|
|
4566
|
+
WHERE def_m.symbol_id = gs.id
|
|
4567
|
+
AND def_m.role = 1
|
|
4568
|
+
AND d2.relative_path IN (${placeholders})
|
|
4569
|
+
)
|
|
4570
|
+
)
|
|
2954
4571
|
AND m.role != 1
|
|
4572
|
+
AND ${db.localSymbolPredicate}
|
|
4573
|
+
${db.pathExclusionsFor("d1")}
|
|
2955
4574
|
ORDER BY d1.relative_path`,
|
|
2956
4575
|
...matchedPaths,
|
|
4576
|
+
...matchedPaths,
|
|
2957
4577
|
...matchedPaths
|
|
2958
4578
|
);
|
|
2959
|
-
|
|
4579
|
+
const exposedDefinitions = matchedPaths.flatMap(
|
|
4580
|
+
(relativePath) => getDefinitionsForFile(db, relativePath).filter((definition) => isCallableSymbol2(definition.symbol)).map((definition) => ({
|
|
4581
|
+
relative_path: relativePath,
|
|
4582
|
+
symbol: definition.symbol
|
|
4583
|
+
}))
|
|
4584
|
+
);
|
|
4585
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4586
|
+
return [...rows, ...exposedDefinitions].filter((r) => !db.isIgnored(r.relative_path)).filter((r) => {
|
|
4587
|
+
const key = `${r.relative_path}|${r.symbol}`;
|
|
4588
|
+
if (seen.has(key)) return false;
|
|
4589
|
+
seen.add(key);
|
|
4590
|
+
return true;
|
|
4591
|
+
}).map((r) => ({
|
|
2960
4592
|
consumer: r.relative_path,
|
|
2961
4593
|
symbol: r.symbol,
|
|
2962
4594
|
shortName: shortenSymbol(r.symbol)
|
|
2963
4595
|
}));
|
|
2964
4596
|
}
|
|
4597
|
+
function isCallableSymbol2(rawSymbol) {
|
|
4598
|
+
return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
|
|
4599
|
+
}
|
|
2965
4600
|
|
|
2966
4601
|
// src/entry-surfaces.ts
|
|
2967
4602
|
var liveBarrelCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2979,11 +4614,11 @@ function isWorkerEntrySurface(path2) {
|
|
|
2979
4614
|
function isStructuralEntrySurface(path2) {
|
|
2980
4615
|
const normalized = normalizePath2(path2);
|
|
2981
4616
|
const segments = normalized.split("/");
|
|
2982
|
-
const
|
|
2983
|
-
if (
|
|
4617
|
+
const basename5 = segments[segments.length - 1] ?? normalized;
|
|
4618
|
+
if (basename5 === "cli.ts" || basename5 === "cli.js" || basename5 === "postinstall.ts" || basename5 === "postinstall.js" || basename5 === "main.ts" || basename5 === "main.js" || basename5 === "main.rs" || basename5 === "main.go" || basename5 === "main.py") {
|
|
2984
4619
|
return true;
|
|
2985
4620
|
}
|
|
2986
|
-
if (
|
|
4621
|
+
if (basename5 === "index.ts" || basename5 === "index.js") {
|
|
2987
4622
|
return segments.length <= 2;
|
|
2988
4623
|
}
|
|
2989
4624
|
return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
|
|
@@ -3046,62 +4681,49 @@ function dead(db, opts = {}) {
|
|
|
3046
4681
|
skipBarrels = false,
|
|
3047
4682
|
includeMembers = false
|
|
3048
4683
|
} = opts;
|
|
3049
|
-
const
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
if (scope) {
|
|
3054
|
-
params.push(`%${scope}%`);
|
|
3055
|
-
}
|
|
3056
|
-
if (!includeTests) {
|
|
3057
|
-
testFileExclusions = `
|
|
3058
|
-
AND ${testFileExclusionSql("d", TEST_SUPPORT_PATH_PATTERNS)}
|
|
3059
|
-
`;
|
|
3060
|
-
}
|
|
3061
|
-
if (!includeMembers) {
|
|
3062
|
-
memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;
|
|
3063
|
-
}
|
|
3064
|
-
if (skipBarrels) {
|
|
3065
|
-
const inactiveBarrelPaths = getInactiveBarrelPaths(db);
|
|
3066
|
-
if (inactiveBarrelPaths.length > 0) {
|
|
3067
|
-
barrelExclusions = `AND ref_d.relative_path NOT IN (${inactiveBarrelPaths.map(() => "?").join(", ")})`;
|
|
3068
|
-
params.push(...inactiveBarrelPaths);
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
const sql = `
|
|
3072
|
-
SELECT
|
|
4684
|
+
const inactiveBarrelPaths = skipBarrels ? new Set(getInactiveBarrelPaths(db)) : /* @__PURE__ */ new Set();
|
|
4685
|
+
const referenceRows = db.all(
|
|
4686
|
+
`SELECT
|
|
4687
|
+
m.symbol_id,
|
|
3073
4688
|
d.relative_path,
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
4689
|
+
COUNT(*) AS ref_count
|
|
4690
|
+
FROM mentions m
|
|
4691
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
4692
|
+
JOIN documents d ON c.document_id = d.id
|
|
4693
|
+
WHERE m.role != 1
|
|
4694
|
+
${db.pathExclusionsFor("d")}
|
|
4695
|
+
GROUP BY m.symbol_id, d.relative_path`
|
|
4696
|
+
);
|
|
4697
|
+
const referencesBySymbol = /* @__PURE__ */ new Map();
|
|
4698
|
+
for (const row of referenceRows) {
|
|
4699
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
4700
|
+
if (inactiveBarrelPaths.has(row.relative_path)) continue;
|
|
4701
|
+
let refsForSymbol = referencesBySymbol.get(row.symbol_id);
|
|
4702
|
+
if (!refsForSymbol) {
|
|
4703
|
+
refsForSymbol = /* @__PURE__ */ new Map();
|
|
4704
|
+
referencesBySymbol.set(row.symbol_id, refsForSymbol);
|
|
4705
|
+
}
|
|
4706
|
+
refsForSymbol.set(row.relative_path, row.ref_count);
|
|
4707
|
+
}
|
|
4708
|
+
const definitions = getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).filter((definition) => !isModuleLikeSymbol(definition.symbol)).filter((definition) => looksValueLikeDefinition(definition.symbol)).filter((definition) => definition.isFunctionLike || !definition.enclosingSymbol || !looksValueLikeDefinition(definition.enclosingSymbol)).filter((definition) => includeTests || passesTestFileFilter(definition.relativePath)).filter((definition) => includeMembers || looksValueLikeDefinition(definition.symbol)).filter((definition) => definition.endLine - definition.startLine + 1 >= minLoc);
|
|
4709
|
+
const rows = definitions.map((definition) => {
|
|
4710
|
+
const refMap = referencesBySymbol.get(definition.symbolId) ?? /* @__PURE__ */ new Map();
|
|
4711
|
+
const sameFileRefs = refMap.get(definition.relativePath) ?? 0;
|
|
4712
|
+
let crossFileRefs = 0;
|
|
4713
|
+
for (const [relativePath, count] of refMap) {
|
|
4714
|
+
if (relativePath === definition.relativePath) continue;
|
|
4715
|
+
crossFileRefs += count;
|
|
4716
|
+
}
|
|
4717
|
+
return {
|
|
4718
|
+
relative_path: definition.relativePath,
|
|
4719
|
+
start_line: definition.startLine,
|
|
4720
|
+
end_line: definition.endLine,
|
|
4721
|
+
loc: definition.endLine - definition.startLine + 1,
|
|
4722
|
+
symbol: definition.symbol,
|
|
4723
|
+
same_file_refs: sameFileRefs,
|
|
4724
|
+
cross_file_refs: crossFileRefs
|
|
4725
|
+
};
|
|
4726
|
+
}).filter((row) => row.cross_file_refs === 0).sort((a, b) => b.loc - a.loc || a.relative_path.localeCompare(b.relative_path) || a.start_line - b.start_line);
|
|
3105
4727
|
let deadCodeCount = 0;
|
|
3106
4728
|
let fileInternalCount = 0;
|
|
3107
4729
|
let totalLoc = 0;
|
|
@@ -3129,6 +4751,17 @@ function dead(db, opts = {}) {
|
|
|
3129
4751
|
totalLoc
|
|
3130
4752
|
};
|
|
3131
4753
|
}
|
|
4754
|
+
function passesTestFileFilter(relativePath) {
|
|
4755
|
+
const patterns = [.../* @__PURE__ */ new Set([...TEST_FILE_PATTERNS, ...TEST_SUPPORT_PATH_PATTERNS])];
|
|
4756
|
+
return patterns.every((pattern) => !likeMatches(relativePath, pattern));
|
|
4757
|
+
}
|
|
4758
|
+
function likeMatches(value, pattern) {
|
|
4759
|
+
const regex = new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".")}$`);
|
|
4760
|
+
return regex.test(value);
|
|
4761
|
+
}
|
|
4762
|
+
function looksValueLikeDefinition(rawSymbol) {
|
|
4763
|
+
return isFunctionLikeSymbol(rawSymbol) || rawSymbol.endsWith("().") || rawSymbol.endsWith(".");
|
|
4764
|
+
}
|
|
3132
4765
|
|
|
3133
4766
|
// src/queries/hotspots.ts
|
|
3134
4767
|
function hotspots(db, opts = {}) {
|
|
@@ -3155,13 +4788,26 @@ function hotspots(db, opts = {}) {
|
|
|
3155
4788
|
LIMIT ?`,
|
|
3156
4789
|
limit
|
|
3157
4790
|
);
|
|
3158
|
-
|
|
4791
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
3159
4792
|
symbol: r.symbol,
|
|
3160
4793
|
shortName: shortenSymbol(r.symbol),
|
|
3161
4794
|
refCount: r.ref_count,
|
|
3162
4795
|
fileCount: r.file_count,
|
|
3163
4796
|
definedIn: r.defined_in
|
|
3164
4797
|
}));
|
|
4798
|
+
if (indexedResults.length > 0) {
|
|
4799
|
+
return indexedResults;
|
|
4800
|
+
}
|
|
4801
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
|
|
4802
|
+
const callerRows = getCallerRowsForSymbol(db, definition, { limit: 500 });
|
|
4803
|
+
return {
|
|
4804
|
+
symbol: definition.symbol,
|
|
4805
|
+
shortName: shortenSymbol(definition.symbol),
|
|
4806
|
+
refCount: callerRows.length,
|
|
4807
|
+
fileCount: new Set(callerRows.map((row) => row.file)).size,
|
|
4808
|
+
definedIn: definition.relativePath
|
|
4809
|
+
};
|
|
4810
|
+
}).filter((row) => row.refCount > 0).sort((left, right) => right.refCount - left.refCount || right.fileCount - left.fileCount).slice(0, limit);
|
|
3165
4811
|
}
|
|
3166
4812
|
|
|
3167
4813
|
// src/queries/imports.ts
|
|
@@ -3234,6 +4880,14 @@ function importedBy(db, symbolPattern) {
|
|
|
3234
4880
|
if (targetFile && normalizePath3(entry.sourcePath) !== normalizePath3(targetFile)) {
|
|
3235
4881
|
continue;
|
|
3236
4882
|
}
|
|
4883
|
+
if (entry.kind === "side-effect") {
|
|
4884
|
+
importers.add(row.relative_path);
|
|
4885
|
+
continue;
|
|
4886
|
+
}
|
|
4887
|
+
if (targetFile && isCLikeImporter(row.relative_path)) {
|
|
4888
|
+
importers.add(row.relative_path);
|
|
4889
|
+
continue;
|
|
4890
|
+
}
|
|
3237
4891
|
if (targetIsModule) {
|
|
3238
4892
|
importers.add(row.relative_path);
|
|
3239
4893
|
continue;
|
|
@@ -3307,6 +4961,9 @@ function renderImportSymbol(importedName, localName, kind) {
|
|
|
3307
4961
|
function normalizePath3(path2) {
|
|
3308
4962
|
return path2.replace(/\\/g, "/");
|
|
3309
4963
|
}
|
|
4964
|
+
function isCLikeImporter(relativePath) {
|
|
4965
|
+
return /\.(?:c|h|cc|cpp|cxx|hpp|hh|hxx)$/i.test(relativePath);
|
|
4966
|
+
}
|
|
3310
4967
|
|
|
3311
4968
|
// src/queries/outline.ts
|
|
3312
4969
|
function outline(db, filePattern) {
|
|
@@ -3411,10 +5068,22 @@ function fanOut(db, filePattern) {
|
|
|
3411
5068
|
ORDER BY symbol_count DESC`,
|
|
3412
5069
|
resolvedFile
|
|
3413
5070
|
);
|
|
3414
|
-
|
|
5071
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
3415
5072
|
name: r.relative_path,
|
|
3416
5073
|
count: r.symbol_count
|
|
3417
5074
|
}));
|
|
5075
|
+
if (indexedResults.length > 0) {
|
|
5076
|
+
return indexedResults;
|
|
5077
|
+
}
|
|
5078
|
+
const graph = buildFileDepGraph(db);
|
|
5079
|
+
const deps2 = graph.get(resolvedFile);
|
|
5080
|
+
if (!deps2 || deps2.size === 0) {
|
|
5081
|
+
return [];
|
|
5082
|
+
}
|
|
5083
|
+
return [{
|
|
5084
|
+
name: resolvedFile,
|
|
5085
|
+
count: deps2.size
|
|
5086
|
+
}];
|
|
3418
5087
|
}
|
|
3419
5088
|
function topFanIn(db, opts = {}) {
|
|
3420
5089
|
const { limit = 30, scope } = opts;
|
|
@@ -3633,7 +5302,7 @@ function bottlenecks(db, opts = {}) {
|
|
|
3633
5302
|
minFanOut,
|
|
3634
5303
|
limit
|
|
3635
5304
|
);
|
|
3636
|
-
|
|
5305
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
3637
5306
|
symbol: r.symbol,
|
|
3638
5307
|
shortName: shortenSymbol(r.symbol),
|
|
3639
5308
|
fanIn: r.fan_in,
|
|
@@ -3641,51 +5310,42 @@ function bottlenecks(db, opts = {}) {
|
|
|
3641
5310
|
score: r.fan_in * r.fan_out,
|
|
3642
5311
|
definedIn: r.defined_in
|
|
3643
5312
|
}));
|
|
5313
|
+
if (indexedResults.length > 0) {
|
|
5314
|
+
return indexedResults;
|
|
5315
|
+
}
|
|
5316
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
|
|
5317
|
+
const fanIn2 = new Set(
|
|
5318
|
+
getCallerRowsForSymbol(db, definition, { limit: 500 }).map((row) => row.file)
|
|
5319
|
+
).size;
|
|
5320
|
+
const fanOut2 = new Set(
|
|
5321
|
+
getCalleeRowsForSymbol(db, definition, { limit: 500 }).filter((row) => row.file !== definition.relativePath).map((row) => `${row.symbol}|${row.file}`)
|
|
5322
|
+
).size;
|
|
5323
|
+
return {
|
|
5324
|
+
symbol: definition.symbol,
|
|
5325
|
+
shortName: shortenSymbol(definition.symbol),
|
|
5326
|
+
fanIn: fanIn2,
|
|
5327
|
+
fanOut: fanOut2,
|
|
5328
|
+
score: fanIn2 * fanOut2,
|
|
5329
|
+
definedIn: definition.relativePath
|
|
5330
|
+
};
|
|
5331
|
+
}).filter((row) => row.fanIn >= minFanIn && row.fanOut >= minFanOut).sort((left, right) => right.score - left.score || right.fanIn - left.fanIn).slice(0, limit);
|
|
3644
5332
|
}
|
|
3645
5333
|
|
|
3646
5334
|
// src/queries/isolated.ts
|
|
3647
5335
|
function isolated(db, opts = {}) {
|
|
3648
5336
|
const { scope, minLoc = 3 } = opts;
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
${db.pathExclusionsFor("d")}
|
|
3662
|
-
AND ${testFileExclusionSql("d")}
|
|
3663
|
-
${db.symbolNoiseFor("gs")}
|
|
3664
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
3665
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
3666
|
-
${scopeFilter}
|
|
3667
|
-
-- No cross-file references TO this symbol
|
|
3668
|
-
AND NOT EXISTS (
|
|
3669
|
-
SELECT 1 FROM mentions m
|
|
3670
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
3671
|
-
WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != d.id
|
|
3672
|
-
)
|
|
3673
|
-
-- No same-file references either
|
|
3674
|
-
AND NOT EXISTS (
|
|
3675
|
-
SELECT 1 FROM mentions m
|
|
3676
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
3677
|
-
WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id = d.id
|
|
3678
|
-
)
|
|
3679
|
-
ORDER BY loc DESC, d.relative_path`,
|
|
3680
|
-
minLoc
|
|
3681
|
-
);
|
|
3682
|
-
return rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => ({
|
|
3683
|
-
symbol: r.symbol,
|
|
3684
|
-
shortName: shortenSymbol(r.symbol),
|
|
3685
|
-
relativePath: r.relative_path,
|
|
3686
|
-
startLine: r.start_line,
|
|
3687
|
-
endLine: r.end_line,
|
|
3688
|
-
loc: r.loc
|
|
5337
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).filter((definition) => !isEntrySurface(db, definition.relativePath)).filter((definition) => definition.isFunctionLike).map((definition) => ({
|
|
5338
|
+
definition,
|
|
5339
|
+
loc: definition.endLine - definition.startLine + 1
|
|
5340
|
+
})).filter((entry) => entry.loc >= minLoc).filter((entry) => getCallerRowsForSymbol(db, entry.definition, { limit: 1 }).length === 0).filter((entry) => getCalleeRowsForSymbol(db, entry.definition, { limit: 1 }).length === 0).sort(
|
|
5341
|
+
(left, right) => right.loc - left.loc || left.definition.relativePath.localeCompare(right.definition.relativePath) || left.definition.startLine - right.definition.startLine
|
|
5342
|
+
).map((entry) => ({
|
|
5343
|
+
symbol: entry.definition.symbol,
|
|
5344
|
+
shortName: shortenSymbol(entry.definition.symbol),
|
|
5345
|
+
relativePath: entry.definition.relativePath,
|
|
5346
|
+
startLine: entry.definition.startLine,
|
|
5347
|
+
endLine: entry.definition.endLine,
|
|
5348
|
+
loc: entry.loc
|
|
3689
5349
|
}));
|
|
3690
5350
|
}
|
|
3691
5351
|
|
|
@@ -3828,24 +5488,18 @@ function kindCounts(db, opts = {}) {
|
|
|
3828
5488
|
}));
|
|
3829
5489
|
}
|
|
3830
5490
|
function loadKindRows(db, scope) {
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
JOIN documents d ON der.document_id = d.id
|
|
3844
|
-
WHERE 1 = 1
|
|
3845
|
-
${db.pathExclusionsFor("d")}
|
|
3846
|
-
${scopeFilter}
|
|
3847
|
-
ORDER BY d.relative_path, der.start_line`
|
|
3848
|
-
).filter((row) => !db.isIgnored(row.relative_path));
|
|
5491
|
+
return getAllDefinitions(db, { scope }).map(mapDefinitionToKindRow);
|
|
5492
|
+
}
|
|
5493
|
+
function mapDefinitionToKindRow(definition) {
|
|
5494
|
+
return {
|
|
5495
|
+
symbol: definition.symbol,
|
|
5496
|
+
kind: definition.kind,
|
|
5497
|
+
documentation: definition.documentation,
|
|
5498
|
+
enclosing_symbol: definition.enclosingSymbol,
|
|
5499
|
+
relative_path: definition.relativePath,
|
|
5500
|
+
start_line: definition.startLine,
|
|
5501
|
+
end_line: definition.endLine
|
|
5502
|
+
};
|
|
3849
5503
|
}
|
|
3850
5504
|
function resolveKindNumber(row) {
|
|
3851
5505
|
if (row.kind !== null && row.kind !== 0) {
|
|
@@ -4027,7 +5681,7 @@ function uniqueRows(rows) {
|
|
|
4027
5681
|
function similar(db, symbolPattern, opts = {}) {
|
|
4028
5682
|
const { minSimilarity = 0.4, limit = 20 } = opts;
|
|
4029
5683
|
const target = findCallees(db, symbolPattern);
|
|
4030
|
-
if (!target
|
|
5684
|
+
if (!target) return [];
|
|
4031
5685
|
if (!isFunctionLikeSymbol(target.symbol)) return [];
|
|
4032
5686
|
const candidates = getAllCalleeFingerprints(db, {
|
|
4033
5687
|
minCallees: 3,
|
|
@@ -4038,7 +5692,7 @@ function similar(db, symbolPattern, opts = {}) {
|
|
|
4038
5692
|
const results = [];
|
|
4039
5693
|
for (const candidate of candidates) {
|
|
4040
5694
|
if (candidate.callees.size < 3) continue;
|
|
4041
|
-
const { similarity, significantShared
|
|
5695
|
+
const { similarity, significantShared } = weightedSimilarity(
|
|
4042
5696
|
target.callees,
|
|
4043
5697
|
candidate.callees,
|
|
4044
5698
|
idfWeights
|
|
@@ -4059,7 +5713,10 @@ function similar(db, symbolPattern, opts = {}) {
|
|
|
4059
5713
|
});
|
|
4060
5714
|
}
|
|
4061
5715
|
results.sort((a, b) => b.similarity - a.similarity);
|
|
4062
|
-
|
|
5716
|
+
if (results.length > 0) {
|
|
5717
|
+
return results.slice(0, limit);
|
|
5718
|
+
}
|
|
5719
|
+
return similarBySourceShape(db, symbolPattern, { minSimilarity, limit });
|
|
4063
5720
|
}
|
|
4064
5721
|
function similarAll(db, opts = {}) {
|
|
4065
5722
|
const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;
|
|
@@ -4206,10 +5863,146 @@ function difference(a, b) {
|
|
|
4206
5863
|
}
|
|
4207
5864
|
return result;
|
|
4208
5865
|
}
|
|
5866
|
+
function similarBySourceShape(db, symbolPattern, opts) {
|
|
5867
|
+
const target = findSourceFingerprint(db, symbolPattern);
|
|
5868
|
+
if (!target || target.tokens.size < 3) {
|
|
5869
|
+
return [];
|
|
5870
|
+
}
|
|
5871
|
+
const minSimilarity = opts.minSimilarity >= 0.5 ? opts.minSimilarity : 0.3;
|
|
5872
|
+
const results = [];
|
|
5873
|
+
for (const candidate of getAllSourceFingerprints(db)) {
|
|
5874
|
+
if (candidate.symbol === target.symbol || candidate.tokens.size < 3) continue;
|
|
5875
|
+
const shared = intersection(target.tokens, candidate.tokens);
|
|
5876
|
+
if (shared.size < 2) continue;
|
|
5877
|
+
const union = /* @__PURE__ */ new Set([...target.tokens, ...candidate.tokens]);
|
|
5878
|
+
const similarity = union.size > 0 ? shared.size / union.size : 0;
|
|
5879
|
+
if (similarity < minSimilarity) continue;
|
|
5880
|
+
results.push({
|
|
5881
|
+
symbolA: target.symbol,
|
|
5882
|
+
shortNameA: shortenSymbol(target.symbol),
|
|
5883
|
+
fileA: target.file,
|
|
5884
|
+
symbolB: candidate.symbol,
|
|
5885
|
+
shortNameB: shortenSymbol(candidate.symbol),
|
|
5886
|
+
fileB: candidate.file,
|
|
5887
|
+
similarity,
|
|
5888
|
+
sharedCallees: [...shared].sort(),
|
|
5889
|
+
uniqueToA: [...difference(target.tokens, candidate.tokens)].sort(),
|
|
5890
|
+
uniqueToB: [...difference(candidate.tokens, target.tokens)].sort()
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
results.sort((a, b) => b.similarity - a.similarity || a.shortNameB.localeCompare(b.shortNameB));
|
|
5894
|
+
return results.slice(0, opts.limit);
|
|
5895
|
+
}
|
|
5896
|
+
function findSourceFingerprint(db, symbolPattern) {
|
|
5897
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
5898
|
+
if (!match || !isFunctionLikeSymbol(match.symbol)) {
|
|
5899
|
+
return null;
|
|
5900
|
+
}
|
|
5901
|
+
const snippet = definitionSnippet(db, match.relativePath, match.startLine, match.endLine, leafName(match.symbol));
|
|
5902
|
+
const tokens = sourceTokens(snippet, leafName(match.symbol));
|
|
5903
|
+
if (tokens.size === 0) {
|
|
5904
|
+
return null;
|
|
5905
|
+
}
|
|
5906
|
+
return {
|
|
5907
|
+
symbol: match.symbol,
|
|
5908
|
+
file: match.relativePath,
|
|
5909
|
+
tokens
|
|
5910
|
+
};
|
|
5911
|
+
}
|
|
5912
|
+
function getAllSourceFingerprints(db) {
|
|
5913
|
+
return getAllDefinitions(db).filter((definition) => definition.isFunctionLike).map((definition) => ({
|
|
5914
|
+
symbol: definition.symbol,
|
|
5915
|
+
file: definition.relativePath,
|
|
5916
|
+
tokens: sourceTokens(
|
|
5917
|
+
definitionSnippet(db, definition.relativePath, definition.startLine, definition.endLine, definition.leaf),
|
|
5918
|
+
definition.leaf
|
|
5919
|
+
)
|
|
5920
|
+
})).filter((fingerprint) => fingerprint.tokens.size > 0);
|
|
5921
|
+
}
|
|
5922
|
+
function definitionSnippet(db, relativePath, startLine, endLine, leaf) {
|
|
5923
|
+
const source = getSourceText(db, relativePath);
|
|
5924
|
+
if (!source) {
|
|
5925
|
+
return "";
|
|
5926
|
+
}
|
|
5927
|
+
const lines = source.split("\n");
|
|
5928
|
+
if (endLine >= startLine && endLine - startLine <= 12) {
|
|
5929
|
+
return lines.slice(startLine, endLine + 1).join("\n");
|
|
5930
|
+
}
|
|
5931
|
+
const markerPatterns = [
|
|
5932
|
+
new RegExp(`\\bdef\\s+${escapeRegex3(leaf)}\\b`),
|
|
5933
|
+
new RegExp(`\\bfun\\s+${escapeRegex3(leaf)}\\b`),
|
|
5934
|
+
new RegExp(`\\bfn\\s+${escapeRegex3(leaf)}\\b`),
|
|
5935
|
+
new RegExp(`\\bfunction\\s+${escapeRegex3(leaf)}\\b`),
|
|
5936
|
+
new RegExp(`\\b${escapeRegex3(leaf)}\\s*\\(`)
|
|
5937
|
+
];
|
|
5938
|
+
const definitionStart = lines.findIndex((line) => markerPatterns.some((pattern) => pattern.test(line)));
|
|
5939
|
+
if (definitionStart >= 0) {
|
|
5940
|
+
let definitionEnd = definitionStart;
|
|
5941
|
+
for (let index = definitionStart + 1; index < lines.length && index <= definitionStart + 8; index++) {
|
|
5942
|
+
const line = lines[index] ?? "";
|
|
5943
|
+
if (index > definitionStart && looksLikeDefinitionBoundary(line)) {
|
|
5944
|
+
break;
|
|
5945
|
+
}
|
|
5946
|
+
definitionEnd = index;
|
|
5947
|
+
if (line.trim() === "" && index > definitionStart + 1) {
|
|
5948
|
+
break;
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
return lines.slice(definitionStart, definitionEnd + 1).join("\n");
|
|
5952
|
+
}
|
|
5953
|
+
return lines.slice(startLine, Math.min(lines.length, startLine + 8)).join("\n");
|
|
5954
|
+
}
|
|
5955
|
+
function sourceTokens(snippet, leaf) {
|
|
5956
|
+
if (!snippet) {
|
|
5957
|
+
return /* @__PURE__ */ new Set();
|
|
5958
|
+
}
|
|
5959
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
5960
|
+
"public",
|
|
5961
|
+
"private",
|
|
5962
|
+
"protected",
|
|
5963
|
+
"final",
|
|
5964
|
+
"static",
|
|
5965
|
+
"class",
|
|
5966
|
+
"def",
|
|
5967
|
+
"fun",
|
|
5968
|
+
"fn",
|
|
5969
|
+
"function",
|
|
5970
|
+
"return",
|
|
5971
|
+
"string",
|
|
5972
|
+
"bool",
|
|
5973
|
+
"boolean",
|
|
5974
|
+
"void",
|
|
5975
|
+
"unit",
|
|
5976
|
+
"self",
|
|
5977
|
+
"this",
|
|
5978
|
+
"new",
|
|
5979
|
+
"const",
|
|
5980
|
+
"let",
|
|
5981
|
+
"var",
|
|
5982
|
+
"end",
|
|
5983
|
+
"pub"
|
|
5984
|
+
]);
|
|
5985
|
+
const normalizedLeafParts = splitIdentifier(leaf);
|
|
5986
|
+
const normalized = snippet.replace(/["'`]/g, " ").replace(/\b\d+\b/g, " NUM ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^A-Za-z0-9_]+/g, " ").replace(/_/g, " ").toLowerCase();
|
|
5987
|
+
const tokens = normalized.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 1).filter((token) => !stopWords.has(token)).filter((token) => !normalizedLeafParts.has(token));
|
|
5988
|
+
return new Set(tokens);
|
|
5989
|
+
}
|
|
5990
|
+
function splitIdentifier(value) {
|
|
5991
|
+
return new Set(
|
|
5992
|
+
value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9_]+|_/).map((part) => part.toLowerCase()).filter((part) => part.length > 1)
|
|
5993
|
+
);
|
|
5994
|
+
}
|
|
5995
|
+
function looksLikeDefinitionBoundary(line) {
|
|
5996
|
+
return /^\s*(?:def|fun|fn|function|class|trait|module|object|enum|interface|public|private|protected)\b/.test(line);
|
|
5997
|
+
}
|
|
5998
|
+
function escapeRegex3(value) {
|
|
5999
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6000
|
+
}
|
|
4209
6001
|
|
|
4210
6002
|
// src/queries/similar-files.ts
|
|
4211
6003
|
function similarFiles(db, opts = {}) {
|
|
4212
|
-
const { minSimilarity = 0.5, limit = 20, scope,
|
|
6004
|
+
const { minSimilarity = 0.5, limit = 20, scope, filePattern } = opts;
|
|
6005
|
+
const minDeps = opts.minDeps ?? (filePattern ? 1 : 3);
|
|
4213
6006
|
const profiles = buildFileProfiles(db, { scope, minDeps });
|
|
4214
6007
|
const results = [];
|
|
4215
6008
|
if (filePattern) {
|
|
@@ -4258,7 +6051,7 @@ function findUniversalDependencies(depMap) {
|
|
|
4258
6051
|
}
|
|
4259
6052
|
}
|
|
4260
6053
|
for (const [dep, count] of depCounts) {
|
|
4261
|
-
if (count / fileCount > 0.
|
|
6054
|
+
if (count >= 6 && count / fileCount > 0.8) {
|
|
4262
6055
|
universalDeps.add(dep);
|
|
4263
6056
|
}
|
|
4264
6057
|
}
|
|
@@ -4317,8 +6110,8 @@ function similarChains(db, opts = {}) {
|
|
|
4317
6110
|
tailFreq.set(chain[t], (tailFreq.get(chain[t]) ?? 0) + 1);
|
|
4318
6111
|
}
|
|
4319
6112
|
}
|
|
4320
|
-
const infraThreshold = rawChains.length * 0.
|
|
4321
|
-
const tailThreshold = rawChains.length * 0.
|
|
6113
|
+
const infraThreshold = rawChains.length * 0.9;
|
|
6114
|
+
const tailThreshold = rawChains.length * 0.8;
|
|
4322
6115
|
const infraNodes = /* @__PURE__ */ new Set();
|
|
4323
6116
|
for (const [node, freq] of nodeFreq) {
|
|
4324
6117
|
if (freq > infraThreshold) infraNodes.add(node);
|
|
@@ -4328,13 +6121,13 @@ function similarChains(db, opts = {}) {
|
|
|
4328
6121
|
}
|
|
4329
6122
|
const structuralNames = ["index.ts", "index.js", "cli.ts", "main.ts", "health.ts", "health.js"];
|
|
4330
6123
|
for (const node of nodeFreq.keys()) {
|
|
4331
|
-
const
|
|
4332
|
-
if (structuralNames.includes(
|
|
6124
|
+
const basename5 = node.split("/").pop() ?? "";
|
|
6125
|
+
if (structuralNames.includes(basename5)) infraNodes.add(node);
|
|
4333
6126
|
}
|
|
4334
6127
|
const filteredChains = [];
|
|
4335
6128
|
for (const chain of rawChains) {
|
|
4336
6129
|
const filtered = chain.filter((n) => !infraNodes.has(n));
|
|
4337
|
-
if (filtered.length >=
|
|
6130
|
+
if (filtered.length >= 2) {
|
|
4338
6131
|
filteredChains.push({ original: chain, filtered });
|
|
4339
6132
|
}
|
|
4340
6133
|
}
|
|
@@ -4499,31 +6292,13 @@ function isSubChain(sub, full) {
|
|
|
4499
6292
|
// src/queries/extract-candidates.ts
|
|
4500
6293
|
function extractCandidates(db, opts = {}) {
|
|
4501
6294
|
const { scope, minLoc = 10, minCallees = 6, limit = 20 } = opts;
|
|
4502
|
-
const
|
|
4503
|
-
const symbols2 = db.all(
|
|
4504
|
-
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
4505
|
-
FROM global_symbols gs
|
|
4506
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
4507
|
-
JOIN documents d ON der.document_id = d.id
|
|
4508
|
-
WHERE 1 = 1
|
|
4509
|
-
${db.pathExclusionsFor("d")}
|
|
4510
|
-
${db.symbolNoiseFor("gs")}
|
|
4511
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
4512
|
-
${scopeFilter}
|
|
4513
|
-
ORDER BY (der.end_line - der.start_line + 1) DESC`,
|
|
4514
|
-
minLoc
|
|
4515
|
-
);
|
|
6295
|
+
const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc(definition) >= minLoc && isFunctionLikeSymbol(definition.symbol)).sort((left, right) => definitionLoc(right) - definitionLoc(left));
|
|
4516
6296
|
const results = [];
|
|
4517
6297
|
for (const sym of symbols2) {
|
|
4518
|
-
if (db.isIgnored(sym.
|
|
4519
|
-
const
|
|
4520
|
-
if (
|
|
4521
|
-
const calleeChunks = getCalleeRowsForSymbol(db,
|
|
4522
|
-
documentId: sym.document_id,
|
|
4523
|
-
startLine: sym.start_line,
|
|
4524
|
-
endLine: sym.end_line,
|
|
4525
|
-
symbolId: sym.id
|
|
4526
|
-
});
|
|
6298
|
+
if (db.isIgnored(sym.relativePath)) continue;
|
|
6299
|
+
const basename5 = sym.relativePath.split("/").pop() ?? "";
|
|
6300
|
+
if (basename5.includes("types")) continue;
|
|
6301
|
+
const calleeChunks = getCalleeRowsForSymbol(db, sym);
|
|
4527
6302
|
const calleeSet = new Set(calleeChunks.map((c) => c.symbol));
|
|
4528
6303
|
if (calleeSet.size < minCallees) continue;
|
|
4529
6304
|
const cooccurrence = /* @__PURE__ */ new Map();
|
|
@@ -4586,10 +6361,10 @@ function extractCandidates(db, opts = {}) {
|
|
|
4586
6361
|
results.push({
|
|
4587
6362
|
symbol: sym.symbol,
|
|
4588
6363
|
shortName: shortenSymbol(sym.symbol),
|
|
4589
|
-
relativePath: sym.
|
|
4590
|
-
startLine: sym.
|
|
4591
|
-
endLine: sym.
|
|
4592
|
-
loc: sym
|
|
6364
|
+
relativePath: sym.relativePath,
|
|
6365
|
+
startLine: sym.startLine,
|
|
6366
|
+
endLine: sym.endLine,
|
|
6367
|
+
loc: definitionLoc(sym),
|
|
4593
6368
|
totalCallees: calleeSet.size,
|
|
4594
6369
|
clusters: scoredClusters
|
|
4595
6370
|
});
|
|
@@ -4598,6 +6373,20 @@ function extractCandidates(db, opts = {}) {
|
|
|
4598
6373
|
results.sort((a, b) => b.clusters.length - a.clusters.length || b.loc - a.loc);
|
|
4599
6374
|
return results.slice(0, limit);
|
|
4600
6375
|
}
|
|
6376
|
+
function getScopedDefinitions(db, scope) {
|
|
6377
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6378
|
+
return db.all(
|
|
6379
|
+
`SELECT relative_path
|
|
6380
|
+
FROM documents
|
|
6381
|
+
WHERE 1 = 1
|
|
6382
|
+
${db.pathExclusionsFor("documents")}
|
|
6383
|
+
${scopeFilter}
|
|
6384
|
+
ORDER BY relative_path`
|
|
6385
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6386
|
+
}
|
|
6387
|
+
function definitionLoc(definition) {
|
|
6388
|
+
return definition.endLine - definition.startLine + 1;
|
|
6389
|
+
}
|
|
4601
6390
|
|
|
4602
6391
|
// src/queries/affected.ts
|
|
4603
6392
|
function affected(db, symbolPattern, opts = {}) {
|
|
@@ -4638,73 +6427,39 @@ function affected(db, symbolPattern, opts = {}) {
|
|
|
4638
6427
|
return results;
|
|
4639
6428
|
}
|
|
4640
6429
|
function getDirectAffectedRows(db, target, scope) {
|
|
4641
|
-
const
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
});
|
|
4657
|
-
continue;
|
|
4658
|
-
}
|
|
4659
|
-
const enclosing = findExactSymbolMatch(db, site.enclosingSymbol);
|
|
4660
|
-
if (!enclosing || enclosing.symbolId === target.symbolId || db.isIgnored(enclosing.relativePath)) {
|
|
4661
|
-
continue;
|
|
4662
|
-
}
|
|
4663
|
-
const key = `${enclosing.symbolId}|${enclosing.relativePath}`;
|
|
4664
|
-
if (seen.has(key)) continue;
|
|
4665
|
-
seen.add(key);
|
|
4666
|
-
rows2.push({
|
|
4667
|
-
symbolId: enclosing.symbolId,
|
|
4668
|
-
symbol: enclosing.symbol,
|
|
4669
|
-
shortName: shortenSymbol(enclosing.symbol),
|
|
4670
|
-
file: enclosing.relativePath,
|
|
4671
|
-
symbolMatch: enclosing
|
|
6430
|
+
const callerRows = getCallerRowsForSymbol(db, target, { limit: 500 }).filter((row) => !db.isIgnored(row.file)).filter((row) => !scope || row.file.includes(scope));
|
|
6431
|
+
const results = [];
|
|
6432
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6433
|
+
for (const row of callerRows) {
|
|
6434
|
+
const match = findExactSymbolMatch(db, row.symbol);
|
|
6435
|
+
if (!match) {
|
|
6436
|
+
const key2 = `${row.file}|${row.symbol}`;
|
|
6437
|
+
if (seen.has(key2)) continue;
|
|
6438
|
+
seen.add(key2);
|
|
6439
|
+
results.push({
|
|
6440
|
+
symbolId: null,
|
|
6441
|
+
symbol: row.symbol,
|
|
6442
|
+
shortName: shortenSymbol(row.symbol),
|
|
6443
|
+
file: row.file,
|
|
6444
|
+
symbolMatch: null
|
|
4672
6445
|
});
|
|
6446
|
+
continue;
|
|
4673
6447
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
JOIN documents enc_d ON enc_der.document_id = enc_d.id
|
|
4690
|
-
WHERE m.symbol_id = ?
|
|
4691
|
-
AND m.role != 1
|
|
4692
|
-
AND enc_gs.id != ?
|
|
4693
|
-
${db.symbolNoiseFor("enc_gs")}
|
|
4694
|
-
${db.pathExclusionsFor("enc_d")}
|
|
4695
|
-
${scope ? `AND enc_d.relative_path LIKE '%${scope}%'` : ""}
|
|
4696
|
-
ORDER BY enc_d.relative_path
|
|
4697
|
-
LIMIT 1`,
|
|
4698
|
-
target.symbolId,
|
|
4699
|
-
target.symbolId
|
|
4700
|
-
);
|
|
4701
|
-
return rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
|
|
4702
|
-
symbolId: row.symbol_id,
|
|
4703
|
-
symbol: row.symbol,
|
|
4704
|
-
shortName: shortenSymbol(row.symbol),
|
|
4705
|
-
file: row.relative_path,
|
|
4706
|
-
symbolMatch: findExactSymbolMatch(db, row.symbol)
|
|
4707
|
-
}));
|
|
6448
|
+
if (match.symbolId === target.symbolId || db.isIgnored(match.relativePath)) {
|
|
6449
|
+
continue;
|
|
6450
|
+
}
|
|
6451
|
+
const key = `${match.symbolId}|${match.relativePath}`;
|
|
6452
|
+
if (seen.has(key)) continue;
|
|
6453
|
+
seen.add(key);
|
|
6454
|
+
results.push({
|
|
6455
|
+
symbolId: match.symbolId,
|
|
6456
|
+
symbol: match.symbol,
|
|
6457
|
+
shortName: shortenSymbol(match.symbol),
|
|
6458
|
+
file: match.relativePath,
|
|
6459
|
+
symbolMatch: match
|
|
6460
|
+
});
|
|
6461
|
+
}
|
|
6462
|
+
return results;
|
|
4708
6463
|
}
|
|
4709
6464
|
|
|
4710
6465
|
// src/queries/change-surface.ts
|
|
@@ -5054,140 +6809,129 @@ function isLikelyTypeOnlyDep(dep) {
|
|
|
5054
6809
|
function shouldSkipDriftFile(filePath) {
|
|
5055
6810
|
return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
|
|
5056
6811
|
}
|
|
5057
|
-
function isStructuralRole(
|
|
5058
|
-
if (
|
|
5059
|
-
if (
|
|
5060
|
-
if (
|
|
5061
|
-
if (
|
|
6812
|
+
function isStructuralRole(basename5) {
|
|
6813
|
+
if (basename5 === "index.ts" || basename5 === "index.js") return true;
|
|
6814
|
+
if (basename5 === "cli.ts" || basename5 === "main.ts" || basename5 === "main.rs") return true;
|
|
6815
|
+
if (basename5.includes("worker.") || basename5.includes("postinstall.")) return true;
|
|
6816
|
+
if (basename5 === "health.ts" || basename5 === "health.js") return true;
|
|
5062
6817
|
return false;
|
|
5063
6818
|
}
|
|
5064
6819
|
function isTestLikePath(filePath) {
|
|
5065
6820
|
const normalized = filePath.replace(/\\/g, "/");
|
|
5066
|
-
const
|
|
5067
|
-
return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(
|
|
6821
|
+
const basename5 = path.basename(normalized);
|
|
6822
|
+
return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(basename5) || /_(test|spec)\.[A-Za-z0-9]+$/.test(basename5) || /^test[_-]/.test(basename5) || /^test\./.test(basename5);
|
|
5068
6823
|
}
|
|
5069
6824
|
|
|
5070
6825
|
// src/queries/wrapper-candidates.ts
|
|
6826
|
+
import { basename as basename4, extname as extname3 } from "path";
|
|
5071
6827
|
function wrapperCandidates(db, opts) {
|
|
5072
6828
|
const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
|
|
5073
|
-
const
|
|
5074
|
-
const
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
}
|
|
6829
|
+
const reverseFanIn = buildReverseFileFanIn(buildFileDepGraph(db, scope));
|
|
6830
|
+
const symbols2 = getScopedDefinitions2(db, scope).filter((definition) => definitionLoc2(definition) <= maxLoc && definitionLoc2(definition) >= 2);
|
|
6831
|
+
const results = [];
|
|
6832
|
+
for (const symbol of symbols2) {
|
|
6833
|
+
if (db.isIgnored(symbol.relativePath) || !isFunctionLikeSymbol(symbol.symbol)) continue;
|
|
6834
|
+
const symbolStem = basename4(symbol.relativePath, extname3(symbol.relativePath));
|
|
6835
|
+
const callerRows = dedupeRows(
|
|
6836
|
+
getCallerRowsForSymbol(db, symbol, { limit: 200 }).filter((row) => row.file !== symbol.relativePath)
|
|
6837
|
+
).filter((row) => basename4(row.file, extname3(row.file)) !== symbolStem);
|
|
6838
|
+
if (callerRows.length !== 1) continue;
|
|
6839
|
+
const caller = callerRows[0];
|
|
6840
|
+
const callerDefinition = getDefinitionsForFile(db, caller.file).find((definition) => definition.symbol === caller.symbol);
|
|
6841
|
+
const useDefinitionFanIn = callerDefinition?.isFunctionLike ?? false;
|
|
6842
|
+
let callerFanIn;
|
|
6843
|
+
if (useDefinitionFanIn && callerDefinition) {
|
|
6844
|
+
callerFanIn = new Set(
|
|
6845
|
+
getCallerRowsForSymbol(db, callerDefinition, { limit: 500 }).filter((row) => row.file !== callerDefinition.relativePath).map((row) => row.file)
|
|
6846
|
+
).size;
|
|
6847
|
+
} else {
|
|
6848
|
+
callerFanIn = fallbackCallerFanIn(db, reverseFanIn, caller.file);
|
|
6849
|
+
}
|
|
6850
|
+
if (callerFanIn <= 3) continue;
|
|
6851
|
+
results.push({
|
|
6852
|
+
symbol: symbol.symbol,
|
|
6853
|
+
shortName: shortenSymbol(symbol.symbol),
|
|
6854
|
+
file: symbol.relativePath,
|
|
6855
|
+
startLine: symbol.startLine,
|
|
6856
|
+
endLine: symbol.endLine,
|
|
6857
|
+
loc: definitionLoc2(symbol),
|
|
6858
|
+
singleCaller: caller.symbol,
|
|
6859
|
+
singleCallerShort: useDefinitionFanIn ? shortenSymbol(caller.symbol) : basename4(caller.file),
|
|
6860
|
+
callerFanIn
|
|
6861
|
+
});
|
|
6862
|
+
}
|
|
6863
|
+
results.sort((left, right) => right.callerFanIn - left.callerFanIn || right.loc - left.loc);
|
|
6864
|
+
return results.slice(0, limit);
|
|
6865
|
+
}
|
|
6866
|
+
function definitionLoc2(definition) {
|
|
6867
|
+
return definition.endLine - definition.startLine + 1;
|
|
6868
|
+
}
|
|
6869
|
+
function getScopedDefinitions2(db, scope) {
|
|
6870
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6871
|
+
return db.all(
|
|
6872
|
+
`SELECT relative_path
|
|
6873
|
+
FROM documents
|
|
6874
|
+
WHERE 1 = 1
|
|
6875
|
+
${db.pathExclusionsFor("documents")}
|
|
6876
|
+
${scopeFilter}
|
|
6877
|
+
ORDER BY relative_path`
|
|
6878
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6879
|
+
}
|
|
6880
|
+
function dedupeRows(rows) {
|
|
6881
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6882
|
+
const unique = [];
|
|
6883
|
+
for (const row of rows) {
|
|
6884
|
+
const key = `${row.symbol}|${row.file}`;
|
|
6885
|
+
if (seen.has(key)) continue;
|
|
6886
|
+
seen.add(key);
|
|
6887
|
+
unique.push(row);
|
|
6888
|
+
}
|
|
6889
|
+
return unique;
|
|
6890
|
+
}
|
|
6891
|
+
function buildReverseFileFanIn(graph) {
|
|
6892
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
6893
|
+
for (const [fromFile, deps2] of graph) {
|
|
6894
|
+
if (!reverse.has(fromFile)) {
|
|
6895
|
+
reverse.set(fromFile, reverse.get(fromFile) ?? 0);
|
|
6896
|
+
}
|
|
6897
|
+
for (const dep of deps2) {
|
|
6898
|
+
reverse.set(dep, (reverse.get(dep) ?? 0) + 1);
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
return reverse;
|
|
6902
|
+
}
|
|
6903
|
+
function fallbackCallerFanIn(db, reverseFanIn, callerFile) {
|
|
6904
|
+
const functionLikeFanIn = getDefinitionsForFile(db, callerFile).filter((definition) => definition.isFunctionLike).map((definition) => new Set(
|
|
6905
|
+
getCallerRowsForSymbol(db, definition, { limit: 500 }).filter((row) => row.file !== definition.relativePath).map((row) => row.file)
|
|
6906
|
+
).size).sort((left, right) => right - left)[0] ?? 0;
|
|
6907
|
+
if (functionLikeFanIn > 0) {
|
|
6908
|
+
return functionLikeFanIn;
|
|
6909
|
+
}
|
|
6910
|
+
const direct = reverseFanIn.get(callerFile) ?? 0;
|
|
6911
|
+
if (direct > 0) {
|
|
6912
|
+
return direct;
|
|
6913
|
+
}
|
|
6914
|
+
const stem = basename4(callerFile, extname3(callerFile));
|
|
6915
|
+
let best = 0;
|
|
6916
|
+
for (const [file, fanIn2] of reverseFanIn) {
|
|
6917
|
+
if (file === callerFile) continue;
|
|
6918
|
+
if (basename4(file, extname3(file)) !== stem) continue;
|
|
6919
|
+
if (fanIn2 > best) {
|
|
6920
|
+
best = fanIn2;
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
return best;
|
|
5154
6924
|
}
|
|
5155
6925
|
|
|
5156
6926
|
// src/queries/passthrough-candidates.ts
|
|
5157
6927
|
function passthroughCandidates(db, opts) {
|
|
5158
6928
|
const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
|
|
5159
|
-
const
|
|
5160
|
-
const symbols2 = db.all(
|
|
5161
|
-
`SELECT
|
|
5162
|
-
gs.id,
|
|
5163
|
-
gs.symbol,
|
|
5164
|
-
der.document_id,
|
|
5165
|
-
der.start_line,
|
|
5166
|
-
der.end_line,
|
|
5167
|
-
(der.end_line - der.start_line + 1) AS loc,
|
|
5168
|
-
d.relative_path
|
|
5169
|
-
FROM global_symbols gs
|
|
5170
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5171
|
-
JOIN documents d ON der.document_id = d.id
|
|
5172
|
-
WHERE 1 = 1
|
|
5173
|
-
${db.pathExclusionsFor("d")}
|
|
5174
|
-
AND ${testFileExclusionSql("d")}
|
|
5175
|
-
${db.symbolNoiseFor("gs")}
|
|
5176
|
-
AND (der.end_line - der.start_line + 1) >= 3
|
|
5177
|
-
AND (der.end_line - der.start_line + 1) <= ?
|
|
5178
|
-
${scopeFilter}
|
|
5179
|
-
ORDER BY d.relative_path`,
|
|
5180
|
-
maxLoc
|
|
5181
|
-
);
|
|
6929
|
+
const symbols2 = getScopedDefinitions3(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
|
|
5182
6930
|
const results = [];
|
|
5183
6931
|
for (const sym of symbols2) {
|
|
5184
|
-
if (db.isIgnored(sym.
|
|
5185
|
-
const
|
|
5186
|
-
|
|
5187
|
-
startLine: sym.start_line,
|
|
5188
|
-
endLine: sym.end_line,
|
|
5189
|
-
symbolId: sym.id
|
|
5190
|
-
});
|
|
6932
|
+
if (db.isIgnored(sym.relativePath) || !isFunctionLikeSymbol(sym.symbol)) continue;
|
|
6933
|
+
const rawCallees = getCalleeRowsForSymbol(db, sym);
|
|
6934
|
+
const callees = rawCallees.some((callee2) => isFunctionLikeSymbol(callee2.symbol)) ? rawCallees.filter((callee2) => isFunctionLikeSymbol(callee2.symbol)) : rawCallees;
|
|
5191
6935
|
const uniqueCallees = /* @__PURE__ */ new Map();
|
|
5192
6936
|
for (const c of callees) {
|
|
5193
6937
|
if (!uniqueCallees.has(c.symbol)) {
|
|
@@ -5199,10 +6943,10 @@ function passthroughCandidates(db, opts) {
|
|
|
5199
6943
|
results.push({
|
|
5200
6944
|
symbol: sym.symbol,
|
|
5201
6945
|
shortName: shortenSymbol(sym.symbol),
|
|
5202
|
-
file: sym.
|
|
5203
|
-
startLine: sym.
|
|
5204
|
-
endLine: sym.
|
|
5205
|
-
loc: sym
|
|
6946
|
+
file: sym.relativePath,
|
|
6947
|
+
startLine: sym.startLine,
|
|
6948
|
+
endLine: sym.endLine,
|
|
6949
|
+
loc: definitionLoc3(sym),
|
|
5206
6950
|
forwardsTo: callee.symbol,
|
|
5207
6951
|
forwardsToShort: shortenSymbol(callee.symbol),
|
|
5208
6952
|
forwardsToFile: callee.file
|
|
@@ -5211,42 +6955,32 @@ function passthroughCandidates(db, opts) {
|
|
|
5211
6955
|
results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
|
|
5212
6956
|
return results.slice(0, limit);
|
|
5213
6957
|
}
|
|
6958
|
+
function getScopedDefinitions3(db, scope) {
|
|
6959
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6960
|
+
return db.all(
|
|
6961
|
+
`SELECT relative_path
|
|
6962
|
+
FROM documents
|
|
6963
|
+
WHERE 1 = 1
|
|
6964
|
+
${db.pathExclusionsFor("documents")}
|
|
6965
|
+
${scopeFilter}
|
|
6966
|
+
ORDER BY relative_path`
|
|
6967
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6968
|
+
}
|
|
6969
|
+
function definitionLoc3(definition) {
|
|
6970
|
+
return definition.endLine - definition.startLine + 1;
|
|
6971
|
+
}
|
|
5214
6972
|
|
|
5215
6973
|
// src/queries/stale-abstractions.ts
|
|
5216
6974
|
function staleAbstractions(db, opts) {
|
|
5217
6975
|
const { scope, minLoc = 3, limit = 30 } = opts ?? {};
|
|
5218
|
-
const
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
(der.end_line - der.start_line + 1) AS loc,
|
|
5227
|
-
(SELECT COUNT(DISTINCT ref_c.document_id)
|
|
5228
|
-
FROM mentions ref_m
|
|
5229
|
-
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
5230
|
-
WHERE ref_m.symbol_id = gs.id
|
|
5231
|
-
AND ref_m.role != 1
|
|
5232
|
-
AND ref_c.document_id != der.document_id
|
|
5233
|
-
) AS consumers
|
|
5234
|
-
FROM global_symbols gs
|
|
5235
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5236
|
-
JOIN documents d ON der.document_id = d.id
|
|
5237
|
-
WHERE 1 = 1
|
|
5238
|
-
${db.pathExclusionsFor("d")}
|
|
5239
|
-
AND ${testFileExclusionSql("d")}
|
|
5240
|
-
${db.symbolNoiseFor("gs")}
|
|
5241
|
-
-- Top-level type symbols: ends with # but does not contain nested #
|
|
5242
|
-
AND gs.symbol LIKE '%#'
|
|
5243
|
-
AND gs.symbol NOT LIKE '%#%#%'
|
|
5244
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
5245
|
-
${scopeFilter}
|
|
5246
|
-
) WHERE consumers <= 1
|
|
5247
|
-
ORDER BY loc DESC, file ASC, start_line ASC`,
|
|
5248
|
-
minLoc
|
|
5249
|
-
);
|
|
6976
|
+
const rows = getScopedDefinitions4(db, scope).filter((definition) => definition.isTypeLike && definitionLoc4(definition) >= minLoc).map((definition) => ({
|
|
6977
|
+
symbol: definition.symbol,
|
|
6978
|
+
file: definition.relativePath,
|
|
6979
|
+
start_line: definition.startLine,
|
|
6980
|
+
end_line: definition.endLine,
|
|
6981
|
+
loc: definitionLoc4(definition),
|
|
6982
|
+
consumers: countCrossFileConsumers(db, definition)
|
|
6983
|
+
})).filter((row) => row.consumers <= 1).sort((left, right) => right.loc - left.loc || left.file.localeCompare(right.file) || left.start_line - right.start_line);
|
|
5250
6984
|
const filesWithFunctions = getFilesWithFunctions(db, scope);
|
|
5251
6985
|
return rows.filter((r) => !db.isIgnored(r.file)).filter((r) => isTrueStaleAbstraction(r, filesWithFunctions)).map((r) => ({
|
|
5252
6986
|
symbol: r.symbol,
|
|
@@ -5259,22 +6993,11 @@ function staleAbstractions(db, opts) {
|
|
|
5259
6993
|
})).slice(0, limit);
|
|
5260
6994
|
}
|
|
5261
6995
|
function getFilesWithFunctions(db, scope) {
|
|
5262
|
-
|
|
5263
|
-
return new Set(
|
|
5264
|
-
db.all(
|
|
5265
|
-
`SELECT DISTINCT d.relative_path
|
|
5266
|
-
FROM global_symbols gs
|
|
5267
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5268
|
-
JOIN documents d ON der.document_id = d.id
|
|
5269
|
-
WHERE gs.symbol LIKE '%().'
|
|
5270
|
-
${db.pathExclusionsFor("d")}
|
|
5271
|
-
${scopeFilter}`
|
|
5272
|
-
).map((row) => row.relative_path).filter((path2) => !db.isIgnored(path2))
|
|
5273
|
-
);
|
|
6996
|
+
return new Set(getScopedDefinitions4(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
|
|
5274
6997
|
}
|
|
5275
6998
|
function isTrueStaleAbstraction(row, filesWithFunctions) {
|
|
5276
|
-
const
|
|
5277
|
-
const isTypeFile =
|
|
6999
|
+
const basename5 = row.file.split("/").pop() ?? "";
|
|
7000
|
+
const isTypeFile = basename5.includes("types") || row.file.includes("/types/");
|
|
5278
7001
|
if (isTypeFile && row.consumers > 0) {
|
|
5279
7002
|
return false;
|
|
5280
7003
|
}
|
|
@@ -5283,6 +7006,33 @@ function isTrueStaleAbstraction(row, filesWithFunctions) {
|
|
|
5283
7006
|
}
|
|
5284
7007
|
return true;
|
|
5285
7008
|
}
|
|
7009
|
+
function getScopedDefinitions4(db, scope) {
|
|
7010
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
7011
|
+
return db.all(
|
|
7012
|
+
`SELECT relative_path
|
|
7013
|
+
FROM documents
|
|
7014
|
+
WHERE 1 = 1
|
|
7015
|
+
${db.pathExclusionsFor("documents")}
|
|
7016
|
+
${scopeFilter}
|
|
7017
|
+
ORDER BY relative_path`
|
|
7018
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
7019
|
+
}
|
|
7020
|
+
function countCrossFileConsumers(db, definition) {
|
|
7021
|
+
const callers = db.all(
|
|
7022
|
+
`SELECT DISTINCT d.relative_path
|
|
7023
|
+
FROM mentions m
|
|
7024
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
7025
|
+
JOIN documents d ON c.document_id = d.id
|
|
7026
|
+
WHERE m.symbol_id = ?
|
|
7027
|
+
AND m.role != 1
|
|
7028
|
+
${db.pathExclusionsFor("d")}`,
|
|
7029
|
+
definition.symbolId
|
|
7030
|
+
).map((row) => row.relative_path).filter((relativePath) => relativePath !== definition.relativePath && !db.isIgnored(relativePath));
|
|
7031
|
+
return new Set(callers).size;
|
|
7032
|
+
}
|
|
7033
|
+
function definitionLoc4(definition) {
|
|
7034
|
+
return definition.endLine - definition.startLine + 1;
|
|
7035
|
+
}
|
|
5286
7036
|
|
|
5287
7037
|
// src/queries/complexity-hotspots.ts
|
|
5288
7038
|
function complexityHotspots(db, opts) {
|
|
@@ -5359,7 +7109,7 @@ function complexityHotspots(db, opts) {
|
|
|
5359
7109
|
minLoc,
|
|
5360
7110
|
limit
|
|
5361
7111
|
);
|
|
5362
|
-
|
|
7112
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.file)).map((r) => ({
|
|
5363
7113
|
symbol: r.symbol,
|
|
5364
7114
|
shortName: shortenSymbol(r.symbol),
|
|
5365
7115
|
file: r.file,
|
|
@@ -5373,6 +7123,31 @@ function complexityHotspots(db, opts) {
|
|
|
5373
7123
|
r.loc / 50 * (r.fan_in / 5) * Math.max(r.fan_out / 5, 1) * 100
|
|
5374
7124
|
) / 100
|
|
5375
7125
|
}));
|
|
7126
|
+
if (indexedResults.length > 0) {
|
|
7127
|
+
return indexedResults;
|
|
7128
|
+
}
|
|
7129
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
|
|
7130
|
+
const loc = definition.endLine - definition.startLine + 1;
|
|
7131
|
+
const callerRows = getCallerRowsForSymbol(db, definition, { limit: 500 });
|
|
7132
|
+
const calleeRows = getCalleeRowsForSymbol(db, definition, { limit: 500 });
|
|
7133
|
+
const fanIn2 = new Set(callerRows.map((row) => row.file)).size;
|
|
7134
|
+
const fanOut2 = new Set(
|
|
7135
|
+
calleeRows.filter((row) => row.file !== definition.relativePath).map((row) => `${row.symbol}|${row.file}`)
|
|
7136
|
+
).size;
|
|
7137
|
+
const calleeCount = new Set(calleeRows.map((row) => `${row.symbol}|${row.file}`)).size;
|
|
7138
|
+
return {
|
|
7139
|
+
symbol: definition.symbol,
|
|
7140
|
+
shortName: shortenSymbol(definition.symbol),
|
|
7141
|
+
file: definition.relativePath,
|
|
7142
|
+
startLine: definition.startLine,
|
|
7143
|
+
endLine: definition.endLine,
|
|
7144
|
+
loc,
|
|
7145
|
+
fanIn: fanIn2,
|
|
7146
|
+
fanOut: fanOut2,
|
|
7147
|
+
calleeCount,
|
|
7148
|
+
score: Math.round(loc / 50 * (fanIn2 / 5) * Math.max(fanOut2 / 5, 1) * 100) / 100
|
|
7149
|
+
};
|
|
7150
|
+
}).filter((row) => row.loc >= minLoc).sort((left, right) => right.score - left.score || right.loc - left.loc).slice(0, limit);
|
|
5376
7151
|
}
|
|
5377
7152
|
|
|
5378
7153
|
// src/queries/health.ts
|
|
@@ -5620,8 +7395,8 @@ function convergence(db, symbolPatternA, symbolPatternB) {
|
|
|
5620
7395
|
}
|
|
5621
7396
|
|
|
5622
7397
|
// src/queries/code.ts
|
|
5623
|
-
import { readFileSync as
|
|
5624
|
-
import { join as
|
|
7398
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
7399
|
+
import { join as join9 } from "path";
|
|
5625
7400
|
function code(db, symbolPattern, opts = {}) {
|
|
5626
7401
|
const { context = 0 } = opts;
|
|
5627
7402
|
const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
|
|
@@ -5634,10 +7409,10 @@ function code(db, symbolPattern, opts = {}) {
|
|
|
5634
7409
|
`SELECT language FROM documents WHERE relative_path = ?`,
|
|
5635
7410
|
match.relativePath
|
|
5636
7411
|
);
|
|
5637
|
-
const filePath =
|
|
7412
|
+
const filePath = join9(db.config.projectRoot, match.relativePath);
|
|
5638
7413
|
let fileContent;
|
|
5639
7414
|
try {
|
|
5640
|
-
fileContent =
|
|
7415
|
+
fileContent = readFileSync4(filePath, "utf-8");
|
|
5641
7416
|
} catch {
|
|
5642
7417
|
return null;
|
|
5643
7418
|
}
|
|
@@ -5664,10 +7439,10 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
5664
7439
|
resolvedPath
|
|
5665
7440
|
);
|
|
5666
7441
|
if (!doc) return null;
|
|
5667
|
-
const fullPath =
|
|
7442
|
+
const fullPath = join9(db.config.projectRoot, doc.relative_path);
|
|
5668
7443
|
let fileContent;
|
|
5669
7444
|
try {
|
|
5670
|
-
fileContent =
|
|
7445
|
+
fileContent = readFileSync4(fullPath, "utf-8");
|
|
5671
7446
|
} catch {
|
|
5672
7447
|
return null;
|
|
5673
7448
|
}
|
|
@@ -5687,8 +7462,8 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
5687
7462
|
}
|
|
5688
7463
|
|
|
5689
7464
|
// src/queries/complexity.ts
|
|
5690
|
-
import { readFileSync as
|
|
5691
|
-
import { join as
|
|
7465
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
7466
|
+
import { join as join10 } from "path";
|
|
5692
7467
|
function complexity(db, symbolPattern) {
|
|
5693
7468
|
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
5694
7469
|
if (!match) return null;
|
|
@@ -5697,10 +7472,10 @@ function complexity(db, symbolPattern) {
|
|
|
5697
7472
|
match.relativePath
|
|
5698
7473
|
);
|
|
5699
7474
|
const language = doc?.language ?? "unknown";
|
|
5700
|
-
const filePath =
|
|
7475
|
+
const filePath = join10(db.config.projectRoot, match.relativePath);
|
|
5701
7476
|
let source = "";
|
|
5702
7477
|
try {
|
|
5703
|
-
const lines =
|
|
7478
|
+
const lines = readFileSync5(filePath, "utf-8").split("\n");
|
|
5704
7479
|
source = lines.slice(match.startLine, match.endLine + 1).join("\n");
|
|
5705
7480
|
} catch {
|
|
5706
7481
|
}
|
|
@@ -6037,46 +7812,100 @@ function redundantReexports(db, opts = {}) {
|
|
|
6037
7812
|
results.sort(
|
|
6038
7813
|
(a, b) => b.directConsumers - a.directConsumers || a.barrelFile.localeCompare(b.barrelFile) || a.shortName.localeCompare(b.shortName)
|
|
6039
7814
|
);
|
|
6040
|
-
|
|
7815
|
+
const withDartFallback = dedupeReexports([
|
|
7816
|
+
...results,
|
|
7817
|
+
...findSourceRedundantReexports(db, scope)
|
|
7818
|
+
]);
|
|
7819
|
+
withDartFallback.sort(
|
|
7820
|
+
(a, b) => b.directConsumers - a.directConsumers || a.barrelFile.localeCompare(b.barrelFile) || a.shortName.localeCompare(b.shortName)
|
|
7821
|
+
);
|
|
7822
|
+
return limit ? withDartFallback.slice(0, limit) : withDartFallback;
|
|
7823
|
+
}
|
|
7824
|
+
function findSourceRedundantReexports(db, scope) {
|
|
7825
|
+
const files2 = db.all(
|
|
7826
|
+
`SELECT relative_path
|
|
7827
|
+
FROM documents
|
|
7828
|
+
WHERE 1 = 1
|
|
7829
|
+
${scope ? "AND relative_path LIKE ?" : ""}
|
|
7830
|
+
${db.pathExclusionsFor("documents")}
|
|
7831
|
+
ORDER BY relative_path`,
|
|
7832
|
+
...scope ? [`%${scope}%`] : []
|
|
7833
|
+
);
|
|
7834
|
+
const candidates = files2.map((row) => row.relative_path).filter((relativePath) => !db.isIgnored(relativePath)).filter((relativePath) => getSourceExports(db, relativePath).length > 0);
|
|
7835
|
+
const results = [];
|
|
7836
|
+
for (const barrelPath of candidates) {
|
|
7837
|
+
const exports = getSourceExports(db, barrelPath).filter((entry) => entry.sourcePath && !db.isIgnored(entry.sourcePath));
|
|
7838
|
+
if (exports.length === 0) continue;
|
|
7839
|
+
const barrelConsumers = countDirectImporters(db, barrelPath, barrelPath);
|
|
7840
|
+
if (barrelConsumers > 0) continue;
|
|
7841
|
+
for (const exported of exports) {
|
|
7842
|
+
const sourcePath = exported.sourcePath;
|
|
7843
|
+
const representative = representativeExportSymbol(db, sourcePath);
|
|
7844
|
+
if (!representative) continue;
|
|
7845
|
+
results.push({
|
|
7846
|
+
barrelFile: barrelPath,
|
|
7847
|
+
symbol: representative.symbol,
|
|
7848
|
+
shortName: shortenSymbol(representative.symbol),
|
|
7849
|
+
originalFile: sourcePath,
|
|
7850
|
+
barrelConsumers: 0,
|
|
7851
|
+
directConsumers: countDirectImporters(db, sourcePath, barrelPath)
|
|
7852
|
+
});
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
return results;
|
|
7856
|
+
}
|
|
7857
|
+
function countDirectImporters(db, targetPath, excludedPath) {
|
|
7858
|
+
const files2 = db.all(
|
|
7859
|
+
`SELECT relative_path
|
|
7860
|
+
FROM documents
|
|
7861
|
+
WHERE 1 = 1
|
|
7862
|
+
${db.pathExclusionsFor("documents")}
|
|
7863
|
+
ORDER BY relative_path`
|
|
7864
|
+
);
|
|
7865
|
+
const importers = /* @__PURE__ */ new Set();
|
|
7866
|
+
for (const row of files2) {
|
|
7867
|
+
if (db.isIgnored(row.relative_path) || row.relative_path === excludedPath) continue;
|
|
7868
|
+
for (const imported of getSourceImports(db, row.relative_path)) {
|
|
7869
|
+
if (imported.sourcePath === targetPath) {
|
|
7870
|
+
importers.add(row.relative_path);
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
return importers.size;
|
|
7875
|
+
}
|
|
7876
|
+
function representativeExportSymbol(db, sourcePath) {
|
|
7877
|
+
const definitions = getDefinitionsForFile(db, sourcePath);
|
|
7878
|
+
return definitions.find((definition) => leafSuffix(definition.symbol) === "method") ?? definitions[0] ?? null;
|
|
7879
|
+
}
|
|
7880
|
+
function dedupeReexports(rows) {
|
|
7881
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7882
|
+
const unique = [];
|
|
7883
|
+
for (const row of rows) {
|
|
7884
|
+
const key = `${row.barrelFile}|${row.symbol}|${row.originalFile}`;
|
|
7885
|
+
if (seen.has(key)) continue;
|
|
7886
|
+
seen.add(key);
|
|
7887
|
+
unique.push(row);
|
|
7888
|
+
}
|
|
7889
|
+
return unique;
|
|
6041
7890
|
}
|
|
6042
7891
|
|
|
6043
7892
|
// src/queries/similar-signatures.ts
|
|
6044
7893
|
function similarSignatures(db, opts = {}) {
|
|
6045
7894
|
const { scope, minLoc = 1, limit } = opts;
|
|
6046
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
6047
|
-
const rows = db.all(
|
|
6048
|
-
`SELECT
|
|
6049
|
-
gs.symbol,
|
|
6050
|
-
d.relative_path,
|
|
6051
|
-
der.start_line,
|
|
6052
|
-
der.end_line,
|
|
6053
|
-
(der.end_line - der.start_line + 1) AS loc,
|
|
6054
|
-
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
6055
|
-
FROM global_symbols gs
|
|
6056
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
6057
|
-
JOIN documents d ON der.document_id = d.id
|
|
6058
|
-
WHERE gs.documentation IS NOT NULL
|
|
6059
|
-
AND gs.documentation != ''
|
|
6060
|
-
AND INSTR(gs.documentation, '|') > 0
|
|
6061
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
6062
|
-
${db.pathExclusionsFor("d")}
|
|
6063
|
-
${db.symbolNoiseFor("gs")}
|
|
6064
|
-
${scopeFilter}
|
|
6065
|
-
ORDER BY d.relative_path, der.start_line`,
|
|
6066
|
-
minLoc
|
|
6067
|
-
);
|
|
6068
7895
|
const sigGroups = /* @__PURE__ */ new Map();
|
|
6069
|
-
for (const
|
|
6070
|
-
if (db.isIgnored(
|
|
6071
|
-
const
|
|
7896
|
+
for (const definition of getAllDefinitions(db, { scope })) {
|
|
7897
|
+
if (!definition.isFunctionLike || db.isIgnored(definition.relativePath)) continue;
|
|
7898
|
+
const loc = definition.endLine - definition.startLine + 1;
|
|
7899
|
+
if (loc < minLoc) continue;
|
|
7900
|
+
const normalized = resolveNormalizedSignature(db, definition);
|
|
6072
7901
|
if (!normalized) continue;
|
|
6073
7902
|
const entry = {
|
|
6074
|
-
symbol:
|
|
6075
|
-
shortName: shortenSymbol(
|
|
6076
|
-
file:
|
|
6077
|
-
startLine:
|
|
6078
|
-
endLine:
|
|
6079
|
-
loc
|
|
7903
|
+
symbol: definition.symbol,
|
|
7904
|
+
shortName: shortenSymbol(definition.symbol),
|
|
7905
|
+
file: definition.relativePath,
|
|
7906
|
+
startLine: definition.startLine,
|
|
7907
|
+
endLine: definition.endLine,
|
|
7908
|
+
loc
|
|
6080
7909
|
};
|
|
6081
7910
|
const existing = sigGroups.get(normalized);
|
|
6082
7911
|
if (existing) {
|
|
@@ -6099,6 +7928,52 @@ function similarSignatures(db, opts = {}) {
|
|
|
6099
7928
|
});
|
|
6100
7929
|
return limit ? results.slice(0, limit) : results;
|
|
6101
7930
|
}
|
|
7931
|
+
function resolveNormalizedSignature(db, definition) {
|
|
7932
|
+
const documented = extractDocumentedSignature(definition.documentation);
|
|
7933
|
+
const normalizedDocumented = documented ? normalizeSignature(documented) : null;
|
|
7934
|
+
if (normalizedDocumented) {
|
|
7935
|
+
return normalizedDocumented;
|
|
7936
|
+
}
|
|
7937
|
+
return normalizeSourceSignature(
|
|
7938
|
+
extractDeclarationHead(db, definition.relativePath, definition.startLine, definition.endLine, definition.leaf),
|
|
7939
|
+
definition.leaf
|
|
7940
|
+
);
|
|
7941
|
+
}
|
|
7942
|
+
function extractDocumentedSignature(documentation) {
|
|
7943
|
+
if (!documentation || !documentation.includes("|")) {
|
|
7944
|
+
return null;
|
|
7945
|
+
}
|
|
7946
|
+
const extracted = documentation.slice(documentation.indexOf("|") + 1).replace(/\n/g, " ").trim();
|
|
7947
|
+
return extracted.length > 0 ? extracted : null;
|
|
7948
|
+
}
|
|
7949
|
+
function extractDeclarationHead(db, relativePath, startLine, endLine, leaf) {
|
|
7950
|
+
const source = getSourceText(db, relativePath);
|
|
7951
|
+
if (!source) return null;
|
|
7952
|
+
const lines = source.split(/\r?\n/);
|
|
7953
|
+
const candidates = declarationStartLines(lines, startLine, endLine, leaf);
|
|
7954
|
+
for (const candidate of candidates) {
|
|
7955
|
+
const maxLine = Math.min(lines.length - 1, Math.max(candidate, candidate + 4));
|
|
7956
|
+
let collected = "";
|
|
7957
|
+
for (let lineIndex = candidate; lineIndex <= maxLine; lineIndex += 1) {
|
|
7958
|
+
const line = lines[lineIndex]?.trim();
|
|
7959
|
+
if (!line) continue;
|
|
7960
|
+
collected = collected ? `${collected} ${line}` : line;
|
|
7961
|
+
if (looksCompleteDeclaration(collected)) {
|
|
7962
|
+
return collected;
|
|
7963
|
+
}
|
|
7964
|
+
}
|
|
7965
|
+
if (collected && collected.includes("(")) {
|
|
7966
|
+
return collected;
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7969
|
+
return null;
|
|
7970
|
+
}
|
|
7971
|
+
function looksCompleteDeclaration(declaration) {
|
|
7972
|
+
const normalized = declaration.replace(/\s+/g, " ").trim();
|
|
7973
|
+
if (!normalized.includes("(")) return false;
|
|
7974
|
+
if (parenBalance(normalized) > 0) return false;
|
|
7975
|
+
return /[;{]$/.test(normalized) || /\)\s*(?::\s*[^={]+)?\s*(?:=>|=|throws\b|where\b|$)/i.test(normalized) || /\)\s*As\s+.+$/i.test(normalized);
|
|
7976
|
+
}
|
|
6102
7977
|
function normalizeSignature(raw) {
|
|
6103
7978
|
if (!raw || !raw.trim()) return null;
|
|
6104
7979
|
let sig = raw.replace(/^```\w*\s*/, "").replace(/\s*```$/, "").replace(/^\(method\)\s*/, "").replace(/^\(property\)\s*/, "").replace(/^\(function\)\s*/, "").replace(/^\(class\)\s*/, "").replace(/^\(interface\)\s*/, "").replace(/^\(enum\)\s*/, "").replace(/^\(type alias\)\s*/, "").replace(/^\(const\)\s*/, "").replace(/^\(var\)\s*/, "").trim();
|
|
@@ -6109,28 +7984,88 @@ function normalizeSignature(raw) {
|
|
|
6109
7984
|
if (sig.length < 3) return null;
|
|
6110
7985
|
return sig;
|
|
6111
7986
|
}
|
|
7987
|
+
function normalizeSourceSignature(raw, leaf) {
|
|
7988
|
+
if (!raw || !raw.trim()) return null;
|
|
7989
|
+
const declaration = raw.replace(/\s+/g, " ").trim();
|
|
7990
|
+
const parenIdx = declaration.indexOf("(");
|
|
7991
|
+
if (parenIdx === -1) return null;
|
|
7992
|
+
let prefix = declaration.slice(0, parenIdx);
|
|
7993
|
+
const leafPattern = new RegExp(`\\b${escapeRegex4(leaf)}\\b`, "i");
|
|
7994
|
+
const leafMatch = leafPattern.exec(prefix);
|
|
7995
|
+
if (leafMatch && typeof leafMatch.index === "number") {
|
|
7996
|
+
prefix = prefix.slice(0, leafMatch.index);
|
|
7997
|
+
}
|
|
7998
|
+
prefix = prefix.replace(/\b(public|private|protected|internal|final|static|abstract|sealed|virtual|override|async|suspend|inline|constexpr|consteval|constinit|const|pub|fn|function|def|sub|friend|shared|readonly|new|open|partial|export)\b/gi, " ").replace(/\s+/g, " ").trim();
|
|
7999
|
+
const suffix = declaration.slice(parenIdx).replace(/\s*\{[\s\S]*$/, "").replace(/\s*=>[\s\S]*$/, "").replace(/\)\s*=\s*[\s\S]*$/, ")").replace(/\s+throws\s+[^={]+$/i, "").replace(/\s+where\s+.+$/i, "").replace(/\s+/g, " ").trim();
|
|
8000
|
+
if (!suffix.startsWith("(")) {
|
|
8001
|
+
return null;
|
|
8002
|
+
}
|
|
8003
|
+
const normalized = `${prefix ? `${prefix} ` : ""}${suffix}`.replace(/\s+/g, "").toLowerCase();
|
|
8004
|
+
return normalized.length >= 3 ? normalized : null;
|
|
8005
|
+
}
|
|
8006
|
+
function declarationStartLines(lines, startLine, endLine, leaf) {
|
|
8007
|
+
const escapedLeaf = escapeRegex4(leaf);
|
|
8008
|
+
const callablePattern = new RegExp(`\\b${escapedLeaf}\\b\\s*\\(`, "i");
|
|
8009
|
+
const rubyPattern = new RegExp(`\\bdef\\s+${escapedLeaf}\\b`, "i");
|
|
8010
|
+
const candidates = [];
|
|
8011
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8012
|
+
const preferredStart = Math.max(0, Math.min(startLine, lines.length - 1));
|
|
8013
|
+
const preferredEnd = Math.max(preferredStart, Math.min(lines.length - 1, Math.max(endLine, startLine + 4)));
|
|
8014
|
+
for (let lineIndex = preferredStart; lineIndex <= preferredEnd; lineIndex += 1) {
|
|
8015
|
+
const line = lines[lineIndex] ?? "";
|
|
8016
|
+
if ((callablePattern.test(line) || rubyPattern.test(line)) && !seen.has(lineIndex)) {
|
|
8017
|
+
seen.add(lineIndex);
|
|
8018
|
+
candidates.push(lineIndex);
|
|
8019
|
+
}
|
|
8020
|
+
}
|
|
8021
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
8022
|
+
const line = lines[lineIndex] ?? "";
|
|
8023
|
+
if ((callablePattern.test(line) || rubyPattern.test(line)) && !seen.has(lineIndex)) {
|
|
8024
|
+
seen.add(lineIndex);
|
|
8025
|
+
candidates.push(lineIndex);
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
return candidates;
|
|
8029
|
+
}
|
|
8030
|
+
function parenBalance(value) {
|
|
8031
|
+
let balance = 0;
|
|
8032
|
+
for (const char of value) {
|
|
8033
|
+
if (char === "(") balance += 1;
|
|
8034
|
+
if (char === ")") balance -= 1;
|
|
8035
|
+
}
|
|
8036
|
+
return balance;
|
|
8037
|
+
}
|
|
8038
|
+
function escapeRegex4(value) {
|
|
8039
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8040
|
+
}
|
|
6112
8041
|
|
|
6113
8042
|
// src/setup.ts
|
|
6114
8043
|
import {
|
|
6115
|
-
existsSync as
|
|
8044
|
+
existsSync as existsSync9,
|
|
6116
8045
|
mkdirSync as mkdirSync2,
|
|
6117
8046
|
symlinkSync,
|
|
6118
8047
|
readlinkSync,
|
|
6119
8048
|
unlinkSync
|
|
6120
8049
|
} from "fs";
|
|
6121
|
-
import { join as
|
|
8050
|
+
import { join as join11, dirname as dirname3, resolve as resolve3 } from "path";
|
|
6122
8051
|
import { homedir as homedir2, platform as platform3 } from "os";
|
|
6123
8052
|
import { fileURLToPath } from "url";
|
|
6124
8053
|
var IS_WINDOWS3 = platform3() === "win32";
|
|
6125
|
-
var
|
|
8054
|
+
var BUILTIN_SKILLS = [
|
|
8055
|
+
"concrete-plan",
|
|
8056
|
+
"scip-explore",
|
|
8057
|
+
"scip-debloat",
|
|
8058
|
+
"scip-verify",
|
|
8059
|
+
"scip-language-playbook"
|
|
8060
|
+
];
|
|
6126
8061
|
function installSkills(opts = {}) {
|
|
6127
8062
|
const log = opts.quiet ? () => {
|
|
6128
8063
|
} : console.log;
|
|
6129
8064
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6130
8065
|
const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
|
|
6131
8066
|
const targets = [
|
|
6132
|
-
|
|
6133
|
-
|
|
8067
|
+
join11(homedir2(), ".claude", "skills"),
|
|
8068
|
+
join11(homedir2(), ".codex", "skills")
|
|
6134
8069
|
];
|
|
6135
8070
|
const result = {
|
|
6136
8071
|
installed: [],
|
|
@@ -6139,19 +8074,19 @@ function installSkills(opts = {}) {
|
|
|
6139
8074
|
};
|
|
6140
8075
|
for (const targetDir of targets) {
|
|
6141
8076
|
const parentDir = dirname3(targetDir);
|
|
6142
|
-
if (!
|
|
8077
|
+
if (!existsSync9(parentDir)) {
|
|
6143
8078
|
continue;
|
|
6144
8079
|
}
|
|
6145
8080
|
mkdirSync2(targetDir, { recursive: true });
|
|
6146
8081
|
const toolName = targetDir.includes(".codex") ? "Codex" : "Claude";
|
|
6147
|
-
for (const skill of
|
|
6148
|
-
const source =
|
|
6149
|
-
const target =
|
|
6150
|
-
if (!
|
|
8082
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
8083
|
+
const source = join11(skillsSource, skill);
|
|
8084
|
+
const target = join11(targetDir, skill);
|
|
8085
|
+
if (!existsSync9(source)) {
|
|
6151
8086
|
result.skipped.push(`${toolName}/${skill}`);
|
|
6152
8087
|
continue;
|
|
6153
8088
|
}
|
|
6154
|
-
if (
|
|
8089
|
+
if (existsSync9(target)) {
|
|
6155
8090
|
try {
|
|
6156
8091
|
const existing = readlinkSync(target);
|
|
6157
8092
|
if (resolve3(existing) === resolve3(source)) {
|
|
@@ -6183,14 +8118,14 @@ function resolveProjectRoot() {
|
|
|
6183
8118
|
function resolveActiveDbPath(projectRoot) {
|
|
6184
8119
|
const config = loadProjectConfig(projectRoot);
|
|
6185
8120
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6186
|
-
return process.env["SCIP_QUERY_INDEX_DB"] ?? (
|
|
8121
|
+
return process.env["SCIP_QUERY_INDEX_DB"] ?? (existsSync10(paths.dbPath) ? paths.dbPath : join12(projectRoot, "index.db"));
|
|
6187
8122
|
}
|
|
6188
8123
|
function openDb() {
|
|
6189
8124
|
const projectRoot = resolveProjectRoot();
|
|
6190
8125
|
const config = loadProjectConfig(projectRoot);
|
|
6191
8126
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6192
8127
|
const dbPath = resolveActiveDbPath(projectRoot);
|
|
6193
|
-
if (!
|
|
8128
|
+
if (!existsSync10(dbPath)) {
|
|
6194
8129
|
console.error(`error: No index.db found. Run: scip-query reindex`);
|
|
6195
8130
|
process.exit(1);
|
|
6196
8131
|
}
|
|
@@ -6361,6 +8296,10 @@ program.command("trace <symbol>").description("Trace a symbol: definition + all
|
|
|
6361
8296
|
for (const d of result.definitions) {
|
|
6362
8297
|
const sig = d.signature ? ` \u2014 ${d.signature}` : "";
|
|
6363
8298
|
console.log(` ${displayPathRange(d.relativePath, d.startLine, d.endLine)}${sig}`);
|
|
8299
|
+
if (d.source) {
|
|
8300
|
+
const numbered = d.source.split("\n").map((line, index) => ` ${displayLine(d.startLine + index)} ${line}`).join("\n");
|
|
8301
|
+
console.log(numbered);
|
|
8302
|
+
}
|
|
6364
8303
|
}
|
|
6365
8304
|
console.log("\n\u2550\u2550\u2550 REFERENCED BY \u2550\u2550\u2550");
|
|
6366
8305
|
let prevFile = "";
|
|
@@ -6767,7 +8706,7 @@ ${results.length} similar pair(s) found.`);
|
|
|
6767
8706
|
}
|
|
6768
8707
|
});
|
|
6769
8708
|
});
|
|
6770
|
-
program.command("similar-files [file]").description("Find files with similar dependency profiles").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.5).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-deps <n>", "Minimum dependencies to consider", parseIntSafe
|
|
8709
|
+
program.command("similar-files [file]").description("Find files with similar dependency profiles").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.5).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-deps <n>", "Minimum dependencies to consider", parseIntSafe).action((file, opts) => {
|
|
6771
8710
|
runQuery(
|
|
6772
8711
|
(db) => queries.similarFiles(db, {
|
|
6773
8712
|
minSimilarity: opts.minSimilarity,
|
|
@@ -7167,7 +9106,7 @@ ${result.connectedSymbols.length} connected symbol(s).`);
|
|
|
7167
9106
|
}
|
|
7168
9107
|
db.close();
|
|
7169
9108
|
});
|
|
7170
|
-
program.command("install-skills").description(
|
|
9109
|
+
program.command("install-skills").description(`Install skills (${BUILTIN_SKILLS.join(", ")}) into Claude Code and Codex`).action(() => {
|
|
7171
9110
|
const result = installSkills();
|
|
7172
9111
|
const total = result.installed.length + result.alreadyLinked.length;
|
|
7173
9112
|
console.log(`
|
|
@@ -7176,12 +9115,41 @@ ${result.installed.length} installed, ${result.alreadyLinked.length} already lin
|
|
|
7176
9115
|
console.log("Skills will be available in your next Claude Code / Codex session.");
|
|
7177
9116
|
}
|
|
7178
9117
|
});
|
|
7179
|
-
program.command("check-deps").description("Check
|
|
9118
|
+
program.command("check-deps").description("Check whether scip-query and the detected language indexers are actually runnable").action(() => {
|
|
9119
|
+
let hasProblems = false;
|
|
7180
9120
|
if (isScipInstalled()) {
|
|
7181
9121
|
console.log("scip CLI: installed");
|
|
7182
9122
|
} else {
|
|
7183
9123
|
printScipInstallInstructions();
|
|
9124
|
+
hasProblems = true;
|
|
9125
|
+
}
|
|
9126
|
+
const projectRoot = resolveProjectRoot();
|
|
9127
|
+
const config = loadProjectConfig(projectRoot);
|
|
9128
|
+
const languages = config.languages ?? detectLanguages(projectRoot);
|
|
9129
|
+
if (languages.length === 0) {
|
|
9130
|
+
console.log("\nNo supported project languages detected in the current directory.");
|
|
9131
|
+
process.exitCode = hasProblems ? 1 : 0;
|
|
9132
|
+
return;
|
|
9133
|
+
}
|
|
9134
|
+
console.log(`
|
|
9135
|
+
Detected languages: ${languages.join(", ")}`);
|
|
9136
|
+
console.log("\nIndexer readiness:");
|
|
9137
|
+
for (const language of languages) {
|
|
9138
|
+
const status = getIndexerDependencyStatus(getIndexerConfig(language), projectRoot);
|
|
9139
|
+
const prefix = status.runnable ? " OK" : status.installed ? " WARN" : " MISSING";
|
|
9140
|
+
const resolved = status.resolvedBinary ? ` (${status.resolvedBinary})` : "";
|
|
9141
|
+
console.log(`${prefix} ${language}: ${status.binaryLabel}${resolved}`);
|
|
9142
|
+
if (status.note) {
|
|
9143
|
+
console.log(` ${status.note}`);
|
|
9144
|
+
}
|
|
9145
|
+
if (!status.installed && status.installUrl) {
|
|
9146
|
+
console.log(` install: ${status.installUrl}`);
|
|
9147
|
+
}
|
|
9148
|
+
if (!status.runnable) {
|
|
9149
|
+
hasProblems = true;
|
|
9150
|
+
}
|
|
7184
9151
|
}
|
|
9152
|
+
process.exitCode = hasProblems ? 1 : 0;
|
|
7185
9153
|
});
|
|
7186
9154
|
program.command("redundant-reexports").description("Find barrel re-exports that nobody imports through").option("-s, --scope <path>", "Limit to files matching path").option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
|
|
7187
9155
|
const db = openDb();
|
|
@@ -7269,8 +9237,8 @@ program.command("status").description("Show index status for this project").acti
|
|
|
7269
9237
|
if (dbPath !== paths.dbPath) {
|
|
7270
9238
|
console.log(`Config: ${paths.dbPath} (fallback to project root index.db)`);
|
|
7271
9239
|
}
|
|
7272
|
-
console.log(`Exists: ${
|
|
7273
|
-
if (
|
|
9240
|
+
console.log(`Exists: ${existsSync10(dbPath) ? "yes" : "no"}`);
|
|
9241
|
+
if (existsSync10(dbPath)) {
|
|
7274
9242
|
withDb((db) => {
|
|
7275
9243
|
const s = queries.stats(db);
|
|
7276
9244
|
console.log(`Symbols: ${s.symbols}`);
|