scip-query 0.3.4 → 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 +2 -26
- package/dist/{chunk-FYYOWQXK.js → chunk-2F2WH5WQ.js} +19 -36
- package/dist/{chunk-C7H5WBTJ.js → chunk-2UISVZGQ.js} +2 -2
- package/dist/{chunk-HLKAFWWJ.js → chunk-4ZT7UGWW.js} +56 -91
- 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-FVH3Y44U.js +1 -0
- package/dist/chunk-GIBETK3W.js +37 -0
- package/dist/{chunk-SMDCNPMK.js → chunk-H3FPW5YN.js} +2 -2
- package/dist/{chunk-GJDHTTR2.js → chunk-HNURMDF4.js} +32 -17
- 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-HJZUSUPU.js → chunk-VCOJRQPP.js} +5 -5
- 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 +2722 -1036
- package/dist/{db-ShvwGDKf.d.ts → db-C4rPbKkI.d.ts} +7 -14
- package/dist/index.d.ts +4 -5
- package/dist/index.js +378 -149
- 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 -2
- package/dist/queries/index.js +49 -53
- 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-2UELLEBI.js +0 -1
- 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-P4WO3BBW.js +0 -64
- package/dist/chunk-TOIEB3LG.js +0 -78
- package/dist/chunk-UQEQ6AHX.js +0 -60
- package/dist/chunk-VJJKSGIX.js +0 -121
- package/dist/chunk-YZ6L7GFO.js +0 -73
- package/dist/chunk-ZEUCXQBN.js +0 -71
- package/dist/queries/doc-coverage.d.ts +0 -14
- package/dist/queries/doc-coverage.js +0 -8
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
|
|
@@ -2578,42 +3864,112 @@ function hasUniqueLeafDefinition(db, leaf, symbolId) {
|
|
|
2578
3864
|
LIMIT 50`,
|
|
2579
3865
|
`%${leaf}%`
|
|
2580
3866
|
);
|
|
2581
|
-
let count = 0;
|
|
2582
|
-
for (const row of rows) {
|
|
2583
|
-
if (leafName(row.symbol) !== leaf) continue;
|
|
2584
|
-
count++;
|
|
2585
|
-
if (count > 1 && row.id !== symbolId) {
|
|
2586
|
-
return false;
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
return count === 1;
|
|
3867
|
+
let count = 0;
|
|
3868
|
+
for (const row of rows) {
|
|
3869
|
+
if (leafName(row.symbol) !== leaf) continue;
|
|
3870
|
+
count++;
|
|
3871
|
+
if (count > 1 && row.id !== symbolId) {
|
|
3872
|
+
return false;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
return count === 1;
|
|
3876
|
+
}
|
|
3877
|
+
function parentTypeName(rawSymbol) {
|
|
3878
|
+
const parsed = parseSymbol(rawSymbol);
|
|
3879
|
+
if ("kind" in parsed) {
|
|
3880
|
+
return null;
|
|
3881
|
+
}
|
|
3882
|
+
for (let index = parsed.descriptors.length - 2; index >= 0; index--) {
|
|
3883
|
+
const descriptor = parsed.descriptors[index];
|
|
3884
|
+
if (descriptor?.suffix === "type") {
|
|
3885
|
+
return descriptor.name;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
return null;
|
|
3889
|
+
}
|
|
3890
|
+
function isPythonDocument(db, relativePath) {
|
|
3891
|
+
const row = db.get(
|
|
3892
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3893
|
+
relativePath
|
|
3894
|
+
);
|
|
3895
|
+
return row?.language === "python" || relativePath.endsWith(".py") || relativePath.endsWith(".pyi");
|
|
3896
|
+
}
|
|
3897
|
+
function isJavaScriptDocument(db, relativePath) {
|
|
3898
|
+
const row = db.get(
|
|
3899
|
+
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
3900
|
+
relativePath
|
|
3901
|
+
);
|
|
3902
|
+
return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
|
|
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);
|
|
2590
3945
|
}
|
|
2591
|
-
function
|
|
2592
|
-
const
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
const descriptor = parsed.descriptors[index];
|
|
2598
|
-
if (descriptor?.suffix === "type") {
|
|
2599
|
-
return descriptor.name;
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
return null;
|
|
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");
|
|
2603
3952
|
}
|
|
2604
|
-
function
|
|
3953
|
+
function isRubyDocument(db, relativePath) {
|
|
2605
3954
|
const row = db.get(
|
|
2606
3955
|
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
2607
3956
|
relativePath
|
|
2608
3957
|
);
|
|
2609
|
-
return row?.language === "
|
|
3958
|
+
return row?.language === "ruby" || relativePath.endsWith(".rb");
|
|
2610
3959
|
}
|
|
2611
|
-
function
|
|
3960
|
+
function isDartDocument(db, relativePath) {
|
|
2612
3961
|
const row = db.get(
|
|
2613
3962
|
`SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
|
|
2614
3963
|
relativePath
|
|
2615
3964
|
);
|
|
2616
|
-
return row?.language === "
|
|
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");
|
|
2617
3973
|
}
|
|
2618
3974
|
function applyLimit(values, limit) {
|
|
2619
3975
|
return typeof limit === "number" ? values.slice(0, limit) : values;
|
|
@@ -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) {
|
|
@@ -3374,21 +4865,22 @@ function members(db, symbolPattern) {
|
|
|
3374
4865
|
|
|
3375
4866
|
// src/queries/fan.ts
|
|
3376
4867
|
function fanIn(db, symbolPattern) {
|
|
3377
|
-
const
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
4868
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
4869
|
+
if (!match) {
|
|
4870
|
+
return [];
|
|
4871
|
+
}
|
|
4872
|
+
const row = db.get(
|
|
4873
|
+
`SELECT COUNT(DISTINCT c.document_id) AS file_count
|
|
4874
|
+
FROM mentions m
|
|
4875
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
4876
|
+
WHERE m.symbol_id = ?
|
|
4877
|
+
AND m.role != 1`,
|
|
4878
|
+
match.symbolId
|
|
3387
4879
|
);
|
|
3388
|
-
return
|
|
3389
|
-
name: shortenSymbol(
|
|
3390
|
-
count:
|
|
3391
|
-
}
|
|
4880
|
+
return [{
|
|
4881
|
+
name: shortenSymbol(match.symbol),
|
|
4882
|
+
count: row?.file_count ?? 0
|
|
4883
|
+
}];
|
|
3392
4884
|
}
|
|
3393
4885
|
function fanOut(db, filePattern) {
|
|
3394
4886
|
const resolvedFile = resolveIndexedFile(db, filePattern);
|
|
@@ -3410,10 +4902,22 @@ function fanOut(db, filePattern) {
|
|
|
3410
4902
|
ORDER BY symbol_count DESC`,
|
|
3411
4903
|
resolvedFile
|
|
3412
4904
|
);
|
|
3413
|
-
|
|
4905
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
3414
4906
|
name: r.relative_path,
|
|
3415
4907
|
count: r.symbol_count
|
|
3416
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
|
+
}];
|
|
3417
4921
|
}
|
|
3418
4922
|
function topFanIn(db, opts = {}) {
|
|
3419
4923
|
const { limit = 30, scope } = opts;
|
|
@@ -3632,59 +5136,50 @@ function bottlenecks(db, opts = {}) {
|
|
|
3632
5136
|
minFanOut,
|
|
3633
5137
|
limit
|
|
3634
5138
|
);
|
|
3635
|
-
|
|
3636
|
-
symbol: r.symbol,
|
|
3637
|
-
shortName: shortenSymbol(r.symbol),
|
|
3638
|
-
fanIn: r.fan_in,
|
|
3639
|
-
fanOut: r.fan_out,
|
|
3640
|
-
score: r.fan_in * r.fan_out,
|
|
3641
|
-
definedIn: r.defined_in
|
|
3642
|
-
}));
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
|
-
// src/queries/isolated.ts
|
|
3646
|
-
function isolated(db, opts = {}) {
|
|
3647
|
-
const { scope, minLoc = 3 } = opts;
|
|
3648
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
3649
|
-
const rows = db.all(
|
|
3650
|
-
`SELECT
|
|
3651
|
-
gs.symbol,
|
|
3652
|
-
d.relative_path,
|
|
3653
|
-
der.start_line,
|
|
3654
|
-
der.end_line,
|
|
3655
|
-
(der.end_line - der.start_line + 1) AS loc
|
|
3656
|
-
FROM global_symbols gs
|
|
3657
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3658
|
-
JOIN documents d ON der.document_id = d.id
|
|
3659
|
-
WHERE 1 = 1
|
|
3660
|
-
${db.pathExclusionsFor("d")}
|
|
3661
|
-
AND ${testFileExclusionSql("d")}
|
|
3662
|
-
${db.symbolNoiseFor("gs")}
|
|
3663
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
3664
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
3665
|
-
${scopeFilter}
|
|
3666
|
-
-- No cross-file references TO this symbol
|
|
3667
|
-
AND NOT EXISTS (
|
|
3668
|
-
SELECT 1 FROM mentions m
|
|
3669
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
3670
|
-
WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != d.id
|
|
3671
|
-
)
|
|
3672
|
-
-- No same-file references either
|
|
3673
|
-
AND NOT EXISTS (
|
|
3674
|
-
SELECT 1 FROM mentions m
|
|
3675
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
3676
|
-
WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id = d.id
|
|
3677
|
-
)
|
|
3678
|
-
ORDER BY loc DESC, d.relative_path`,
|
|
3679
|
-
minLoc
|
|
3680
|
-
);
|
|
3681
|
-
return rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => ({
|
|
5139
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
3682
5140
|
symbol: r.symbol,
|
|
3683
|
-
shortName: shortenSymbol(r.symbol),
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
5141
|
+
shortName: shortenSymbol(r.symbol),
|
|
5142
|
+
fanIn: r.fan_in,
|
|
5143
|
+
fanOut: r.fan_out,
|
|
5144
|
+
score: r.fan_in * r.fan_out,
|
|
5145
|
+
definedIn: r.defined_in
|
|
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);
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
// src/queries/isolated.ts
|
|
5169
|
+
function isolated(db, opts = {}) {
|
|
5170
|
+
const { scope, minLoc = 3 } = opts;
|
|
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
|
|
3688
5183
|
}));
|
|
3689
5184
|
}
|
|
3690
5185
|
|
|
@@ -3799,102 +5294,26 @@ function byKind(db, kindQuery, opts = {}) {
|
|
|
3799
5294
|
if (kindNum === null) {
|
|
3800
5295
|
return [];
|
|
3801
5296
|
}
|
|
3802
|
-
const
|
|
3803
|
-
const hasKinds = db.get(
|
|
3804
|
-
`SELECT COUNT(*) AS c FROM global_symbols WHERE kind IS NOT NULL`
|
|
3805
|
-
);
|
|
3806
|
-
if (!hasKinds || hasKinds.c === 0) {
|
|
3807
|
-
return inferByKind(db, kindNum, scope, limit);
|
|
3808
|
-
}
|
|
3809
|
-
const rows = db.all(
|
|
3810
|
-
`SELECT gs.symbol, gs.kind, d.relative_path, der.start_line, der.end_line
|
|
3811
|
-
FROM global_symbols gs
|
|
3812
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3813
|
-
JOIN documents d ON der.document_id = d.id
|
|
3814
|
-
WHERE gs.kind = ?
|
|
3815
|
-
${db.pathExclusionsFor("d")}
|
|
3816
|
-
${scopeFilter}
|
|
3817
|
-
ORDER BY d.relative_path, der.start_line
|
|
3818
|
-
LIMIT ?`,
|
|
3819
|
-
kindNum,
|
|
3820
|
-
limit
|
|
3821
|
-
);
|
|
3822
|
-
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
3823
|
-
symbol: r.symbol,
|
|
3824
|
-
shortName: shortenSymbol(r.symbol),
|
|
3825
|
-
kind: r.kind,
|
|
3826
|
-
kindName: KIND_NAMES[r.kind] ?? "Unknown",
|
|
3827
|
-
relativePath: r.relative_path,
|
|
3828
|
-
startLine: r.start_line,
|
|
3829
|
-
endLine: r.end_line
|
|
3830
|
-
}));
|
|
3831
|
-
}
|
|
3832
|
-
function kindCounts(db, opts = {}) {
|
|
3833
|
-
const scopeFilter = opts.scope ? `AND d.relative_path LIKE '%${opts.scope}%'` : "";
|
|
3834
|
-
const rows = db.all(
|
|
3835
|
-
`SELECT gs.kind, COUNT(*) AS cnt
|
|
3836
|
-
FROM global_symbols gs
|
|
3837
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3838
|
-
JOIN documents d ON der.document_id = d.id
|
|
3839
|
-
WHERE 1 = 1
|
|
3840
|
-
${db.pathExclusionsFor("d")}
|
|
3841
|
-
AND gs.kind IS NOT NULL
|
|
3842
|
-
AND gs.kind != 0
|
|
3843
|
-
${scopeFilter}
|
|
3844
|
-
GROUP BY gs.kind
|
|
3845
|
-
ORDER BY cnt DESC`
|
|
3846
|
-
);
|
|
3847
|
-
if (rows.length === 0) {
|
|
3848
|
-
return inferKindCounts(db, opts.scope);
|
|
3849
|
-
}
|
|
3850
|
-
return rows.map((r) => ({
|
|
3851
|
-
kind: r.kind,
|
|
3852
|
-
kindName: KIND_NAMES[r.kind] ?? "Unknown",
|
|
3853
|
-
count: r.cnt
|
|
3854
|
-
}));
|
|
3855
|
-
}
|
|
3856
|
-
function inferByKind(db, kindNum, scope, limit = 100) {
|
|
3857
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
3858
|
-
const rows = db.all(
|
|
3859
|
-
`SELECT gs.symbol, d.relative_path, der.start_line, der.end_line, gs.documentation, gs.enclosing_symbol
|
|
3860
|
-
FROM global_symbols gs
|
|
3861
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3862
|
-
JOIN documents d ON der.document_id = d.id
|
|
3863
|
-
WHERE 1 = 1
|
|
3864
|
-
${db.pathExclusionsFor("d")}
|
|
3865
|
-
${scopeFilter}
|
|
3866
|
-
ORDER BY d.relative_path, der.start_line`
|
|
3867
|
-
);
|
|
3868
|
-
return rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
|
|
5297
|
+
const rows = loadKindRows(db, scope).map((row) => ({
|
|
3869
5298
|
row,
|
|
3870
|
-
|
|
3871
|
-
})).filter((entry) => entry.
|
|
5299
|
+
resolvedKind: resolveKindNumber(row)
|
|
5300
|
+
})).filter((entry) => entry.resolvedKind === kindNum).slice(0, limit);
|
|
5301
|
+
return rows.map(({ row, resolvedKind }) => ({
|
|
3872
5302
|
symbol: row.symbol,
|
|
3873
5303
|
shortName: shortenSymbol(row.symbol),
|
|
3874
|
-
kind:
|
|
3875
|
-
kindName: KIND_NAMES[
|
|
5304
|
+
kind: resolvedKind,
|
|
5305
|
+
kindName: KIND_NAMES[resolvedKind] ?? "Unknown",
|
|
3876
5306
|
relativePath: row.relative_path,
|
|
3877
5307
|
startLine: row.start_line,
|
|
3878
5308
|
endLine: row.end_line
|
|
3879
5309
|
}));
|
|
3880
5310
|
}
|
|
3881
|
-
function
|
|
3882
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
3883
|
-
const rows = db.all(
|
|
3884
|
-
`SELECT gs.symbol, gs.documentation, gs.enclosing_symbol, d.relative_path
|
|
3885
|
-
FROM global_symbols gs
|
|
3886
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3887
|
-
JOIN documents d ON der.document_id = d.id
|
|
3888
|
-
WHERE 1 = 1
|
|
3889
|
-
${db.pathExclusionsFor("d")}
|
|
3890
|
-
${scopeFilter}`
|
|
3891
|
-
);
|
|
5311
|
+
function kindCounts(db, opts = {}) {
|
|
3892
5312
|
const counts = /* @__PURE__ */ new Map();
|
|
3893
|
-
for (const row of
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
counts.set(inferred, (counts.get(inferred) ?? 0) + 1);
|
|
5313
|
+
for (const row of loadKindRows(db, opts.scope)) {
|
|
5314
|
+
const kind = resolveKindNumber(row);
|
|
5315
|
+
if (kind === null || kind === 0) continue;
|
|
5316
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
3898
5317
|
}
|
|
3899
5318
|
return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0]).map(([kind, count]) => ({
|
|
3900
5319
|
kind,
|
|
@@ -3902,22 +5321,60 @@ function inferKindCounts(db, scope) {
|
|
|
3902
5321
|
count
|
|
3903
5322
|
}));
|
|
3904
5323
|
}
|
|
5324
|
+
function loadKindRows(db, scope) {
|
|
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
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
function resolveKindNumber(row) {
|
|
5339
|
+
if (row.kind !== null && row.kind !== 0) {
|
|
5340
|
+
return normalizeIndexedKind(row.kind, row.symbol, row.documentation);
|
|
5341
|
+
}
|
|
5342
|
+
return inferKindNumber(row.symbol, row.documentation, row.enclosing_symbol);
|
|
5343
|
+
}
|
|
5344
|
+
function normalizeIndexedKind(kind, symbol, documentation) {
|
|
5345
|
+
const signature = (documentation ?? "").toLowerCase();
|
|
5346
|
+
const suffix = leafSuffix(symbol);
|
|
5347
|
+
if (suffix === "type") {
|
|
5348
|
+
if (signature.includes("type ")) return 76;
|
|
5349
|
+
if (signature.includes("interface ")) return 27;
|
|
5350
|
+
if (signature.includes("struct ")) return 68;
|
|
5351
|
+
if (signature.includes("trait ")) return 73;
|
|
5352
|
+
if (signature.includes("class ")) return 9;
|
|
5353
|
+
}
|
|
5354
|
+
return kind;
|
|
5355
|
+
}
|
|
3905
5356
|
function inferKindNumber(symbol, documentation, enclosingSymbol) {
|
|
3906
5357
|
const parsed = parseSymbol(symbol);
|
|
3907
5358
|
if ("kind" in parsed) {
|
|
3908
5359
|
return null;
|
|
3909
5360
|
}
|
|
3910
5361
|
const descriptors = parsed.descriptors;
|
|
3911
|
-
const last = descriptors[descriptors.length - 1] ?? null;
|
|
3912
5362
|
const parent = descriptors[descriptors.length - 2] ?? null;
|
|
3913
5363
|
const suffix = leafSuffix(symbol);
|
|
3914
|
-
|
|
5364
|
+
const signature = (documentation ?? "").toLowerCase();
|
|
5365
|
+
if (suffix === "type") {
|
|
5366
|
+
if (signature.includes("type ")) return 76;
|
|
5367
|
+
if (signature.includes("interface ")) return 27;
|
|
5368
|
+
if (signature.includes("struct ")) return 68;
|
|
5369
|
+
if (signature.includes("trait ")) return 73;
|
|
5370
|
+
if (signature.includes("class ")) return 9;
|
|
5371
|
+
return 9;
|
|
5372
|
+
}
|
|
3915
5373
|
if (suffix === "method") {
|
|
3916
5374
|
return parent?.suffix === "type" ? 33 : 23;
|
|
3917
5375
|
}
|
|
3918
5376
|
if (suffix === "namespace") return 39;
|
|
3919
5377
|
if (suffix !== "term") return null;
|
|
3920
|
-
const signature = (documentation ?? "").toLowerCase();
|
|
3921
5378
|
if (signature.includes("async def ") || signature.includes("def ")) {
|
|
3922
5379
|
return 23;
|
|
3923
5380
|
}
|
|
@@ -3928,71 +5385,6 @@ function inferKindNumber(symbol, documentation, enclosingSymbol) {
|
|
|
3928
5385
|
return 83;
|
|
3929
5386
|
}
|
|
3930
5387
|
|
|
3931
|
-
// src/queries/doc-coverage.ts
|
|
3932
|
-
function docCoverage(db, opts = {}) {
|
|
3933
|
-
const { scope, minLoc = 3, limit = 50 } = opts;
|
|
3934
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
3935
|
-
const totalRow = db.get(
|
|
3936
|
-
`SELECT COUNT(*) AS c
|
|
3937
|
-
FROM global_symbols gs
|
|
3938
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3939
|
-
JOIN documents d ON der.document_id = d.id
|
|
3940
|
-
WHERE 1 = 1
|
|
3941
|
-
${db.pathExclusionsFor("d")}
|
|
3942
|
-
${db.symbolNoiseFor("gs")}
|
|
3943
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
3944
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
3945
|
-
${scopeFilter}`,
|
|
3946
|
-
minLoc
|
|
3947
|
-
);
|
|
3948
|
-
const docRow = db.get(
|
|
3949
|
-
`SELECT COUNT(*) AS c
|
|
3950
|
-
FROM global_symbols gs
|
|
3951
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3952
|
-
JOIN documents d ON der.document_id = d.id
|
|
3953
|
-
WHERE 1 = 1
|
|
3954
|
-
${db.pathExclusionsFor("d")}
|
|
3955
|
-
${db.symbolNoiseFor("gs")}
|
|
3956
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
3957
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
3958
|
-
AND gs.documentation IS NOT NULL
|
|
3959
|
-
AND gs.documentation != ''
|
|
3960
|
-
${scopeFilter}`,
|
|
3961
|
-
minLoc
|
|
3962
|
-
);
|
|
3963
|
-
const total = totalRow?.c ?? 0;
|
|
3964
|
-
const documented = docRow?.c ?? 0;
|
|
3965
|
-
const undocRows = db.all(
|
|
3966
|
-
`SELECT gs.symbol, d.relative_path, der.start_line
|
|
3967
|
-
FROM global_symbols gs
|
|
3968
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3969
|
-
JOIN documents d ON der.document_id = d.id
|
|
3970
|
-
WHERE 1 = 1
|
|
3971
|
-
${db.pathExclusionsFor("d")}
|
|
3972
|
-
${db.symbolNoiseFor("gs")}
|
|
3973
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
3974
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
3975
|
-
AND (gs.documentation IS NULL OR gs.documentation = '')
|
|
3976
|
-
${scopeFilter}
|
|
3977
|
-
ORDER BY d.relative_path, der.start_line
|
|
3978
|
-
LIMIT ?`,
|
|
3979
|
-
minLoc,
|
|
3980
|
-
limit
|
|
3981
|
-
);
|
|
3982
|
-
return {
|
|
3983
|
-
totalSymbols: total,
|
|
3984
|
-
documented,
|
|
3985
|
-
undocumented: total - documented,
|
|
3986
|
-
coveragePercent: total > 0 ? Math.round(documented / total * 100) : 0,
|
|
3987
|
-
undocumentedSymbols: undocRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
3988
|
-
symbol: r.symbol,
|
|
3989
|
-
shortName: shortenSymbol(r.symbol),
|
|
3990
|
-
relativePath: r.relative_path,
|
|
3991
|
-
startLine: r.start_line
|
|
3992
|
-
}))
|
|
3993
|
-
};
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
5388
|
// src/queries/deep-chains.ts
|
|
3997
5389
|
function deepChains(db, opts = {}) {
|
|
3998
5390
|
const { limit = 10, scope, minDepth = 3 } = opts;
|
|
@@ -4123,7 +5515,7 @@ function uniqueRows(rows) {
|
|
|
4123
5515
|
function similar(db, symbolPattern, opts = {}) {
|
|
4124
5516
|
const { minSimilarity = 0.4, limit = 20 } = opts;
|
|
4125
5517
|
const target = findCallees(db, symbolPattern);
|
|
4126
|
-
if (!target
|
|
5518
|
+
if (!target) return [];
|
|
4127
5519
|
if (!isFunctionLikeSymbol(target.symbol)) return [];
|
|
4128
5520
|
const candidates = getAllCalleeFingerprints(db, {
|
|
4129
5521
|
minCallees: 3,
|
|
@@ -4155,7 +5547,10 @@ function similar(db, symbolPattern, opts = {}) {
|
|
|
4155
5547
|
});
|
|
4156
5548
|
}
|
|
4157
5549
|
results.sort((a, b) => b.similarity - a.similarity);
|
|
4158
|
-
|
|
5550
|
+
if (results.length > 0) {
|
|
5551
|
+
return results.slice(0, limit);
|
|
5552
|
+
}
|
|
5553
|
+
return similarBySourceShape(db, symbolPattern, { minSimilarity, limit });
|
|
4159
5554
|
}
|
|
4160
5555
|
function similarAll(db, opts = {}) {
|
|
4161
5556
|
const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;
|
|
@@ -4302,10 +5697,146 @@ function difference(a, b) {
|
|
|
4302
5697
|
}
|
|
4303
5698
|
return result;
|
|
4304
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
|
+
}
|
|
4305
5835
|
|
|
4306
5836
|
// src/queries/similar-files.ts
|
|
4307
5837
|
function similarFiles(db, opts = {}) {
|
|
4308
|
-
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);
|
|
4309
5840
|
const profiles = buildFileProfiles(db, { scope, minDeps });
|
|
4310
5841
|
const results = [];
|
|
4311
5842
|
if (filePattern) {
|
|
@@ -4354,7 +5885,7 @@ function findUniversalDependencies(depMap) {
|
|
|
4354
5885
|
}
|
|
4355
5886
|
}
|
|
4356
5887
|
for (const [dep, count] of depCounts) {
|
|
4357
|
-
if (count / fileCount > 0.
|
|
5888
|
+
if (count >= 6 && count / fileCount > 0.8) {
|
|
4358
5889
|
universalDeps.add(dep);
|
|
4359
5890
|
}
|
|
4360
5891
|
}
|
|
@@ -4413,8 +5944,8 @@ function similarChains(db, opts = {}) {
|
|
|
4413
5944
|
tailFreq.set(chain[t], (tailFreq.get(chain[t]) ?? 0) + 1);
|
|
4414
5945
|
}
|
|
4415
5946
|
}
|
|
4416
|
-
const infraThreshold = rawChains.length * 0.
|
|
4417
|
-
const tailThreshold = rawChains.length * 0.
|
|
5947
|
+
const infraThreshold = rawChains.length * 0.9;
|
|
5948
|
+
const tailThreshold = rawChains.length * 0.8;
|
|
4418
5949
|
const infraNodes = /* @__PURE__ */ new Set();
|
|
4419
5950
|
for (const [node, freq] of nodeFreq) {
|
|
4420
5951
|
if (freq > infraThreshold) infraNodes.add(node);
|
|
@@ -4424,13 +5955,13 @@ function similarChains(db, opts = {}) {
|
|
|
4424
5955
|
}
|
|
4425
5956
|
const structuralNames = ["index.ts", "index.js", "cli.ts", "main.ts", "health.ts", "health.js"];
|
|
4426
5957
|
for (const node of nodeFreq.keys()) {
|
|
4427
|
-
const
|
|
4428
|
-
if (structuralNames.includes(
|
|
5958
|
+
const basename5 = node.split("/").pop() ?? "";
|
|
5959
|
+
if (structuralNames.includes(basename5)) infraNodes.add(node);
|
|
4429
5960
|
}
|
|
4430
5961
|
const filteredChains = [];
|
|
4431
5962
|
for (const chain of rawChains) {
|
|
4432
5963
|
const filtered = chain.filter((n) => !infraNodes.has(n));
|
|
4433
|
-
if (filtered.length >=
|
|
5964
|
+
if (filtered.length >= 2) {
|
|
4434
5965
|
filteredChains.push({ original: chain, filtered });
|
|
4435
5966
|
}
|
|
4436
5967
|
}
|
|
@@ -4595,31 +6126,13 @@ function isSubChain(sub, full) {
|
|
|
4595
6126
|
// src/queries/extract-candidates.ts
|
|
4596
6127
|
function extractCandidates(db, opts = {}) {
|
|
4597
6128
|
const { scope, minLoc = 10, minCallees = 6, limit = 20 } = opts;
|
|
4598
|
-
const
|
|
4599
|
-
const symbols2 = db.all(
|
|
4600
|
-
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
4601
|
-
FROM global_symbols gs
|
|
4602
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
4603
|
-
JOIN documents d ON der.document_id = d.id
|
|
4604
|
-
WHERE 1 = 1
|
|
4605
|
-
${db.pathExclusionsFor("d")}
|
|
4606
|
-
${db.symbolNoiseFor("gs")}
|
|
4607
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
4608
|
-
${scopeFilter}
|
|
4609
|
-
ORDER BY (der.end_line - der.start_line + 1) DESC`,
|
|
4610
|
-
minLoc
|
|
4611
|
-
);
|
|
6129
|
+
const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc(definition) >= minLoc && isFunctionLikeSymbol(definition.symbol)).sort((left, right) => definitionLoc(right) - definitionLoc(left));
|
|
4612
6130
|
const results = [];
|
|
4613
6131
|
for (const sym of symbols2) {
|
|
4614
|
-
if (db.isIgnored(sym.
|
|
4615
|
-
const
|
|
4616
|
-
if (
|
|
4617
|
-
const calleeChunks = getCalleeRowsForSymbol(db,
|
|
4618
|
-
documentId: sym.document_id,
|
|
4619
|
-
startLine: sym.start_line,
|
|
4620
|
-
endLine: sym.end_line,
|
|
4621
|
-
symbolId: sym.id
|
|
4622
|
-
});
|
|
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);
|
|
4623
6136
|
const calleeSet = new Set(calleeChunks.map((c) => c.symbol));
|
|
4624
6137
|
if (calleeSet.size < minCallees) continue;
|
|
4625
6138
|
const cooccurrence = /* @__PURE__ */ new Map();
|
|
@@ -4682,10 +6195,10 @@ function extractCandidates(db, opts = {}) {
|
|
|
4682
6195
|
results.push({
|
|
4683
6196
|
symbol: sym.symbol,
|
|
4684
6197
|
shortName: shortenSymbol(sym.symbol),
|
|
4685
|
-
relativePath: sym.
|
|
4686
|
-
startLine: sym.
|
|
4687
|
-
endLine: sym.
|
|
4688
|
-
loc: sym
|
|
6198
|
+
relativePath: sym.relativePath,
|
|
6199
|
+
startLine: sym.startLine,
|
|
6200
|
+
endLine: sym.endLine,
|
|
6201
|
+
loc: definitionLoc(sym),
|
|
4689
6202
|
totalCallees: calleeSet.size,
|
|
4690
6203
|
clusters: scoredClusters
|
|
4691
6204
|
});
|
|
@@ -4694,6 +6207,20 @@ function extractCandidates(db, opts = {}) {
|
|
|
4694
6207
|
results.sort((a, b) => b.clusters.length - a.clusters.length || b.loc - a.loc);
|
|
4695
6208
|
return results.slice(0, limit);
|
|
4696
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
|
+
}
|
|
4697
6224
|
|
|
4698
6225
|
// src/queries/affected.ts
|
|
4699
6226
|
function affected(db, symbolPattern, opts = {}) {
|
|
@@ -4734,73 +6261,39 @@ function affected(db, symbolPattern, opts = {}) {
|
|
|
4734
6261
|
return results;
|
|
4735
6262
|
}
|
|
4736
6263
|
function getDirectAffectedRows(db, target, scope) {
|
|
4737
|
-
const
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
});
|
|
4753
|
-
continue;
|
|
4754
|
-
}
|
|
4755
|
-
const enclosing = findExactSymbolMatch(db, site.enclosingSymbol);
|
|
4756
|
-
if (!enclosing || enclosing.symbolId === target.symbolId || db.isIgnored(enclosing.relativePath)) {
|
|
4757
|
-
continue;
|
|
4758
|
-
}
|
|
4759
|
-
const key = `${enclosing.symbolId}|${enclosing.relativePath}`;
|
|
4760
|
-
if (seen.has(key)) continue;
|
|
4761
|
-
seen.add(key);
|
|
4762
|
-
rows2.push({
|
|
4763
|
-
symbolId: enclosing.symbolId,
|
|
4764
|
-
symbol: enclosing.symbol,
|
|
4765
|
-
shortName: shortenSymbol(enclosing.symbol),
|
|
4766
|
-
file: enclosing.relativePath,
|
|
4767
|
-
symbolMatch: enclosing
|
|
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
|
|
4768
6279
|
});
|
|
6280
|
+
continue;
|
|
6281
|
+
}
|
|
6282
|
+
if (match.symbolId === target.symbolId || db.isIgnored(match.relativePath)) {
|
|
6283
|
+
continue;
|
|
4769
6284
|
}
|
|
4770
|
-
|
|
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
|
+
});
|
|
4771
6295
|
}
|
|
4772
|
-
|
|
4773
|
-
`SELECT DISTINCT
|
|
4774
|
-
enc_gs.id AS symbol_id,
|
|
4775
|
-
enc_gs.symbol AS symbol,
|
|
4776
|
-
enc_d.relative_path AS relative_path
|
|
4777
|
-
FROM mentions m
|
|
4778
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
4779
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
4780
|
-
JOIN defn_enclosing_ranges enc_der
|
|
4781
|
-
ON enc_der.document_id = ref_d.id
|
|
4782
|
-
AND c.start_line >= enc_der.start_line
|
|
4783
|
-
AND c.end_line <= enc_der.end_line
|
|
4784
|
-
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
4785
|
-
JOIN documents enc_d ON enc_der.document_id = enc_d.id
|
|
4786
|
-
WHERE m.symbol_id = ?
|
|
4787
|
-
AND m.role != 1
|
|
4788
|
-
AND enc_gs.id != ?
|
|
4789
|
-
${db.symbolNoiseFor("enc_gs")}
|
|
4790
|
-
${db.pathExclusionsFor("enc_d")}
|
|
4791
|
-
${scope ? `AND enc_d.relative_path LIKE '%${scope}%'` : ""}
|
|
4792
|
-
ORDER BY enc_d.relative_path
|
|
4793
|
-
LIMIT 1`,
|
|
4794
|
-
target.symbolId,
|
|
4795
|
-
target.symbolId
|
|
4796
|
-
);
|
|
4797
|
-
return rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
|
|
4798
|
-
symbolId: row.symbol_id,
|
|
4799
|
-
symbol: row.symbol,
|
|
4800
|
-
shortName: shortenSymbol(row.symbol),
|
|
4801
|
-
file: row.relative_path,
|
|
4802
|
-
symbolMatch: findExactSymbolMatch(db, row.symbol)
|
|
4803
|
-
}));
|
|
6296
|
+
return results;
|
|
4804
6297
|
}
|
|
4805
6298
|
|
|
4806
6299
|
// src/queries/change-surface.ts
|
|
@@ -5150,140 +6643,129 @@ function isLikelyTypeOnlyDep(dep) {
|
|
|
5150
6643
|
function shouldSkipDriftFile(filePath) {
|
|
5151
6644
|
return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
|
|
5152
6645
|
}
|
|
5153
|
-
function isStructuralRole(
|
|
5154
|
-
if (
|
|
5155
|
-
if (
|
|
5156
|
-
if (
|
|
5157
|
-
if (
|
|
5158
|
-
return false;
|
|
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;
|
|
6651
|
+
return false;
|
|
6652
|
+
}
|
|
6653
|
+
function isTestLikePath(filePath) {
|
|
6654
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
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);
|
|
6657
|
+
}
|
|
6658
|
+
|
|
6659
|
+
// src/queries/wrapper-candidates.ts
|
|
6660
|
+
import { basename as basename4, extname as extname3 } from "path";
|
|
6661
|
+
function wrapperCandidates(db, opts) {
|
|
6662
|
+
const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
|
|
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;
|
|
5159
6724
|
}
|
|
5160
|
-
function
|
|
5161
|
-
const
|
|
5162
|
-
const
|
|
5163
|
-
|
|
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;
|
|
5164
6736
|
}
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
AND ref_c.end_line <= caller_der.end_line
|
|
5187
|
-
JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
|
|
5188
|
-
WHERE ref_m.symbol_id = gs.id
|
|
5189
|
-
AND ref_m.role != 1
|
|
5190
|
-
AND ref_c.document_id != der.document_id
|
|
5191
|
-
LIMIT 1
|
|
5192
|
-
) AS caller_symbol,
|
|
5193
|
-
-- Fan-in of that single caller
|
|
5194
|
-
(SELECT COUNT(DISTINCT caller_ref_c.document_id)
|
|
5195
|
-
FROM mentions caller_ref_m
|
|
5196
|
-
JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id
|
|
5197
|
-
WHERE caller_ref_m.symbol_id = (
|
|
5198
|
-
SELECT caller_der2.symbol_id
|
|
5199
|
-
FROM mentions ref_m2
|
|
5200
|
-
JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id
|
|
5201
|
-
JOIN defn_enclosing_ranges caller_der2
|
|
5202
|
-
ON caller_der2.document_id = ref_c2.document_id
|
|
5203
|
-
AND ref_c2.start_line >= caller_der2.start_line
|
|
5204
|
-
AND ref_c2.end_line <= caller_der2.end_line
|
|
5205
|
-
WHERE ref_m2.symbol_id = gs.id
|
|
5206
|
-
AND ref_m2.role != 1
|
|
5207
|
-
AND ref_c2.document_id != der.document_id
|
|
5208
|
-
LIMIT 1
|
|
5209
|
-
)
|
|
5210
|
-
AND caller_ref_m.role != 1
|
|
5211
|
-
) AS caller_fan_in
|
|
5212
|
-
FROM global_symbols gs
|
|
5213
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5214
|
-
JOIN documents d ON der.document_id = d.id
|
|
5215
|
-
WHERE 1 = 1
|
|
5216
|
-
${db.pathExclusionsFor("d")}
|
|
5217
|
-
AND ${testFileExclusionSql("d")}
|
|
5218
|
-
${db.symbolNoiseFor("gs")}
|
|
5219
|
-
-- Only functions/terms, not type definitions (types with # are not wrappers)
|
|
5220
|
-
AND gs.symbol NOT LIKE '%#'
|
|
5221
|
-
AND (der.end_line - der.start_line + 1) <= ?
|
|
5222
|
-
AND (der.end_line - der.start_line + 1) >= 2
|
|
5223
|
-
${scopeFilter}
|
|
5224
|
-
-- Exactly 1 cross-file consumer
|
|
5225
|
-
AND (
|
|
5226
|
-
SELECT COUNT(DISTINCT ref_c.document_id)
|
|
5227
|
-
FROM mentions ref_m
|
|
5228
|
-
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
5229
|
-
WHERE ref_m.symbol_id = gs.id
|
|
5230
|
-
AND ref_m.role != 1
|
|
5231
|
-
AND ref_c.document_id != der.document_id
|
|
5232
|
-
) = 1
|
|
5233
|
-
) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3
|
|
5234
|
-
ORDER BY caller_fan_in DESC, loc DESC
|
|
5235
|
-
LIMIT ?`,
|
|
5236
|
-
maxLoc,
|
|
5237
|
-
limit
|
|
5238
|
-
);
|
|
5239
|
-
return rows.filter((r) => !db.isIgnored(r.file)).map((r) => ({
|
|
5240
|
-
symbol: r.symbol,
|
|
5241
|
-
shortName: shortenSymbol(r.symbol),
|
|
5242
|
-
file: r.file,
|
|
5243
|
-
startLine: r.start_line,
|
|
5244
|
-
endLine: r.end_line,
|
|
5245
|
-
loc: r.loc,
|
|
5246
|
-
singleCaller: r.caller_symbol,
|
|
5247
|
-
singleCallerShort: shortenSymbol(r.caller_symbol),
|
|
5248
|
-
callerFanIn: r.caller_fan_in
|
|
5249
|
-
}));
|
|
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;
|
|
5250
6758
|
}
|
|
5251
6759
|
|
|
5252
6760
|
// src/queries/passthrough-candidates.ts
|
|
5253
6761
|
function passthroughCandidates(db, opts) {
|
|
5254
6762
|
const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
|
|
5255
|
-
const
|
|
5256
|
-
const symbols2 = db.all(
|
|
5257
|
-
`SELECT
|
|
5258
|
-
gs.id,
|
|
5259
|
-
gs.symbol,
|
|
5260
|
-
der.document_id,
|
|
5261
|
-
der.start_line,
|
|
5262
|
-
der.end_line,
|
|
5263
|
-
(der.end_line - der.start_line + 1) AS loc,
|
|
5264
|
-
d.relative_path
|
|
5265
|
-
FROM global_symbols gs
|
|
5266
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5267
|
-
JOIN documents d ON der.document_id = d.id
|
|
5268
|
-
WHERE 1 = 1
|
|
5269
|
-
${db.pathExclusionsFor("d")}
|
|
5270
|
-
AND ${testFileExclusionSql("d")}
|
|
5271
|
-
${db.symbolNoiseFor("gs")}
|
|
5272
|
-
AND (der.end_line - der.start_line + 1) >= 3
|
|
5273
|
-
AND (der.end_line - der.start_line + 1) <= ?
|
|
5274
|
-
${scopeFilter}
|
|
5275
|
-
ORDER BY d.relative_path`,
|
|
5276
|
-
maxLoc
|
|
5277
|
-
);
|
|
6763
|
+
const symbols2 = getScopedDefinitions3(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
|
|
5278
6764
|
const results = [];
|
|
5279
6765
|
for (const sym of symbols2) {
|
|
5280
|
-
if (db.isIgnored(sym.
|
|
5281
|
-
const
|
|
5282
|
-
|
|
5283
|
-
startLine: sym.start_line,
|
|
5284
|
-
endLine: sym.end_line,
|
|
5285
|
-
symbolId: sym.id
|
|
5286
|
-
});
|
|
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;
|
|
5287
6769
|
const uniqueCallees = /* @__PURE__ */ new Map();
|
|
5288
6770
|
for (const c of callees) {
|
|
5289
6771
|
if (!uniqueCallees.has(c.symbol)) {
|
|
@@ -5295,10 +6777,10 @@ function passthroughCandidates(db, opts) {
|
|
|
5295
6777
|
results.push({
|
|
5296
6778
|
symbol: sym.symbol,
|
|
5297
6779
|
shortName: shortenSymbol(sym.symbol),
|
|
5298
|
-
file: sym.
|
|
5299
|
-
startLine: sym.
|
|
5300
|
-
endLine: sym.
|
|
5301
|
-
loc: sym
|
|
6780
|
+
file: sym.relativePath,
|
|
6781
|
+
startLine: sym.startLine,
|
|
6782
|
+
endLine: sym.endLine,
|
|
6783
|
+
loc: definitionLoc3(sym),
|
|
5302
6784
|
forwardsTo: callee.symbol,
|
|
5303
6785
|
forwardsToShort: shortenSymbol(callee.symbol),
|
|
5304
6786
|
forwardsToFile: callee.file
|
|
@@ -5307,50 +6789,34 @@ function passthroughCandidates(db, opts) {
|
|
|
5307
6789
|
results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
|
|
5308
6790
|
return results.slice(0, limit);
|
|
5309
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
|
+
}
|
|
5310
6806
|
|
|
5311
6807
|
// src/queries/stale-abstractions.ts
|
|
5312
6808
|
function staleAbstractions(db, opts) {
|
|
5313
6809
|
const { scope, minLoc = 3, limit = 30 } = opts ?? {};
|
|
5314
|
-
const
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
FROM mentions ref_m
|
|
5325
|
-
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
5326
|
-
WHERE ref_m.symbol_id = gs.id
|
|
5327
|
-
AND ref_m.role != 1
|
|
5328
|
-
AND ref_c.document_id != der.document_id
|
|
5329
|
-
) AS consumers
|
|
5330
|
-
FROM global_symbols gs
|
|
5331
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5332
|
-
JOIN documents d ON der.document_id = d.id
|
|
5333
|
-
WHERE 1 = 1
|
|
5334
|
-
${db.pathExclusionsFor("d")}
|
|
5335
|
-
AND ${testFileExclusionSql("d")}
|
|
5336
|
-
${db.symbolNoiseFor("gs")}
|
|
5337
|
-
-- Top-level type symbols: ends with # but does not contain nested #
|
|
5338
|
-
AND gs.symbol LIKE '%#'
|
|
5339
|
-
AND gs.symbol NOT LIKE '%#%#%'
|
|
5340
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
5341
|
-
${scopeFilter}
|
|
5342
|
-
) WHERE consumers <= 1
|
|
5343
|
-
ORDER BY loc DESC
|
|
5344
|
-
LIMIT ?`,
|
|
5345
|
-
minLoc,
|
|
5346
|
-
limit
|
|
5347
|
-
);
|
|
5348
|
-
return rows.filter((r) => !db.isIgnored(r.file)).filter((r) => {
|
|
5349
|
-
const basename2 = r.file.split("/").pop() ?? "";
|
|
5350
|
-
const isTypeFile = basename2.includes("types") || r.file.includes("/types/");
|
|
5351
|
-
if (isTypeFile && r.consumers > 0) return false;
|
|
5352
|
-
return true;
|
|
5353
|
-
}).map((r) => ({
|
|
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);
|
|
6818
|
+
const filesWithFunctions = getFilesWithFunctions(db, scope);
|
|
6819
|
+
return rows.filter((r) => !db.isIgnored(r.file)).filter((r) => isTrueStaleAbstraction(r, filesWithFunctions)).map((r) => ({
|
|
5354
6820
|
symbol: r.symbol,
|
|
5355
6821
|
shortName: shortenSymbol(r.symbol),
|
|
5356
6822
|
file: r.file,
|
|
@@ -5358,7 +6824,48 @@ function staleAbstractions(db, opts) {
|
|
|
5358
6824
|
endLine: r.end_line,
|
|
5359
6825
|
loc: r.loc,
|
|
5360
6826
|
consumers: r.consumers
|
|
5361
|
-
}));
|
|
6827
|
+
})).slice(0, limit);
|
|
6828
|
+
}
|
|
6829
|
+
function getFilesWithFunctions(db, scope) {
|
|
6830
|
+
return new Set(getScopedDefinitions4(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
|
|
6831
|
+
}
|
|
6832
|
+
function isTrueStaleAbstraction(row, filesWithFunctions) {
|
|
6833
|
+
const basename5 = row.file.split("/").pop() ?? "";
|
|
6834
|
+
const isTypeFile = basename5.includes("types") || row.file.includes("/types/");
|
|
6835
|
+
if (isTypeFile && row.consumers > 0) {
|
|
6836
|
+
return false;
|
|
6837
|
+
}
|
|
6838
|
+
if (row.consumers === 0 && filesWithFunctions.has(row.file)) {
|
|
6839
|
+
return false;
|
|
6840
|
+
}
|
|
6841
|
+
return true;
|
|
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;
|
|
5362
6869
|
}
|
|
5363
6870
|
|
|
5364
6871
|
// src/queries/complexity-hotspots.ts
|
|
@@ -5436,7 +6943,7 @@ function complexityHotspots(db, opts) {
|
|
|
5436
6943
|
minLoc,
|
|
5437
6944
|
limit
|
|
5438
6945
|
);
|
|
5439
|
-
|
|
6946
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.file)).map((r) => ({
|
|
5440
6947
|
symbol: r.symbol,
|
|
5441
6948
|
shortName: shortenSymbol(r.symbol),
|
|
5442
6949
|
file: r.file,
|
|
@@ -5450,6 +6957,31 @@ function complexityHotspots(db, opts) {
|
|
|
5450
6957
|
r.loc / 50 * (r.fan_in / 5) * Math.max(r.fan_out / 5, 1) * 100
|
|
5451
6958
|
) / 100
|
|
5452
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);
|
|
5453
6985
|
}
|
|
5454
6986
|
|
|
5455
6987
|
// src/queries/health.ts
|
|
@@ -5474,20 +7006,7 @@ function health(db, opts = {}) {
|
|
|
5474
7006
|
const trueIsolatedCount = isolatedResult.filter(
|
|
5475
7007
|
(s2) => !isEntrySurface(db, s2.relativePath)
|
|
5476
7008
|
).length;
|
|
5477
|
-
const
|
|
5478
|
-
db.all(
|
|
5479
|
-
`SELECT DISTINCT d.relative_path
|
|
5480
|
-
FROM global_symbols gs
|
|
5481
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
5482
|
-
JOIN documents d ON der.document_id = d.id
|
|
5483
|
-
WHERE gs.symbol LIKE '%().'
|
|
5484
|
-
${db.pathExclusionsFor("d")}`
|
|
5485
|
-
).map((r) => r.relative_path)
|
|
5486
|
-
);
|
|
5487
|
-
const trueStaleCount = staleResult.filter((s2) => {
|
|
5488
|
-
if (s2.consumers === 0 && filesWithFunctions.has(s2.file)) return false;
|
|
5489
|
-
return true;
|
|
5490
|
-
}).length;
|
|
7009
|
+
const trueStaleCount = staleResult.length;
|
|
5491
7010
|
const trueDriftCount = driftResult.results.length;
|
|
5492
7011
|
const trueSimilarCount = similarResult.length;
|
|
5493
7012
|
const actions = [];
|
|
@@ -5562,11 +7081,7 @@ function health(db, opts = {}) {
|
|
|
5562
7081
|
});
|
|
5563
7082
|
}
|
|
5564
7083
|
if (trueStaleCount > 0) {
|
|
5565
|
-
const
|
|
5566
|
-
if (s2.consumers === 0 && filesWithFunctions.has(s2.file)) return false;
|
|
5567
|
-
return true;
|
|
5568
|
-
});
|
|
5569
|
-
const unused = trueStaleSymbols.filter((s2) => s2.consumers === 0).length;
|
|
7084
|
+
const unused = staleResult.filter((s2) => s2.consumers === 0).length;
|
|
5570
7085
|
const singleUse = trueStaleCount - unused;
|
|
5571
7086
|
const parts = [];
|
|
5572
7087
|
if (unused > 0) parts.push(`${unused} unused`);
|
|
@@ -5577,7 +7092,7 @@ function health(db, opts = {}) {
|
|
|
5577
7092
|
effort: "low",
|
|
5578
7093
|
impact: "medium",
|
|
5579
7094
|
count: trueStaleCount,
|
|
5580
|
-
locRecoverable: staleResult.
|
|
7095
|
+
locRecoverable: staleResult.reduce((sum, r) => sum + r.loc, 0)
|
|
5581
7096
|
});
|
|
5582
7097
|
}
|
|
5583
7098
|
if (trueDriftCount > 0) {
|
|
@@ -5680,11 +7195,11 @@ function convergence(db, symbolPatternA, symbolPatternB) {
|
|
|
5680
7195
|
} else if (shared.length === 0) {
|
|
5681
7196
|
strategy = "These functions do not share any callees. They are not a callee-based consolidation candidate.";
|
|
5682
7197
|
} else if (uniqueA.length === 0 && uniqueB.length === 0) {
|
|
5683
|
-
strategy = "These functions have identical callee sets.
|
|
7198
|
+
strategy = "These functions have identical tracked callee sets. They are a strong structural match, but identical callees do not prove interchangeable semantics; inspect signatures, control flow, and return values before consolidating.";
|
|
5684
7199
|
} else if (uniqueA.length === 0) {
|
|
5685
|
-
strategy = `A
|
|
7200
|
+
strategy = `A's tracked callees are a subset of B's. B may subsume part of A's structure, but verify signatures, guards, and non-call logic before replacing A with B.`;
|
|
5686
7201
|
} else if (uniqueB.length === 0) {
|
|
5687
|
-
strategy = `B
|
|
7202
|
+
strategy = `B's tracked callees are a subset of A's. A may subsume part of B's structure, but verify signatures, guards, and non-call logic before replacing B with A.`;
|
|
5688
7203
|
} else if (uniqueA.length <= 2 && uniqueB.length <= 2) {
|
|
5689
7204
|
strategy = `Create a shared function with the ${shared.length} common callees. Pass the ${uniqueA.length + uniqueB.length} divergent callees as parameters or strategy callbacks.`;
|
|
5690
7205
|
} else {
|
|
@@ -5715,7 +7230,7 @@ function convergence(db, symbolPatternA, symbolPatternB) {
|
|
|
5715
7230
|
|
|
5716
7231
|
// src/queries/code.ts
|
|
5717
7232
|
import { readFileSync as readFileSync5 } from "fs";
|
|
5718
|
-
import { join as
|
|
7233
|
+
import { join as join9 } from "path";
|
|
5719
7234
|
function code(db, symbolPattern, opts = {}) {
|
|
5720
7235
|
const { context = 0 } = opts;
|
|
5721
7236
|
const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
|
|
@@ -5728,7 +7243,7 @@ function code(db, symbolPattern, opts = {}) {
|
|
|
5728
7243
|
`SELECT language FROM documents WHERE relative_path = ?`,
|
|
5729
7244
|
match.relativePath
|
|
5730
7245
|
);
|
|
5731
|
-
const filePath =
|
|
7246
|
+
const filePath = join9(db.config.projectRoot, match.relativePath);
|
|
5732
7247
|
let fileContent;
|
|
5733
7248
|
try {
|
|
5734
7249
|
fileContent = readFileSync5(filePath, "utf-8");
|
|
@@ -5758,7 +7273,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
5758
7273
|
resolvedPath
|
|
5759
7274
|
);
|
|
5760
7275
|
if (!doc) return null;
|
|
5761
|
-
const fullPath =
|
|
7276
|
+
const fullPath = join9(db.config.projectRoot, doc.relative_path);
|
|
5762
7277
|
let fileContent;
|
|
5763
7278
|
try {
|
|
5764
7279
|
fileContent = readFileSync5(fullPath, "utf-8");
|
|
@@ -5782,7 +7297,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
5782
7297
|
|
|
5783
7298
|
// src/queries/complexity.ts
|
|
5784
7299
|
import { readFileSync as readFileSync6 } from "fs";
|
|
5785
|
-
import { join as
|
|
7300
|
+
import { join as join10 } from "path";
|
|
5786
7301
|
function complexity(db, symbolPattern) {
|
|
5787
7302
|
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
5788
7303
|
if (!match) return null;
|
|
@@ -5791,7 +7306,7 @@ function complexity(db, symbolPattern) {
|
|
|
5791
7306
|
match.relativePath
|
|
5792
7307
|
);
|
|
5793
7308
|
const language = doc?.language ?? "unknown";
|
|
5794
|
-
const filePath =
|
|
7309
|
+
const filePath = join10(db.config.projectRoot, match.relativePath);
|
|
5795
7310
|
let source = "";
|
|
5796
7311
|
try {
|
|
5797
7312
|
const lines = readFileSync6(filePath, "utf-8").split("\n");
|
|
@@ -6131,46 +7646,100 @@ function redundantReexports(db, opts = {}) {
|
|
|
6131
7646
|
results.sort(
|
|
6132
7647
|
(a, b) => b.directConsumers - a.directConsumers || a.barrelFile.localeCompare(b.barrelFile) || a.shortName.localeCompare(b.shortName)
|
|
6133
7648
|
);
|
|
6134
|
-
|
|
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;
|
|
6135
7724
|
}
|
|
6136
7725
|
|
|
6137
7726
|
// src/queries/similar-signatures.ts
|
|
6138
7727
|
function similarSignatures(db, opts = {}) {
|
|
6139
7728
|
const { scope, minLoc = 1, limit } = opts;
|
|
6140
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
6141
|
-
const rows = db.all(
|
|
6142
|
-
`SELECT
|
|
6143
|
-
gs.symbol,
|
|
6144
|
-
d.relative_path,
|
|
6145
|
-
der.start_line,
|
|
6146
|
-
der.end_line,
|
|
6147
|
-
(der.end_line - der.start_line + 1) AS loc,
|
|
6148
|
-
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
6149
|
-
FROM global_symbols gs
|
|
6150
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
6151
|
-
JOIN documents d ON der.document_id = d.id
|
|
6152
|
-
WHERE gs.documentation IS NOT NULL
|
|
6153
|
-
AND gs.documentation != ''
|
|
6154
|
-
AND INSTR(gs.documentation, '|') > 0
|
|
6155
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
6156
|
-
${db.pathExclusionsFor("d")}
|
|
6157
|
-
${db.symbolNoiseFor("gs")}
|
|
6158
|
-
${scopeFilter}
|
|
6159
|
-
ORDER BY d.relative_path, der.start_line`,
|
|
6160
|
-
minLoc
|
|
6161
|
-
);
|
|
6162
7729
|
const sigGroups = /* @__PURE__ */ new Map();
|
|
6163
|
-
for (const
|
|
6164
|
-
if (db.isIgnored(
|
|
6165
|
-
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);
|
|
6166
7735
|
if (!normalized) continue;
|
|
6167
7736
|
const entry = {
|
|
6168
|
-
symbol:
|
|
6169
|
-
shortName: shortenSymbol(
|
|
6170
|
-
file:
|
|
6171
|
-
startLine:
|
|
6172
|
-
endLine:
|
|
6173
|
-
loc
|
|
7737
|
+
symbol: definition.symbol,
|
|
7738
|
+
shortName: shortenSymbol(definition.symbol),
|
|
7739
|
+
file: definition.relativePath,
|
|
7740
|
+
startLine: definition.startLine,
|
|
7741
|
+
endLine: definition.endLine,
|
|
7742
|
+
loc
|
|
6174
7743
|
};
|
|
6175
7744
|
const existing = sigGroups.get(normalized);
|
|
6176
7745
|
if (existing) {
|
|
@@ -6193,6 +7762,52 @@ function similarSignatures(db, opts = {}) {
|
|
|
6193
7762
|
});
|
|
6194
7763
|
return limit ? results.slice(0, limit) : results;
|
|
6195
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
|
+
}
|
|
6196
7811
|
function normalizeSignature(raw) {
|
|
6197
7812
|
if (!raw || !raw.trim()) return null;
|
|
6198
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();
|
|
@@ -6203,28 +7818,88 @@ function normalizeSignature(raw) {
|
|
|
6203
7818
|
if (sig.length < 3) return null;
|
|
6204
7819
|
return sig;
|
|
6205
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
|
+
}
|
|
6206
7875
|
|
|
6207
7876
|
// src/setup.ts
|
|
6208
7877
|
import {
|
|
6209
|
-
existsSync as
|
|
7878
|
+
existsSync as existsSync9,
|
|
6210
7879
|
mkdirSync as mkdirSync2,
|
|
6211
7880
|
symlinkSync,
|
|
6212
7881
|
readlinkSync,
|
|
6213
7882
|
unlinkSync
|
|
6214
7883
|
} from "fs";
|
|
6215
|
-
import { join as
|
|
7884
|
+
import { join as join11, dirname as dirname3, resolve as resolve3 } from "path";
|
|
6216
7885
|
import { homedir as homedir2, platform as platform3 } from "os";
|
|
6217
7886
|
import { fileURLToPath } from "url";
|
|
6218
7887
|
var IS_WINDOWS3 = platform3() === "win32";
|
|
6219
|
-
var
|
|
7888
|
+
var BUILTIN_SKILLS = [
|
|
7889
|
+
"concrete-plan",
|
|
7890
|
+
"scip-explore",
|
|
7891
|
+
"scip-debloat",
|
|
7892
|
+
"scip-verify",
|
|
7893
|
+
"scip-language-playbook"
|
|
7894
|
+
];
|
|
6220
7895
|
function installSkills(opts = {}) {
|
|
6221
7896
|
const log = opts.quiet ? () => {
|
|
6222
7897
|
} : console.log;
|
|
6223
7898
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6224
7899
|
const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
|
|
6225
7900
|
const targets = [
|
|
6226
|
-
|
|
6227
|
-
|
|
7901
|
+
join11(homedir2(), ".claude", "skills"),
|
|
7902
|
+
join11(homedir2(), ".codex", "skills")
|
|
6228
7903
|
];
|
|
6229
7904
|
const result = {
|
|
6230
7905
|
installed: [],
|
|
@@ -6233,19 +7908,19 @@ function installSkills(opts = {}) {
|
|
|
6233
7908
|
};
|
|
6234
7909
|
for (const targetDir of targets) {
|
|
6235
7910
|
const parentDir = dirname3(targetDir);
|
|
6236
|
-
if (!
|
|
7911
|
+
if (!existsSync9(parentDir)) {
|
|
6237
7912
|
continue;
|
|
6238
7913
|
}
|
|
6239
7914
|
mkdirSync2(targetDir, { recursive: true });
|
|
6240
7915
|
const toolName = targetDir.includes(".codex") ? "Codex" : "Claude";
|
|
6241
|
-
for (const skill of
|
|
6242
|
-
const source =
|
|
6243
|
-
const target =
|
|
6244
|
-
if (!
|
|
7916
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
7917
|
+
const source = join11(skillsSource, skill);
|
|
7918
|
+
const target = join11(targetDir, skill);
|
|
7919
|
+
if (!existsSync9(source)) {
|
|
6245
7920
|
result.skipped.push(`${toolName}/${skill}`);
|
|
6246
7921
|
continue;
|
|
6247
7922
|
}
|
|
6248
|
-
if (
|
|
7923
|
+
if (existsSync9(target)) {
|
|
6249
7924
|
try {
|
|
6250
7925
|
const existing = readlinkSync(target);
|
|
6251
7926
|
if (resolve3(existing) === resolve3(source)) {
|
|
@@ -6277,14 +7952,14 @@ function resolveProjectRoot() {
|
|
|
6277
7952
|
function resolveActiveDbPath(projectRoot) {
|
|
6278
7953
|
const config = loadProjectConfig(projectRoot);
|
|
6279
7954
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6280
|
-
return process.env["SCIP_QUERY_INDEX_DB"] ?? (
|
|
7955
|
+
return process.env["SCIP_QUERY_INDEX_DB"] ?? (existsSync10(paths.dbPath) ? paths.dbPath : join12(projectRoot, "index.db"));
|
|
6281
7956
|
}
|
|
6282
7957
|
function openDb() {
|
|
6283
7958
|
const projectRoot = resolveProjectRoot();
|
|
6284
7959
|
const config = loadProjectConfig(projectRoot);
|
|
6285
7960
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6286
7961
|
const dbPath = resolveActiveDbPath(projectRoot);
|
|
6287
|
-
if (!
|
|
7962
|
+
if (!existsSync10(dbPath)) {
|
|
6288
7963
|
console.error(`error: No index.db found. Run: scip-query reindex`);
|
|
6289
7964
|
process.exit(1);
|
|
6290
7965
|
}
|
|
@@ -6347,7 +8022,6 @@ var queries = {
|
|
|
6347
8022
|
isolated,
|
|
6348
8023
|
byKind,
|
|
6349
8024
|
kindCounts,
|
|
6350
|
-
docCoverage,
|
|
6351
8025
|
deepChains,
|
|
6352
8026
|
hierarchy,
|
|
6353
8027
|
callGraph,
|
|
@@ -6456,6 +8130,10 @@ program.command("trace <symbol>").description("Trace a symbol: definition + all
|
|
|
6456
8130
|
for (const d of result.definitions) {
|
|
6457
8131
|
const sig = d.signature ? ` \u2014 ${d.signature}` : "";
|
|
6458
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
|
+
}
|
|
6459
8137
|
}
|
|
6460
8138
|
console.log("\n\u2550\u2550\u2550 REFERENCED BY \u2550\u2550\u2550");
|
|
6461
8139
|
let prevFile = "";
|
|
@@ -6760,27 +8438,6 @@ program.command("kind-counts").description("Histogram of symbol kinds in the cod
|
|
|
6760
8438
|
}
|
|
6761
8439
|
);
|
|
6762
8440
|
});
|
|
6763
|
-
program.command("doc-coverage").description("Check documentation coverage across symbols").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC to consider", parseIntSafe, 3).option("-n, --limit <n>", "Max undocumented symbols to show", parseIntSafe, 50).action((opts) => {
|
|
6764
|
-
runQuery(
|
|
6765
|
-
(db) => queries.docCoverage(db, {
|
|
6766
|
-
scope: opts.scope,
|
|
6767
|
-
minLoc: opts.minLoc,
|
|
6768
|
-
limit: opts.limit
|
|
6769
|
-
}),
|
|
6770
|
-
(result) => {
|
|
6771
|
-
console.log(`Documentation coverage: ${result.coveragePercent}%`);
|
|
6772
|
-
console.log(` Total symbols: ${result.totalSymbols}`);
|
|
6773
|
-
console.log(` Documented: ${result.documented}`);
|
|
6774
|
-
console.log(` Undocumented: ${result.undocumented}`);
|
|
6775
|
-
if (result.undocumentedSymbols.length > 0) {
|
|
6776
|
-
console.log("\nUndocumented:");
|
|
6777
|
-
for (const s of result.undocumentedSymbols) {
|
|
6778
|
-
console.log(` ${s.relativePath}:${displayLine(s.startLine)} ${s.shortName}`);
|
|
6779
|
-
}
|
|
6780
|
-
}
|
|
6781
|
-
}
|
|
6782
|
-
);
|
|
6783
|
-
});
|
|
6784
8441
|
program.command("deep-chains").description("Find the longest transitive dependency chains").option("-n, --limit <n>", "Number of chains to show", parseIntSafe, 10).option("-s, --scope <path>", "Limit to files matching path").option("--min-depth <n>", "Minimum chain depth", parseIntSafe, 3).action((opts) => {
|
|
6785
8442
|
runQuery(
|
|
6786
8443
|
(db) => queries.deepChains(db, {
|
|
@@ -6883,7 +8540,7 @@ ${results.length} similar pair(s) found.`);
|
|
|
6883
8540
|
}
|
|
6884
8541
|
});
|
|
6885
8542
|
});
|
|
6886
|
-
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) => {
|
|
6887
8544
|
runQuery(
|
|
6888
8545
|
(db) => queries.similarFiles(db, {
|
|
6889
8546
|
minSimilarity: opts.minSimilarity,
|
|
@@ -7283,7 +8940,7 @@ ${result.connectedSymbols.length} connected symbol(s).`);
|
|
|
7283
8940
|
}
|
|
7284
8941
|
db.close();
|
|
7285
8942
|
});
|
|
7286
|
-
program.command("install-skills").description(
|
|
8943
|
+
program.command("install-skills").description(`Install skills (${BUILTIN_SKILLS.join(", ")}) into Claude Code and Codex`).action(() => {
|
|
7287
8944
|
const result = installSkills();
|
|
7288
8945
|
const total = result.installed.length + result.alreadyLinked.length;
|
|
7289
8946
|
console.log(`
|
|
@@ -7292,12 +8949,41 @@ ${result.installed.length} installed, ${result.alreadyLinked.length} already lin
|
|
|
7292
8949
|
console.log("Skills will be available in your next Claude Code / Codex session.");
|
|
7293
8950
|
}
|
|
7294
8951
|
});
|
|
7295
|
-
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;
|
|
7296
8954
|
if (isScipInstalled()) {
|
|
7297
8955
|
console.log("scip CLI: installed");
|
|
7298
8956
|
} else {
|
|
7299
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
|
+
}
|
|
7300
8985
|
}
|
|
8986
|
+
process.exitCode = hasProblems ? 1 : 0;
|
|
7301
8987
|
});
|
|
7302
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) => {
|
|
7303
8989
|
const db = openDb();
|
|
@@ -7385,8 +9071,8 @@ program.command("status").description("Show index status for this project").acti
|
|
|
7385
9071
|
if (dbPath !== paths.dbPath) {
|
|
7386
9072
|
console.log(`Config: ${paths.dbPath} (fallback to project root index.db)`);
|
|
7387
9073
|
}
|
|
7388
|
-
console.log(`Exists: ${
|
|
7389
|
-
if (
|
|
9074
|
+
console.log(`Exists: ${existsSync10(dbPath) ? "yes" : "no"}`);
|
|
9075
|
+
if (existsSync10(dbPath)) {
|
|
7390
9076
|
withDb((db) => {
|
|
7391
9077
|
const s = queries.stats(db);
|
|
7392
9078
|
console.log(`Symbols: ${s.symbols}`);
|