scip-query 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/{chunk-6FKIA6EI.js → chunk-2F2WH5WQ.js} +12 -12
- package/dist/{chunk-C7H5WBTJ.js → chunk-2UISVZGQ.js} +2 -2
- package/dist/{chunk-EN2Z2CLO.js → chunk-4ZT7UGWW.js} +16 -19
- package/dist/chunk-5AJJGPZE.js +60 -0
- package/dist/chunk-5RKYZSQ6.js +75 -0
- package/dist/chunk-7YBLWIXY.js +115 -0
- package/dist/{chunk-O7Q7FDUJ.js → chunk-A4GWYETB.js} +2 -2
- package/dist/{chunk-CHDJXYBG.js → chunk-A5BGEBM7.js} +2 -2
- package/dist/{chunk-KKCHYLVI.js → chunk-A7YY7IDA.js} +2 -2
- package/dist/chunk-AS7N27JK.js +115 -0
- package/dist/{chunk-F7XU27LU.js → chunk-CQRYLK33.js} +26 -2
- package/dist/{chunk-NFS5W3PP.js → chunk-CQUNEJYM.js} +2 -2
- package/dist/chunk-D4I3ZMN5.js +38 -0
- package/dist/{chunk-J3JSOSUO.js → chunk-E7J7Q7UW.js} +2 -2
- package/dist/{chunk-GEXE2T6I.js → chunk-EOHPASDV.js} +22 -14
- package/dist/chunk-GIBETK3W.js +37 -0
- package/dist/{chunk-SMDCNPMK.js → chunk-H3FPW5YN.js} +2 -2
- package/dist/{chunk-VMM4SYV4.js → chunk-HNURMDF4.js} +16 -3
- package/dist/{chunk-7KIMF5PV.js → chunk-HRDPUTIQ.js} +2 -2
- package/dist/{chunk-OIDHN6GD.js → chunk-I2JM34UV.js} +146 -5
- package/dist/{chunk-EPWLXXBL.js → chunk-IV6NZ426.js} +27 -29
- package/dist/{chunk-VT4JBH6L.js → chunk-KDCQJTYW.js} +2 -2
- package/dist/{chunk-5OMVSV6E.js → chunk-LOVDB4C6.js} +2 -2
- package/dist/chunk-MA3B3IUT.js +75 -0
- package/dist/{chunk-26DOJ63W.js → chunk-N2LH3M2P.js} +13 -2
- package/dist/chunk-NWCJWA36.js +162 -0
- package/dist/{chunk-NG5F43OU.js → chunk-P3VCDYMJ.js} +70 -1
- package/dist/{chunk-P3E6L7KW.js → chunk-P42KQKJZ.js} +83 -3
- package/dist/{chunk-YDBXNPYU.js → chunk-PGHN5UTM.js} +2 -2
- package/dist/{chunk-UGQKAVCD.js → chunk-QCYR4S6T.js} +2 -2
- package/dist/chunk-QGCEAVJD.js +2529 -0
- package/dist/{chunk-KBOQX573.js → chunk-RIEA5DOB.js} +19 -2
- package/dist/{chunk-VIYSWZCO.js → chunk-SL674KAW.js} +31 -3
- package/dist/chunk-SRELHCMG.js +110 -0
- package/dist/chunk-UTRKBUCB.js +87 -0
- package/dist/{chunk-TRESG7OB.js → chunk-VCOJRQPP.js} +2 -2
- package/dist/chunk-VISMEWYP.js +34 -0
- package/dist/{chunk-LFJQVJYJ.js → chunk-VU7FDTWV.js} +2 -2
- package/dist/{chunk-HLUS2HEB.js → chunk-VUBLUTMU.js} +5 -4
- package/dist/{chunk-WGAD3GNR.js → chunk-WNPF2I25.js} +5 -5
- package/dist/{chunk-YY4QGUQ5.js → chunk-X3J4VPWM.js} +2 -2
- package/dist/{chunk-GSH2FPKV.js → chunk-XH56HXLC.js} +2 -2
- package/dist/{chunk-DH7G3DDV.js → chunk-ZU2AQQB5.js} +2 -2
- package/dist/cli.js +2607 -805
- package/dist/{db-viWlyVtv.d.ts → db-C4rPbKkI.d.ts} +6 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +371 -138
- package/dist/postinstall.js +9 -3
- package/dist/queries/affected.d.ts +1 -1
- package/dist/queries/affected.js +2 -2
- package/dist/queries/bottlenecks.d.ts +1 -1
- package/dist/queries/bottlenecks.js +2 -1
- package/dist/queries/by-kind.d.ts +1 -1
- package/dist/queries/by-kind.js +2 -1
- package/dist/queries/call-graph.d.ts +1 -1
- package/dist/queries/call-graph.js +2 -2
- package/dist/queries/change-surface.d.ts +1 -1
- package/dist/queries/change-surface.js +2 -2
- package/dist/queries/code.d.ts +1 -1
- package/dist/queries/code.js +2 -2
- package/dist/queries/complexity-hotspots.d.ts +1 -1
- package/dist/queries/complexity-hotspots.js +2 -2
- package/dist/queries/complexity.d.ts +1 -1
- package/dist/queries/complexity.js +2 -2
- package/dist/queries/convergence.d.ts +1 -1
- package/dist/queries/convergence.js +2 -2
- package/dist/queries/coupling.d.ts +1 -1
- package/dist/queries/coupling.js +2 -2
- package/dist/queries/cycles.d.ts +1 -1
- package/dist/queries/cycles.js +2 -2
- package/dist/queries/dataflow.d.ts +1 -1
- package/dist/queries/dataflow.js +2 -2
- package/dist/queries/dead.d.ts +1 -1
- package/dist/queries/dead.js +3 -3
- package/dist/queries/deep-chains.d.ts +1 -1
- package/dist/queries/deep-chains.js +2 -2
- package/dist/queries/deps.d.ts +1 -1
- package/dist/queries/deps.js +2 -2
- package/dist/queries/diff-impact.d.ts +1 -1
- package/dist/queries/drift.d.ts +1 -1
- package/dist/queries/drift.js +2 -2
- package/dist/queries/extract-candidates.d.ts +1 -1
- package/dist/queries/extract-candidates.js +2 -2
- package/dist/queries/fan.d.ts +1 -1
- package/dist/queries/fan.js +2 -2
- package/dist/queries/files.d.ts +1 -1
- package/dist/queries/health.d.ts +1 -1
- package/dist/queries/health.js +13 -13
- package/dist/queries/hierarchy.d.ts +1 -1
- package/dist/queries/hierarchy.js +2 -2
- package/dist/queries/hotspots.d.ts +1 -1
- package/dist/queries/hotspots.js +2 -1
- package/dist/queries/imports.d.ts +1 -1
- package/dist/queries/imports.js +2 -2
- package/dist/queries/index.d.ts +1 -1
- package/dist/queries/index.js +42 -42
- package/dist/queries/isolated.d.ts +3 -4
- package/dist/queries/isolated.js +3 -3
- package/dist/queries/members.d.ts +1 -1
- package/dist/queries/members.js +2 -2
- package/dist/queries/methods.d.ts +1 -1
- package/dist/queries/methods.js +2 -1
- package/dist/queries/outline.d.ts +1 -1
- package/dist/queries/outline.js +2 -2
- package/dist/queries/passthrough-candidates.d.ts +1 -1
- package/dist/queries/passthrough-candidates.js +2 -2
- package/dist/queries/redundant-reexports.d.ts +1 -1
- package/dist/queries/redundant-reexports.js +3 -3
- package/dist/queries/refs.d.ts +1 -1
- package/dist/queries/refs.js +2 -2
- package/dist/queries/similar-chains.d.ts +1 -1
- package/dist/queries/similar-chains.js +2 -2
- package/dist/queries/similar-files.d.ts +1 -1
- package/dist/queries/similar-files.js +2 -2
- package/dist/queries/similar-signatures.d.ts +5 -3
- package/dist/queries/similar-signatures.js +2 -1
- package/dist/queries/similar.d.ts +1 -1
- package/dist/queries/similar.js +2 -2
- package/dist/queries/slice.d.ts +1 -1
- package/dist/queries/slice.js +2 -2
- package/dist/queries/stale-abstractions.d.ts +1 -1
- package/dist/queries/stale-abstractions.js +2 -2
- package/dist/queries/stats.d.ts +1 -1
- package/dist/queries/surface.d.ts +1 -1
- package/dist/queries/surface.js +2 -2
- package/dist/queries/symbols.d.ts +1 -1
- package/dist/queries/symbols.js +2 -2
- package/dist/queries/system.d.ts +1 -1
- package/dist/queries/system.js +2 -2
- package/dist/queries/trace.d.ts +1 -1
- package/dist/queries/trace.js +2 -2
- package/dist/queries/wrapper-candidates.d.ts +1 -1
- package/dist/queries/wrapper-candidates.js +2 -2
- package/dist/reindex-worker.js +213 -62
- package/package.json +1 -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
|
});
|
|
@@ -780,9 +1051,10 @@ async function reindex(opts) {
|
|
|
780
1051
|
Make sure ${binaryLabel} is installed and available on PATH.`
|
|
781
1052
|
);
|
|
782
1053
|
}
|
|
1054
|
+
moveDefaultOutputIfNeeded(config, projectRoot, outputScip);
|
|
783
1055
|
}
|
|
784
1056
|
onStatus("Converting to SQLite...");
|
|
785
|
-
if (!
|
|
1057
|
+
if (!existsSync6(outputScip)) {
|
|
786
1058
|
throw new Error(`SCIP index not found at ${outputScip} after indexing`);
|
|
787
1059
|
}
|
|
788
1060
|
try {
|
|
@@ -799,11 +1071,20 @@ Make sure ${binaryLabel} is installed and available on PATH.`
|
|
|
799
1071
|
onStatus(`Done in ${(durationMs / 1e3).toFixed(1)}s`);
|
|
800
1072
|
return { languages, indexPath: outputScip, dbPath: outputDb, durationMs };
|
|
801
1073
|
}
|
|
1074
|
+
function moveDefaultOutputIfNeeded(config, projectRoot, outputScip) {
|
|
1075
|
+
if (!config.defaultOutputPath) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const defaultOutputPath = join6(projectRoot, config.defaultOutputPath);
|
|
1079
|
+
if (outputScip !== defaultOutputPath && existsSync6(defaultOutputPath)) {
|
|
1080
|
+
renameSync(defaultOutputPath, outputScip);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
802
1083
|
|
|
803
1084
|
// src/watch.ts
|
|
804
1085
|
import { watch } from "fs";
|
|
805
|
-
import { existsSync as
|
|
806
|
-
import { join as
|
|
1086
|
+
import { existsSync as existsSync7, renameSync as renameSync2 } from "fs";
|
|
1087
|
+
import { join as join7, relative } from "path";
|
|
807
1088
|
import { fork } from "child_process";
|
|
808
1089
|
import ignore2 from "ignore";
|
|
809
1090
|
var Watcher = class {
|
|
@@ -881,7 +1162,7 @@ var Watcher = class {
|
|
|
881
1162
|
}
|
|
882
1163
|
// ── Internal ─────────────────────────────────────────────
|
|
883
1164
|
handleFileChange(filename) {
|
|
884
|
-
const rel = relative(this.projectRoot,
|
|
1165
|
+
const rel = relative(this.projectRoot, join7(this.projectRoot, filename));
|
|
885
1166
|
if (this.gitignoreFilter.isIgnored(rel)) return;
|
|
886
1167
|
if (this.extraIgnore.ignores(rel)) return;
|
|
887
1168
|
if (filename.endsWith("index.db") || filename.endsWith("index.scip") || filename.endsWith("index.db.tmp") || filename.endsWith(".scipquery.json")) {
|
|
@@ -984,11 +1265,11 @@ var Watcher = class {
|
|
|
984
1265
|
child.on("exit", (code2) => {
|
|
985
1266
|
if (code2 === 0) {
|
|
986
1267
|
try {
|
|
987
|
-
if (
|
|
988
|
-
|
|
1268
|
+
if (existsSync7(tmpDb)) {
|
|
1269
|
+
renameSync2(tmpDb, this.indexPaths.dbPath);
|
|
989
1270
|
}
|
|
990
|
-
if (
|
|
991
|
-
|
|
1271
|
+
if (existsSync7(tmpScip)) {
|
|
1272
|
+
renameSync2(tmpScip, this.indexPaths.indexPath);
|
|
992
1273
|
}
|
|
993
1274
|
resolve4(Date.now() - start);
|
|
994
1275
|
} catch (err) {
|
|
@@ -1042,27 +1323,36 @@ function files(db, pattern) {
|
|
|
1042
1323
|
}
|
|
1043
1324
|
|
|
1044
1325
|
// src/query-support.ts
|
|
1045
|
-
import { basename } from "path";
|
|
1326
|
+
import { basename as basename2 } from "path";
|
|
1046
1327
|
|
|
1047
1328
|
// src/source-analysis.ts
|
|
1048
1329
|
import {
|
|
1049
|
-
existsSync as
|
|
1330
|
+
existsSync as existsSync8,
|
|
1050
1331
|
readFileSync as readFileSync4
|
|
1051
1332
|
} from "fs";
|
|
1052
1333
|
import {
|
|
1334
|
+
basename,
|
|
1053
1335
|
dirname as dirname2,
|
|
1054
|
-
extname,
|
|
1055
|
-
join as
|
|
1336
|
+
extname as extname2,
|
|
1337
|
+
join as join8,
|
|
1056
1338
|
relative as relative2,
|
|
1057
1339
|
resolve as resolve2
|
|
1058
1340
|
} from "path";
|
|
1059
1341
|
var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1342
|
+
var SOURCE_EXPORT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1060
1343
|
var SOURCE_TEXT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1061
1344
|
var SOURCE_CALL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1062
1345
|
var SOURCE_BINDING_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1063
1346
|
var INDEXED_PATH_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1064
1347
|
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1065
1348
|
var PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
|
|
1349
|
+
var JVM_SOURCE_EXTENSIONS = [".java", ".scala", ".kt", ".kts"];
|
|
1350
|
+
var RUST_SOURCE_EXTENSIONS = [".rs"];
|
|
1351
|
+
var RUBY_SOURCE_EXTENSIONS = [".rb"];
|
|
1352
|
+
var C_LIKE_SOURCE_EXTENSIONS = [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"];
|
|
1353
|
+
var DOTNET_SOURCE_EXTENSIONS = [".cs", ".vb"];
|
|
1354
|
+
var DART_SOURCE_EXTENSIONS = [".dart"];
|
|
1355
|
+
var PHP_SOURCE_EXTENSIONS = [".php"];
|
|
1066
1356
|
function getSourceImports(db, relativePath) {
|
|
1067
1357
|
const cache = getCachedMap(SOURCE_IMPORT_CACHE, db);
|
|
1068
1358
|
const normalized = normalizePath(relativePath);
|
|
@@ -1070,13 +1360,30 @@ function getSourceImports(db, relativePath) {
|
|
|
1070
1360
|
if (cached) {
|
|
1071
1361
|
return cached;
|
|
1072
1362
|
}
|
|
1073
|
-
const fullPath =
|
|
1074
|
-
if (!
|
|
1363
|
+
const fullPath = join8(db.config.projectRoot, normalized);
|
|
1364
|
+
if (!existsSync8(fullPath)) {
|
|
1365
|
+
cache.set(normalized, []);
|
|
1366
|
+
return [];
|
|
1367
|
+
}
|
|
1368
|
+
const source = readFileSync4(fullPath, "utf-8");
|
|
1369
|
+
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) : [];
|
|
1370
|
+
cache.set(normalized, parsed);
|
|
1371
|
+
return parsed;
|
|
1372
|
+
}
|
|
1373
|
+
function getSourceExports(db, relativePath) {
|
|
1374
|
+
const cache = getCachedMap(SOURCE_EXPORT_CACHE, db);
|
|
1375
|
+
const normalized = normalizePath(relativePath);
|
|
1376
|
+
const cached = cache.get(normalized);
|
|
1377
|
+
if (cached) {
|
|
1378
|
+
return cached;
|
|
1379
|
+
}
|
|
1380
|
+
const fullPath = join8(db.config.projectRoot, normalized);
|
|
1381
|
+
if (!existsSync8(fullPath)) {
|
|
1075
1382
|
cache.set(normalized, []);
|
|
1076
1383
|
return [];
|
|
1077
1384
|
}
|
|
1078
1385
|
const source = readFileSync4(fullPath, "utf-8");
|
|
1079
|
-
const parsed =
|
|
1386
|
+
const parsed = isDartSourcePath(normalized) ? parseDartExports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustExports(db, normalized, source) : [];
|
|
1080
1387
|
cache.set(normalized, parsed);
|
|
1081
1388
|
return parsed;
|
|
1082
1389
|
}
|
|
@@ -1225,96 +1532,357 @@ function parseJavaScriptImportStatement(db, importerPath, clause, specifier, sta
|
|
|
1225
1532
|
};
|
|
1226
1533
|
});
|
|
1227
1534
|
}
|
|
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
|
-
}
|
|
1535
|
+
function parseJvmImports(db, importerPath, source) {
|
|
1536
|
+
const statements = [];
|
|
1537
|
+
for (const match of source.matchAll(/^[ \t]*import\s+(?:static\s+)?(.+?)\s*;?$/gm)) {
|
|
1538
|
+
const clause = match[1]?.trim();
|
|
1539
|
+
const full = match[0];
|
|
1540
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1541
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1542
|
+
statements.push(...parseJvmImportClause(db, importerPath, clause, body));
|
|
1280
1543
|
}
|
|
1281
|
-
return
|
|
1544
|
+
return statements;
|
|
1282
1545
|
}
|
|
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
|
-
|
|
1546
|
+
function parseJvmImportClause(db, importerPath, clause, body) {
|
|
1547
|
+
if (clause.includes("{") && clause.includes("}")) {
|
|
1548
|
+
const prefix = clause.slice(0, clause.indexOf("{")).replace(/\.$/, "").trim();
|
|
1549
|
+
const inner = clause.slice(clause.indexOf("{") + 1, clause.lastIndexOf("}")).trim();
|
|
1550
|
+
return splitTopLevel(inner).flatMap((entry) => {
|
|
1551
|
+
const cleaned = entry.trim();
|
|
1552
|
+
if (!cleaned) return [];
|
|
1553
|
+
const [importedPart, aliasPart] = cleaned.includes("=>") ? cleaned.split(/\s*=>\s*/) : cleaned.split(/\s+as\s+/);
|
|
1554
|
+
const importedName = importedPart?.trim();
|
|
1555
|
+
if (!importedName || importedName === "_") return [];
|
|
1556
|
+
const localName = (aliasPart ?? importedName.split(".").pop() ?? importedName).trim();
|
|
1557
|
+
const qualified = importedName === "_" ? prefix : `${prefix}.${importedName}`.replace(/\.\./g, ".");
|
|
1558
|
+
return [buildSimpleImport(db, importerPath, body, qualified, importedName, localName)];
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
return [buildSimpleImport(
|
|
1562
|
+
db,
|
|
1563
|
+
importerPath,
|
|
1564
|
+
body,
|
|
1565
|
+
clause,
|
|
1566
|
+
clause.split(".").pop() ?? clause,
|
|
1567
|
+
clause.split(".").pop() ?? clause
|
|
1568
|
+
)];
|
|
1569
|
+
}
|
|
1570
|
+
function parseRustImports(db, importerPath, source) {
|
|
1571
|
+
const statements = [];
|
|
1572
|
+
for (const match of source.matchAll(/^[ \t]*use\s+(.+?)\s*;$/gm)) {
|
|
1573
|
+
const clause = match[1]?.trim();
|
|
1574
|
+
const full = match[0];
|
|
1575
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1576
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1577
|
+
statements.push(...parseRustUseClause(db, importerPath, clause, body));
|
|
1578
|
+
}
|
|
1579
|
+
return statements;
|
|
1580
|
+
}
|
|
1581
|
+
function parseRustUseClause(db, importerPath, clause, body) {
|
|
1582
|
+
const trimmed = clause.trim();
|
|
1583
|
+
if (trimmed.includes("{") && trimmed.includes("}")) {
|
|
1584
|
+
const prefix = trimmed.slice(0, trimmed.indexOf("{")).replace(/::$/, "").trim();
|
|
1585
|
+
const inner = trimmed.slice(trimmed.indexOf("{") + 1, trimmed.lastIndexOf("}")).trim();
|
|
1586
|
+
return splitTopLevel(inner).flatMap((entry) => {
|
|
1587
|
+
const cleaned = entry.trim();
|
|
1588
|
+
if (!cleaned || cleaned === "self") return [];
|
|
1589
|
+
const [importedPart2, aliasPart2] = cleaned.split(/\s+as\s+/);
|
|
1590
|
+
const importedName2 = importedPart2?.trim();
|
|
1591
|
+
if (!importedName2) return [];
|
|
1592
|
+
const localName2 = (aliasPart2 ?? importedName2.split("::").pop() ?? importedName2).trim();
|
|
1593
|
+
const moduleSpecifier = `${prefix}::${importedName2}`.replace(/::::/g, "::");
|
|
1594
|
+
return [buildSimpleImport(
|
|
1595
|
+
db,
|
|
1596
|
+
importerPath,
|
|
1597
|
+
body,
|
|
1598
|
+
moduleSpecifier,
|
|
1599
|
+
importedName2.split("::").pop() ?? importedName2,
|
|
1600
|
+
localName2,
|
|
1601
|
+
resolveRustImportPath(db, importerPath, prefix)
|
|
1602
|
+
)];
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
const [importedPart, aliasPart] = trimmed.split(/\s+as\s+/);
|
|
1606
|
+
const importedName = importedPart?.trim() ?? trimmed;
|
|
1607
|
+
const localName = (aliasPart ?? importedName.split("::").pop() ?? importedName).trim();
|
|
1608
|
+
const resolved = resolveRustImportPath(db, importerPath, importedName) ?? resolveRustImportPath(db, importerPath, importedName.split("::").slice(0, -1).join("::"));
|
|
1609
|
+
return [buildSimpleImport(
|
|
1610
|
+
db,
|
|
1611
|
+
importerPath,
|
|
1612
|
+
body,
|
|
1613
|
+
importedName,
|
|
1614
|
+
importedName.split("::").pop() ?? importedName,
|
|
1615
|
+
localName,
|
|
1616
|
+
resolved
|
|
1617
|
+
)];
|
|
1618
|
+
}
|
|
1619
|
+
function parseRustExports(db, importerPath, source) {
|
|
1620
|
+
const statements = [];
|
|
1621
|
+
for (const match of source.matchAll(/^[ \t]*pub\s+use\s+(.+?)\s*;$/gm)) {
|
|
1622
|
+
const clause = match[1]?.trim();
|
|
1623
|
+
if (!clause) continue;
|
|
1624
|
+
statements.push(...parseRustExportClause(db, importerPath, clause));
|
|
1625
|
+
}
|
|
1626
|
+
return statements;
|
|
1627
|
+
}
|
|
1628
|
+
function parseRustExportClause(db, importerPath, clause) {
|
|
1629
|
+
const trimmed = clause.trim();
|
|
1630
|
+
if (trimmed.includes("{") && trimmed.includes("}")) {
|
|
1631
|
+
const prefix = trimmed.slice(0, trimmed.indexOf("{")).replace(/::$/, "").trim();
|
|
1632
|
+
const inner = trimmed.slice(trimmed.indexOf("{") + 1, trimmed.lastIndexOf("}")).trim();
|
|
1633
|
+
return splitTopLevel(inner).flatMap((entry) => {
|
|
1634
|
+
const cleaned = entry.trim();
|
|
1635
|
+
if (!cleaned || cleaned === "self") return [];
|
|
1636
|
+
const [qualifiedPart] = cleaned.split(/\s+as\s+/);
|
|
1637
|
+
const qualified = `${prefix}::${qualifiedPart?.trim() ?? cleaned}`.replace(/::::/g, "::");
|
|
1638
|
+
return [buildRustExport(db, importerPath, qualified)];
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
return [buildRustExport(db, importerPath, trimmed)];
|
|
1642
|
+
}
|
|
1643
|
+
function buildRustExport(db, importerPath, specifier) {
|
|
1644
|
+
return {
|
|
1645
|
+
specifier,
|
|
1646
|
+
sourcePath: resolveRustImportPath(db, importerPath, specifier) ?? resolveRustImportPath(db, importerPath, specifier.split("::").slice(0, -1).join("::"))
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
function parseRubyImports(db, importerPath, source) {
|
|
1650
|
+
const statements = [];
|
|
1651
|
+
for (const match of source.matchAll(/^[ \t]*(require_relative|require)\s+["']([^"']+)["']\s*$/gm)) {
|
|
1652
|
+
const kind = match[1];
|
|
1653
|
+
const specifier = match[2];
|
|
1654
|
+
const full = match[0];
|
|
1655
|
+
if (!kind || !specifier || !full || typeof match.index !== "number") continue;
|
|
1656
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1657
|
+
const sourcePath = kind === "require_relative" ? resolveRubyImportPath(db, importerPath, specifier) : null;
|
|
1658
|
+
if (sourcePath) {
|
|
1659
|
+
const localName = rubyConstantName(specifier);
|
|
1660
|
+
statements.push({
|
|
1661
|
+
importedName: localName,
|
|
1662
|
+
localName,
|
|
1663
|
+
sourcePath,
|
|
1664
|
+
kind: "named",
|
|
1665
|
+
used: hasIdentifierUsage(body, localName),
|
|
1666
|
+
usedMembers: []
|
|
1667
|
+
});
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
statements.push({
|
|
1671
|
+
importedName: specifier,
|
|
1672
|
+
localName: null,
|
|
1673
|
+
sourcePath,
|
|
1674
|
+
kind: "side-effect",
|
|
1675
|
+
used: true,
|
|
1676
|
+
usedMembers: []
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
return statements;
|
|
1680
|
+
}
|
|
1681
|
+
function rubyConstantName(specifier) {
|
|
1682
|
+
return basename(specifier).replace(/\.[^.]+$/, "").split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1683
|
+
}
|
|
1684
|
+
function parseCLikeImports(db, importerPath, source) {
|
|
1685
|
+
const statements = [];
|
|
1686
|
+
for (const match of source.matchAll(/^[ \t]*#include\s+[<"]([^">]+)[">]\s*$/gm)) {
|
|
1687
|
+
const specifier = match[1]?.trim();
|
|
1688
|
+
const full = match[0];
|
|
1689
|
+
if (!specifier || !full || typeof match.index !== "number") continue;
|
|
1690
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1691
|
+
const localName = basename(specifier).replace(/\.[^.]+$/, "");
|
|
1692
|
+
statements.push({
|
|
1693
|
+
importedName: specifier,
|
|
1694
|
+
localName,
|
|
1695
|
+
sourcePath: resolveCLikeImportPath(db, importerPath, specifier),
|
|
1696
|
+
kind: "named",
|
|
1697
|
+
used: hasIdentifierUsage(body, localName),
|
|
1698
|
+
usedMembers: []
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
return statements;
|
|
1702
|
+
}
|
|
1703
|
+
function parseDotNetImports(db, importerPath, source) {
|
|
1704
|
+
const statements = [];
|
|
1705
|
+
const lineRegex = isVisualBasicSourcePath(importerPath) ? /^[ \t]*Imports\s+(.+?)\s*$/gm : /^[ \t]*using\s+(.+?)\s*;$/gm;
|
|
1706
|
+
for (const match of source.matchAll(lineRegex)) {
|
|
1707
|
+
const clause = match[1]?.trim();
|
|
1708
|
+
const full = match[0];
|
|
1709
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1710
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1711
|
+
const [aliasPart, targetPart] = isVisualBasicSourcePath(importerPath) ? clause.split(/\s*=\s*/) : clause.split(/\s*=\s*/);
|
|
1712
|
+
const hasAlias = Boolean(targetPart);
|
|
1713
|
+
const qualified = (hasAlias ? targetPart : aliasPart)?.trim() ?? clause;
|
|
1714
|
+
const importedName = qualified.split(".").pop() ?? qualified;
|
|
1715
|
+
const localName = hasAlias ? aliasPart?.trim() ?? importedName : importedName;
|
|
1716
|
+
statements.push(buildSimpleImport(
|
|
1717
|
+
db,
|
|
1718
|
+
importerPath,
|
|
1719
|
+
body,
|
|
1720
|
+
qualified,
|
|
1721
|
+
importedName,
|
|
1722
|
+
localName,
|
|
1723
|
+
resolveQualifiedImportPath(db, qualified, DOTNET_SOURCE_EXTENSIONS)
|
|
1724
|
+
));
|
|
1725
|
+
}
|
|
1726
|
+
return statements;
|
|
1727
|
+
}
|
|
1728
|
+
function parseDartImports(db, importerPath, source) {
|
|
1729
|
+
const statements = [];
|
|
1730
|
+
for (const match of source.matchAll(/^[ \t]*import\s+['"]([^'"]+)['"](?:\s+as\s+([A-Za-z_]\w*))?[\s\S]*?;$/gm)) {
|
|
1731
|
+
const specifier = match[1]?.trim();
|
|
1732
|
+
const alias = match[2]?.trim() ?? null;
|
|
1733
|
+
const full = match[0];
|
|
1734
|
+
if (!specifier || !full || typeof match.index !== "number") continue;
|
|
1735
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1736
|
+
statements.push({
|
|
1737
|
+
importedName: specifier,
|
|
1738
|
+
localName: alias,
|
|
1739
|
+
sourcePath: resolveDartImportPath(db, importerPath, specifier),
|
|
1740
|
+
kind: alias ? "namespace" : "side-effect",
|
|
1741
|
+
used: alias ? hasIdentifierUsage(body, alias) : true,
|
|
1742
|
+
usedMembers: alias ? collectNamespaceMembers(body, alias) : []
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
return statements;
|
|
1746
|
+
}
|
|
1747
|
+
function parseDartExports(db, importerPath, source) {
|
|
1748
|
+
const statements = [];
|
|
1749
|
+
for (const match of source.matchAll(/^[ \t]*export\s+['"]([^'"]+)['"][\s\S]*?;$/gm)) {
|
|
1750
|
+
const specifier = match[1]?.trim();
|
|
1751
|
+
if (!specifier) continue;
|
|
1752
|
+
statements.push({
|
|
1753
|
+
specifier,
|
|
1754
|
+
sourcePath: resolveDartImportPath(db, importerPath, specifier)
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
return statements;
|
|
1758
|
+
}
|
|
1759
|
+
function parsePhpImports(db, importerPath, source) {
|
|
1760
|
+
const statements = [];
|
|
1761
|
+
for (const match of source.matchAll(/^[ \t]*use\s+(.+?)\s*;$/gm)) {
|
|
1762
|
+
const clause = match[1]?.trim();
|
|
1763
|
+
const full = match[0];
|
|
1764
|
+
if (!clause || !full || typeof match.index !== "number") continue;
|
|
1765
|
+
const body = buildUsageBody(source, match.index, match.index + full.length);
|
|
1766
|
+
for (const entry of splitTopLevel(clause)) {
|
|
1767
|
+
const cleaned = entry.trim();
|
|
1768
|
+
if (!cleaned) continue;
|
|
1769
|
+
const [qualifiedPart, aliasPart] = cleaned.split(/\s+as\s+/i);
|
|
1770
|
+
const qualified = qualifiedPart?.trim() ?? cleaned;
|
|
1771
|
+
const importedName = qualified.split("\\").pop() ?? qualified;
|
|
1772
|
+
const localName = (aliasPart ?? importedName).trim();
|
|
1773
|
+
statements.push(buildSimpleImport(
|
|
1774
|
+
db,
|
|
1775
|
+
importerPath,
|
|
1776
|
+
body,
|
|
1777
|
+
qualified,
|
|
1778
|
+
importedName,
|
|
1779
|
+
localName,
|
|
1780
|
+
resolveQualifiedImportPath(db, qualified.replace(/\\/g, "."), PHP_SOURCE_EXTENSIONS)
|
|
1781
|
+
));
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return statements;
|
|
1785
|
+
}
|
|
1786
|
+
function buildSimpleImport(db, importerPath, body, qualifiedName, importedName, localName, sourcePath) {
|
|
1787
|
+
return {
|
|
1788
|
+
importedName,
|
|
1789
|
+
localName,
|
|
1790
|
+
sourcePath: sourcePath ?? resolveQualifiedImportPath(db, qualifiedName, extensionFamilyFor(importerPath)),
|
|
1791
|
+
kind: "named",
|
|
1792
|
+
used: hasIdentifierUsage(body, localName),
|
|
1793
|
+
usedMembers: []
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
function parsePythonCalls(lines, baseLine) {
|
|
1797
|
+
const calls = [];
|
|
1798
|
+
const controlKeywords = /* @__PURE__ */ new Set([
|
|
1799
|
+
"if",
|
|
1800
|
+
"for",
|
|
1801
|
+
"while",
|
|
1802
|
+
"with",
|
|
1803
|
+
"except",
|
|
1804
|
+
"elif",
|
|
1805
|
+
"return",
|
|
1806
|
+
"yield",
|
|
1807
|
+
"assert",
|
|
1808
|
+
"raise",
|
|
1809
|
+
"lambda",
|
|
1810
|
+
"class",
|
|
1811
|
+
"def"
|
|
1812
|
+
]);
|
|
1813
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1814
|
+
const rawLine = lines[index] ?? "";
|
|
1815
|
+
const stripped = stripCommentsAndStrings(rawLine);
|
|
1816
|
+
if (!stripped.trim()) continue;
|
|
1817
|
+
const attributeMatches = [...stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w]*)\s*\(/g)];
|
|
1818
|
+
const attributeRanges = attributeMatches.map((match) => ({
|
|
1819
|
+
start: match.index ?? -1,
|
|
1820
|
+
end: (match.index ?? -1) + match[0].length
|
|
1821
|
+
}));
|
|
1822
|
+
for (const match of attributeMatches) {
|
|
1823
|
+
const receiverName = match[1];
|
|
1824
|
+
const calleeName = match[2];
|
|
1825
|
+
if (!receiverName || !calleeName) continue;
|
|
1826
|
+
calls.push({
|
|
1827
|
+
receiverName,
|
|
1828
|
+
calleeName,
|
|
1829
|
+
line: baseLine + index
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
for (const match of stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
|
|
1833
|
+
const calleeName = match[1];
|
|
1834
|
+
const start = match.index ?? -1;
|
|
1835
|
+
if (!calleeName || start < 0) continue;
|
|
1836
|
+
if (controlKeywords.has(calleeName)) continue;
|
|
1837
|
+
if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
|
|
1838
|
+
const prefix = stripped.slice(0, start).trimEnd();
|
|
1839
|
+
if (prefix.endsWith("def") || prefix.endsWith("class") || prefix.endsWith("async def")) {
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
calls.push({
|
|
1843
|
+
receiverName: null,
|
|
1844
|
+
calleeName,
|
|
1845
|
+
line: baseLine + index
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return calls;
|
|
1850
|
+
}
|
|
1851
|
+
function parseJavaScriptCalls(lines, baseLine) {
|
|
1852
|
+
const calls = [];
|
|
1853
|
+
const controlKeywords = /* @__PURE__ */ new Set([
|
|
1854
|
+
"if",
|
|
1855
|
+
"for",
|
|
1856
|
+
"while",
|
|
1857
|
+
"switch",
|
|
1858
|
+
"catch",
|
|
1859
|
+
"function",
|
|
1860
|
+
"class",
|
|
1861
|
+
"return",
|
|
1862
|
+
"typeof",
|
|
1863
|
+
"import"
|
|
1864
|
+
]);
|
|
1865
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1866
|
+
const stripped = stripCommentsAndStrings(lines[index] ?? "");
|
|
1867
|
+
if (!stripped.trim()) continue;
|
|
1868
|
+
const attributeMatches = [
|
|
1869
|
+
...stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g),
|
|
1870
|
+
...stripped.matchAll(/\bthis\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g)
|
|
1871
|
+
];
|
|
1872
|
+
const attributeRanges = attributeMatches.map((match) => ({
|
|
1873
|
+
start: match.index ?? -1,
|
|
1874
|
+
end: (match.index ?? -1) + match[0].length
|
|
1875
|
+
}));
|
|
1876
|
+
for (const match of attributeMatches) {
|
|
1877
|
+
const receiverName = match[2] ? match[1] : "this";
|
|
1878
|
+
const calleeName = match[2] ?? match[1];
|
|
1879
|
+
if (!receiverName || !calleeName) continue;
|
|
1880
|
+
calls.push({
|
|
1881
|
+
receiverName,
|
|
1882
|
+
calleeName,
|
|
1883
|
+
line: baseLine + index
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1318
1886
|
for (const match of stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
1319
1887
|
const calleeName = match[1];
|
|
1320
1888
|
const start = match.index ?? -1;
|
|
@@ -1574,18 +2142,33 @@ function resolveImportPath(db, importerPath, specifier) {
|
|
|
1574
2142
|
if (isPythonSourcePath(importerPath)) {
|
|
1575
2143
|
return resolvePythonImportPath(db, importerPath, specifier);
|
|
1576
2144
|
}
|
|
2145
|
+
if (isRustSourcePath(importerPath)) {
|
|
2146
|
+
return resolveRustImportPath(db, importerPath, specifier);
|
|
2147
|
+
}
|
|
2148
|
+
if (isRubySourcePath(importerPath)) {
|
|
2149
|
+
return resolveRubyImportPath(db, importerPath, specifier);
|
|
2150
|
+
}
|
|
2151
|
+
if (isCLikeSourcePath(importerPath)) {
|
|
2152
|
+
return resolveCLikeImportPath(db, importerPath, specifier);
|
|
2153
|
+
}
|
|
2154
|
+
if (isJvmSourcePath(importerPath) || isDotNetSourcePath(importerPath) || isPhpSourcePath(importerPath)) {
|
|
2155
|
+
return resolveQualifiedImportPath(db, specifier.replace(/\\/g, "."), extensionFamilyFor(importerPath));
|
|
2156
|
+
}
|
|
2157
|
+
if (isDartSourcePath(importerPath)) {
|
|
2158
|
+
return resolveDartImportPath(db, importerPath, specifier);
|
|
2159
|
+
}
|
|
1577
2160
|
return resolveJavaScriptImportPath(db, importerPath, specifier);
|
|
1578
2161
|
}
|
|
1579
2162
|
function resolveJavaScriptImportPath(db, importerPath, specifier) {
|
|
1580
2163
|
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
1581
2164
|
return null;
|
|
1582
2165
|
}
|
|
1583
|
-
const importerDir = dirname2(
|
|
2166
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
1584
2167
|
const absolute = resolve2(importerDir, specifier);
|
|
1585
2168
|
const indexedPaths = getIndexedPaths(db);
|
|
1586
2169
|
for (const candidate of candidateImportPaths(absolute)) {
|
|
1587
2170
|
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
1588
|
-
if (indexedPaths.has(relativeCandidate) ||
|
|
2171
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
1589
2172
|
return relativeCandidate;
|
|
1590
2173
|
}
|
|
1591
2174
|
}
|
|
@@ -1599,7 +2182,7 @@ function resolvePythonImportPath(db, importerPath, specifier) {
|
|
|
1599
2182
|
if (!match) return null;
|
|
1600
2183
|
const dots = match[1].length;
|
|
1601
2184
|
const remainder = match[2].replace(/^\./, "");
|
|
1602
|
-
let baseDir = dirname2(
|
|
2185
|
+
let baseDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
1603
2186
|
for (let i = 1; i < dots; i++) {
|
|
1604
2187
|
baseDir = dirname2(baseDir);
|
|
1605
2188
|
}
|
|
@@ -1609,26 +2192,145 @@ function resolvePythonImportPath(db, importerPath, specifier) {
|
|
|
1609
2192
|
}
|
|
1610
2193
|
for (const candidate of pythonCandidateImportPaths(basePath)) {
|
|
1611
2194
|
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
1612
|
-
if (indexedPaths.has(relativeCandidate) ||
|
|
2195
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
2196
|
+
return relativeCandidate;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
function resolveRustImportPath(db, importerPath, specifier) {
|
|
2202
|
+
if (!specifier) return null;
|
|
2203
|
+
const normalizedSpecifier = specifier.replace(/\s+as\s+.+$/, "").trim();
|
|
2204
|
+
if (!normalizedSpecifier.startsWith("crate::") && !normalizedSpecifier.startsWith("self::") && !normalizedSpecifier.startsWith("super::")) {
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2208
|
+
let basePath;
|
|
2209
|
+
if (normalizedSpecifier.startsWith("crate::")) {
|
|
2210
|
+
basePath = resolve2(db.config.projectRoot, "src", normalizedSpecifier.slice("crate::".length).replace(/::/g, "/"));
|
|
2211
|
+
} else if (normalizedSpecifier.startsWith("self::")) {
|
|
2212
|
+
basePath = resolve2(importerDir, normalizedSpecifier.slice("self::".length).replace(/::/g, "/"));
|
|
2213
|
+
} else {
|
|
2214
|
+
basePath = resolve2(dirname2(importerDir), normalizedSpecifier.slice("super::".length).replace(/::/g, "/"));
|
|
2215
|
+
}
|
|
2216
|
+
for (const candidate of rustCandidateImportPaths(basePath)) {
|
|
2217
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2218
|
+
if (getIndexedPaths(db).has(relativeCandidate) || existsSync8(candidate)) {
|
|
2219
|
+
return relativeCandidate;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
function resolveRubyImportPath(db, importerPath, specifier) {
|
|
2225
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2226
|
+
const absolute = resolve2(importerDir, specifier);
|
|
2227
|
+
for (const candidate of rubyCandidateImportPaths(absolute)) {
|
|
2228
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2229
|
+
if (getIndexedPaths(db).has(relativeCandidate) || existsSync8(candidate)) {
|
|
2230
|
+
return relativeCandidate;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return null;
|
|
2234
|
+
}
|
|
2235
|
+
function resolveCLikeImportPath(db, importerPath, specifier) {
|
|
2236
|
+
const indexedPaths = getIndexedPaths(db);
|
|
2237
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2238
|
+
const candidates = [
|
|
2239
|
+
resolve2(importerDir, specifier),
|
|
2240
|
+
resolve2(db.config.projectRoot, specifier),
|
|
2241
|
+
resolve2(db.config.projectRoot, "include", specifier),
|
|
2242
|
+
resolve2(db.config.projectRoot, "src", specifier)
|
|
2243
|
+
];
|
|
2244
|
+
for (const candidate of candidates) {
|
|
2245
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2246
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
2247
|
+
return relativeCandidate;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
return null;
|
|
2251
|
+
}
|
|
2252
|
+
function resolveQualifiedImportPath(db, specifier, extensions) {
|
|
2253
|
+
const indexedPaths = getIndexedPaths(db);
|
|
2254
|
+
const normalized = specifier.replace(/\\/g, ".").replace(/::/g, ".").replace(/^global::/, "");
|
|
2255
|
+
const pathified = normalized.replace(/\./g, "/");
|
|
2256
|
+
const basenameOnly = normalized.split(".").pop() ?? normalized;
|
|
2257
|
+
for (const ext of extensions) {
|
|
2258
|
+
const exactSuffix = `${pathified}${ext}`;
|
|
2259
|
+
const exact = [...indexedPaths].find((relativePath) => relativePath.endsWith(exactSuffix));
|
|
2260
|
+
if (exact) return exact;
|
|
2261
|
+
}
|
|
2262
|
+
for (const ext of extensions) {
|
|
2263
|
+
const basenameMatch = [...indexedPaths].find((relativePath) => basename(relativePath) === `${basenameOnly}${ext}`);
|
|
2264
|
+
if (basenameMatch) return basenameMatch;
|
|
2265
|
+
}
|
|
2266
|
+
const folderMatches = [...indexedPaths].filter((relativePath) => extensions.includes(extname2(relativePath).toLowerCase())).filter((relativePath) => relativePath.includes(`/${pathified}/`) || relativePath.includes(`/${basenameOnly}/`)).sort((left, right) => left.localeCompare(right));
|
|
2267
|
+
if (folderMatches.length === 1) {
|
|
2268
|
+
return folderMatches[0];
|
|
2269
|
+
}
|
|
2270
|
+
return null;
|
|
2271
|
+
}
|
|
2272
|
+
function resolveDartImportPath(db, importerPath, specifier) {
|
|
2273
|
+
const indexedPaths = getIndexedPaths(db);
|
|
2274
|
+
if (specifier.startsWith("package:")) {
|
|
2275
|
+
const withoutScheme = specifier.slice("package:".length);
|
|
2276
|
+
const slashIndex = withoutScheme.indexOf("/");
|
|
2277
|
+
if (slashIndex < 0) return null;
|
|
2278
|
+
const packageRelative = withoutScheme.slice(slashIndex + 1);
|
|
2279
|
+
const candidate = normalizePath(packageRelative.startsWith("lib/") ? packageRelative : `lib/${packageRelative}`);
|
|
2280
|
+
if (indexedPaths.has(candidate)) return candidate;
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
|
|
2284
|
+
const absolute = resolve2(importerDir, specifier);
|
|
2285
|
+
for (const candidate of dartCandidateImportPaths(absolute)) {
|
|
2286
|
+
const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
|
|
2287
|
+
if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
|
|
1613
2288
|
return relativeCandidate;
|
|
1614
2289
|
}
|
|
1615
2290
|
}
|
|
1616
2291
|
return null;
|
|
1617
2292
|
}
|
|
1618
2293
|
function pythonCandidateImportPaths(basePath) {
|
|
1619
|
-
const ext =
|
|
2294
|
+
const ext = extname2(basePath);
|
|
1620
2295
|
if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
|
|
1621
2296
|
return [basePath];
|
|
1622
2297
|
}
|
|
1623
2298
|
return [
|
|
1624
2299
|
`${basePath}.py`,
|
|
1625
2300
|
`${basePath}.pyi`,
|
|
1626
|
-
|
|
1627
|
-
|
|
2301
|
+
join8(basePath, "__init__.py"),
|
|
2302
|
+
join8(basePath, "__init__.pyi")
|
|
2303
|
+
];
|
|
2304
|
+
}
|
|
2305
|
+
function rustCandidateImportPaths(basePath) {
|
|
2306
|
+
const ext = extname2(basePath);
|
|
2307
|
+
if (RUST_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2308
|
+
return [basePath];
|
|
2309
|
+
}
|
|
2310
|
+
return [
|
|
2311
|
+
`${basePath}.rs`,
|
|
2312
|
+
join8(basePath, "mod.rs")
|
|
2313
|
+
];
|
|
2314
|
+
}
|
|
2315
|
+
function rubyCandidateImportPaths(basePath) {
|
|
2316
|
+
const ext = extname2(basePath);
|
|
2317
|
+
if (RUBY_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2318
|
+
return [basePath];
|
|
2319
|
+
}
|
|
2320
|
+
return [
|
|
2321
|
+
`${basePath}.rb`,
|
|
2322
|
+
join8(basePath, "index.rb")
|
|
1628
2323
|
];
|
|
1629
2324
|
}
|
|
2325
|
+
function dartCandidateImportPaths(basePath) {
|
|
2326
|
+
const ext = extname2(basePath);
|
|
2327
|
+
if (DART_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2328
|
+
return [basePath];
|
|
2329
|
+
}
|
|
2330
|
+
return [`${basePath}.dart`, basePath];
|
|
2331
|
+
}
|
|
1630
2332
|
function candidateImportPaths(absolute) {
|
|
1631
|
-
const ext =
|
|
2333
|
+
const ext = extname2(absolute);
|
|
1632
2334
|
const candidates = /* @__PURE__ */ new Set();
|
|
1633
2335
|
if (ext) {
|
|
1634
2336
|
candidates.add(absolute);
|
|
@@ -1638,7 +2340,7 @@ function candidateImportPaths(absolute) {
|
|
|
1638
2340
|
} else {
|
|
1639
2341
|
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
1640
2342
|
candidates.add(`${absolute}${sourceExt}`);
|
|
1641
|
-
candidates.add(
|
|
2343
|
+
candidates.add(join8(absolute, `index${sourceExt}`));
|
|
1642
2344
|
}
|
|
1643
2345
|
}
|
|
1644
2346
|
return [...candidates];
|
|
@@ -1671,10 +2373,44 @@ function normalizePath(path2) {
|
|
|
1671
2373
|
return path2.replace(/\\/g, "/");
|
|
1672
2374
|
}
|
|
1673
2375
|
function isJavaScriptSourcePath(relativePath) {
|
|
1674
|
-
return SOURCE_EXTENSIONS.includes(
|
|
2376
|
+
return SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
1675
2377
|
}
|
|
1676
2378
|
function isPythonSourcePath(relativePath) {
|
|
1677
|
-
return PYTHON_SOURCE_EXTENSIONS.includes(
|
|
2379
|
+
return PYTHON_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2380
|
+
}
|
|
2381
|
+
function isJvmSourcePath(relativePath) {
|
|
2382
|
+
return JVM_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2383
|
+
}
|
|
2384
|
+
function isRustSourcePath(relativePath) {
|
|
2385
|
+
return RUST_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2386
|
+
}
|
|
2387
|
+
function isRubySourcePath(relativePath) {
|
|
2388
|
+
return RUBY_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2389
|
+
}
|
|
2390
|
+
function isCLikeSourcePath(relativePath) {
|
|
2391
|
+
return C_LIKE_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2392
|
+
}
|
|
2393
|
+
function isDotNetSourcePath(relativePath) {
|
|
2394
|
+
return DOTNET_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2395
|
+
}
|
|
2396
|
+
function isVisualBasicSourcePath(relativePath) {
|
|
2397
|
+
return extname2(relativePath).toLowerCase() === ".vb";
|
|
2398
|
+
}
|
|
2399
|
+
function isDartSourcePath(relativePath) {
|
|
2400
|
+
return DART_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2401
|
+
}
|
|
2402
|
+
function isPhpSourcePath(relativePath) {
|
|
2403
|
+
return PHP_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
|
|
2404
|
+
}
|
|
2405
|
+
function extensionFamilyFor(relativePath) {
|
|
2406
|
+
if (isJvmSourcePath(relativePath)) return JVM_SOURCE_EXTENSIONS;
|
|
2407
|
+
if (isDotNetSourcePath(relativePath)) return DOTNET_SOURCE_EXTENSIONS;
|
|
2408
|
+
if (isPhpSourcePath(relativePath)) return PHP_SOURCE_EXTENSIONS;
|
|
2409
|
+
if (isDartSourcePath(relativePath)) return DART_SOURCE_EXTENSIONS;
|
|
2410
|
+
if (isCLikeSourcePath(relativePath)) return C_LIKE_SOURCE_EXTENSIONS;
|
|
2411
|
+
if (isRustSourcePath(relativePath)) return RUST_SOURCE_EXTENSIONS;
|
|
2412
|
+
if (isRubySourcePath(relativePath)) return RUBY_SOURCE_EXTENSIONS;
|
|
2413
|
+
return SOURCE_EXTENSIONS;
|
|
1678
2414
|
}
|
|
1679
2415
|
function getSourceText(db, relativePath) {
|
|
1680
2416
|
const cache = getCachedMap(SOURCE_TEXT_CACHE, db);
|
|
@@ -1683,8 +2419,8 @@ function getSourceText(db, relativePath) {
|
|
|
1683
2419
|
if (typeof cached === "string") {
|
|
1684
2420
|
return cached;
|
|
1685
2421
|
}
|
|
1686
|
-
const fullPath =
|
|
1687
|
-
if (!
|
|
2422
|
+
const fullPath = join8(db.config.projectRoot, normalized);
|
|
2423
|
+
if (!existsSync8(fullPath)) {
|
|
1688
2424
|
cache.set(normalized, "");
|
|
1689
2425
|
return "";
|
|
1690
2426
|
}
|
|
@@ -1942,18 +2678,39 @@ function buildFileDepGraph(db, scope) {
|
|
|
1942
2678
|
${scopeFilter}`
|
|
1943
2679
|
);
|
|
1944
2680
|
const graph = /* @__PURE__ */ new Map();
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
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
|
+
}
|
|
2709
|
+
function findFirstSymbolMatch(db, symbolPattern) {
|
|
1953
2710
|
const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
|
|
1954
2711
|
if (fileLineMatch) {
|
|
1955
2712
|
const [, filePath, startStr, endStr] = fileLineMatch;
|
|
1956
|
-
|
|
2713
|
+
let row = db.get(
|
|
1957
2714
|
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
1958
2715
|
FROM global_symbols gs
|
|
1959
2716
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
@@ -1962,11 +2719,30 @@ function findFirstSymbolMatch(db, symbolPattern) {
|
|
|
1962
2719
|
AND der.start_line <= ? AND der.end_line >= ?
|
|
1963
2720
|
${db.pathExclusionsFor("d")}
|
|
1964
2721
|
ORDER BY (der.end_line - der.start_line) ASC
|
|
1965
|
-
|
|
2722
|
+
LIMIT 1`,
|
|
1966
2723
|
`%${filePath}%`,
|
|
1967
2724
|
parseInt(startStr, 10),
|
|
1968
2725
|
parseInt(endStr, 10)
|
|
1969
2726
|
);
|
|
2727
|
+
if (!row) {
|
|
2728
|
+
row = db.get(
|
|
2729
|
+
`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
|
|
2730
|
+
FROM global_symbols gs
|
|
2731
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
2732
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2733
|
+
JOIN documents d ON c.document_id = d.id
|
|
2734
|
+
WHERE m.role = 1
|
|
2735
|
+
AND d.relative_path LIKE ?
|
|
2736
|
+
AND c.start_line <= ? AND c.end_line >= ?
|
|
2737
|
+
${db.pathExclusionsFor("d")}
|
|
2738
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path
|
|
2739
|
+
ORDER BY (MAX(c.end_line) - MIN(c.start_line)) ASC
|
|
2740
|
+
LIMIT 1`,
|
|
2741
|
+
`%${filePath}%`,
|
|
2742
|
+
parseInt(startStr, 10),
|
|
2743
|
+
parseInt(endStr, 10)
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
1970
2746
|
if (row && !db.isIgnored(row.relative_path)) {
|
|
1971
2747
|
return {
|
|
1972
2748
|
symbolId: row.id,
|
|
@@ -2048,28 +2824,63 @@ function getSymbolLookupCandidates(db, tokens) {
|
|
|
2048
2824
|
const like = `%${token}%`;
|
|
2049
2825
|
return [like, like, like];
|
|
2050
2826
|
});
|
|
2051
|
-
|
|
2827
|
+
const primary = db.all(
|
|
2052
2828
|
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
|
|
2053
2829
|
FROM global_symbols gs
|
|
2054
2830
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2055
2831
|
JOIN documents d ON der.document_id = d.id
|
|
2056
2832
|
WHERE ${tokenClauses.join("\n AND ")}
|
|
2057
2833
|
${db.pathExclusionsFor("d")}
|
|
2834
|
+
LIMIT 200`,
|
|
2835
|
+
...params
|
|
2836
|
+
);
|
|
2837
|
+
if (primary.length > 0) {
|
|
2838
|
+
return primary;
|
|
2839
|
+
}
|
|
2840
|
+
return db.all(
|
|
2841
|
+
`SELECT
|
|
2842
|
+
gs.id,
|
|
2843
|
+
gs.symbol,
|
|
2844
|
+
c.document_id,
|
|
2845
|
+
MIN(c.start_line) AS start_line,
|
|
2846
|
+
MAX(c.end_line) AS end_line,
|
|
2847
|
+
d.relative_path,
|
|
2848
|
+
gs.display_name
|
|
2849
|
+
FROM global_symbols gs
|
|
2850
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
2851
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2852
|
+
JOIN documents d ON c.document_id = d.id
|
|
2853
|
+
WHERE m.role = 1
|
|
2854
|
+
AND ${tokenClauses.join("\n AND ")}
|
|
2855
|
+
${db.pathExclusionsFor("d")}
|
|
2856
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path, gs.display_name
|
|
2058
2857
|
LIMIT 200`,
|
|
2059
2858
|
...params
|
|
2060
2859
|
);
|
|
2061
2860
|
}
|
|
2062
2861
|
function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
|
|
2862
|
+
const originalCase = originalPattern.trim();
|
|
2863
|
+
const cleanedCase = cleanedPattern;
|
|
2864
|
+
const noParensCase = cleanedCase.replace(/\(\)$/, "");
|
|
2063
2865
|
const original = originalPattern.toLowerCase();
|
|
2064
2866
|
const cleaned = cleanedPattern.toLowerCase();
|
|
2065
2867
|
const noParens = cleaned.replace(/\(\)$/, "");
|
|
2868
|
+
const rawCase = row.symbol;
|
|
2869
|
+
const shortCase = shortenSymbol(row.symbol);
|
|
2870
|
+
const leafCase = leafName(row.symbol);
|
|
2871
|
+
const displayCase = row.display_name ?? "";
|
|
2066
2872
|
const raw = row.symbol.toLowerCase();
|
|
2067
|
-
const short =
|
|
2068
|
-
const leaf =
|
|
2069
|
-
const display =
|
|
2873
|
+
const short = shortCase.toLowerCase();
|
|
2874
|
+
const leaf = leafCase.toLowerCase();
|
|
2875
|
+
const display = displayCase.toLowerCase();
|
|
2070
2876
|
const path2 = row.relative_path.toLowerCase();
|
|
2071
2877
|
const looksPathLike = /[/:.]/.test(cleanedPattern);
|
|
2072
2878
|
let score = 0;
|
|
2879
|
+
if (rawCase === originalCase || rawCase === cleanedCase) score += 1150;
|
|
2880
|
+
if (shortCase === originalCase || shortCase === cleanedCase) score += 1100;
|
|
2881
|
+
if (displayCase === noParensCase) score += 980;
|
|
2882
|
+
if (leafCase === noParensCase) score += 960;
|
|
2883
|
+
if (`${leafCase}()` === originalCase || `${leafCase}()` === cleanedCase) score += 955;
|
|
2073
2884
|
if (raw === original || raw === cleaned) score += 1e3;
|
|
2074
2885
|
if (short === original || short === cleaned) score += 950;
|
|
2075
2886
|
if (path2 === original || path2 === cleaned) score += 925;
|
|
@@ -2098,32 +2909,37 @@ function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
|
|
|
2098
2909
|
return score;
|
|
2099
2910
|
}
|
|
2100
2911
|
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 ?" : ""}`,
|
|
2912
|
+
const mentions = db.all(
|
|
2913
|
+
`SELECT DISTINCT m.symbol_id, c.id AS chunk_id
|
|
2914
|
+
FROM mentions m
|
|
2915
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2916
|
+
WHERE c.document_id = ?
|
|
2917
|
+
AND c.start_line >= ?
|
|
2918
|
+
AND c.end_line <= ?
|
|
2919
|
+
AND m.role != 1
|
|
2920
|
+
AND m.symbol_id != ?
|
|
2921
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
2120
2922
|
...calleeQueryParams(symbol, opts.limit)
|
|
2121
2923
|
);
|
|
2122
|
-
const primary =
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2924
|
+
const primary = [];
|
|
2925
|
+
const directSeen = /* @__PURE__ */ new Set();
|
|
2926
|
+
for (const mention of mentions) {
|
|
2927
|
+
const callee = getFullSymbolMatch(db, {
|
|
2928
|
+
symbolId: mention.symbol_id,
|
|
2929
|
+
documentId: symbol.documentId,
|
|
2930
|
+
startLine: symbol.startLine,
|
|
2931
|
+
endLine: symbol.endLine
|
|
2932
|
+
});
|
|
2933
|
+
if (!callee || db.isIgnored(callee.relativePath)) continue;
|
|
2934
|
+
const key = `${callee.symbol}|${callee.relativePath}|${mention.chunk_id}`;
|
|
2935
|
+
if (directSeen.has(key)) continue;
|
|
2936
|
+
directSeen.add(key);
|
|
2937
|
+
primary.push({
|
|
2938
|
+
symbol: callee.symbol,
|
|
2939
|
+
file: callee.relativePath,
|
|
2940
|
+
chunkId: mention.chunk_id
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2127
2943
|
const sourceFallback = getSourceBackedCalleeRows(db, symbol, opts.limit);
|
|
2128
2944
|
if (sourceFallback.length === 0) {
|
|
2129
2945
|
return primary;
|
|
@@ -2131,9 +2947,15 @@ function getCalleeRowsForSymbol(db, symbol, opts = {}) {
|
|
|
2131
2947
|
if (primary.length === 0) {
|
|
2132
2948
|
return applyLimit(sourceFallback, opts.limit);
|
|
2133
2949
|
}
|
|
2134
|
-
const
|
|
2135
|
-
const
|
|
2136
|
-
|
|
2950
|
+
const merged = [...sourceFallback];
|
|
2951
|
+
const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
|
|
2952
|
+
const preferredFallbackLeaves = new Set(
|
|
2953
|
+
sourceFallback.filter((row) => !isLikelyTestPath(row.file)).map((row) => leafName(row.symbol))
|
|
2954
|
+
);
|
|
2955
|
+
for (const row of primary) {
|
|
2956
|
+
if (isLikelyTestPath(row.file) && preferredFallbackLeaves.has(leafName(row.symbol))) {
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2137
2959
|
const key = `${row.symbol}|${row.file}`;
|
|
2138
2960
|
if (seen.has(key)) continue;
|
|
2139
2961
|
seen.add(key);
|
|
@@ -2147,33 +2969,34 @@ function getCallerRowsForSymbol(db, symbol, opts = {}) {
|
|
|
2147
2969
|
return [];
|
|
2148
2970
|
}
|
|
2149
2971
|
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
|
-
|
|
2972
|
+
`SELECT DISTINCT ref_d.relative_path AS caller_file, c.start_line AS line
|
|
2973
|
+
FROM mentions m
|
|
2974
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
2975
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
2976
|
+
WHERE m.symbol_id = ?
|
|
2977
|
+
AND m.role != 1
|
|
2978
|
+
${db.pathExclusionsFor("ref_d")}
|
|
2979
|
+
ORDER BY ref_d.relative_path
|
|
2980
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
2981
|
+
...referenceQueryParams(match, opts.limit)
|
|
2982
|
+
).filter((row) => !db.isIgnored(row.caller_file)).flatMap((row) => {
|
|
2983
|
+
const enclosing = findEnclosingDefinition(getDefinitionsForFile(db, row.caller_file), row.line);
|
|
2984
|
+
if (!enclosing || enclosing.symbolId === match.symbolId) {
|
|
2985
|
+
return [];
|
|
2986
|
+
}
|
|
2987
|
+
return [{
|
|
2988
|
+
symbol: enclosing.symbol,
|
|
2989
|
+
file: row.caller_file
|
|
2990
|
+
}];
|
|
2991
|
+
});
|
|
2992
|
+
const sourceFallback = dedupeCallerRows([
|
|
2993
|
+
...getPythonSourceCallerRows(db, match, opts.limit),
|
|
2994
|
+
...getGenericSourceCallerRows(db, match, opts.limit)
|
|
2995
|
+
]);
|
|
2173
2996
|
if (sourceFallback.length === 0) {
|
|
2174
2997
|
return primary;
|
|
2175
2998
|
}
|
|
2176
|
-
const merged =
|
|
2999
|
+
const merged = [...sourceFallback];
|
|
2177
3000
|
const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
|
|
2178
3001
|
for (const row of primary) {
|
|
2179
3002
|
const key = `${row.symbol}|${row.file}`;
|
|
@@ -2185,6 +3008,26 @@ function getCallerRowsForSymbol(db, symbol, opts = {}) {
|
|
|
2185
3008
|
}
|
|
2186
3009
|
return applyLimit(merged, opts.limit);
|
|
2187
3010
|
}
|
|
3011
|
+
function getGenericSourceCallerRows(db, symbol, limit) {
|
|
3012
|
+
return applyLimit(
|
|
3013
|
+
getSourceReferenceSites(db, symbol).filter((site) => site.enclosingSymbol && site.enclosingSymbol !== symbol.symbol).map((site) => ({
|
|
3014
|
+
symbol: site.enclosingSymbol,
|
|
3015
|
+
file: site.file
|
|
3016
|
+
})),
|
|
3017
|
+
limit
|
|
3018
|
+
);
|
|
3019
|
+
}
|
|
3020
|
+
function dedupeCallerRows(rows) {
|
|
3021
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3022
|
+
const unique = [];
|
|
3023
|
+
for (const row of rows) {
|
|
3024
|
+
const key = `${row.symbol}|${row.file}`;
|
|
3025
|
+
if (seen.has(key)) continue;
|
|
3026
|
+
seen.add(key);
|
|
3027
|
+
unique.push(row);
|
|
3028
|
+
}
|
|
3029
|
+
return unique;
|
|
3030
|
+
}
|
|
2188
3031
|
function getSourceReferenceSites(db, symbol) {
|
|
2189
3032
|
const match = getFullSymbolMatch(db, symbol);
|
|
2190
3033
|
if (!match) {
|
|
@@ -2206,21 +3049,9 @@ function getSourceReferenceSites(db, symbol) {
|
|
|
2206
3049
|
for (const document of documents) {
|
|
2207
3050
|
if (db.isIgnored(document.relative_path)) continue;
|
|
2208
3051
|
const lines = findIdentifierLines(db, document.relative_path, identifier, document.relative_path === match.relativePath ? { excludeStartLine: match.startLine, excludeEndLine: match.endLine } : {});
|
|
3052
|
+
const definitions = getDefinitionsForFile(db, document.relative_path);
|
|
2209
3053
|
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
|
-
);
|
|
3054
|
+
const enclosing = findEnclosingDefinition(definitions, line);
|
|
2224
3055
|
const key = `${document.relative_path}|${line}|${enclosing?.symbol ?? ""}`;
|
|
2225
3056
|
if (seen.has(key)) continue;
|
|
2226
3057
|
seen.add(key);
|
|
@@ -2245,13 +3076,23 @@ function calleeQueryParams(symbol, limit) {
|
|
|
2245
3076
|
}
|
|
2246
3077
|
return params;
|
|
2247
3078
|
}
|
|
2248
|
-
function
|
|
2249
|
-
const params = [symbol.symbolId
|
|
3079
|
+
function referenceQueryParams(symbol, limit) {
|
|
3080
|
+
const params = [symbol.symbolId];
|
|
2250
3081
|
if (typeof limit === "number") {
|
|
2251
3082
|
params.push(limit);
|
|
2252
3083
|
}
|
|
2253
3084
|
return params;
|
|
2254
3085
|
}
|
|
3086
|
+
function findEnclosingDefinition(definitions, line) {
|
|
3087
|
+
let best = null;
|
|
3088
|
+
for (const definition of definitions) {
|
|
3089
|
+
if (definition.startLine > line || definition.endLine < line) continue;
|
|
3090
|
+
if (!best || definition.endLine - definition.startLine < best.endLine - best.startLine) {
|
|
3091
|
+
best = definition;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
return best;
|
|
3095
|
+
}
|
|
2255
3096
|
function getPythonSourceCalleeRows(db, symbol, limit) {
|
|
2256
3097
|
const match = getFullSymbolMatch(db, symbol);
|
|
2257
3098
|
if (!match || !isPythonDocument(db, match.relativePath)) {
|
|
@@ -2342,6 +3183,94 @@ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
|
|
|
2342
3183
|
}
|
|
2343
3184
|
return applyLimit(rows, limit);
|
|
2344
3185
|
}
|
|
3186
|
+
function getJavaSourceCalleeRows(db, symbol, limit) {
|
|
3187
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3188
|
+
if (!match || !isJavaDocument(db, match.relativePath)) {
|
|
3189
|
+
return [];
|
|
3190
|
+
}
|
|
3191
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3192
|
+
const bindings = parseJavaFieldBindings(getSourceText(db, match.relativePath));
|
|
3193
|
+
return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
|
|
3194
|
+
}
|
|
3195
|
+
function getKotlinSourceCalleeRows(db, symbol, limit) {
|
|
3196
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3197
|
+
if (!match || !isKotlinDocument(db, match.relativePath)) {
|
|
3198
|
+
return [];
|
|
3199
|
+
}
|
|
3200
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3201
|
+
const bindings = parseKotlinFieldBindings(getSourceText(db, match.relativePath));
|
|
3202
|
+
return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
|
|
3203
|
+
}
|
|
3204
|
+
function getScalaSourceCalleeRows(db, symbol, limit) {
|
|
3205
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3206
|
+
if (!match || !isScalaDocument(db, match.relativePath)) {
|
|
3207
|
+
return [];
|
|
3208
|
+
}
|
|
3209
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3210
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3211
|
+
}
|
|
3212
|
+
function getCSharpSourceCalleeRows(db, symbol, limit) {
|
|
3213
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3214
|
+
if (!match || !isCSharpDocument(db, match.relativePath)) {
|
|
3215
|
+
return [];
|
|
3216
|
+
}
|
|
3217
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3218
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3219
|
+
}
|
|
3220
|
+
function getVisualBasicSourceCalleeRows(db, symbol, limit) {
|
|
3221
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3222
|
+
if (!match || !isVisualBasicDocument(db, match.relativePath)) {
|
|
3223
|
+
return [];
|
|
3224
|
+
}
|
|
3225
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3226
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3227
|
+
}
|
|
3228
|
+
function getCppSourceCalleeRows(db, symbol, limit) {
|
|
3229
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3230
|
+
if (!match || !isCppDocument(db, match.relativePath)) {
|
|
3231
|
+
return [];
|
|
3232
|
+
}
|
|
3233
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3234
|
+
const bindings = parseCppReceiverBindings(getSourceText(db, match.relativePath));
|
|
3235
|
+
return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
|
|
3236
|
+
}
|
|
3237
|
+
function getRustSourceCalleeRows(db, symbol, limit) {
|
|
3238
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3239
|
+
if (!match || !isRustDocument(db, match.relativePath)) {
|
|
3240
|
+
return [];
|
|
3241
|
+
}
|
|
3242
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3243
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3244
|
+
}
|
|
3245
|
+
function getRubySourceCalleeRows(db, symbol, limit) {
|
|
3246
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3247
|
+
if (!match || !isRubyDocument(db, match.relativePath)) {
|
|
3248
|
+
return [];
|
|
3249
|
+
}
|
|
3250
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, { allowInstanceVariables: true });
|
|
3251
|
+
const rubyCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, {
|
|
3252
|
+
allowInstanceVariables: true,
|
|
3253
|
+
allowBareMemberCalls: true
|
|
3254
|
+
});
|
|
3255
|
+
const bindings = parseRubyReceiverBindings(db, getSourceText(db, match.relativePath));
|
|
3256
|
+
return resolveSimpleSourceCallees(db, match, rubyCalls.length > 0 ? rubyCalls : calls, bindings, limit);
|
|
3257
|
+
}
|
|
3258
|
+
function getDartSourceCalleeRows(db, symbol, limit) {
|
|
3259
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3260
|
+
if (!match || !isDartDocument(db, match.relativePath)) {
|
|
3261
|
+
return [];
|
|
3262
|
+
}
|
|
3263
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3264
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3265
|
+
}
|
|
3266
|
+
function getPhpSourceCalleeRows(db, symbol, limit) {
|
|
3267
|
+
const match = getFullSymbolMatch(db, symbol);
|
|
3268
|
+
if (!match || !isPhpDocument(db, match.relativePath)) {
|
|
3269
|
+
return [];
|
|
3270
|
+
}
|
|
3271
|
+
const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
|
|
3272
|
+
return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
|
|
3273
|
+
}
|
|
2345
3274
|
function getSourceBackedCalleeRows(db, symbol, limit) {
|
|
2346
3275
|
const match = getFullSymbolMatch(db, symbol);
|
|
2347
3276
|
if (!match) {
|
|
@@ -2353,6 +3282,36 @@ function getSourceBackedCalleeRows(db, symbol, limit) {
|
|
|
2353
3282
|
if (isJavaScriptDocument(db, match.relativePath)) {
|
|
2354
3283
|
return getJavaScriptSourceCalleeRows(db, match, limit);
|
|
2355
3284
|
}
|
|
3285
|
+
if (isJavaDocument(db, match.relativePath)) {
|
|
3286
|
+
return getJavaSourceCalleeRows(db, match, limit);
|
|
3287
|
+
}
|
|
3288
|
+
if (isScalaDocument(db, match.relativePath)) {
|
|
3289
|
+
return getScalaSourceCalleeRows(db, match, limit);
|
|
3290
|
+
}
|
|
3291
|
+
if (isKotlinDocument(db, match.relativePath)) {
|
|
3292
|
+
return getKotlinSourceCalleeRows(db, match, limit);
|
|
3293
|
+
}
|
|
3294
|
+
if (isCSharpDocument(db, match.relativePath)) {
|
|
3295
|
+
return getCSharpSourceCalleeRows(db, match, limit);
|
|
3296
|
+
}
|
|
3297
|
+
if (isVisualBasicDocument(db, match.relativePath)) {
|
|
3298
|
+
return getVisualBasicSourceCalleeRows(db, match, limit);
|
|
3299
|
+
}
|
|
3300
|
+
if (isCppDocument(db, match.relativePath)) {
|
|
3301
|
+
return getCppSourceCalleeRows(db, match, limit);
|
|
3302
|
+
}
|
|
3303
|
+
if (isRustDocument(db, match.relativePath)) {
|
|
3304
|
+
return getRustSourceCalleeRows(db, match, limit);
|
|
3305
|
+
}
|
|
3306
|
+
if (isRubyDocument(db, match.relativePath)) {
|
|
3307
|
+
return getRubySourceCalleeRows(db, match, limit);
|
|
3308
|
+
}
|
|
3309
|
+
if (isDartDocument(db, match.relativePath)) {
|
|
3310
|
+
return getDartSourceCalleeRows(db, match, limit);
|
|
3311
|
+
}
|
|
3312
|
+
if (isPhpDocument(db, match.relativePath)) {
|
|
3313
|
+
return getPhpSourceCalleeRows(db, match, limit);
|
|
3314
|
+
}
|
|
2356
3315
|
return [];
|
|
2357
3316
|
}
|
|
2358
3317
|
function getPythonSourceCallerRows(db, target, limit) {
|
|
@@ -2378,24 +3337,53 @@ function getPythonSourceCallerRows(db, target, limit) {
|
|
|
2378
3337
|
}
|
|
2379
3338
|
return rows;
|
|
2380
3339
|
}
|
|
2381
|
-
function
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
}
|
|
2385
|
-
const row = db.get(
|
|
2386
|
-
`SELECT gs.symbol, d.relative_path
|
|
3340
|
+
function getDefinitionRowsForSymbolId(db, symbolId) {
|
|
3341
|
+
const primary = db.all(
|
|
3342
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
|
|
2387
3343
|
FROM global_symbols gs
|
|
2388
3344
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2389
3345
|
JOIN documents d ON der.document_id = d.id
|
|
2390
3346
|
WHERE gs.id = ?
|
|
2391
|
-
|
|
2392
|
-
|
|
3347
|
+
ORDER BY der.start_line, der.end_line`,
|
|
3348
|
+
symbolId
|
|
3349
|
+
);
|
|
3350
|
+
if (primary.length > 0) {
|
|
3351
|
+
return primary;
|
|
3352
|
+
}
|
|
3353
|
+
return db.all(
|
|
3354
|
+
`SELECT
|
|
3355
|
+
gs.id,
|
|
3356
|
+
gs.symbol,
|
|
3357
|
+
c.document_id,
|
|
3358
|
+
MIN(c.start_line) AS start_line,
|
|
3359
|
+
MAX(c.end_line) AS end_line,
|
|
3360
|
+
d.relative_path,
|
|
3361
|
+
gs.display_name
|
|
3362
|
+
FROM global_symbols gs
|
|
3363
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
3364
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
3365
|
+
JOIN documents d ON c.document_id = d.id
|
|
3366
|
+
WHERE gs.id = ?
|
|
3367
|
+
AND m.role = 1
|
|
3368
|
+
${db.pathExclusionsFor("d")}
|
|
3369
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path, gs.display_name
|
|
3370
|
+
ORDER BY start_line, end_line`,
|
|
3371
|
+
symbolId
|
|
2393
3372
|
);
|
|
3373
|
+
}
|
|
3374
|
+
function getFullSymbolMatch(db, symbol) {
|
|
3375
|
+
if ("symbol" in symbol && "relativePath" in symbol) {
|
|
3376
|
+
return symbol;
|
|
3377
|
+
}
|
|
3378
|
+
const row = getDefinitionRowsForSymbolId(db, symbol.symbolId)[0];
|
|
2394
3379
|
if (!row) {
|
|
2395
3380
|
return null;
|
|
2396
3381
|
}
|
|
2397
3382
|
return {
|
|
2398
|
-
|
|
3383
|
+
symbolId: row.id,
|
|
3384
|
+
documentId: row.document_id,
|
|
3385
|
+
startLine: row.start_line,
|
|
3386
|
+
endLine: row.end_line,
|
|
2399
3387
|
symbol: row.symbol,
|
|
2400
3388
|
relativePath: row.relative_path
|
|
2401
3389
|
};
|
|
@@ -2410,8 +3398,17 @@ function getDefinitionsForFile(db, relativePath) {
|
|
|
2410
3398
|
if (cached) {
|
|
2411
3399
|
return cached;
|
|
2412
3400
|
}
|
|
2413
|
-
const
|
|
2414
|
-
`SELECT
|
|
3401
|
+
const primary = db.all(
|
|
3402
|
+
`SELECT
|
|
3403
|
+
gs.id,
|
|
3404
|
+
gs.symbol,
|
|
3405
|
+
der.document_id,
|
|
3406
|
+
der.start_line,
|
|
3407
|
+
der.end_line,
|
|
3408
|
+
d.relative_path,
|
|
3409
|
+
gs.kind,
|
|
3410
|
+
gs.documentation,
|
|
3411
|
+
gs.enclosing_symbol
|
|
2415
3412
|
FROM global_symbols gs
|
|
2416
3413
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2417
3414
|
JOIN documents d ON der.document_id = d.id
|
|
@@ -2419,7 +3416,30 @@ function getDefinitionsForFile(db, relativePath) {
|
|
|
2419
3416
|
${db.symbolNoiseFor("gs")}
|
|
2420
3417
|
ORDER BY der.start_line, der.end_line`,
|
|
2421
3418
|
relativePath
|
|
2422
|
-
)
|
|
3419
|
+
);
|
|
3420
|
+
const fallback = primary.length > 0 ? [] : db.all(
|
|
3421
|
+
`SELECT
|
|
3422
|
+
gs.id,
|
|
3423
|
+
gs.symbol,
|
|
3424
|
+
c.document_id,
|
|
3425
|
+
MIN(c.start_line) AS start_line,
|
|
3426
|
+
MAX(c.end_line) AS end_line,
|
|
3427
|
+
d.relative_path,
|
|
3428
|
+
gs.kind,
|
|
3429
|
+
gs.documentation,
|
|
3430
|
+
gs.enclosing_symbol
|
|
3431
|
+
FROM global_symbols gs
|
|
3432
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
3433
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
3434
|
+
JOIN documents d ON c.document_id = d.id
|
|
3435
|
+
WHERE d.relative_path = ?
|
|
3436
|
+
AND m.role = 1
|
|
3437
|
+
${db.symbolNoiseFor("gs")}
|
|
3438
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path
|
|
3439
|
+
ORDER BY start_line, end_line`,
|
|
3440
|
+
relativePath
|
|
3441
|
+
);
|
|
3442
|
+
const definitions = (primary.length > 0 ? primary : fallback).map((row) => ({
|
|
2423
3443
|
symbolId: row.id,
|
|
2424
3444
|
symbol: row.symbol,
|
|
2425
3445
|
documentId: row.document_id,
|
|
@@ -2429,34 +3449,29 @@ function getDefinitionsForFile(db, relativePath) {
|
|
|
2429
3449
|
leaf: leafName(row.symbol),
|
|
2430
3450
|
parentTypeName: parentTypeName(row.symbol),
|
|
2431
3451
|
isFunctionLike: isFunctionLikeSymbol(row.symbol),
|
|
2432
|
-
isTypeLike: leafSuffix(row.symbol) === "type"
|
|
3452
|
+
isTypeLike: leafSuffix(row.symbol) === "type",
|
|
3453
|
+
kind: row.kind,
|
|
3454
|
+
documentation: row.documentation,
|
|
3455
|
+
enclosingSymbol: row.enclosing_symbol
|
|
2433
3456
|
}));
|
|
2434
3457
|
cache.set(relativePath, definitions);
|
|
2435
3458
|
return definitions;
|
|
2436
3459
|
}
|
|
2437
|
-
function
|
|
3460
|
+
function getAllDefinitions(db, opts = {}) {
|
|
3461
|
+
const { scope } = opts;
|
|
2438
3462
|
const rows = db.all(
|
|
2439
|
-
`SELECT
|
|
2440
|
-
FROM
|
|
2441
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2442
|
-
JOIN documents d ON der.document_id = d.id
|
|
3463
|
+
`SELECT relative_path
|
|
3464
|
+
FROM documents
|
|
2443
3465
|
WHERE 1 = 1
|
|
2444
|
-
${db.pathExclusionsFor("
|
|
2445
|
-
${
|
|
2446
|
-
ORDER BY
|
|
3466
|
+
${db.pathExclusionsFor("documents")}
|
|
3467
|
+
${scope ? "AND relative_path LIKE ?" : ""}
|
|
3468
|
+
ORDER BY relative_path`,
|
|
3469
|
+
...scope ? [`%${scope}%`] : []
|
|
2447
3470
|
);
|
|
2448
|
-
return rows.filter((row) => !db.isIgnored(row.relative_path)).
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
startLine: row.start_line,
|
|
2453
|
-
endLine: row.end_line,
|
|
2454
|
-
relativePath: row.relative_path,
|
|
2455
|
-
leaf: leafName(row.symbol),
|
|
2456
|
-
parentTypeName: parentTypeName(row.symbol),
|
|
2457
|
-
isFunctionLike: true,
|
|
2458
|
-
isTypeLike: false
|
|
2459
|
-
}));
|
|
3471
|
+
return rows.filter((row) => !db.isIgnored(row.relative_path)).flatMap((row) => getDefinitionsForFile(db, row.relative_path));
|
|
3472
|
+
}
|
|
3473
|
+
function getAllFunctionLikeDefinitions(db) {
|
|
3474
|
+
return getAllDefinitions(db).filter((definition) => definition.isFunctionLike);
|
|
2460
3475
|
}
|
|
2461
3476
|
function resolvePythonCallTarget(db, current, currentFileDefinitions, imports2, constructorBindings, receiverName, calleeName) {
|
|
2462
3477
|
if (receiverName === "self" || receiverName === "cls") {
|
|
@@ -2557,6 +3572,225 @@ function resolveJavaScriptCallTarget(db, current, currentFileDefinitions, import
|
|
|
2557
3572
|
}
|
|
2558
3573
|
return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
|
|
2559
3574
|
}
|
|
3575
|
+
function resolveSimpleSourceCallees(db, current, calls, bindings, limit) {
|
|
3576
|
+
const currentFileDefinitions = getDefinitionsForFile(db, current.relativePath);
|
|
3577
|
+
const currentDefinition = currentFileDefinitions.find((definition) => definition.symbolId === current.symbolId);
|
|
3578
|
+
if (!currentDefinition) {
|
|
3579
|
+
return [];
|
|
3580
|
+
}
|
|
3581
|
+
const rows = [];
|
|
3582
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3583
|
+
for (const call of calls) {
|
|
3584
|
+
const resolved = resolveSimpleSourceCallTarget(
|
|
3585
|
+
db,
|
|
3586
|
+
currentDefinition,
|
|
3587
|
+
currentFileDefinitions,
|
|
3588
|
+
bindings,
|
|
3589
|
+
call.receiverName,
|
|
3590
|
+
call.calleeName
|
|
3591
|
+
);
|
|
3592
|
+
if (!resolved || resolved.symbolId === current.symbolId || db.isIgnored(resolved.relativePath)) continue;
|
|
3593
|
+
const chunkId = 2e9 + call.line;
|
|
3594
|
+
const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
|
|
3595
|
+
if (seen.has(key)) continue;
|
|
3596
|
+
seen.add(key);
|
|
3597
|
+
rows.push({
|
|
3598
|
+
symbol: resolved.symbol,
|
|
3599
|
+
file: resolved.relativePath,
|
|
3600
|
+
chunkId
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
return applyLimit(rows, limit);
|
|
3604
|
+
}
|
|
3605
|
+
function resolveSimpleSourceCallTarget(db, current, currentFileDefinitions, bindings, receiverName, calleeName) {
|
|
3606
|
+
if (!receiverName) {
|
|
3607
|
+
const localMethod = findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
|
|
3608
|
+
if (localMethod) {
|
|
3609
|
+
return localMethod;
|
|
3610
|
+
}
|
|
3611
|
+
if (current.parentTypeName) {
|
|
3612
|
+
return findProjectDefinitionByTypeAndLeaf(db, current.parentTypeName, calleeName);
|
|
3613
|
+
}
|
|
3614
|
+
return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
|
|
3615
|
+
}
|
|
3616
|
+
const normalizedReceiver = normalizeReceiverName(receiverName);
|
|
3617
|
+
const inferredType = bindings.get(normalizedReceiver) ?? inferTypeNameFromReceiver(db, normalizedReceiver);
|
|
3618
|
+
if (!inferredType) {
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3621
|
+
return findProjectDefinitionByTypeAndLeaf(db, inferredType, calleeName);
|
|
3622
|
+
}
|
|
3623
|
+
function findProjectDefinitionByTypeAndLeaf(db, typeName, calleeName) {
|
|
3624
|
+
const definitions = getAllDefinitions(db).filter((definition) => definition.isFunctionLike || definition.symbol.endsWith("()."));
|
|
3625
|
+
const exact = definitions.find((definition) => definition.leaf === calleeName && (definition.parentTypeName === typeName || definition.symbol.includes(typeName)));
|
|
3626
|
+
if (exact) {
|
|
3627
|
+
return exact;
|
|
3628
|
+
}
|
|
3629
|
+
const normalizedType = normalizeLookupName(typeName);
|
|
3630
|
+
const normalizedMatch = definitions.find((definition) => definition.leaf === calleeName && normalizeLookupName(definition.parentTypeName ?? "").includes(normalizedType));
|
|
3631
|
+
if (normalizedMatch) {
|
|
3632
|
+
return normalizedMatch;
|
|
3633
|
+
}
|
|
3634
|
+
return findLooseProjectDefinitionByTypeAndLeaf(db, typeName, calleeName);
|
|
3635
|
+
}
|
|
3636
|
+
function inferTypeNameFromReceiver(db, receiverName) {
|
|
3637
|
+
const normalizedReceiver = normalizeLookupName(receiverName);
|
|
3638
|
+
if (!normalizedReceiver) {
|
|
3639
|
+
return null;
|
|
3640
|
+
}
|
|
3641
|
+
const candidates = getAllDefinitions(db).filter((definition) => definition.isTypeLike || definition.symbol.endsWith("#")).map((definition) => definition.leaf).filter((leaf) => leaf.length > 0);
|
|
3642
|
+
let best = null;
|
|
3643
|
+
for (const candidate of candidates) {
|
|
3644
|
+
const normalizedCandidate = normalizeLookupName(candidate);
|
|
3645
|
+
let score = 0;
|
|
3646
|
+
if (normalizedCandidate === normalizedReceiver) score += 100;
|
|
3647
|
+
if (normalizedCandidate.endsWith(normalizedReceiver)) score += 80;
|
|
3648
|
+
if (normalizedCandidate.includes(normalizedReceiver)) score += 40;
|
|
3649
|
+
if (normalizedReceiver.includes(normalizedCandidate)) score += 20;
|
|
3650
|
+
if (score > 0 && (!best || score > best.score || score === best.score && candidate.length < best.name.length)) {
|
|
3651
|
+
best = { name: candidate, score };
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
return best?.name ?? null;
|
|
3655
|
+
}
|
|
3656
|
+
function getSimpleSourceCalls(db, relativePath, startLine, endLine, opts = {}) {
|
|
3657
|
+
const source = getSourceText(db, relativePath);
|
|
3658
|
+
if (!source) {
|
|
3659
|
+
return [];
|
|
3660
|
+
}
|
|
3661
|
+
const lines = source.split("\n");
|
|
3662
|
+
const scoped = lines.slice(startLine, endLine + 1);
|
|
3663
|
+
const calls = [];
|
|
3664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3665
|
+
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;
|
|
3666
|
+
for (let index = 0; index < scoped.length; index++) {
|
|
3667
|
+
const rawLine = scoped[index] ?? "";
|
|
3668
|
+
for (const match of rawLine.matchAll(pattern)) {
|
|
3669
|
+
const receiverName = match[1];
|
|
3670
|
+
const calleeName = match[2];
|
|
3671
|
+
if (!receiverName || !calleeName) continue;
|
|
3672
|
+
const key = `${startLine + index}|${receiverName}|${calleeName}`;
|
|
3673
|
+
if (seen.has(key)) continue;
|
|
3674
|
+
seen.add(key);
|
|
3675
|
+
calls.push({
|
|
3676
|
+
receiverName,
|
|
3677
|
+
calleeName,
|
|
3678
|
+
line: startLine + index
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
if (opts.allowBareMemberCalls) {
|
|
3682
|
+
for (const match of rawLine.matchAll(/(@?[A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w!?=]*)\b(?!\s*[:=])/g)) {
|
|
3683
|
+
const receiverName = match[1];
|
|
3684
|
+
const calleeName = match[2];
|
|
3685
|
+
if (!receiverName || !calleeName) continue;
|
|
3686
|
+
const key = `${startLine + index}|${receiverName}|${calleeName}`;
|
|
3687
|
+
if (seen.has(key)) continue;
|
|
3688
|
+
seen.add(key);
|
|
3689
|
+
calls.push({
|
|
3690
|
+
receiverName,
|
|
3691
|
+
calleeName,
|
|
3692
|
+
line: startLine + index
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
for (const match of rawLine.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
|
|
3697
|
+
const calleeName = match[1];
|
|
3698
|
+
const start = match.index ?? -1;
|
|
3699
|
+
if (!calleeName || start < 0) continue;
|
|
3700
|
+
const prefix = rawLine.slice(0, start).trimEnd();
|
|
3701
|
+
if (prefix.endsWith("def") || prefix.endsWith("fun") || prefix.endsWith("fn") || /\b(?:class|interface|trait|module|object)\s*$/.test(prefix)) {
|
|
3702
|
+
continue;
|
|
3703
|
+
}
|
|
3704
|
+
if (rawLine.slice(Math.max(0, start - 3), start).includes(".")) {
|
|
3705
|
+
continue;
|
|
3706
|
+
}
|
|
3707
|
+
calls.push({
|
|
3708
|
+
receiverName: null,
|
|
3709
|
+
calleeName,
|
|
3710
|
+
line: startLine + index
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
return calls;
|
|
3715
|
+
}
|
|
3716
|
+
function parseJavaFieldBindings(source) {
|
|
3717
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3718
|
+
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)) {
|
|
3719
|
+
const typeName = stripGenericType(match[1]);
|
|
3720
|
+
const localName = match[2];
|
|
3721
|
+
if (typeName && localName) {
|
|
3722
|
+
bindings.set(localName, typeName);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
return bindings;
|
|
3726
|
+
}
|
|
3727
|
+
function parseKotlinFieldBindings(source) {
|
|
3728
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3729
|
+
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)) {
|
|
3730
|
+
const localName = match[1];
|
|
3731
|
+
const typeName = stripGenericType(match[2]);
|
|
3732
|
+
if (typeName && localName) {
|
|
3733
|
+
bindings.set(localName, typeName);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return bindings;
|
|
3737
|
+
}
|
|
3738
|
+
function parseCppReceiverBindings(source) {
|
|
3739
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3740
|
+
const constructorMatch = source.match(/RunCoordinator::RunCoordinator\s*\(([\s\S]*?)\)\s*(?::\s*([\s\S]*?))?\s*\{/);
|
|
3741
|
+
if (!constructorMatch) {
|
|
3742
|
+
return bindings;
|
|
3743
|
+
}
|
|
3744
|
+
const params = constructorMatch[1] ?? "";
|
|
3745
|
+
const initializers = constructorMatch[2] ?? "";
|
|
3746
|
+
const parameterTypes = /* @__PURE__ */ new Map();
|
|
3747
|
+
for (const param of params.split(",")) {
|
|
3748
|
+
const trimmed = param.trim();
|
|
3749
|
+
const match = trimmed.match(/(.+?)\s+([A-Za-z_][\w]*)$/);
|
|
3750
|
+
if (!match) continue;
|
|
3751
|
+
const [, rawType, rawName] = match;
|
|
3752
|
+
if (!rawType || !rawName) continue;
|
|
3753
|
+
const typeName = stripGenericType(rawType.replace(/[&*]+/g, " ").replace(/\bconst\b/g, " ").trim());
|
|
3754
|
+
if (!typeName) continue;
|
|
3755
|
+
parameterTypes.set(normalizeReceiverName(rawName), typeName);
|
|
3756
|
+
}
|
|
3757
|
+
for (const initializer of initializers.split(",")) {
|
|
3758
|
+
const trimmed = initializer.trim();
|
|
3759
|
+
const match = trimmed.match(/([A-Za-z_][\w]*)\s*\(\s*([A-Za-z_][\w]*)\s*\)$/);
|
|
3760
|
+
if (!match) continue;
|
|
3761
|
+
const [, fieldName, sourceName] = match;
|
|
3762
|
+
if (!fieldName || !sourceName) continue;
|
|
3763
|
+
const typeName = parameterTypes.get(normalizeReceiverName(sourceName));
|
|
3764
|
+
if (!typeName) continue;
|
|
3765
|
+
bindings.set(normalizeReceiverName(fieldName), typeName);
|
|
3766
|
+
}
|
|
3767
|
+
return bindings;
|
|
3768
|
+
}
|
|
3769
|
+
function parseRubyReceiverBindings(db, source) {
|
|
3770
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3771
|
+
for (const match of source.matchAll(/@([A-Za-z_][\w]*)\s*=\s*([A-Za-z_][\w]*)/g)) {
|
|
3772
|
+
const ivarName = `@${match[1]}`;
|
|
3773
|
+
const localName = match[2];
|
|
3774
|
+
const typeName = inferTypeNameFromReceiver(db, localName ?? match[1] ?? "");
|
|
3775
|
+
if (typeName) {
|
|
3776
|
+
bindings.set(ivarName, typeName);
|
|
3777
|
+
bindings.set(match[1], typeName);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
return bindings;
|
|
3781
|
+
}
|
|
3782
|
+
function stripGenericType(typeName) {
|
|
3783
|
+
return (typeName ?? "").replace(/<.*$/, "").replace(/^.*\./, "").trim();
|
|
3784
|
+
}
|
|
3785
|
+
function normalizeReceiverName(receiverName) {
|
|
3786
|
+
return receiverName.replace(/^@/, "").replace(/^this(?:\.|->)/, "").replace(/^_+/, "").replace(/_+$/, "").trim();
|
|
3787
|
+
}
|
|
3788
|
+
function normalizeLookupName(value) {
|
|
3789
|
+
return value.replace(/[^A-Za-z0-9]+/g, "").toLowerCase();
|
|
3790
|
+
}
|
|
3791
|
+
function isLikelyTestPath(relativePath) {
|
|
3792
|
+
return /(^|\/)(tests?|__tests__)\//.test(relativePath) || /\.(?:spec|test)\./.test(relativePath) || /_test\./.test(relativePath);
|
|
3793
|
+
}
|
|
2560
3794
|
function findDefinitionByName(definitions, leaf, parentType, preference) {
|
|
2561
3795
|
const candidates = definitions.filter((definition) => definition.leaf === leaf && definition.parentTypeName === parentType);
|
|
2562
3796
|
if (candidates.length === 0) {
|
|
@@ -2570,6 +3804,58 @@ function findDefinitionByName(definitions, leaf, parentType, preference) {
|
|
|
2570
3804
|
}
|
|
2571
3805
|
return candidates[0] ?? null;
|
|
2572
3806
|
}
|
|
3807
|
+
function findLooseProjectDefinitionByTypeAndLeaf(db, typeName, calleeName) {
|
|
3808
|
+
const rows = db.all(
|
|
3809
|
+
`SELECT
|
|
3810
|
+
gs.id,
|
|
3811
|
+
gs.symbol,
|
|
3812
|
+
c.document_id,
|
|
3813
|
+
MIN(c.start_line) AS start_line,
|
|
3814
|
+
MAX(c.end_line) AS end_line,
|
|
3815
|
+
d.relative_path,
|
|
3816
|
+
gs.kind,
|
|
3817
|
+
gs.documentation,
|
|
3818
|
+
gs.enclosing_symbol
|
|
3819
|
+
FROM global_symbols gs
|
|
3820
|
+
JOIN mentions m ON m.symbol_id = gs.id
|
|
3821
|
+
JOIN chunks c ON c.id = m.chunk_id
|
|
3822
|
+
JOIN documents d ON d.id = c.document_id
|
|
3823
|
+
WHERE gs.symbol LIKE ?
|
|
3824
|
+
AND gs.symbol LIKE ?
|
|
3825
|
+
${db.pathExclusionsFor("d")}
|
|
3826
|
+
GROUP BY gs.id, gs.symbol, c.document_id, d.relative_path, gs.kind, gs.documentation, gs.enclosing_symbol`,
|
|
3827
|
+
`%${typeName}%`,
|
|
3828
|
+
`%${calleeName}%`
|
|
3829
|
+
);
|
|
3830
|
+
const normalizedType = normalizeLookupName(typeName);
|
|
3831
|
+
const candidates = rows.filter((row2) => leafName(row2.symbol) === calleeName).filter((row2) => {
|
|
3832
|
+
const parentType = parentTypeName(row2.symbol);
|
|
3833
|
+
return parentType === typeName || row2.symbol.includes(typeName) || normalizeLookupName(parentType ?? "").includes(normalizedType);
|
|
3834
|
+
}).sort((left, right) => {
|
|
3835
|
+
const leftTest = isLikelyTestPath(left.relative_path) ? 1 : 0;
|
|
3836
|
+
const rightTest = isLikelyTestPath(right.relative_path) ? 1 : 0;
|
|
3837
|
+
return leftTest - rightTest || left.relative_path.localeCompare(right.relative_path) || left.start_line - right.start_line;
|
|
3838
|
+
});
|
|
3839
|
+
const row = candidates[0];
|
|
3840
|
+
if (!row) {
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
return {
|
|
3844
|
+
symbolId: row.id,
|
|
3845
|
+
symbol: row.symbol,
|
|
3846
|
+
documentId: row.document_id,
|
|
3847
|
+
startLine: row.start_line,
|
|
3848
|
+
endLine: row.end_line,
|
|
3849
|
+
relativePath: row.relative_path,
|
|
3850
|
+
leaf: leafName(row.symbol),
|
|
3851
|
+
parentTypeName: parentTypeName(row.symbol),
|
|
3852
|
+
isFunctionLike: isFunctionLikeSymbol(row.symbol),
|
|
3853
|
+
isTypeLike: leafSuffix(row.symbol) === "type",
|
|
3854
|
+
kind: row.kind,
|
|
3855
|
+
documentation: row.documentation,
|
|
3856
|
+
enclosingSymbol: row.enclosing_symbol
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
2573
3859
|
function hasUniqueLeafDefinition(db, leaf, symbolId) {
|
|
2574
3860
|
const rows = db.all(
|
|
2575
3861
|
`SELECT id, symbol
|
|
@@ -2615,6 +3901,76 @@ function isJavaScriptDocument(db, relativePath) {
|
|
|
2615
3901
|
);
|
|
2616
3902
|
return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
|
|
2617
3903
|
}
|
|
3904
|
+
function isJavaDocument(db, relativePath) {
|
|
3905
|
+
const row = db.get(
|
|
3906
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3907
|
+
relativePath
|
|
3908
|
+
);
|
|
3909
|
+
return row?.language === "java" || relativePath.endsWith(".java");
|
|
3910
|
+
}
|
|
3911
|
+
function isKotlinDocument(db, relativePath) {
|
|
3912
|
+
const row = db.get(
|
|
3913
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3914
|
+
relativePath
|
|
3915
|
+
);
|
|
3916
|
+
return row?.language === "kotlin" || relativePath.endsWith(".kt") || relativePath.endsWith(".kts");
|
|
3917
|
+
}
|
|
3918
|
+
function isScalaDocument(db, relativePath) {
|
|
3919
|
+
const row = db.get(
|
|
3920
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3921
|
+
relativePath
|
|
3922
|
+
);
|
|
3923
|
+
return row?.language === "scala" || relativePath.endsWith(".scala");
|
|
3924
|
+
}
|
|
3925
|
+
function isCSharpDocument(db, relativePath) {
|
|
3926
|
+
const row = db.get(
|
|
3927
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3928
|
+
relativePath
|
|
3929
|
+
);
|
|
3930
|
+
return row?.language === "C#" || relativePath.endsWith(".cs");
|
|
3931
|
+
}
|
|
3932
|
+
function isVisualBasicDocument(db, relativePath) {
|
|
3933
|
+
const row = db.get(
|
|
3934
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3935
|
+
relativePath
|
|
3936
|
+
);
|
|
3937
|
+
return row?.language === "Visual Basic" || relativePath.endsWith(".vb");
|
|
3938
|
+
}
|
|
3939
|
+
function isCppDocument(db, relativePath) {
|
|
3940
|
+
const row = db.get(
|
|
3941
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3942
|
+
relativePath
|
|
3943
|
+
);
|
|
3944
|
+
return row?.language === "CPP" || /\.(?:cc|cpp|cxx|hpp|hh|hxx)$/.test(relativePath);
|
|
3945
|
+
}
|
|
3946
|
+
function isRustDocument(db, relativePath) {
|
|
3947
|
+
const row = db.get(
|
|
3948
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3949
|
+
relativePath
|
|
3950
|
+
);
|
|
3951
|
+
return row?.language === "Rust" || relativePath.endsWith(".rs");
|
|
3952
|
+
}
|
|
3953
|
+
function isRubyDocument(db, relativePath) {
|
|
3954
|
+
const row = db.get(
|
|
3955
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3956
|
+
relativePath
|
|
3957
|
+
);
|
|
3958
|
+
return row?.language === "ruby" || relativePath.endsWith(".rb");
|
|
3959
|
+
}
|
|
3960
|
+
function isDartDocument(db, relativePath) {
|
|
3961
|
+
const row = db.get(
|
|
3962
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3963
|
+
relativePath
|
|
3964
|
+
);
|
|
3965
|
+
return row?.language === "Dart" || relativePath.endsWith(".dart");
|
|
3966
|
+
}
|
|
3967
|
+
function isPhpDocument(db, relativePath) {
|
|
3968
|
+
const row = db.get(
|
|
3969
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3970
|
+
relativePath
|
|
3971
|
+
);
|
|
3972
|
+
return row?.language === "PHP" || relativePath.endsWith(".php");
|
|
3973
|
+
}
|
|
2618
3974
|
function applyLimit(values, limit) {
|
|
2619
3975
|
return typeof limit === "number" ? values.slice(0, limit) : values;
|
|
2620
3976
|
}
|
|
@@ -2635,7 +3991,14 @@ function resolveDocumentCandidates(db, filePattern, opts) {
|
|
|
2635
3991
|
score: scoreDocumentPath(row.relative_path, normalizedPattern)
|
|
2636
3992
|
})).filter((row) => row.score > 0).sort((a, b) => b.score - a.score || a.relativePath.localeCompare(b.relativePath));
|
|
2637
3993
|
if (scored.length === 0) {
|
|
2638
|
-
|
|
3994
|
+
const symbolMatch = findFirstSymbolMatch(db, filePattern);
|
|
3995
|
+
if (!symbolMatch || db.isIgnored(symbolMatch.relativePath)) {
|
|
3996
|
+
return [];
|
|
3997
|
+
}
|
|
3998
|
+
return [{
|
|
3999
|
+
relativePath: symbolMatch.relativePath,
|
|
4000
|
+
score: 700
|
|
4001
|
+
}];
|
|
2639
4002
|
}
|
|
2640
4003
|
const exactish = scored.filter((row) => row.score >= 800);
|
|
2641
4004
|
if (exactish.length > 0) {
|
|
@@ -2645,8 +4008,8 @@ function resolveDocumentCandidates(db, filePattern, opts) {
|
|
|
2645
4008
|
}
|
|
2646
4009
|
function scoreDocumentPath(relativePath, rawPattern) {
|
|
2647
4010
|
const normalizedPath = normalizeLookupPath(relativePath);
|
|
2648
|
-
const pathBase =
|
|
2649
|
-
const patternBase =
|
|
4011
|
+
const pathBase = basename2(normalizedPath);
|
|
4012
|
+
const patternBase = basename2(rawPattern);
|
|
2650
4013
|
let score = 0;
|
|
2651
4014
|
if (normalizedPath === rawPattern) score += 1200;
|
|
2652
4015
|
if (normalizedPath.endsWith(`/${rawPattern}`)) score += 1100;
|
|
@@ -2674,62 +4037,69 @@ function symbols(db, filePattern) {
|
|
|
2674
4037
|
if (resolvedPaths.length === 0) {
|
|
2675
4038
|
return [];
|
|
2676
4039
|
}
|
|
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
|
-
}));
|
|
4040
|
+
return resolvedPaths.flatMap((relativePath) => getDefinitionsForFile(db, relativePath)).filter((row) => !db.isIgnored(row.relativePath)).map((row) => {
|
|
4041
|
+
const docRow = db.get(
|
|
4042
|
+
`SELECT REPLACE(SUBSTR(documentation, INSTR(documentation, '|') + 1), char(10), ' ') AS sig
|
|
4043
|
+
FROM global_symbols
|
|
4044
|
+
WHERE id = ?`,
|
|
4045
|
+
row.symbolId
|
|
4046
|
+
);
|
|
4047
|
+
return {
|
|
4048
|
+
startLine: row.startLine,
|
|
4049
|
+
endLine: row.endLine,
|
|
4050
|
+
symbol: row.symbol,
|
|
4051
|
+
shortName: shortenSymbol(row.symbol),
|
|
4052
|
+
signature: cleanSignature(docRow?.sig ?? null)
|
|
4053
|
+
};
|
|
4054
|
+
});
|
|
2701
4055
|
}
|
|
2702
4056
|
|
|
2703
4057
|
// src/queries/methods.ts
|
|
4058
|
+
import { basename as basename3 } from "path";
|
|
2704
4059
|
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)
|
|
4060
|
+
const classMatch = findFirstSymbolMatch(db, className);
|
|
4061
|
+
if (!classMatch) {
|
|
4062
|
+
return [];
|
|
4063
|
+
}
|
|
4064
|
+
const ownerName = leafName(classMatch.symbol);
|
|
4065
|
+
const definitions = getDefinitionsForFile(db, classMatch.relativePath).filter((definition) => isCallableSymbol(definition.symbol));
|
|
4066
|
+
const directMethods = definitions.filter((definition) => definition.parentTypeName === ownerName || definition.symbol.includes(ownerName));
|
|
4067
|
+
const fileScopedMethods = directMethods.length > 0 ? directMethods : stripExtension(basename3(classMatch.relativePath)) === ownerName ? definitions.filter((definition) => definition.symbol.includes("<invalid-global-code>")) : [];
|
|
4068
|
+
return fileScopedMethods.map((definition) => ({
|
|
4069
|
+
startLine: definition.startLine,
|
|
4070
|
+
endLine: definition.endLine,
|
|
4071
|
+
name: leafName(definition.symbol)
|
|
2720
4072
|
}));
|
|
2721
4073
|
}
|
|
4074
|
+
function isCallableSymbol(rawSymbol) {
|
|
4075
|
+
return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
|
|
4076
|
+
}
|
|
4077
|
+
function stripExtension(relativePath) {
|
|
4078
|
+
return relativePath.replace(/\.[^.]+$/, "");
|
|
4079
|
+
}
|
|
2722
4080
|
|
|
2723
4081
|
// src/queries/refs.ts
|
|
2724
4082
|
function refs(db, symbolPattern) {
|
|
2725
4083
|
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
2726
4084
|
if (match) {
|
|
4085
|
+
const includeDefinitionSite = !isFunctionLikeSymbol(match.symbol);
|
|
4086
|
+
const definitionRows = includeDefinitionSite ? [{
|
|
4087
|
+
relativePath: match.relativePath,
|
|
4088
|
+
line: match.startLine
|
|
4089
|
+
}] : [];
|
|
2727
4090
|
const sourceSites = getSourceReferenceSites(db, match).filter((site) => !db.isIgnored(site.file)).map((site) => ({
|
|
2728
4091
|
relativePath: site.file,
|
|
2729
4092
|
line: site.line
|
|
2730
4093
|
}));
|
|
2731
4094
|
if (sourceSites.length > 0) {
|
|
2732
|
-
|
|
4095
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
4096
|
+
const rows2 = [...definitionRows, ...sourceSites, ...getRubySemanticRefs(db, match)].filter((site) => {
|
|
4097
|
+
const key = `${site.relativePath}:${site.line}`;
|
|
4098
|
+
if (seen2.has(key)) return false;
|
|
4099
|
+
seen2.add(key);
|
|
4100
|
+
return true;
|
|
4101
|
+
});
|
|
4102
|
+
return rows2;
|
|
2733
4103
|
}
|
|
2734
4104
|
}
|
|
2735
4105
|
const rows = db.all(
|
|
@@ -2744,10 +4114,70 @@ function refs(db, symbolPattern) {
|
|
|
2744
4114
|
ORDER BY d.relative_path, c.start_line`,
|
|
2745
4115
|
match?.symbolId ?? -1
|
|
2746
4116
|
);
|
|
2747
|
-
|
|
4117
|
+
const referenceRows = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
2748
4118
|
relativePath: r.relative_path,
|
|
2749
4119
|
line: r.start_line
|
|
2750
4120
|
}));
|
|
4121
|
+
if (!match || db.isIgnored(match.relativePath) || isFunctionLikeSymbol(match.symbol)) {
|
|
4122
|
+
return referenceRows;
|
|
4123
|
+
}
|
|
4124
|
+
const seen = new Set(referenceRows.map((row) => `${row.relativePath}:${row.line}`));
|
|
4125
|
+
if (!seen.has(`${match.relativePath}:${match.startLine}`)) {
|
|
4126
|
+
referenceRows.unshift({
|
|
4127
|
+
relativePath: match.relativePath,
|
|
4128
|
+
line: match.startLine
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
for (const row of getRubySemanticRefs(db, match)) {
|
|
4132
|
+
const key = `${row.relativePath}:${row.line}`;
|
|
4133
|
+
if (seen.has(key)) continue;
|
|
4134
|
+
seen.add(key);
|
|
4135
|
+
referenceRows.push(row);
|
|
4136
|
+
}
|
|
4137
|
+
return referenceRows;
|
|
4138
|
+
}
|
|
4139
|
+
function getRubySemanticRefs(db, match) {
|
|
4140
|
+
if (!match.relativePath.endsWith(".rb")) {
|
|
4141
|
+
return [];
|
|
4142
|
+
}
|
|
4143
|
+
const tokens = rubyReferenceTokens(match.symbol);
|
|
4144
|
+
if (tokens.length === 0) {
|
|
4145
|
+
return [];
|
|
4146
|
+
}
|
|
4147
|
+
const rows = db.all(
|
|
4148
|
+
`SELECT relative_path
|
|
4149
|
+
FROM documents
|
|
4150
|
+
WHERE relative_path LIKE '%.rb'
|
|
4151
|
+
${db.pathExclusionsFor("documents")}
|
|
4152
|
+
ORDER BY relative_path`
|
|
4153
|
+
);
|
|
4154
|
+
const results = [];
|
|
4155
|
+
for (const row of rows) {
|
|
4156
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
4157
|
+
const source = getSourceText(db, row.relative_path);
|
|
4158
|
+
if (!source) continue;
|
|
4159
|
+
const lines = source.split("\n");
|
|
4160
|
+
for (let index = 0; index < lines.length; index++) {
|
|
4161
|
+
const line = lines[index] ?? "";
|
|
4162
|
+
if (tokens.some((token) => new RegExp(`@${token}\\b|\\b${token}:`).test(line))) {
|
|
4163
|
+
results.push({
|
|
4164
|
+
relativePath: row.relative_path,
|
|
4165
|
+
line: index
|
|
4166
|
+
});
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
return results;
|
|
4171
|
+
}
|
|
4172
|
+
function rubyReferenceTokens(rawSymbol) {
|
|
4173
|
+
const leaf = rawSymbol.split(":").pop() ?? rawSymbol;
|
|
4174
|
+
const snake = leaf.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase().replace(/^_+|_+$/g, "");
|
|
4175
|
+
const parts = snake.split("_").filter(Boolean);
|
|
4176
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4177
|
+
if (snake) candidates.add(snake);
|
|
4178
|
+
if (parts.length >= 1) candidates.add(parts[parts.length - 1]);
|
|
4179
|
+
if (parts.length >= 2) candidates.add(parts.slice(-2).join("_"));
|
|
4180
|
+
return [...candidates];
|
|
2751
4181
|
}
|
|
2752
4182
|
|
|
2753
4183
|
// src/queries/trace.ts
|
|
@@ -2756,24 +4186,22 @@ function trace(db, symbolPattern) {
|
|
|
2756
4186
|
if (!match) {
|
|
2757
4187
|
return { definitions: [], referencedBy: [] };
|
|
2758
4188
|
}
|
|
2759
|
-
const
|
|
2760
|
-
`SELECT
|
|
4189
|
+
const definitionMeta = db.get(
|
|
4190
|
+
`SELECT
|
|
2761
4191
|
gs.display_name,
|
|
2762
4192
|
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
2763
4193
|
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
4194
|
WHERE gs.id = ?
|
|
2767
|
-
ORDER BY d.relative_path, der.start_line
|
|
2768
4195
|
LIMIT 10`,
|
|
2769
4196
|
match.symbolId
|
|
2770
4197
|
);
|
|
2771
|
-
const definitions =
|
|
2772
|
-
relativePath:
|
|
2773
|
-
startLine:
|
|
2774
|
-
endLine:
|
|
2775
|
-
signature: buildTraceSignature(
|
|
2776
|
-
|
|
4198
|
+
const definitions = db.isIgnored(match.relativePath) ? [] : [{
|
|
4199
|
+
relativePath: match.relativePath,
|
|
4200
|
+
startLine: match.startLine,
|
|
4201
|
+
endLine: match.endLine,
|
|
4202
|
+
signature: buildTraceSignature(definitionMeta?.sig ?? null, definitionMeta?.display_name ?? null, match.symbol),
|
|
4203
|
+
source: definitionSource(db, match.relativePath, match.startLine, match.endLine)
|
|
4204
|
+
}];
|
|
2777
4205
|
const sourceSites = getSourceReferenceSites(db, match);
|
|
2778
4206
|
const referencedBy = sourceSites.length > 0 ? sourceSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
|
|
2779
4207
|
relativePath: site.file,
|
|
@@ -2806,6 +4234,15 @@ function trace(db, symbolPattern) {
|
|
|
2806
4234
|
}));
|
|
2807
4235
|
return { definitions, referencedBy };
|
|
2808
4236
|
}
|
|
4237
|
+
function definitionSource(db, relativePath, startLine, endLine) {
|
|
4238
|
+
const source = getSourceText(db, relativePath);
|
|
4239
|
+
if (!source) {
|
|
4240
|
+
return null;
|
|
4241
|
+
}
|
|
4242
|
+
const lines = source.split("\n");
|
|
4243
|
+
const slice2 = lines.slice(startLine, endLine + 1).join("\n").trimEnd();
|
|
4244
|
+
return slice2.length > 0 ? slice2 : null;
|
|
4245
|
+
}
|
|
2809
4246
|
function buildTraceSignature(signature, displayName, rawSymbol) {
|
|
2810
4247
|
const cleaned = cleanSignature(signature);
|
|
2811
4248
|
if (cleaned && !looksBogusSignature(cleaned)) {
|
|
@@ -2946,22 +4383,54 @@ function surface(db, modulePattern) {
|
|
|
2946
4383
|
JOIN chunks c ON m.chunk_id = c.id
|
|
2947
4384
|
JOIN documents d1 ON c.document_id = d1.id
|
|
2948
4385
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
4386
|
+
WHERE d1.relative_path NOT IN (${placeholders})
|
|
4387
|
+
AND (
|
|
4388
|
+
EXISTS (
|
|
4389
|
+
SELECT 1
|
|
4390
|
+
FROM defn_enclosing_ranges der
|
|
4391
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
4392
|
+
WHERE der.symbol_id = gs.id
|
|
4393
|
+
AND d2.relative_path IN (${placeholders})
|
|
4394
|
+
)
|
|
4395
|
+
OR EXISTS (
|
|
4396
|
+
SELECT 1
|
|
4397
|
+
FROM mentions def_m
|
|
4398
|
+
JOIN chunks def_c ON def_m.chunk_id = def_c.id
|
|
4399
|
+
JOIN documents d2 ON def_c.document_id = d2.id
|
|
4400
|
+
WHERE def_m.symbol_id = gs.id
|
|
4401
|
+
AND def_m.role = 1
|
|
4402
|
+
AND d2.relative_path IN (${placeholders})
|
|
4403
|
+
)
|
|
4404
|
+
)
|
|
2954
4405
|
AND m.role != 1
|
|
4406
|
+
AND ${db.localSymbolPredicate}
|
|
4407
|
+
${db.pathExclusionsFor("d1")}
|
|
2955
4408
|
ORDER BY d1.relative_path`,
|
|
2956
4409
|
...matchedPaths,
|
|
4410
|
+
...matchedPaths,
|
|
2957
4411
|
...matchedPaths
|
|
2958
4412
|
);
|
|
2959
|
-
|
|
4413
|
+
const exposedDefinitions = matchedPaths.flatMap(
|
|
4414
|
+
(relativePath) => getDefinitionsForFile(db, relativePath).filter((definition) => isCallableSymbol2(definition.symbol)).map((definition) => ({
|
|
4415
|
+
relative_path: relativePath,
|
|
4416
|
+
symbol: definition.symbol
|
|
4417
|
+
}))
|
|
4418
|
+
);
|
|
4419
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4420
|
+
return [...rows, ...exposedDefinitions].filter((r) => !db.isIgnored(r.relative_path)).filter((r) => {
|
|
4421
|
+
const key = `${r.relative_path}|${r.symbol}`;
|
|
4422
|
+
if (seen.has(key)) return false;
|
|
4423
|
+
seen.add(key);
|
|
4424
|
+
return true;
|
|
4425
|
+
}).map((r) => ({
|
|
2960
4426
|
consumer: r.relative_path,
|
|
2961
4427
|
symbol: r.symbol,
|
|
2962
4428
|
shortName: shortenSymbol(r.symbol)
|
|
2963
4429
|
}));
|
|
2964
4430
|
}
|
|
4431
|
+
function isCallableSymbol2(rawSymbol) {
|
|
4432
|
+
return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
|
|
4433
|
+
}
|
|
2965
4434
|
|
|
2966
4435
|
// src/entry-surfaces.ts
|
|
2967
4436
|
var liveBarrelCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2979,11 +4448,11 @@ function isWorkerEntrySurface(path2) {
|
|
|
2979
4448
|
function isStructuralEntrySurface(path2) {
|
|
2980
4449
|
const normalized = normalizePath2(path2);
|
|
2981
4450
|
const segments = normalized.split("/");
|
|
2982
|
-
const
|
|
2983
|
-
if (
|
|
4451
|
+
const basename5 = segments[segments.length - 1] ?? normalized;
|
|
4452
|
+
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
4453
|
return true;
|
|
2985
4454
|
}
|
|
2986
|
-
if (
|
|
4455
|
+
if (basename5 === "index.ts" || basename5 === "index.js") {
|
|
2987
4456
|
return segments.length <= 2;
|
|
2988
4457
|
}
|
|
2989
4458
|
return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
|
|
@@ -3046,62 +4515,49 @@ function dead(db, opts = {}) {
|
|
|
3046
4515
|
skipBarrels = false,
|
|
3047
4516
|
includeMembers = false
|
|
3048
4517
|
} = 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
|
|
4518
|
+
const inactiveBarrelPaths = skipBarrels ? new Set(getInactiveBarrelPaths(db)) : /* @__PURE__ */ new Set();
|
|
4519
|
+
const referenceRows = db.all(
|
|
4520
|
+
`SELECT
|
|
4521
|
+
m.symbol_id,
|
|
3073
4522
|
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
|
-
|
|
4523
|
+
COUNT(*) AS ref_count
|
|
4524
|
+
FROM mentions m
|
|
4525
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
4526
|
+
JOIN documents d ON c.document_id = d.id
|
|
4527
|
+
WHERE m.role != 1
|
|
4528
|
+
${db.pathExclusionsFor("d")}
|
|
4529
|
+
GROUP BY m.symbol_id, d.relative_path`
|
|
4530
|
+
);
|
|
4531
|
+
const referencesBySymbol = /* @__PURE__ */ new Map();
|
|
4532
|
+
for (const row of referenceRows) {
|
|
4533
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
4534
|
+
if (inactiveBarrelPaths.has(row.relative_path)) continue;
|
|
4535
|
+
let refsForSymbol = referencesBySymbol.get(row.symbol_id);
|
|
4536
|
+
if (!refsForSymbol) {
|
|
4537
|
+
refsForSymbol = /* @__PURE__ */ new Map();
|
|
4538
|
+
referencesBySymbol.set(row.symbol_id, refsForSymbol);
|
|
4539
|
+
}
|
|
4540
|
+
refsForSymbol.set(row.relative_path, row.ref_count);
|
|
4541
|
+
}
|
|
4542
|
+
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);
|
|
4543
|
+
const rows = definitions.map((definition) => {
|
|
4544
|
+
const refMap = referencesBySymbol.get(definition.symbolId) ?? /* @__PURE__ */ new Map();
|
|
4545
|
+
const sameFileRefs = refMap.get(definition.relativePath) ?? 0;
|
|
4546
|
+
let crossFileRefs = 0;
|
|
4547
|
+
for (const [relativePath, count] of refMap) {
|
|
4548
|
+
if (relativePath === definition.relativePath) continue;
|
|
4549
|
+
crossFileRefs += count;
|
|
4550
|
+
}
|
|
4551
|
+
return {
|
|
4552
|
+
relative_path: definition.relativePath,
|
|
4553
|
+
start_line: definition.startLine,
|
|
4554
|
+
end_line: definition.endLine,
|
|
4555
|
+
loc: definition.endLine - definition.startLine + 1,
|
|
4556
|
+
symbol: definition.symbol,
|
|
4557
|
+
same_file_refs: sameFileRefs,
|
|
4558
|
+
cross_file_refs: crossFileRefs
|
|
4559
|
+
};
|
|
4560
|
+
}).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
4561
|
let deadCodeCount = 0;
|
|
3106
4562
|
let fileInternalCount = 0;
|
|
3107
4563
|
let totalLoc = 0;
|
|
@@ -3129,6 +4585,17 @@ function dead(db, opts = {}) {
|
|
|
3129
4585
|
totalLoc
|
|
3130
4586
|
};
|
|
3131
4587
|
}
|
|
4588
|
+
function passesTestFileFilter(relativePath) {
|
|
4589
|
+
const patterns = [.../* @__PURE__ */ new Set([...TEST_FILE_PATTERNS, ...TEST_SUPPORT_PATH_PATTERNS])];
|
|
4590
|
+
return patterns.every((pattern) => !likeMatches(relativePath, pattern));
|
|
4591
|
+
}
|
|
4592
|
+
function likeMatches(value, pattern) {
|
|
4593
|
+
const regex = new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".")}$`);
|
|
4594
|
+
return regex.test(value);
|
|
4595
|
+
}
|
|
4596
|
+
function looksValueLikeDefinition(rawSymbol) {
|
|
4597
|
+
return isFunctionLikeSymbol(rawSymbol) || rawSymbol.endsWith("().") || rawSymbol.endsWith(".");
|
|
4598
|
+
}
|
|
3132
4599
|
|
|
3133
4600
|
// src/queries/hotspots.ts
|
|
3134
4601
|
function hotspots(db, opts = {}) {
|
|
@@ -3155,13 +4622,26 @@ function hotspots(db, opts = {}) {
|
|
|
3155
4622
|
LIMIT ?`,
|
|
3156
4623
|
limit
|
|
3157
4624
|
);
|
|
3158
|
-
|
|
4625
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
3159
4626
|
symbol: r.symbol,
|
|
3160
4627
|
shortName: shortenSymbol(r.symbol),
|
|
3161
4628
|
refCount: r.ref_count,
|
|
3162
4629
|
fileCount: r.file_count,
|
|
3163
4630
|
definedIn: r.defined_in
|
|
3164
4631
|
}));
|
|
4632
|
+
if (indexedResults.length > 0) {
|
|
4633
|
+
return indexedResults;
|
|
4634
|
+
}
|
|
4635
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
|
|
4636
|
+
const callerRows = getCallerRowsForSymbol(db, definition, { limit: 500 });
|
|
4637
|
+
return {
|
|
4638
|
+
symbol: definition.symbol,
|
|
4639
|
+
shortName: shortenSymbol(definition.symbol),
|
|
4640
|
+
refCount: callerRows.length,
|
|
4641
|
+
fileCount: new Set(callerRows.map((row) => row.file)).size,
|
|
4642
|
+
definedIn: definition.relativePath
|
|
4643
|
+
};
|
|
4644
|
+
}).filter((row) => row.refCount > 0).sort((left, right) => right.refCount - left.refCount || right.fileCount - left.fileCount).slice(0, limit);
|
|
3165
4645
|
}
|
|
3166
4646
|
|
|
3167
4647
|
// src/queries/imports.ts
|
|
@@ -3234,6 +4714,14 @@ function importedBy(db, symbolPattern) {
|
|
|
3234
4714
|
if (targetFile && normalizePath3(entry.sourcePath) !== normalizePath3(targetFile)) {
|
|
3235
4715
|
continue;
|
|
3236
4716
|
}
|
|
4717
|
+
if (entry.kind === "side-effect") {
|
|
4718
|
+
importers.add(row.relative_path);
|
|
4719
|
+
continue;
|
|
4720
|
+
}
|
|
4721
|
+
if (targetFile && isCLikeImporter(row.relative_path)) {
|
|
4722
|
+
importers.add(row.relative_path);
|
|
4723
|
+
continue;
|
|
4724
|
+
}
|
|
3237
4725
|
if (targetIsModule) {
|
|
3238
4726
|
importers.add(row.relative_path);
|
|
3239
4727
|
continue;
|
|
@@ -3307,6 +4795,9 @@ function renderImportSymbol(importedName, localName, kind) {
|
|
|
3307
4795
|
function normalizePath3(path2) {
|
|
3308
4796
|
return path2.replace(/\\/g, "/");
|
|
3309
4797
|
}
|
|
4798
|
+
function isCLikeImporter(relativePath) {
|
|
4799
|
+
return /\.(?:c|h|cc|cpp|cxx|hpp|hh|hxx)$/i.test(relativePath);
|
|
4800
|
+
}
|
|
3310
4801
|
|
|
3311
4802
|
// src/queries/outline.ts
|
|
3312
4803
|
function outline(db, filePattern) {
|
|
@@ -3411,10 +4902,22 @@ function fanOut(db, filePattern) {
|
|
|
3411
4902
|
ORDER BY symbol_count DESC`,
|
|
3412
4903
|
resolvedFile
|
|
3413
4904
|
);
|
|
3414
|
-
|
|
4905
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
3415
4906
|
name: r.relative_path,
|
|
3416
4907
|
count: r.symbol_count
|
|
3417
4908
|
}));
|
|
4909
|
+
if (indexedResults.length > 0) {
|
|
4910
|
+
return indexedResults;
|
|
4911
|
+
}
|
|
4912
|
+
const graph = buildFileDepGraph(db);
|
|
4913
|
+
const deps2 = graph.get(resolvedFile);
|
|
4914
|
+
if (!deps2 || deps2.size === 0) {
|
|
4915
|
+
return [];
|
|
4916
|
+
}
|
|
4917
|
+
return [{
|
|
4918
|
+
name: resolvedFile,
|
|
4919
|
+
count: deps2.size
|
|
4920
|
+
}];
|
|
3418
4921
|
}
|
|
3419
4922
|
function topFanIn(db, opts = {}) {
|
|
3420
4923
|
const { limit = 30, scope } = opts;
|
|
@@ -3633,7 +5136,7 @@ function bottlenecks(db, opts = {}) {
|
|
|
3633
5136
|
minFanOut,
|
|
3634
5137
|
limit
|
|
3635
5138
|
);
|
|
3636
|
-
|
|
5139
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
3637
5140
|
symbol: r.symbol,
|
|
3638
5141
|
shortName: shortenSymbol(r.symbol),
|
|
3639
5142
|
fanIn: r.fan_in,
|
|
@@ -3641,51 +5144,42 @@ function bottlenecks(db, opts = {}) {
|
|
|
3641
5144
|
score: r.fan_in * r.fan_out,
|
|
3642
5145
|
definedIn: r.defined_in
|
|
3643
5146
|
}));
|
|
5147
|
+
if (indexedResults.length > 0) {
|
|
5148
|
+
return indexedResults;
|
|
5149
|
+
}
|
|
5150
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
|
|
5151
|
+
const fanIn2 = new Set(
|
|
5152
|
+
getCallerRowsForSymbol(db, definition, { limit: 500 }).map((row) => row.file)
|
|
5153
|
+
).size;
|
|
5154
|
+
const fanOut2 = new Set(
|
|
5155
|
+
getCalleeRowsForSymbol(db, definition, { limit: 500 }).filter((row) => row.file !== definition.relativePath).map((row) => `${row.symbol}|${row.file}`)
|
|
5156
|
+
).size;
|
|
5157
|
+
return {
|
|
5158
|
+
symbol: definition.symbol,
|
|
5159
|
+
shortName: shortenSymbol(definition.symbol),
|
|
5160
|
+
fanIn: fanIn2,
|
|
5161
|
+
fanOut: fanOut2,
|
|
5162
|
+
score: fanIn2 * fanOut2,
|
|
5163
|
+
definedIn: definition.relativePath
|
|
5164
|
+
};
|
|
5165
|
+
}).filter((row) => row.fanIn >= minFanIn && row.fanOut >= minFanOut).sort((left, right) => right.score - left.score || right.fanIn - left.fanIn).slice(0, limit);
|
|
3644
5166
|
}
|
|
3645
5167
|
|
|
3646
5168
|
// src/queries/isolated.ts
|
|
3647
5169
|
function isolated(db, opts = {}) {
|
|
3648
5170
|
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
|
|
5171
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).filter((definition) => !isEntrySurface(db, definition.relativePath)).filter((definition) => definition.isFunctionLike).map((definition) => ({
|
|
5172
|
+
definition,
|
|
5173
|
+
loc: definition.endLine - definition.startLine + 1
|
|
5174
|
+
})).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(
|
|
5175
|
+
(left, right) => right.loc - left.loc || left.definition.relativePath.localeCompare(right.definition.relativePath) || left.definition.startLine - right.definition.startLine
|
|
5176
|
+
).map((entry) => ({
|
|
5177
|
+
symbol: entry.definition.symbol,
|
|
5178
|
+
shortName: shortenSymbol(entry.definition.symbol),
|
|
5179
|
+
relativePath: entry.definition.relativePath,
|
|
5180
|
+
startLine: entry.definition.startLine,
|
|
5181
|
+
endLine: entry.definition.endLine,
|
|
5182
|
+
loc: entry.loc
|
|
3689
5183
|
}));
|
|
3690
5184
|
}
|
|
3691
5185
|
|
|
@@ -3828,24 +5322,18 @@ function kindCounts(db, opts = {}) {
|
|
|
3828
5322
|
}));
|
|
3829
5323
|
}
|
|
3830
5324
|
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));
|
|
5325
|
+
return getAllDefinitions(db, { scope }).map(mapDefinitionToKindRow);
|
|
5326
|
+
}
|
|
5327
|
+
function mapDefinitionToKindRow(definition) {
|
|
5328
|
+
return {
|
|
5329
|
+
symbol: definition.symbol,
|
|
5330
|
+
kind: definition.kind,
|
|
5331
|
+
documentation: definition.documentation,
|
|
5332
|
+
enclosing_symbol: definition.enclosingSymbol,
|
|
5333
|
+
relative_path: definition.relativePath,
|
|
5334
|
+
start_line: definition.startLine,
|
|
5335
|
+
end_line: definition.endLine
|
|
5336
|
+
};
|
|
3849
5337
|
}
|
|
3850
5338
|
function resolveKindNumber(row) {
|
|
3851
5339
|
if (row.kind !== null && row.kind !== 0) {
|
|
@@ -4027,7 +5515,7 @@ function uniqueRows(rows) {
|
|
|
4027
5515
|
function similar(db, symbolPattern, opts = {}) {
|
|
4028
5516
|
const { minSimilarity = 0.4, limit = 20 } = opts;
|
|
4029
5517
|
const target = findCallees(db, symbolPattern);
|
|
4030
|
-
if (!target
|
|
5518
|
+
if (!target) return [];
|
|
4031
5519
|
if (!isFunctionLikeSymbol(target.symbol)) return [];
|
|
4032
5520
|
const candidates = getAllCalleeFingerprints(db, {
|
|
4033
5521
|
minCallees: 3,
|
|
@@ -4059,7 +5547,10 @@ function similar(db, symbolPattern, opts = {}) {
|
|
|
4059
5547
|
});
|
|
4060
5548
|
}
|
|
4061
5549
|
results.sort((a, b) => b.similarity - a.similarity);
|
|
4062
|
-
|
|
5550
|
+
if (results.length > 0) {
|
|
5551
|
+
return results.slice(0, limit);
|
|
5552
|
+
}
|
|
5553
|
+
return similarBySourceShape(db, symbolPattern, { minSimilarity, limit });
|
|
4063
5554
|
}
|
|
4064
5555
|
function similarAll(db, opts = {}) {
|
|
4065
5556
|
const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;
|
|
@@ -4206,10 +5697,146 @@ function difference(a, b) {
|
|
|
4206
5697
|
}
|
|
4207
5698
|
return result;
|
|
4208
5699
|
}
|
|
5700
|
+
function similarBySourceShape(db, symbolPattern, opts) {
|
|
5701
|
+
const target = findSourceFingerprint(db, symbolPattern);
|
|
5702
|
+
if (!target || target.tokens.size < 3) {
|
|
5703
|
+
return [];
|
|
5704
|
+
}
|
|
5705
|
+
const minSimilarity = opts.minSimilarity >= 0.5 ? opts.minSimilarity : 0.3;
|
|
5706
|
+
const results = [];
|
|
5707
|
+
for (const candidate of getAllSourceFingerprints(db)) {
|
|
5708
|
+
if (candidate.symbol === target.symbol || candidate.tokens.size < 3) continue;
|
|
5709
|
+
const shared = intersection(target.tokens, candidate.tokens);
|
|
5710
|
+
if (shared.size < 2) continue;
|
|
5711
|
+
const union = /* @__PURE__ */ new Set([...target.tokens, ...candidate.tokens]);
|
|
5712
|
+
const similarity = union.size > 0 ? shared.size / union.size : 0;
|
|
5713
|
+
if (similarity < minSimilarity) continue;
|
|
5714
|
+
results.push({
|
|
5715
|
+
symbolA: target.symbol,
|
|
5716
|
+
shortNameA: shortenSymbol(target.symbol),
|
|
5717
|
+
fileA: target.file,
|
|
5718
|
+
symbolB: candidate.symbol,
|
|
5719
|
+
shortNameB: shortenSymbol(candidate.symbol),
|
|
5720
|
+
fileB: candidate.file,
|
|
5721
|
+
similarity,
|
|
5722
|
+
sharedCallees: [...shared].sort(),
|
|
5723
|
+
uniqueToA: [...difference(target.tokens, candidate.tokens)].sort(),
|
|
5724
|
+
uniqueToB: [...difference(candidate.tokens, target.tokens)].sort()
|
|
5725
|
+
});
|
|
5726
|
+
}
|
|
5727
|
+
results.sort((a, b) => b.similarity - a.similarity || a.shortNameB.localeCompare(b.shortNameB));
|
|
5728
|
+
return results.slice(0, opts.limit);
|
|
5729
|
+
}
|
|
5730
|
+
function findSourceFingerprint(db, symbolPattern) {
|
|
5731
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
5732
|
+
if (!match || !isFunctionLikeSymbol(match.symbol)) {
|
|
5733
|
+
return null;
|
|
5734
|
+
}
|
|
5735
|
+
const snippet = definitionSnippet(db, match.relativePath, match.startLine, match.endLine, leafName(match.symbol));
|
|
5736
|
+
const tokens = sourceTokens(snippet, leafName(match.symbol));
|
|
5737
|
+
if (tokens.size === 0) {
|
|
5738
|
+
return null;
|
|
5739
|
+
}
|
|
5740
|
+
return {
|
|
5741
|
+
symbol: match.symbol,
|
|
5742
|
+
file: match.relativePath,
|
|
5743
|
+
tokens
|
|
5744
|
+
};
|
|
5745
|
+
}
|
|
5746
|
+
function getAllSourceFingerprints(db) {
|
|
5747
|
+
return getAllDefinitions(db).filter((definition) => definition.isFunctionLike).map((definition) => ({
|
|
5748
|
+
symbol: definition.symbol,
|
|
5749
|
+
file: definition.relativePath,
|
|
5750
|
+
tokens: sourceTokens(
|
|
5751
|
+
definitionSnippet(db, definition.relativePath, definition.startLine, definition.endLine, definition.leaf),
|
|
5752
|
+
definition.leaf
|
|
5753
|
+
)
|
|
5754
|
+
})).filter((fingerprint) => fingerprint.tokens.size > 0);
|
|
5755
|
+
}
|
|
5756
|
+
function definitionSnippet(db, relativePath, startLine, endLine, leaf) {
|
|
5757
|
+
const source = getSourceText(db, relativePath);
|
|
5758
|
+
if (!source) {
|
|
5759
|
+
return "";
|
|
5760
|
+
}
|
|
5761
|
+
const lines = source.split("\n");
|
|
5762
|
+
if (endLine >= startLine && endLine - startLine <= 12) {
|
|
5763
|
+
return lines.slice(startLine, endLine + 1).join("\n");
|
|
5764
|
+
}
|
|
5765
|
+
const markerPatterns = [
|
|
5766
|
+
new RegExp(`\\bdef\\s+${escapeRegex2(leaf)}\\b`),
|
|
5767
|
+
new RegExp(`\\bfun\\s+${escapeRegex2(leaf)}\\b`),
|
|
5768
|
+
new RegExp(`\\bfn\\s+${escapeRegex2(leaf)}\\b`),
|
|
5769
|
+
new RegExp(`\\bfunction\\s+${escapeRegex2(leaf)}\\b`),
|
|
5770
|
+
new RegExp(`\\b${escapeRegex2(leaf)}\\s*\\(`)
|
|
5771
|
+
];
|
|
5772
|
+
const definitionStart = lines.findIndex((line) => markerPatterns.some((pattern) => pattern.test(line)));
|
|
5773
|
+
if (definitionStart >= 0) {
|
|
5774
|
+
let definitionEnd = definitionStart;
|
|
5775
|
+
for (let index = definitionStart + 1; index < lines.length && index <= definitionStart + 8; index++) {
|
|
5776
|
+
const line = lines[index] ?? "";
|
|
5777
|
+
if (index > definitionStart && looksLikeDefinitionBoundary(line)) {
|
|
5778
|
+
break;
|
|
5779
|
+
}
|
|
5780
|
+
definitionEnd = index;
|
|
5781
|
+
if (line.trim() === "" && index > definitionStart + 1) {
|
|
5782
|
+
break;
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
return lines.slice(definitionStart, definitionEnd + 1).join("\n");
|
|
5786
|
+
}
|
|
5787
|
+
return lines.slice(startLine, Math.min(lines.length, startLine + 8)).join("\n");
|
|
5788
|
+
}
|
|
5789
|
+
function sourceTokens(snippet, leaf) {
|
|
5790
|
+
if (!snippet) {
|
|
5791
|
+
return /* @__PURE__ */ new Set();
|
|
5792
|
+
}
|
|
5793
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
5794
|
+
"public",
|
|
5795
|
+
"private",
|
|
5796
|
+
"protected",
|
|
5797
|
+
"final",
|
|
5798
|
+
"static",
|
|
5799
|
+
"class",
|
|
5800
|
+
"def",
|
|
5801
|
+
"fun",
|
|
5802
|
+
"fn",
|
|
5803
|
+
"function",
|
|
5804
|
+
"return",
|
|
5805
|
+
"string",
|
|
5806
|
+
"bool",
|
|
5807
|
+
"boolean",
|
|
5808
|
+
"void",
|
|
5809
|
+
"unit",
|
|
5810
|
+
"self",
|
|
5811
|
+
"this",
|
|
5812
|
+
"new",
|
|
5813
|
+
"const",
|
|
5814
|
+
"let",
|
|
5815
|
+
"var",
|
|
5816
|
+
"end",
|
|
5817
|
+
"pub"
|
|
5818
|
+
]);
|
|
5819
|
+
const normalizedLeafParts = splitIdentifier(leaf);
|
|
5820
|
+
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();
|
|
5821
|
+
const tokens = normalized.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 1).filter((token) => !stopWords.has(token)).filter((token) => !normalizedLeafParts.has(token));
|
|
5822
|
+
return new Set(tokens);
|
|
5823
|
+
}
|
|
5824
|
+
function splitIdentifier(value) {
|
|
5825
|
+
return new Set(
|
|
5826
|
+
value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9_]+|_/).map((part) => part.toLowerCase()).filter((part) => part.length > 1)
|
|
5827
|
+
);
|
|
5828
|
+
}
|
|
5829
|
+
function looksLikeDefinitionBoundary(line) {
|
|
5830
|
+
return /^\s*(?:def|fun|fn|function|class|trait|module|object|enum|interface|public|private|protected)\b/.test(line);
|
|
5831
|
+
}
|
|
5832
|
+
function escapeRegex2(value) {
|
|
5833
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5834
|
+
}
|
|
4209
5835
|
|
|
4210
5836
|
// src/queries/similar-files.ts
|
|
4211
5837
|
function similarFiles(db, opts = {}) {
|
|
4212
|
-
const { minSimilarity = 0.5, limit = 20, scope,
|
|
5838
|
+
const { minSimilarity = 0.5, limit = 20, scope, filePattern } = opts;
|
|
5839
|
+
const minDeps = opts.minDeps ?? (filePattern ? 1 : 3);
|
|
4213
5840
|
const profiles = buildFileProfiles(db, { scope, minDeps });
|
|
4214
5841
|
const results = [];
|
|
4215
5842
|
if (filePattern) {
|
|
@@ -4258,7 +5885,7 @@ function findUniversalDependencies(depMap) {
|
|
|
4258
5885
|
}
|
|
4259
5886
|
}
|
|
4260
5887
|
for (const [dep, count] of depCounts) {
|
|
4261
|
-
if (count / fileCount > 0.
|
|
5888
|
+
if (count >= 6 && count / fileCount > 0.8) {
|
|
4262
5889
|
universalDeps.add(dep);
|
|
4263
5890
|
}
|
|
4264
5891
|
}
|
|
@@ -4317,8 +5944,8 @@ function similarChains(db, opts = {}) {
|
|
|
4317
5944
|
tailFreq.set(chain[t], (tailFreq.get(chain[t]) ?? 0) + 1);
|
|
4318
5945
|
}
|
|
4319
5946
|
}
|
|
4320
|
-
const infraThreshold = rawChains.length * 0.
|
|
4321
|
-
const tailThreshold = rawChains.length * 0.
|
|
5947
|
+
const infraThreshold = rawChains.length * 0.9;
|
|
5948
|
+
const tailThreshold = rawChains.length * 0.8;
|
|
4322
5949
|
const infraNodes = /* @__PURE__ */ new Set();
|
|
4323
5950
|
for (const [node, freq] of nodeFreq) {
|
|
4324
5951
|
if (freq > infraThreshold) infraNodes.add(node);
|
|
@@ -4328,13 +5955,13 @@ function similarChains(db, opts = {}) {
|
|
|
4328
5955
|
}
|
|
4329
5956
|
const structuralNames = ["index.ts", "index.js", "cli.ts", "main.ts", "health.ts", "health.js"];
|
|
4330
5957
|
for (const node of nodeFreq.keys()) {
|
|
4331
|
-
const
|
|
4332
|
-
if (structuralNames.includes(
|
|
5958
|
+
const basename5 = node.split("/").pop() ?? "";
|
|
5959
|
+
if (structuralNames.includes(basename5)) infraNodes.add(node);
|
|
4333
5960
|
}
|
|
4334
5961
|
const filteredChains = [];
|
|
4335
5962
|
for (const chain of rawChains) {
|
|
4336
5963
|
const filtered = chain.filter((n) => !infraNodes.has(n));
|
|
4337
|
-
if (filtered.length >=
|
|
5964
|
+
if (filtered.length >= 2) {
|
|
4338
5965
|
filteredChains.push({ original: chain, filtered });
|
|
4339
5966
|
}
|
|
4340
5967
|
}
|
|
@@ -4499,31 +6126,13 @@ function isSubChain(sub, full) {
|
|
|
4499
6126
|
// src/queries/extract-candidates.ts
|
|
4500
6127
|
function extractCandidates(db, opts = {}) {
|
|
4501
6128
|
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
|
-
);
|
|
6129
|
+
const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc(definition) >= minLoc && isFunctionLikeSymbol(definition.symbol)).sort((left, right) => definitionLoc(right) - definitionLoc(left));
|
|
4516
6130
|
const results = [];
|
|
4517
6131
|
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
|
-
});
|
|
6132
|
+
if (db.isIgnored(sym.relativePath)) continue;
|
|
6133
|
+
const basename5 = sym.relativePath.split("/").pop() ?? "";
|
|
6134
|
+
if (basename5.includes("types")) continue;
|
|
6135
|
+
const calleeChunks = getCalleeRowsForSymbol(db, sym);
|
|
4527
6136
|
const calleeSet = new Set(calleeChunks.map((c) => c.symbol));
|
|
4528
6137
|
if (calleeSet.size < minCallees) continue;
|
|
4529
6138
|
const cooccurrence = /* @__PURE__ */ new Map();
|
|
@@ -4586,10 +6195,10 @@ function extractCandidates(db, opts = {}) {
|
|
|
4586
6195
|
results.push({
|
|
4587
6196
|
symbol: sym.symbol,
|
|
4588
6197
|
shortName: shortenSymbol(sym.symbol),
|
|
4589
|
-
relativePath: sym.
|
|
4590
|
-
startLine: sym.
|
|
4591
|
-
endLine: sym.
|
|
4592
|
-
loc: sym
|
|
6198
|
+
relativePath: sym.relativePath,
|
|
6199
|
+
startLine: sym.startLine,
|
|
6200
|
+
endLine: sym.endLine,
|
|
6201
|
+
loc: definitionLoc(sym),
|
|
4593
6202
|
totalCallees: calleeSet.size,
|
|
4594
6203
|
clusters: scoredClusters
|
|
4595
6204
|
});
|
|
@@ -4598,6 +6207,20 @@ function extractCandidates(db, opts = {}) {
|
|
|
4598
6207
|
results.sort((a, b) => b.clusters.length - a.clusters.length || b.loc - a.loc);
|
|
4599
6208
|
return results.slice(0, limit);
|
|
4600
6209
|
}
|
|
6210
|
+
function getScopedDefinitions(db, scope) {
|
|
6211
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6212
|
+
return db.all(
|
|
6213
|
+
`SELECT relative_path
|
|
6214
|
+
FROM documents
|
|
6215
|
+
WHERE 1 = 1
|
|
6216
|
+
${db.pathExclusionsFor("documents")}
|
|
6217
|
+
${scopeFilter}
|
|
6218
|
+
ORDER BY relative_path`
|
|
6219
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6220
|
+
}
|
|
6221
|
+
function definitionLoc(definition) {
|
|
6222
|
+
return definition.endLine - definition.startLine + 1;
|
|
6223
|
+
}
|
|
4601
6224
|
|
|
4602
6225
|
// src/queries/affected.ts
|
|
4603
6226
|
function affected(db, symbolPattern, opts = {}) {
|
|
@@ -4638,73 +6261,39 @@ function affected(db, symbolPattern, opts = {}) {
|
|
|
4638
6261
|
return results;
|
|
4639
6262
|
}
|
|
4640
6263
|
function getDirectAffectedRows(db, target, scope) {
|
|
4641
|
-
const
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
return rows2;
|
|
4675
|
-
}
|
|
4676
|
-
const rows = db.all(
|
|
4677
|
-
`SELECT DISTINCT
|
|
4678
|
-
enc_gs.id AS symbol_id,
|
|
4679
|
-
enc_gs.symbol AS symbol,
|
|
4680
|
-
enc_d.relative_path AS relative_path
|
|
4681
|
-
FROM mentions m
|
|
4682
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
4683
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
4684
|
-
JOIN defn_enclosing_ranges enc_der
|
|
4685
|
-
ON enc_der.document_id = ref_d.id
|
|
4686
|
-
AND c.start_line >= enc_der.start_line
|
|
4687
|
-
AND c.end_line <= enc_der.end_line
|
|
4688
|
-
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
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
|
-
}));
|
|
6264
|
+
const callerRows = getCallerRowsForSymbol(db, target, { limit: 500 }).filter((row) => !db.isIgnored(row.file)).filter((row) => !scope || row.file.includes(scope));
|
|
6265
|
+
const results = [];
|
|
6266
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6267
|
+
for (const row of callerRows) {
|
|
6268
|
+
const match = findExactSymbolMatch(db, row.symbol);
|
|
6269
|
+
if (!match) {
|
|
6270
|
+
const key2 = `${row.file}|${row.symbol}`;
|
|
6271
|
+
if (seen.has(key2)) continue;
|
|
6272
|
+
seen.add(key2);
|
|
6273
|
+
results.push({
|
|
6274
|
+
symbolId: null,
|
|
6275
|
+
symbol: row.symbol,
|
|
6276
|
+
shortName: shortenSymbol(row.symbol),
|
|
6277
|
+
file: row.file,
|
|
6278
|
+
symbolMatch: null
|
|
6279
|
+
});
|
|
6280
|
+
continue;
|
|
6281
|
+
}
|
|
6282
|
+
if (match.symbolId === target.symbolId || db.isIgnored(match.relativePath)) {
|
|
6283
|
+
continue;
|
|
6284
|
+
}
|
|
6285
|
+
const key = `${match.symbolId}|${match.relativePath}`;
|
|
6286
|
+
if (seen.has(key)) continue;
|
|
6287
|
+
seen.add(key);
|
|
6288
|
+
results.push({
|
|
6289
|
+
symbolId: match.symbolId,
|
|
6290
|
+
symbol: match.symbol,
|
|
6291
|
+
shortName: shortenSymbol(match.symbol),
|
|
6292
|
+
file: match.relativePath,
|
|
6293
|
+
symbolMatch: match
|
|
6294
|
+
});
|
|
6295
|
+
}
|
|
6296
|
+
return results;
|
|
4708
6297
|
}
|
|
4709
6298
|
|
|
4710
6299
|
// src/queries/change-surface.ts
|
|
@@ -5054,140 +6643,129 @@ function isLikelyTypeOnlyDep(dep) {
|
|
|
5054
6643
|
function shouldSkipDriftFile(filePath) {
|
|
5055
6644
|
return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
|
|
5056
6645
|
}
|
|
5057
|
-
function isStructuralRole(
|
|
5058
|
-
if (
|
|
5059
|
-
if (
|
|
5060
|
-
if (
|
|
5061
|
-
if (
|
|
6646
|
+
function isStructuralRole(basename5) {
|
|
6647
|
+
if (basename5 === "index.ts" || basename5 === "index.js") return true;
|
|
6648
|
+
if (basename5 === "cli.ts" || basename5 === "main.ts" || basename5 === "main.rs") return true;
|
|
6649
|
+
if (basename5.includes("worker.") || basename5.includes("postinstall.")) return true;
|
|
6650
|
+
if (basename5 === "health.ts" || basename5 === "health.js") return true;
|
|
5062
6651
|
return false;
|
|
5063
6652
|
}
|
|
5064
6653
|
function isTestLikePath(filePath) {
|
|
5065
6654
|
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(
|
|
6655
|
+
const basename5 = path.basename(normalized);
|
|
6656
|
+
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
6657
|
}
|
|
5069
6658
|
|
|
5070
6659
|
// src/queries/wrapper-candidates.ts
|
|
6660
|
+
import { basename as basename4, extname as extname3 } from "path";
|
|
5071
6661
|
function wrapperCandidates(db, opts) {
|
|
5072
6662
|
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
|
-
}
|
|
6663
|
+
const reverseFanIn = buildReverseFileFanIn(buildFileDepGraph(db, scope));
|
|
6664
|
+
const symbols2 = getScopedDefinitions2(db, scope).filter((definition) => definitionLoc2(definition) <= maxLoc && definitionLoc2(definition) >= 2);
|
|
6665
|
+
const results = [];
|
|
6666
|
+
for (const symbol of symbols2) {
|
|
6667
|
+
if (db.isIgnored(symbol.relativePath) || !isFunctionLikeSymbol(symbol.symbol)) continue;
|
|
6668
|
+
const symbolStem = basename4(symbol.relativePath, extname3(symbol.relativePath));
|
|
6669
|
+
const callerRows = dedupeRows(
|
|
6670
|
+
getCallerRowsForSymbol(db, symbol, { limit: 200 }).filter((row) => row.file !== symbol.relativePath)
|
|
6671
|
+
).filter((row) => basename4(row.file, extname3(row.file)) !== symbolStem);
|
|
6672
|
+
if (callerRows.length !== 1) continue;
|
|
6673
|
+
const caller = callerRows[0];
|
|
6674
|
+
const callerDefinition = getDefinitionsForFile(db, caller.file).find((definition) => definition.symbol === caller.symbol);
|
|
6675
|
+
const useDefinitionFanIn = callerDefinition?.isFunctionLike ?? false;
|
|
6676
|
+
let callerFanIn;
|
|
6677
|
+
if (useDefinitionFanIn && callerDefinition) {
|
|
6678
|
+
callerFanIn = new Set(
|
|
6679
|
+
getCallerRowsForSymbol(db, callerDefinition, { limit: 500 }).filter((row) => row.file !== callerDefinition.relativePath).map((row) => row.file)
|
|
6680
|
+
).size;
|
|
6681
|
+
} else {
|
|
6682
|
+
callerFanIn = fallbackCallerFanIn(db, reverseFanIn, caller.file);
|
|
6683
|
+
}
|
|
6684
|
+
if (callerFanIn <= 3) continue;
|
|
6685
|
+
results.push({
|
|
6686
|
+
symbol: symbol.symbol,
|
|
6687
|
+
shortName: shortenSymbol(symbol.symbol),
|
|
6688
|
+
file: symbol.relativePath,
|
|
6689
|
+
startLine: symbol.startLine,
|
|
6690
|
+
endLine: symbol.endLine,
|
|
6691
|
+
loc: definitionLoc2(symbol),
|
|
6692
|
+
singleCaller: caller.symbol,
|
|
6693
|
+
singleCallerShort: useDefinitionFanIn ? shortenSymbol(caller.symbol) : basename4(caller.file),
|
|
6694
|
+
callerFanIn
|
|
6695
|
+
});
|
|
6696
|
+
}
|
|
6697
|
+
results.sort((left, right) => right.callerFanIn - left.callerFanIn || right.loc - left.loc);
|
|
6698
|
+
return results.slice(0, limit);
|
|
6699
|
+
}
|
|
6700
|
+
function definitionLoc2(definition) {
|
|
6701
|
+
return definition.endLine - definition.startLine + 1;
|
|
6702
|
+
}
|
|
6703
|
+
function getScopedDefinitions2(db, scope) {
|
|
6704
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6705
|
+
return db.all(
|
|
6706
|
+
`SELECT relative_path
|
|
6707
|
+
FROM documents
|
|
6708
|
+
WHERE 1 = 1
|
|
6709
|
+
${db.pathExclusionsFor("documents")}
|
|
6710
|
+
${scopeFilter}
|
|
6711
|
+
ORDER BY relative_path`
|
|
6712
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6713
|
+
}
|
|
6714
|
+
function dedupeRows(rows) {
|
|
6715
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6716
|
+
const unique = [];
|
|
6717
|
+
for (const row of rows) {
|
|
6718
|
+
const key = `${row.symbol}|${row.file}`;
|
|
6719
|
+
if (seen.has(key)) continue;
|
|
6720
|
+
seen.add(key);
|
|
6721
|
+
unique.push(row);
|
|
6722
|
+
}
|
|
6723
|
+
return unique;
|
|
6724
|
+
}
|
|
6725
|
+
function buildReverseFileFanIn(graph) {
|
|
6726
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
6727
|
+
for (const [fromFile, deps2] of graph) {
|
|
6728
|
+
if (!reverse.has(fromFile)) {
|
|
6729
|
+
reverse.set(fromFile, reverse.get(fromFile) ?? 0);
|
|
6730
|
+
}
|
|
6731
|
+
for (const dep of deps2) {
|
|
6732
|
+
reverse.set(dep, (reverse.get(dep) ?? 0) + 1);
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
return reverse;
|
|
6736
|
+
}
|
|
6737
|
+
function fallbackCallerFanIn(db, reverseFanIn, callerFile) {
|
|
6738
|
+
const functionLikeFanIn = getDefinitionsForFile(db, callerFile).filter((definition) => definition.isFunctionLike).map((definition) => new Set(
|
|
6739
|
+
getCallerRowsForSymbol(db, definition, { limit: 500 }).filter((row) => row.file !== definition.relativePath).map((row) => row.file)
|
|
6740
|
+
).size).sort((left, right) => right - left)[0] ?? 0;
|
|
6741
|
+
if (functionLikeFanIn > 0) {
|
|
6742
|
+
return functionLikeFanIn;
|
|
6743
|
+
}
|
|
6744
|
+
const direct = reverseFanIn.get(callerFile) ?? 0;
|
|
6745
|
+
if (direct > 0) {
|
|
6746
|
+
return direct;
|
|
6747
|
+
}
|
|
6748
|
+
const stem = basename4(callerFile, extname3(callerFile));
|
|
6749
|
+
let best = 0;
|
|
6750
|
+
for (const [file, fanIn2] of reverseFanIn) {
|
|
6751
|
+
if (file === callerFile) continue;
|
|
6752
|
+
if (basename4(file, extname3(file)) !== stem) continue;
|
|
6753
|
+
if (fanIn2 > best) {
|
|
6754
|
+
best = fanIn2;
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
return best;
|
|
5154
6758
|
}
|
|
5155
6759
|
|
|
5156
6760
|
// src/queries/passthrough-candidates.ts
|
|
5157
6761
|
function passthroughCandidates(db, opts) {
|
|
5158
6762
|
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
|
-
);
|
|
6763
|
+
const symbols2 = getScopedDefinitions3(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
|
|
5182
6764
|
const results = [];
|
|
5183
6765
|
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
|
-
});
|
|
6766
|
+
if (db.isIgnored(sym.relativePath) || !isFunctionLikeSymbol(sym.symbol)) continue;
|
|
6767
|
+
const rawCallees = getCalleeRowsForSymbol(db, sym);
|
|
6768
|
+
const callees = rawCallees.some((callee2) => isFunctionLikeSymbol(callee2.symbol)) ? rawCallees.filter((callee2) => isFunctionLikeSymbol(callee2.symbol)) : rawCallees;
|
|
5191
6769
|
const uniqueCallees = /* @__PURE__ */ new Map();
|
|
5192
6770
|
for (const c of callees) {
|
|
5193
6771
|
if (!uniqueCallees.has(c.symbol)) {
|
|
@@ -5199,10 +6777,10 @@ function passthroughCandidates(db, opts) {
|
|
|
5199
6777
|
results.push({
|
|
5200
6778
|
symbol: sym.symbol,
|
|
5201
6779
|
shortName: shortenSymbol(sym.symbol),
|
|
5202
|
-
file: sym.
|
|
5203
|
-
startLine: sym.
|
|
5204
|
-
endLine: sym.
|
|
5205
|
-
loc: sym
|
|
6780
|
+
file: sym.relativePath,
|
|
6781
|
+
startLine: sym.startLine,
|
|
6782
|
+
endLine: sym.endLine,
|
|
6783
|
+
loc: definitionLoc3(sym),
|
|
5206
6784
|
forwardsTo: callee.symbol,
|
|
5207
6785
|
forwardsToShort: shortenSymbol(callee.symbol),
|
|
5208
6786
|
forwardsToFile: callee.file
|
|
@@ -5211,42 +6789,32 @@ function passthroughCandidates(db, opts) {
|
|
|
5211
6789
|
results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
|
|
5212
6790
|
return results.slice(0, limit);
|
|
5213
6791
|
}
|
|
6792
|
+
function getScopedDefinitions3(db, scope) {
|
|
6793
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6794
|
+
return db.all(
|
|
6795
|
+
`SELECT relative_path
|
|
6796
|
+
FROM documents
|
|
6797
|
+
WHERE 1 = 1
|
|
6798
|
+
${db.pathExclusionsFor("documents")}
|
|
6799
|
+
${scopeFilter}
|
|
6800
|
+
ORDER BY relative_path`
|
|
6801
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6802
|
+
}
|
|
6803
|
+
function definitionLoc3(definition) {
|
|
6804
|
+
return definition.endLine - definition.startLine + 1;
|
|
6805
|
+
}
|
|
5214
6806
|
|
|
5215
6807
|
// src/queries/stale-abstractions.ts
|
|
5216
6808
|
function staleAbstractions(db, opts) {
|
|
5217
6809
|
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
|
-
);
|
|
6810
|
+
const rows = getScopedDefinitions4(db, scope).filter((definition) => definition.isTypeLike && definitionLoc4(definition) >= minLoc).map((definition) => ({
|
|
6811
|
+
symbol: definition.symbol,
|
|
6812
|
+
file: definition.relativePath,
|
|
6813
|
+
start_line: definition.startLine,
|
|
6814
|
+
end_line: definition.endLine,
|
|
6815
|
+
loc: definitionLoc4(definition),
|
|
6816
|
+
consumers: countCrossFileConsumers(db, definition)
|
|
6817
|
+
})).filter((row) => row.consumers <= 1).sort((left, right) => right.loc - left.loc || left.file.localeCompare(right.file) || left.start_line - right.start_line);
|
|
5250
6818
|
const filesWithFunctions = getFilesWithFunctions(db, scope);
|
|
5251
6819
|
return rows.filter((r) => !db.isIgnored(r.file)).filter((r) => isTrueStaleAbstraction(r, filesWithFunctions)).map((r) => ({
|
|
5252
6820
|
symbol: r.symbol,
|
|
@@ -5259,22 +6827,11 @@ function staleAbstractions(db, opts) {
|
|
|
5259
6827
|
})).slice(0, limit);
|
|
5260
6828
|
}
|
|
5261
6829
|
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
|
-
);
|
|
6830
|
+
return new Set(getScopedDefinitions4(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
|
|
5274
6831
|
}
|
|
5275
6832
|
function isTrueStaleAbstraction(row, filesWithFunctions) {
|
|
5276
|
-
const
|
|
5277
|
-
const isTypeFile =
|
|
6833
|
+
const basename5 = row.file.split("/").pop() ?? "";
|
|
6834
|
+
const isTypeFile = basename5.includes("types") || row.file.includes("/types/");
|
|
5278
6835
|
if (isTypeFile && row.consumers > 0) {
|
|
5279
6836
|
return false;
|
|
5280
6837
|
}
|
|
@@ -5283,6 +6840,33 @@ function isTrueStaleAbstraction(row, filesWithFunctions) {
|
|
|
5283
6840
|
}
|
|
5284
6841
|
return true;
|
|
5285
6842
|
}
|
|
6843
|
+
function getScopedDefinitions4(db, scope) {
|
|
6844
|
+
const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
|
|
6845
|
+
return db.all(
|
|
6846
|
+
`SELECT relative_path
|
|
6847
|
+
FROM documents
|
|
6848
|
+
WHERE 1 = 1
|
|
6849
|
+
${db.pathExclusionsFor("documents")}
|
|
6850
|
+
${scopeFilter}
|
|
6851
|
+
ORDER BY relative_path`
|
|
6852
|
+
).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
|
|
6853
|
+
}
|
|
6854
|
+
function countCrossFileConsumers(db, definition) {
|
|
6855
|
+
const callers = db.all(
|
|
6856
|
+
`SELECT DISTINCT d.relative_path
|
|
6857
|
+
FROM mentions m
|
|
6858
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
6859
|
+
JOIN documents d ON c.document_id = d.id
|
|
6860
|
+
WHERE m.symbol_id = ?
|
|
6861
|
+
AND m.role != 1
|
|
6862
|
+
${db.pathExclusionsFor("d")}`,
|
|
6863
|
+
definition.symbolId
|
|
6864
|
+
).map((row) => row.relative_path).filter((relativePath) => relativePath !== definition.relativePath && !db.isIgnored(relativePath));
|
|
6865
|
+
return new Set(callers).size;
|
|
6866
|
+
}
|
|
6867
|
+
function definitionLoc4(definition) {
|
|
6868
|
+
return definition.endLine - definition.startLine + 1;
|
|
6869
|
+
}
|
|
5286
6870
|
|
|
5287
6871
|
// src/queries/complexity-hotspots.ts
|
|
5288
6872
|
function complexityHotspots(db, opts) {
|
|
@@ -5359,7 +6943,7 @@ function complexityHotspots(db, opts) {
|
|
|
5359
6943
|
minLoc,
|
|
5360
6944
|
limit
|
|
5361
6945
|
);
|
|
5362
|
-
|
|
6946
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.file)).map((r) => ({
|
|
5363
6947
|
symbol: r.symbol,
|
|
5364
6948
|
shortName: shortenSymbol(r.symbol),
|
|
5365
6949
|
file: r.file,
|
|
@@ -5373,6 +6957,31 @@ function complexityHotspots(db, opts) {
|
|
|
5373
6957
|
r.loc / 50 * (r.fan_in / 5) * Math.max(r.fan_out / 5, 1) * 100
|
|
5374
6958
|
) / 100
|
|
5375
6959
|
}));
|
|
6960
|
+
if (indexedResults.length > 0) {
|
|
6961
|
+
return indexedResults;
|
|
6962
|
+
}
|
|
6963
|
+
return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
|
|
6964
|
+
const loc = definition.endLine - definition.startLine + 1;
|
|
6965
|
+
const callerRows = getCallerRowsForSymbol(db, definition, { limit: 500 });
|
|
6966
|
+
const calleeRows = getCalleeRowsForSymbol(db, definition, { limit: 500 });
|
|
6967
|
+
const fanIn2 = new Set(callerRows.map((row) => row.file)).size;
|
|
6968
|
+
const fanOut2 = new Set(
|
|
6969
|
+
calleeRows.filter((row) => row.file !== definition.relativePath).map((row) => `${row.symbol}|${row.file}`)
|
|
6970
|
+
).size;
|
|
6971
|
+
const calleeCount = new Set(calleeRows.map((row) => `${row.symbol}|${row.file}`)).size;
|
|
6972
|
+
return {
|
|
6973
|
+
symbol: definition.symbol,
|
|
6974
|
+
shortName: shortenSymbol(definition.symbol),
|
|
6975
|
+
file: definition.relativePath,
|
|
6976
|
+
startLine: definition.startLine,
|
|
6977
|
+
endLine: definition.endLine,
|
|
6978
|
+
loc,
|
|
6979
|
+
fanIn: fanIn2,
|
|
6980
|
+
fanOut: fanOut2,
|
|
6981
|
+
calleeCount,
|
|
6982
|
+
score: Math.round(loc / 50 * (fanIn2 / 5) * Math.max(fanOut2 / 5, 1) * 100) / 100
|
|
6983
|
+
};
|
|
6984
|
+
}).filter((row) => row.loc >= minLoc).sort((left, right) => right.score - left.score || right.loc - left.loc).slice(0, limit);
|
|
5376
6985
|
}
|
|
5377
6986
|
|
|
5378
6987
|
// src/queries/health.ts
|
|
@@ -5621,7 +7230,7 @@ function convergence(db, symbolPatternA, symbolPatternB) {
|
|
|
5621
7230
|
|
|
5622
7231
|
// src/queries/code.ts
|
|
5623
7232
|
import { readFileSync as readFileSync5 } from "fs";
|
|
5624
|
-
import { join as
|
|
7233
|
+
import { join as join9 } from "path";
|
|
5625
7234
|
function code(db, symbolPattern, opts = {}) {
|
|
5626
7235
|
const { context = 0 } = opts;
|
|
5627
7236
|
const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
|
|
@@ -5634,7 +7243,7 @@ function code(db, symbolPattern, opts = {}) {
|
|
|
5634
7243
|
`SELECT language FROM documents WHERE relative_path = ?`,
|
|
5635
7244
|
match.relativePath
|
|
5636
7245
|
);
|
|
5637
|
-
const filePath =
|
|
7246
|
+
const filePath = join9(db.config.projectRoot, match.relativePath);
|
|
5638
7247
|
let fileContent;
|
|
5639
7248
|
try {
|
|
5640
7249
|
fileContent = readFileSync5(filePath, "utf-8");
|
|
@@ -5664,7 +7273,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
5664
7273
|
resolvedPath
|
|
5665
7274
|
);
|
|
5666
7275
|
if (!doc) return null;
|
|
5667
|
-
const fullPath =
|
|
7276
|
+
const fullPath = join9(db.config.projectRoot, doc.relative_path);
|
|
5668
7277
|
let fileContent;
|
|
5669
7278
|
try {
|
|
5670
7279
|
fileContent = readFileSync5(fullPath, "utf-8");
|
|
@@ -5688,7 +7297,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
5688
7297
|
|
|
5689
7298
|
// src/queries/complexity.ts
|
|
5690
7299
|
import { readFileSync as readFileSync6 } from "fs";
|
|
5691
|
-
import { join as
|
|
7300
|
+
import { join as join10 } from "path";
|
|
5692
7301
|
function complexity(db, symbolPattern) {
|
|
5693
7302
|
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
5694
7303
|
if (!match) return null;
|
|
@@ -5697,7 +7306,7 @@ function complexity(db, symbolPattern) {
|
|
|
5697
7306
|
match.relativePath
|
|
5698
7307
|
);
|
|
5699
7308
|
const language = doc?.language ?? "unknown";
|
|
5700
|
-
const filePath =
|
|
7309
|
+
const filePath = join10(db.config.projectRoot, match.relativePath);
|
|
5701
7310
|
let source = "";
|
|
5702
7311
|
try {
|
|
5703
7312
|
const lines = readFileSync6(filePath, "utf-8").split("\n");
|
|
@@ -6037,46 +7646,100 @@ function redundantReexports(db, opts = {}) {
|
|
|
6037
7646
|
results.sort(
|
|
6038
7647
|
(a, b) => b.directConsumers - a.directConsumers || a.barrelFile.localeCompare(b.barrelFile) || a.shortName.localeCompare(b.shortName)
|
|
6039
7648
|
);
|
|
6040
|
-
|
|
7649
|
+
const withDartFallback = dedupeReexports([
|
|
7650
|
+
...results,
|
|
7651
|
+
...findSourceRedundantReexports(db, scope)
|
|
7652
|
+
]);
|
|
7653
|
+
withDartFallback.sort(
|
|
7654
|
+
(a, b) => b.directConsumers - a.directConsumers || a.barrelFile.localeCompare(b.barrelFile) || a.shortName.localeCompare(b.shortName)
|
|
7655
|
+
);
|
|
7656
|
+
return limit ? withDartFallback.slice(0, limit) : withDartFallback;
|
|
7657
|
+
}
|
|
7658
|
+
function findSourceRedundantReexports(db, scope) {
|
|
7659
|
+
const files2 = db.all(
|
|
7660
|
+
`SELECT relative_path
|
|
7661
|
+
FROM documents
|
|
7662
|
+
WHERE 1 = 1
|
|
7663
|
+
${scope ? "AND relative_path LIKE ?" : ""}
|
|
7664
|
+
${db.pathExclusionsFor("documents")}
|
|
7665
|
+
ORDER BY relative_path`,
|
|
7666
|
+
...scope ? [`%${scope}%`] : []
|
|
7667
|
+
);
|
|
7668
|
+
const candidates = files2.map((row) => row.relative_path).filter((relativePath) => !db.isIgnored(relativePath)).filter((relativePath) => getSourceExports(db, relativePath).length > 0);
|
|
7669
|
+
const results = [];
|
|
7670
|
+
for (const barrelPath of candidates) {
|
|
7671
|
+
const exports = getSourceExports(db, barrelPath).filter((entry) => entry.sourcePath && !db.isIgnored(entry.sourcePath));
|
|
7672
|
+
if (exports.length === 0) continue;
|
|
7673
|
+
const barrelConsumers = countDirectImporters(db, barrelPath, barrelPath);
|
|
7674
|
+
if (barrelConsumers > 0) continue;
|
|
7675
|
+
for (const exported of exports) {
|
|
7676
|
+
const sourcePath = exported.sourcePath;
|
|
7677
|
+
const representative = representativeExportSymbol(db, sourcePath);
|
|
7678
|
+
if (!representative) continue;
|
|
7679
|
+
results.push({
|
|
7680
|
+
barrelFile: barrelPath,
|
|
7681
|
+
symbol: representative.symbol,
|
|
7682
|
+
shortName: shortenSymbol(representative.symbol),
|
|
7683
|
+
originalFile: sourcePath,
|
|
7684
|
+
barrelConsumers: 0,
|
|
7685
|
+
directConsumers: countDirectImporters(db, sourcePath, barrelPath)
|
|
7686
|
+
});
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
return results;
|
|
7690
|
+
}
|
|
7691
|
+
function countDirectImporters(db, targetPath, excludedPath) {
|
|
7692
|
+
const files2 = db.all(
|
|
7693
|
+
`SELECT relative_path
|
|
7694
|
+
FROM documents
|
|
7695
|
+
WHERE 1 = 1
|
|
7696
|
+
${db.pathExclusionsFor("documents")}
|
|
7697
|
+
ORDER BY relative_path`
|
|
7698
|
+
);
|
|
7699
|
+
const importers = /* @__PURE__ */ new Set();
|
|
7700
|
+
for (const row of files2) {
|
|
7701
|
+
if (db.isIgnored(row.relative_path) || row.relative_path === excludedPath) continue;
|
|
7702
|
+
for (const imported of getSourceImports(db, row.relative_path)) {
|
|
7703
|
+
if (imported.sourcePath === targetPath) {
|
|
7704
|
+
importers.add(row.relative_path);
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
return importers.size;
|
|
7709
|
+
}
|
|
7710
|
+
function representativeExportSymbol(db, sourcePath) {
|
|
7711
|
+
const definitions = getDefinitionsForFile(db, sourcePath);
|
|
7712
|
+
return definitions.find((definition) => leafSuffix(definition.symbol) === "method") ?? definitions[0] ?? null;
|
|
7713
|
+
}
|
|
7714
|
+
function dedupeReexports(rows) {
|
|
7715
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7716
|
+
const unique = [];
|
|
7717
|
+
for (const row of rows) {
|
|
7718
|
+
const key = `${row.barrelFile}|${row.symbol}|${row.originalFile}`;
|
|
7719
|
+
if (seen.has(key)) continue;
|
|
7720
|
+
seen.add(key);
|
|
7721
|
+
unique.push(row);
|
|
7722
|
+
}
|
|
7723
|
+
return unique;
|
|
6041
7724
|
}
|
|
6042
7725
|
|
|
6043
7726
|
// src/queries/similar-signatures.ts
|
|
6044
7727
|
function similarSignatures(db, opts = {}) {
|
|
6045
7728
|
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
7729
|
const sigGroups = /* @__PURE__ */ new Map();
|
|
6069
|
-
for (const
|
|
6070
|
-
if (db.isIgnored(
|
|
6071
|
-
const
|
|
7730
|
+
for (const definition of getAllDefinitions(db, { scope })) {
|
|
7731
|
+
if (!definition.isFunctionLike || db.isIgnored(definition.relativePath)) continue;
|
|
7732
|
+
const loc = definition.endLine - definition.startLine + 1;
|
|
7733
|
+
if (loc < minLoc) continue;
|
|
7734
|
+
const normalized = resolveNormalizedSignature(db, definition);
|
|
6072
7735
|
if (!normalized) continue;
|
|
6073
7736
|
const entry = {
|
|
6074
|
-
symbol:
|
|
6075
|
-
shortName: shortenSymbol(
|
|
6076
|
-
file:
|
|
6077
|
-
startLine:
|
|
6078
|
-
endLine:
|
|
6079
|
-
loc
|
|
7737
|
+
symbol: definition.symbol,
|
|
7738
|
+
shortName: shortenSymbol(definition.symbol),
|
|
7739
|
+
file: definition.relativePath,
|
|
7740
|
+
startLine: definition.startLine,
|
|
7741
|
+
endLine: definition.endLine,
|
|
7742
|
+
loc
|
|
6080
7743
|
};
|
|
6081
7744
|
const existing = sigGroups.get(normalized);
|
|
6082
7745
|
if (existing) {
|
|
@@ -6099,6 +7762,52 @@ function similarSignatures(db, opts = {}) {
|
|
|
6099
7762
|
});
|
|
6100
7763
|
return limit ? results.slice(0, limit) : results;
|
|
6101
7764
|
}
|
|
7765
|
+
function resolveNormalizedSignature(db, definition) {
|
|
7766
|
+
const documented = extractDocumentedSignature(definition.documentation);
|
|
7767
|
+
const normalizedDocumented = documented ? normalizeSignature(documented) : null;
|
|
7768
|
+
if (normalizedDocumented) {
|
|
7769
|
+
return normalizedDocumented;
|
|
7770
|
+
}
|
|
7771
|
+
return normalizeSourceSignature(
|
|
7772
|
+
extractDeclarationHead(db, definition.relativePath, definition.startLine, definition.endLine, definition.leaf),
|
|
7773
|
+
definition.leaf
|
|
7774
|
+
);
|
|
7775
|
+
}
|
|
7776
|
+
function extractDocumentedSignature(documentation) {
|
|
7777
|
+
if (!documentation || !documentation.includes("|")) {
|
|
7778
|
+
return null;
|
|
7779
|
+
}
|
|
7780
|
+
const extracted = documentation.slice(documentation.indexOf("|") + 1).replace(/\n/g, " ").trim();
|
|
7781
|
+
return extracted.length > 0 ? extracted : null;
|
|
7782
|
+
}
|
|
7783
|
+
function extractDeclarationHead(db, relativePath, startLine, endLine, leaf) {
|
|
7784
|
+
const source = getSourceText(db, relativePath);
|
|
7785
|
+
if (!source) return null;
|
|
7786
|
+
const lines = source.split(/\r?\n/);
|
|
7787
|
+
const candidates = declarationStartLines(lines, startLine, endLine, leaf);
|
|
7788
|
+
for (const candidate of candidates) {
|
|
7789
|
+
const maxLine = Math.min(lines.length - 1, Math.max(candidate, candidate + 4));
|
|
7790
|
+
let collected = "";
|
|
7791
|
+
for (let lineIndex = candidate; lineIndex <= maxLine; lineIndex += 1) {
|
|
7792
|
+
const line = lines[lineIndex]?.trim();
|
|
7793
|
+
if (!line) continue;
|
|
7794
|
+
collected = collected ? `${collected} ${line}` : line;
|
|
7795
|
+
if (looksCompleteDeclaration(collected)) {
|
|
7796
|
+
return collected;
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
if (collected && collected.includes("(")) {
|
|
7800
|
+
return collected;
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7803
|
+
return null;
|
|
7804
|
+
}
|
|
7805
|
+
function looksCompleteDeclaration(declaration) {
|
|
7806
|
+
const normalized = declaration.replace(/\s+/g, " ").trim();
|
|
7807
|
+
if (!normalized.includes("(")) return false;
|
|
7808
|
+
if (parenBalance(normalized) > 0) return false;
|
|
7809
|
+
return /[;{]$/.test(normalized) || /\)\s*(?::\s*[^={]+)?\s*(?:=>|=|throws\b|where\b|$)/i.test(normalized) || /\)\s*As\s+.+$/i.test(normalized);
|
|
7810
|
+
}
|
|
6102
7811
|
function normalizeSignature(raw) {
|
|
6103
7812
|
if (!raw || !raw.trim()) return null;
|
|
6104
7813
|
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 +7818,88 @@ function normalizeSignature(raw) {
|
|
|
6109
7818
|
if (sig.length < 3) return null;
|
|
6110
7819
|
return sig;
|
|
6111
7820
|
}
|
|
7821
|
+
function normalizeSourceSignature(raw, leaf) {
|
|
7822
|
+
if (!raw || !raw.trim()) return null;
|
|
7823
|
+
let declaration = raw.replace(/\s+/g, " ").trim();
|
|
7824
|
+
const parenIdx = declaration.indexOf("(");
|
|
7825
|
+
if (parenIdx === -1) return null;
|
|
7826
|
+
let prefix = declaration.slice(0, parenIdx);
|
|
7827
|
+
const leafPattern = new RegExp(`\\b${escapeRegex3(leaf)}\\b`, "i");
|
|
7828
|
+
const leafMatch = leafPattern.exec(prefix);
|
|
7829
|
+
if (leafMatch && typeof leafMatch.index === "number") {
|
|
7830
|
+
prefix = prefix.slice(0, leafMatch.index);
|
|
7831
|
+
}
|
|
7832
|
+
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();
|
|
7833
|
+
let 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();
|
|
7834
|
+
if (!suffix.startsWith("(")) {
|
|
7835
|
+
return null;
|
|
7836
|
+
}
|
|
7837
|
+
const normalized = `${prefix ? `${prefix} ` : ""}${suffix}`.replace(/\s+/g, "").toLowerCase();
|
|
7838
|
+
return normalized.length >= 3 ? normalized : null;
|
|
7839
|
+
}
|
|
7840
|
+
function declarationStartLines(lines, startLine, endLine, leaf) {
|
|
7841
|
+
const escapedLeaf = escapeRegex3(leaf);
|
|
7842
|
+
const callablePattern = new RegExp(`\\b${escapedLeaf}\\b\\s*\\(`, "i");
|
|
7843
|
+
const rubyPattern = new RegExp(`\\bdef\\s+${escapedLeaf}\\b`, "i");
|
|
7844
|
+
const candidates = [];
|
|
7845
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7846
|
+
const preferredStart = Math.max(0, Math.min(startLine, lines.length - 1));
|
|
7847
|
+
const preferredEnd = Math.max(preferredStart, Math.min(lines.length - 1, Math.max(endLine, startLine + 4)));
|
|
7848
|
+
for (let lineIndex = preferredStart; lineIndex <= preferredEnd; lineIndex += 1) {
|
|
7849
|
+
const line = lines[lineIndex] ?? "";
|
|
7850
|
+
if ((callablePattern.test(line) || rubyPattern.test(line)) && !seen.has(lineIndex)) {
|
|
7851
|
+
seen.add(lineIndex);
|
|
7852
|
+
candidates.push(lineIndex);
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
7856
|
+
const line = lines[lineIndex] ?? "";
|
|
7857
|
+
if ((callablePattern.test(line) || rubyPattern.test(line)) && !seen.has(lineIndex)) {
|
|
7858
|
+
seen.add(lineIndex);
|
|
7859
|
+
candidates.push(lineIndex);
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
return candidates;
|
|
7863
|
+
}
|
|
7864
|
+
function parenBalance(value) {
|
|
7865
|
+
let balance = 0;
|
|
7866
|
+
for (const char of value) {
|
|
7867
|
+
if (char === "(") balance += 1;
|
|
7868
|
+
if (char === ")") balance -= 1;
|
|
7869
|
+
}
|
|
7870
|
+
return balance;
|
|
7871
|
+
}
|
|
7872
|
+
function escapeRegex3(value) {
|
|
7873
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7874
|
+
}
|
|
6112
7875
|
|
|
6113
7876
|
// src/setup.ts
|
|
6114
7877
|
import {
|
|
6115
|
-
existsSync as
|
|
7878
|
+
existsSync as existsSync9,
|
|
6116
7879
|
mkdirSync as mkdirSync2,
|
|
6117
7880
|
symlinkSync,
|
|
6118
7881
|
readlinkSync,
|
|
6119
7882
|
unlinkSync
|
|
6120
7883
|
} from "fs";
|
|
6121
|
-
import { join as
|
|
7884
|
+
import { join as join11, dirname as dirname3, resolve as resolve3 } from "path";
|
|
6122
7885
|
import { homedir as homedir2, platform as platform3 } from "os";
|
|
6123
7886
|
import { fileURLToPath } from "url";
|
|
6124
7887
|
var IS_WINDOWS3 = platform3() === "win32";
|
|
6125
|
-
var
|
|
7888
|
+
var BUILTIN_SKILLS = [
|
|
7889
|
+
"concrete-plan",
|
|
7890
|
+
"scip-explore",
|
|
7891
|
+
"scip-debloat",
|
|
7892
|
+
"scip-verify",
|
|
7893
|
+
"scip-language-playbook"
|
|
7894
|
+
];
|
|
6126
7895
|
function installSkills(opts = {}) {
|
|
6127
7896
|
const log = opts.quiet ? () => {
|
|
6128
7897
|
} : console.log;
|
|
6129
7898
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6130
7899
|
const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
|
|
6131
7900
|
const targets = [
|
|
6132
|
-
|
|
6133
|
-
|
|
7901
|
+
join11(homedir2(), ".claude", "skills"),
|
|
7902
|
+
join11(homedir2(), ".codex", "skills")
|
|
6134
7903
|
];
|
|
6135
7904
|
const result = {
|
|
6136
7905
|
installed: [],
|
|
@@ -6139,19 +7908,19 @@ function installSkills(opts = {}) {
|
|
|
6139
7908
|
};
|
|
6140
7909
|
for (const targetDir of targets) {
|
|
6141
7910
|
const parentDir = dirname3(targetDir);
|
|
6142
|
-
if (!
|
|
7911
|
+
if (!existsSync9(parentDir)) {
|
|
6143
7912
|
continue;
|
|
6144
7913
|
}
|
|
6145
7914
|
mkdirSync2(targetDir, { recursive: true });
|
|
6146
7915
|
const toolName = targetDir.includes(".codex") ? "Codex" : "Claude";
|
|
6147
|
-
for (const skill of
|
|
6148
|
-
const source =
|
|
6149
|
-
const target =
|
|
6150
|
-
if (!
|
|
7916
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
7917
|
+
const source = join11(skillsSource, skill);
|
|
7918
|
+
const target = join11(targetDir, skill);
|
|
7919
|
+
if (!existsSync9(source)) {
|
|
6151
7920
|
result.skipped.push(`${toolName}/${skill}`);
|
|
6152
7921
|
continue;
|
|
6153
7922
|
}
|
|
6154
|
-
if (
|
|
7923
|
+
if (existsSync9(target)) {
|
|
6155
7924
|
try {
|
|
6156
7925
|
const existing = readlinkSync(target);
|
|
6157
7926
|
if (resolve3(existing) === resolve3(source)) {
|
|
@@ -6183,14 +7952,14 @@ function resolveProjectRoot() {
|
|
|
6183
7952
|
function resolveActiveDbPath(projectRoot) {
|
|
6184
7953
|
const config = loadProjectConfig(projectRoot);
|
|
6185
7954
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6186
|
-
return process.env["SCIP_QUERY_INDEX_DB"] ?? (
|
|
7955
|
+
return process.env["SCIP_QUERY_INDEX_DB"] ?? (existsSync10(paths.dbPath) ? paths.dbPath : join12(projectRoot, "index.db"));
|
|
6187
7956
|
}
|
|
6188
7957
|
function openDb() {
|
|
6189
7958
|
const projectRoot = resolveProjectRoot();
|
|
6190
7959
|
const config = loadProjectConfig(projectRoot);
|
|
6191
7960
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6192
7961
|
const dbPath = resolveActiveDbPath(projectRoot);
|
|
6193
|
-
if (!
|
|
7962
|
+
if (!existsSync10(dbPath)) {
|
|
6194
7963
|
console.error(`error: No index.db found. Run: scip-query reindex`);
|
|
6195
7964
|
process.exit(1);
|
|
6196
7965
|
}
|
|
@@ -6361,6 +8130,10 @@ program.command("trace <symbol>").description("Trace a symbol: definition + all
|
|
|
6361
8130
|
for (const d of result.definitions) {
|
|
6362
8131
|
const sig = d.signature ? ` \u2014 ${d.signature}` : "";
|
|
6363
8132
|
console.log(` ${displayPathRange(d.relativePath, d.startLine, d.endLine)}${sig}`);
|
|
8133
|
+
if (d.source) {
|
|
8134
|
+
const numbered = d.source.split("\n").map((line, index) => ` ${displayLine(d.startLine + index)} ${line}`).join("\n");
|
|
8135
|
+
console.log(numbered);
|
|
8136
|
+
}
|
|
6364
8137
|
}
|
|
6365
8138
|
console.log("\n\u2550\u2550\u2550 REFERENCED BY \u2550\u2550\u2550");
|
|
6366
8139
|
let prevFile = "";
|
|
@@ -6767,7 +8540,7 @@ ${results.length} similar pair(s) found.`);
|
|
|
6767
8540
|
}
|
|
6768
8541
|
});
|
|
6769
8542
|
});
|
|
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
|
|
8543
|
+
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
8544
|
runQuery(
|
|
6772
8545
|
(db) => queries.similarFiles(db, {
|
|
6773
8546
|
minSimilarity: opts.minSimilarity,
|
|
@@ -7167,7 +8940,7 @@ ${result.connectedSymbols.length} connected symbol(s).`);
|
|
|
7167
8940
|
}
|
|
7168
8941
|
db.close();
|
|
7169
8942
|
});
|
|
7170
|
-
program.command("install-skills").description(
|
|
8943
|
+
program.command("install-skills").description(`Install skills (${BUILTIN_SKILLS.join(", ")}) into Claude Code and Codex`).action(() => {
|
|
7171
8944
|
const result = installSkills();
|
|
7172
8945
|
const total = result.installed.length + result.alreadyLinked.length;
|
|
7173
8946
|
console.log(`
|
|
@@ -7176,12 +8949,41 @@ ${result.installed.length} installed, ${result.alreadyLinked.length} already lin
|
|
|
7176
8949
|
console.log("Skills will be available in your next Claude Code / Codex session.");
|
|
7177
8950
|
}
|
|
7178
8951
|
});
|
|
7179
|
-
program.command("check-deps").description("Check
|
|
8952
|
+
program.command("check-deps").description("Check whether scip-query and the detected language indexers are actually runnable").action(() => {
|
|
8953
|
+
let hasProblems = false;
|
|
7180
8954
|
if (isScipInstalled()) {
|
|
7181
8955
|
console.log("scip CLI: installed");
|
|
7182
8956
|
} else {
|
|
7183
8957
|
printScipInstallInstructions();
|
|
8958
|
+
hasProblems = true;
|
|
8959
|
+
}
|
|
8960
|
+
const projectRoot = resolveProjectRoot();
|
|
8961
|
+
const config = loadProjectConfig(projectRoot);
|
|
8962
|
+
const languages = config.languages ?? detectLanguages(projectRoot);
|
|
8963
|
+
if (languages.length === 0) {
|
|
8964
|
+
console.log("\nNo supported project languages detected in the current directory.");
|
|
8965
|
+
process.exitCode = hasProblems ? 1 : 0;
|
|
8966
|
+
return;
|
|
8967
|
+
}
|
|
8968
|
+
console.log(`
|
|
8969
|
+
Detected languages: ${languages.join(", ")}`);
|
|
8970
|
+
console.log("\nIndexer readiness:");
|
|
8971
|
+
for (const language of languages) {
|
|
8972
|
+
const status = getIndexerDependencyStatus(getIndexerConfig(language), projectRoot);
|
|
8973
|
+
const prefix = status.runnable ? " OK" : status.installed ? " WARN" : " MISSING";
|
|
8974
|
+
const resolved = status.resolvedBinary ? ` (${status.resolvedBinary})` : "";
|
|
8975
|
+
console.log(`${prefix} ${language}: ${status.binaryLabel}${resolved}`);
|
|
8976
|
+
if (status.note) {
|
|
8977
|
+
console.log(` ${status.note}`);
|
|
8978
|
+
}
|
|
8979
|
+
if (!status.installed && status.installUrl) {
|
|
8980
|
+
console.log(` install: ${status.installUrl}`);
|
|
8981
|
+
}
|
|
8982
|
+
if (!status.runnable) {
|
|
8983
|
+
hasProblems = true;
|
|
8984
|
+
}
|
|
7184
8985
|
}
|
|
8986
|
+
process.exitCode = hasProblems ? 1 : 0;
|
|
7185
8987
|
});
|
|
7186
8988
|
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
8989
|
const db = openDb();
|
|
@@ -7269,8 +9071,8 @@ program.command("status").description("Show index status for this project").acti
|
|
|
7269
9071
|
if (dbPath !== paths.dbPath) {
|
|
7270
9072
|
console.log(`Config: ${paths.dbPath} (fallback to project root index.db)`);
|
|
7271
9073
|
}
|
|
7272
|
-
console.log(`Exists: ${
|
|
7273
|
-
if (
|
|
9074
|
+
console.log(`Exists: ${existsSync10(dbPath) ? "yes" : "no"}`);
|
|
9075
|
+
if (existsSync10(dbPath)) {
|
|
7274
9076
|
withDb((db) => {
|
|
7275
9077
|
const s = queries.stats(db);
|
|
7276
9078
|
console.log(`Symbols: ${s.symbols}`);
|