scip-query 0.1.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/IMPROVEMENTS.md +143 -0
- package/PLAN.md +320 -0
- package/README.md +1213 -0
- package/dist/chunk-2QZ23IBN.js +55 -0
- package/dist/chunk-2QZ23IBN.js.map +1 -0
- package/dist/chunk-36OMT7ZJ.js +144 -0
- package/dist/chunk-36OMT7ZJ.js.map +1 -0
- package/dist/chunk-3E2X7RIE.js +101 -0
- package/dist/chunk-3E2X7RIE.js.map +1 -0
- package/dist/chunk-3UOUTZQT.js +45 -0
- package/dist/chunk-3UOUTZQT.js.map +1 -0
- package/dist/chunk-3ZZJVBIO.js +88 -0
- package/dist/chunk-3ZZJVBIO.js.map +1 -0
- package/dist/chunk-4TYLS5XX.js +10 -0
- package/dist/chunk-4TYLS5XX.js.map +1 -0
- package/dist/chunk-5FGUEU7N.js +101 -0
- package/dist/chunk-5FGUEU7N.js.map +1 -0
- package/dist/chunk-5WTJAXY2.js +61 -0
- package/dist/chunk-5WTJAXY2.js.map +1 -0
- package/dist/chunk-6NBLIDF4.js +24 -0
- package/dist/chunk-6NBLIDF4.js.map +1 -0
- package/dist/chunk-6SXADWLW.js +43 -0
- package/dist/chunk-6SXADWLW.js.map +1 -0
- package/dist/chunk-6VJ6Q7IE.js +65 -0
- package/dist/chunk-6VJ6Q7IE.js.map +1 -0
- package/dist/chunk-7OZPA5OO.js +258 -0
- package/dist/chunk-7OZPA5OO.js.map +1 -0
- package/dist/chunk-BEPIEVLR.js +76 -0
- package/dist/chunk-BEPIEVLR.js.map +1 -0
- package/dist/chunk-BFSCMC22.js +42 -0
- package/dist/chunk-BFSCMC22.js.map +1 -0
- package/dist/chunk-BP2ATLK2.js +110 -0
- package/dist/chunk-BP2ATLK2.js.map +1 -0
- package/dist/chunk-CM454WL3.js +114 -0
- package/dist/chunk-CM454WL3.js.map +1 -0
- package/dist/chunk-DCKMSTJ4.js +74 -0
- package/dist/chunk-DCKMSTJ4.js.map +1 -0
- package/dist/chunk-DEZKCZXD.js +40 -0
- package/dist/chunk-DEZKCZXD.js.map +1 -0
- package/dist/chunk-DVWGWHFW.js +99 -0
- package/dist/chunk-DVWGWHFW.js.map +1 -0
- package/dist/chunk-EMDQWNYR.js +102 -0
- package/dist/chunk-EMDQWNYR.js.map +1 -0
- package/dist/chunk-FFSWWE5O.js +33 -0
- package/dist/chunk-FFSWWE5O.js.map +1 -0
- package/dist/chunk-FGXRVW7G.js +73 -0
- package/dist/chunk-FGXRVW7G.js.map +1 -0
- package/dist/chunk-FUHJCHS4.js +158 -0
- package/dist/chunk-FUHJCHS4.js.map +1 -0
- package/dist/chunk-GJFURBEW.js +64 -0
- package/dist/chunk-GJFURBEW.js.map +1 -0
- package/dist/chunk-GTILYBH6.js +102 -0
- package/dist/chunk-GTILYBH6.js.map +1 -0
- package/dist/chunk-JJP7KQND.js +1 -0
- package/dist/chunk-JJP7KQND.js.map +1 -0
- package/dist/chunk-JKP5GH6T.js +213 -0
- package/dist/chunk-JKP5GH6T.js.map +1 -0
- package/dist/chunk-KCBMVQL5.js +38 -0
- package/dist/chunk-KCBMVQL5.js.map +1 -0
- package/dist/chunk-KVSW5KYP.js +78 -0
- package/dist/chunk-KVSW5KYP.js.map +1 -0
- package/dist/chunk-LAWMH22O.js +172 -0
- package/dist/chunk-LAWMH22O.js.map +1 -0
- package/dist/chunk-LB7OS35Q.js +72 -0
- package/dist/chunk-LB7OS35Q.js.map +1 -0
- package/dist/chunk-LUSIFBXO.js +57 -0
- package/dist/chunk-LUSIFBXO.js.map +1 -0
- package/dist/chunk-MBVNHJVN.js +44 -0
- package/dist/chunk-MBVNHJVN.js.map +1 -0
- package/dist/chunk-MGNMHKX3.js +15 -0
- package/dist/chunk-MGNMHKX3.js.map +1 -0
- package/dist/chunk-N5KEREIA.js +41 -0
- package/dist/chunk-N5KEREIA.js.map +1 -0
- package/dist/chunk-NDSQYIWT.js +71 -0
- package/dist/chunk-NDSQYIWT.js.map +1 -0
- package/dist/chunk-NUZ4OMU3.js +28 -0
- package/dist/chunk-NUZ4OMU3.js.map +1 -0
- package/dist/chunk-QOV2R2WT.js +170 -0
- package/dist/chunk-QOV2R2WT.js.map +1 -0
- package/dist/chunk-SEFSL2GF.js +78 -0
- package/dist/chunk-SEFSL2GF.js.map +1 -0
- package/dist/chunk-T6ARFSBZ.js +103 -0
- package/dist/chunk-T6ARFSBZ.js.map +1 -0
- package/dist/chunk-TBP6BICL.js +46 -0
- package/dist/chunk-TBP6BICL.js.map +1 -0
- package/dist/chunk-TDNNOR6D.js +97 -0
- package/dist/chunk-TDNNOR6D.js.map +1 -0
- package/dist/chunk-TSPZOMHC.js +195 -0
- package/dist/chunk-TSPZOMHC.js.map +1 -0
- package/dist/chunk-UNTPVD36.js +55 -0
- package/dist/chunk-UNTPVD36.js.map +1 -0
- package/dist/chunk-VRUJH4BO.js +88 -0
- package/dist/chunk-VRUJH4BO.js.map +1 -0
- package/dist/chunk-VZ7AMAFL.js +76 -0
- package/dist/chunk-VZ7AMAFL.js.map +1 -0
- package/dist/chunk-XFXDXEUN.js +24 -0
- package/dist/chunk-XFXDXEUN.js.map +1 -0
- package/dist/chunk-YZAA4LYG.js +169 -0
- package/dist/chunk-YZAA4LYG.js.map +1 -0
- package/dist/chunk-Z73NYSBZ.js +92 -0
- package/dist/chunk-Z73NYSBZ.js.map +1 -0
- package/dist/chunk-ZJRYBOEE.js +125 -0
- package/dist/chunk-ZJRYBOEE.js.map +1 -0
- package/dist/cli.js +5798 -0
- package/dist/cli.js.map +1 -0
- package/dist/db-BxaevAyc.d.ts +683 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +1271 -0
- package/dist/index.js.map +1 -0
- package/dist/postinstall.js +167 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/queries/affected.d.ts +14 -0
- package/dist/queries/affected.js +9 -0
- package/dist/queries/affected.js.map +1 -0
- package/dist/queries/bottlenecks.d.ts +18 -0
- package/dist/queries/bottlenecks.js +8 -0
- package/dist/queries/bottlenecks.js.map +1 -0
- package/dist/queries/by-kind.d.ts +20 -0
- package/dist/queries/by-kind.js +10 -0
- package/dist/queries/by-kind.js.map +1 -0
- package/dist/queries/call-graph.d.ts +13 -0
- package/dist/queries/call-graph.js +9 -0
- package/dist/queries/call-graph.js.map +1 -0
- package/dist/queries/change-surface.d.ts +10 -0
- package/dist/queries/change-surface.js +9 -0
- package/dist/queries/change-surface.js.map +1 -0
- package/dist/queries/clean-signature.d.ts +9 -0
- package/dist/queries/clean-signature.js +7 -0
- package/dist/queries/clean-signature.js.map +1 -0
- package/dist/queries/code.d.ts +17 -0
- package/dist/queries/code.js +9 -0
- package/dist/queries/code.js.map +1 -0
- package/dist/queries/complexity-hotspots.d.ts +19 -0
- package/dist/queries/complexity-hotspots.js +9 -0
- package/dist/queries/complexity-hotspots.js.map +1 -0
- package/dist/queries/complexity.d.ts +13 -0
- package/dist/queries/complexity.js +9 -0
- package/dist/queries/complexity.js.map +1 -0
- package/dist/queries/convergence.d.ts +11 -0
- package/dist/queries/convergence.js +9 -0
- package/dist/queries/convergence.js.map +1 -0
- package/dist/queries/coupling.d.ts +17 -0
- package/dist/queries/coupling.js +9 -0
- package/dist/queries/coupling.js.map +1 -0
- package/dist/queries/cycles.d.ts +16 -0
- package/dist/queries/cycles.js +8 -0
- package/dist/queries/cycles.js.map +1 -0
- package/dist/queries/dataflow.d.ts +19 -0
- package/dist/queries/dataflow.js +9 -0
- package/dist/queries/dataflow.js.map +1 -0
- package/dist/queries/dead.d.ts +10 -0
- package/dist/queries/dead.js +9 -0
- package/dist/queries/dead.js.map +1 -0
- package/dist/queries/deep-chains.d.ts +16 -0
- package/dist/queries/deep-chains.js +8 -0
- package/dist/queries/deep-chains.js.map +1 -0
- package/dist/queries/deps.d.ts +9 -0
- package/dist/queries/deps.js +9 -0
- package/dist/queries/deps.js.map +1 -0
- package/dist/queries/diff-impact.d.ts +13 -0
- package/dist/queries/diff-impact.js +9 -0
- package/dist/queries/diff-impact.js.map +1 -0
- package/dist/queries/doc-coverage.d.ts +14 -0
- package/dist/queries/doc-coverage.js +8 -0
- package/dist/queries/doc-coverage.js.map +1 -0
- package/dist/queries/drift.d.ts +25 -0
- package/dist/queries/drift.js +8 -0
- package/dist/queries/drift.js.map +1 -0
- package/dist/queries/extract-candidates.d.ts +25 -0
- package/dist/queries/extract-candidates.js +9 -0
- package/dist/queries/extract-candidates.js.map +1 -0
- package/dist/queries/fan.d.ts +29 -0
- package/dist/queries/fan.js +14 -0
- package/dist/queries/fan.js.map +1 -0
- package/dist/queries/files.d.ts +6 -0
- package/dist/queries/files.js +7 -0
- package/dist/queries/files.js.map +1 -0
- package/dist/queries/health.d.ts +18 -0
- package/dist/queries/health.js +21 -0
- package/dist/queries/health.js.map +1 -0
- package/dist/queries/hierarchy.d.ts +13 -0
- package/dist/queries/hierarchy.js +8 -0
- package/dist/queries/hierarchy.js.map +1 -0
- package/dist/queries/hotspots.d.ts +13 -0
- package/dist/queries/hotspots.js +8 -0
- package/dist/queries/hotspots.js.map +1 -0
- package/dist/queries/imports.d.ts +19 -0
- package/dist/queries/imports.js +12 -0
- package/dist/queries/imports.js.map +1 -0
- package/dist/queries/index.d.ts +47 -0
- package/dist/queries/index.js +207 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/isolated.d.ts +14 -0
- package/dist/queries/isolated.js +9 -0
- package/dist/queries/isolated.js.map +1 -0
- package/dist/queries/members.d.ts +10 -0
- package/dist/queries/members.js +8 -0
- package/dist/queries/members.js.map +1 -0
- package/dist/queries/methods.d.ts +6 -0
- package/dist/queries/methods.js +8 -0
- package/dist/queries/methods.js.map +1 -0
- package/dist/queries/outline.d.ts +10 -0
- package/dist/queries/outline.js +8 -0
- package/dist/queries/outline.js.map +1 -0
- package/dist/queries/passthrough-candidates.d.ts +18 -0
- package/dist/queries/passthrough-candidates.js +9 -0
- package/dist/queries/passthrough-candidates.js.map +1 -0
- package/dist/queries/redundant-reexports.d.ts +22 -0
- package/dist/queries/redundant-reexports.js +8 -0
- package/dist/queries/redundant-reexports.js.map +1 -0
- package/dist/queries/refs.d.ts +6 -0
- package/dist/queries/refs.js +7 -0
- package/dist/queries/refs.js.map +1 -0
- package/dist/queries/similar-chains.d.ts +29 -0
- package/dist/queries/similar-chains.js +8 -0
- package/dist/queries/similar-chains.js.map +1 -0
- package/dist/queries/similar-files.d.ts +19 -0
- package/dist/queries/similar-files.js +8 -0
- package/dist/queries/similar-files.js.map +1 -0
- package/dist/queries/similar-signatures.d.ts +21 -0
- package/dist/queries/similar-signatures.js +8 -0
- package/dist/queries/similar-signatures.js.map +1 -0
- package/dist/queries/similar.d.ts +34 -0
- package/dist/queries/similar.js +11 -0
- package/dist/queries/similar.js.map +1 -0
- package/dist/queries/slice.d.ts +21 -0
- package/dist/queries/slice.js +9 -0
- package/dist/queries/slice.js.map +1 -0
- package/dist/queries/stale-abstractions.d.ts +18 -0
- package/dist/queries/stale-abstractions.js +9 -0
- package/dist/queries/stale-abstractions.js.map +1 -0
- package/dist/queries/stats.d.ts +6 -0
- package/dist/queries/stats.js +7 -0
- package/dist/queries/stats.js.map +1 -0
- package/dist/queries/surface.d.ts +7 -0
- package/dist/queries/surface.js +8 -0
- package/dist/queries/surface.js.map +1 -0
- package/dist/queries/symbols.d.ts +6 -0
- package/dist/queries/symbols.js +9 -0
- package/dist/queries/symbols.js.map +1 -0
- package/dist/queries/system.d.ts +7 -0
- package/dist/queries/system.js +9 -0
- package/dist/queries/system.js.map +1 -0
- package/dist/queries/test-coverage.d.ts +22 -0
- package/dist/queries/test-coverage.js +11 -0
- package/dist/queries/test-coverage.js.map +1 -0
- package/dist/queries/trace.d.ts +6 -0
- package/dist/queries/trace.js +8 -0
- package/dist/queries/trace.js.map +1 -0
- package/dist/queries/wrapper-candidates.d.ts +17 -0
- package/dist/queries/wrapper-candidates.js +9 -0
- package/dist/queries/wrapper-candidates.js.map +1 -0
- package/dist/reindex-worker.js +368 -0
- package/dist/reindex-worker.js.map +1 -0
- package/docs/AGENT_GUIDE.md +359 -0
- package/package.json +70 -0
- package/reports/debloat/2026-04-10-scip-query-self-audit.md +161 -0
- package/skills/concrete-plan/SKILL.md +318 -0
- package/skills/scip-debloat/SKILL.md +413 -0
- package/skills/scip-explore/SKILL.md +235 -0
- package/skills/scip-verify/SKILL.md +323 -0
- package/src/cli.ts +1480 -0
- package/src/config.ts +117 -0
- package/src/db.ts +127 -0
- package/src/gitignore-filter.ts +143 -0
- package/src/index.ts +11 -0
- package/src/postinstall.ts +8 -0
- package/src/queries/affected.ts +86 -0
- package/src/queries/bottlenecks.ts +67 -0
- package/src/queries/by-kind.ts +204 -0
- package/src/queries/call-graph.ts +66 -0
- package/src/queries/change-surface.ts +110 -0
- package/src/queries/clean-signature.ts +22 -0
- package/src/queries/code.ts +101 -0
- package/src/queries/complexity-hotspots.ts +119 -0
- package/src/queries/complexity.ts +152 -0
- package/src/queries/convergence.ts +82 -0
- package/src/queries/coupling.ts +99 -0
- package/src/queries/cycles.ts +78 -0
- package/src/queries/dataflow.ts +128 -0
- package/src/queries/dead.ts +122 -0
- package/src/queries/deep-chains.ts +59 -0
- package/src/queries/deps.ts +46 -0
- package/src/queries/diff-impact.ts +204 -0
- package/src/queries/doc-coverage.ts +86 -0
- package/src/queries/drift.ts +224 -0
- package/src/queries/extract-candidates.ts +167 -0
- package/src/queries/fan.ts +148 -0
- package/src/queries/files.ts +16 -0
- package/src/queries/health.ts +324 -0
- package/src/queries/hierarchy.ts +49 -0
- package/src/queries/hotspots.ts +53 -0
- package/src/queries/imports.ts +95 -0
- package/src/queries/index.ts +45 -0
- package/src/queries/isolated.ts +67 -0
- package/src/queries/members.ts +54 -0
- package/src/queries/methods.ts +27 -0
- package/src/queries/outline.ts +52 -0
- package/src/queries/passthrough-candidates.ts +94 -0
- package/src/queries/redundant-reexports.ts +170 -0
- package/src/queries/refs.ts +27 -0
- package/src/queries/similar-chains.ts +314 -0
- package/src/queries/similar-files.ts +140 -0
- package/src/queries/similar-signatures.ts +151 -0
- package/src/queries/similar.ts +305 -0
- package/src/queries/slice.ts +154 -0
- package/src/queries/stale-abstractions.ts +82 -0
- package/src/queries/stats.ts +22 -0
- package/src/queries/surface.ts +34 -0
- package/src/queries/symbols.ts +39 -0
- package/src/queries/system.ts +86 -0
- package/src/queries/test-coverage.ts +106 -0
- package/src/queries/trace.ts +55 -0
- package/src/queries/wrapper-candidates.ts +112 -0
- package/src/query-support.ts +226 -0
- package/src/reindex/detect.ts +58 -0
- package/src/reindex/index.ts +153 -0
- package/src/reindex/indexers.ts +220 -0
- package/src/reindex/install.ts +125 -0
- package/src/reindex-worker.ts +35 -0
- package/src/setup.ts +202 -0
- package/src/symbol-parser.ts +278 -0
- package/src/types.ts +654 -0
- package/src/watch.ts +274 -0
- package/tests/gitignore-filter.test.ts +48 -0
- package/tests/queries.test.ts +300 -0
- package/tests/symbol-parser.test.ts +157 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +40 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
testFileExclusionSql
|
|
3
|
+
} from "./chunk-FUHJCHS4.js";
|
|
4
|
+
import {
|
|
5
|
+
shortenSymbol
|
|
6
|
+
} from "./chunk-QOV2R2WT.js";
|
|
7
|
+
|
|
8
|
+
// src/queries/isolated.ts
|
|
9
|
+
function isolated(db, opts = {}) {
|
|
10
|
+
const { scope, minLoc = 3 } = opts;
|
|
11
|
+
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
12
|
+
const rows = db.all(
|
|
13
|
+
`SELECT
|
|
14
|
+
gs.symbol,
|
|
15
|
+
d.relative_path,
|
|
16
|
+
der.start_line,
|
|
17
|
+
der.end_line,
|
|
18
|
+
(der.end_line - der.start_line + 1) AS loc
|
|
19
|
+
FROM global_symbols gs
|
|
20
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
21
|
+
JOIN documents d ON der.document_id = d.id
|
|
22
|
+
WHERE 1 = 1
|
|
23
|
+
${db.pathExclusionsFor("d")}
|
|
24
|
+
AND ${testFileExclusionSql("d")}
|
|
25
|
+
${db.symbolNoiseFor("gs")}
|
|
26
|
+
AND gs.symbol NOT LIKE '%#%'
|
|
27
|
+
AND (der.end_line - der.start_line + 1) >= ?
|
|
28
|
+
${scopeFilter}
|
|
29
|
+
-- No cross-file references TO this symbol
|
|
30
|
+
AND NOT EXISTS (
|
|
31
|
+
SELECT 1 FROM mentions m
|
|
32
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
33
|
+
WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id != d.id
|
|
34
|
+
)
|
|
35
|
+
-- No same-file references either
|
|
36
|
+
AND NOT EXISTS (
|
|
37
|
+
SELECT 1 FROM mentions m
|
|
38
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
39
|
+
WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id = d.id
|
|
40
|
+
)
|
|
41
|
+
ORDER BY loc DESC, d.relative_path`,
|
|
42
|
+
minLoc
|
|
43
|
+
);
|
|
44
|
+
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
45
|
+
symbol: r.symbol,
|
|
46
|
+
shortName: shortenSymbol(r.symbol),
|
|
47
|
+
relativePath: r.relative_path,
|
|
48
|
+
startLine: r.start_line,
|
|
49
|
+
endLine: r.end_line,
|
|
50
|
+
loc: r.loc
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
isolated
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=chunk-LUSIFBXO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/isolated.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { IsolatedResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find isolated symbols: defined locally, referenced by nothing,\n * and referencing nothing external. These are truly orphaned code —\n * not just unused exports, but completely disconnected from the graph.\n */\nexport function isolated(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number } = {},\n): IsolatedResult[] {\n const { scope, minLoc = 3 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n -- No cross-file references TO this symbol\n AND NOT EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id != d.id\n )\n -- No same-file references either\n AND NOT EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id = d.id\n )\n ORDER BY loc DESC, d.relative_path`,\n minLoc,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n }));\n}\n"],"mappings":";;;;;;;;AAUO,SAAS,SACd,IACA,OAA4C,CAAC,GAC3B;AAClB,QAAM,EAAE,OAAO,SAAS,EAAE,IAAI;AAC9B,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAEtE,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,EACT,EAAE;AACN;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildFileDepGraph
|
|
3
|
+
} from "./chunk-FUHJCHS4.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/deep-chains.ts
|
|
6
|
+
function deepChains(db, opts = {}) {
|
|
7
|
+
const { limit = 10, scope, minDepth = 3 } = opts;
|
|
8
|
+
const graph = buildFileDepGraph(db, scope);
|
|
9
|
+
const results = [];
|
|
10
|
+
function dfs(node, path, visited) {
|
|
11
|
+
const neighbors = graph.get(node);
|
|
12
|
+
if (!neighbors || neighbors.size === 0) {
|
|
13
|
+
if (path.length >= minDepth) {
|
|
14
|
+
results.push({ chain: [...path], depth: path.length });
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let extended = false;
|
|
19
|
+
for (const next of neighbors) {
|
|
20
|
+
if (visited.has(next)) continue;
|
|
21
|
+
visited.add(next);
|
|
22
|
+
path.push(next);
|
|
23
|
+
dfs(next, path, visited);
|
|
24
|
+
path.pop();
|
|
25
|
+
visited.delete(next);
|
|
26
|
+
extended = true;
|
|
27
|
+
}
|
|
28
|
+
if (!extended && path.length >= minDepth) {
|
|
29
|
+
results.push({ chain: [...path], depth: path.length });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const startNode of graph.keys()) {
|
|
33
|
+
const visited = /* @__PURE__ */ new Set([startNode]);
|
|
34
|
+
dfs(startNode, [startNode], visited);
|
|
35
|
+
if (results.length > limit * 10) break;
|
|
36
|
+
}
|
|
37
|
+
results.sort((a, b) => b.depth - a.depth);
|
|
38
|
+
return results.slice(0, limit);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
deepChains
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=chunk-MBVNHJVN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/deep-chains.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { DeepChainResult } from '../types.js';\n\n/**\n * Find the longest transitive dependency chains between files.\n * A chain A → B → C → D means A depends on B, B on C, C on D.\n *\n * Long chains = high coupling depth = changes at the end ripple through many layers.\n */\nexport function deepChains(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string; minDepth?: number } = {},\n): DeepChainResult[] {\n const { limit = 10, scope, minDepth = 3 } = opts;\n const graph = buildFileDepGraph(db, scope);\n\n // DFS to find longest paths (with cycle detection)\n const results: DeepChainResult[] = [];\n\n function dfs(node: string, path: string[], visited: Set<string>): void {\n const neighbors = graph.get(node);\n if (!neighbors || neighbors.size === 0) {\n if (path.length >= minDepth) {\n results.push({ chain: [...path], depth: path.length });\n }\n return;\n }\n\n let extended = false;\n for (const next of neighbors) {\n if (visited.has(next)) continue; // skip cycles\n visited.add(next);\n path.push(next);\n dfs(next, path, visited);\n path.pop();\n visited.delete(next);\n extended = true;\n }\n\n // If no unvisited neighbors, this is a leaf in this path\n if (!extended && path.length >= minDepth) {\n results.push({ chain: [...path], depth: path.length });\n }\n }\n\n // Start DFS from each node\n for (const startNode of graph.keys()) {\n const visited = new Set<string>([startNode]);\n dfs(startNode, [startNode], visited);\n\n // Early termination if we have enough results\n if (results.length > limit * 10) break;\n }\n\n // Sort by depth descending, take top N\n results.sort((a, b) => b.depth - a.depth);\n return results.slice(0, limit);\n}\n"],"mappings":";;;;;AAUO,SAAS,WACd,IACA,OAA8D,CAAC,GAC5C;AACnB,QAAM,EAAE,QAAQ,IAAI,OAAO,WAAW,EAAE,IAAI;AAC5C,QAAM,QAAQ,kBAAkB,IAAI,KAAK;AAGzC,QAAM,UAA6B,CAAC;AAEpC,WAAS,IAAI,MAAc,MAAgB,SAA4B;AACrE,UAAM,YAAY,MAAM,IAAI,IAAI;AAChC,QAAI,CAAC,aAAa,UAAU,SAAS,GAAG;AACtC,UAAI,KAAK,UAAU,UAAU;AAC3B,gBAAQ,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,KAAK,OAAO,CAAC;AAAA,MACvD;AACA;AAAA,IACF;AAEA,QAAI,WAAW;AACf,eAAW,QAAQ,WAAW;AAC5B,UAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,cAAQ,IAAI,IAAI;AAChB,WAAK,KAAK,IAAI;AACd,UAAI,MAAM,MAAM,OAAO;AACvB,WAAK,IAAI;AACT,cAAQ,OAAO,IAAI;AACnB,iBAAW;AAAA,IACb;AAGA,QAAI,CAAC,YAAY,KAAK,UAAU,UAAU;AACxC,cAAQ,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,KAAK,OAAO,CAAC;AAAA,IACvD;AAAA,EACF;AAGA,aAAW,aAAa,MAAM,KAAK,GAAG;AACpC,UAAM,UAAU,oBAAI,IAAY,CAAC,SAAS,CAAC;AAC3C,QAAI,WAAW,CAAC,SAAS,GAAG,OAAO;AAGnC,QAAI,QAAQ,SAAS,QAAQ,GAAI;AAAA,EACnC;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxC,SAAO,QAAQ,MAAM,GAAG,KAAK;AAC/B;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/queries/files.ts
|
|
2
|
+
function files(db, pattern) {
|
|
3
|
+
const rows = db.all(
|
|
4
|
+
`SELECT relative_path FROM documents
|
|
5
|
+
WHERE relative_path LIKE ?
|
|
6
|
+
ORDER BY relative_path`,
|
|
7
|
+
`%${pattern}%`
|
|
8
|
+
);
|
|
9
|
+
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
files
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=chunk-MGNMHKX3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/files.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { FileResult } from '../types.js';\n\nexport function files(db: ScipDatabase, pattern: string): FileResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT relative_path FROM documents\n WHERE relative_path LIKE ?\n ORDER BY relative_path`,\n `%${pattern}%`,\n );\n\n // Apply gitignore filtering\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAAkB,SAA+B;AACrE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA,IAGA,IAAI,OAAO;AAAA,EACb;AAGA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;","names":[]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/queries/deps.ts
|
|
2
|
+
function deps(db, filePattern) {
|
|
3
|
+
const rows = db.all(
|
|
4
|
+
`SELECT DISTINCT d2.relative_path
|
|
5
|
+
FROM mentions m
|
|
6
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
7
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
8
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
9
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
10
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
11
|
+
WHERE d1.relative_path LIKE ?
|
|
12
|
+
AND d2.relative_path <> d1.relative_path
|
|
13
|
+
AND ${db.localSymbolPredicate}
|
|
14
|
+
ORDER BY d2.relative_path`,
|
|
15
|
+
`%${filePattern}%`
|
|
16
|
+
);
|
|
17
|
+
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
|
|
18
|
+
}
|
|
19
|
+
function rdeps(db, filePattern) {
|
|
20
|
+
const rows = db.all(
|
|
21
|
+
`SELECT DISTINCT d1.relative_path
|
|
22
|
+
FROM mentions m
|
|
23
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
24
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
25
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
26
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
27
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
28
|
+
WHERE d2.relative_path LIKE ?
|
|
29
|
+
AND d1.relative_path NOT LIKE ?
|
|
30
|
+
ORDER BY d1.relative_path`,
|
|
31
|
+
`%${filePattern}%`,
|
|
32
|
+
`%${filePattern}%`
|
|
33
|
+
);
|
|
34
|
+
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
deps,
|
|
39
|
+
rdeps
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=chunk-N5KEREIA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/deps.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { DepResult } from '../types.js';\n\n/** What internal files does this file depend on? (forward dependencies) */\nexport function deps(db: ScipDatabase, filePattern: string): DepResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d2.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.relative_path LIKE ?\n AND d2.relative_path <> d1.relative_path\n AND ${db.localSymbolPredicate}\n ORDER BY d2.relative_path`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n\n/** What files depend on this file/module? (reverse dependencies) */\nexport function rdeps(db: ScipDatabase, filePattern: string): DepResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d1.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n ORDER BY d1.relative_path`,\n `%${filePattern}%`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n"],"mappings":";AAIO,SAAS,KAAK,IAAkB,aAAkC;AACvE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA,IAE/B,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;AAGO,SAAS,MAAM,IAAkB,aAAkC;AACxE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,WAAW;AAAA,IACf,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;","names":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCalleeRowsForSymbol,
|
|
3
|
+
testFileExclusionSql
|
|
4
|
+
} from "./chunk-FUHJCHS4.js";
|
|
5
|
+
import {
|
|
6
|
+
shortenSymbol
|
|
7
|
+
} from "./chunk-QOV2R2WT.js";
|
|
8
|
+
|
|
9
|
+
// src/queries/passthrough-candidates.ts
|
|
10
|
+
function passthroughCandidates(db, opts) {
|
|
11
|
+
const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
|
|
12
|
+
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
13
|
+
const symbols = db.all(
|
|
14
|
+
`SELECT
|
|
15
|
+
gs.id,
|
|
16
|
+
gs.symbol,
|
|
17
|
+
der.document_id,
|
|
18
|
+
der.start_line,
|
|
19
|
+
der.end_line,
|
|
20
|
+
(der.end_line - der.start_line + 1) AS loc,
|
|
21
|
+
d.relative_path
|
|
22
|
+
FROM global_symbols gs
|
|
23
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
24
|
+
JOIN documents d ON der.document_id = d.id
|
|
25
|
+
WHERE 1 = 1
|
|
26
|
+
${db.pathExclusionsFor("d")}
|
|
27
|
+
AND ${testFileExclusionSql("d")}
|
|
28
|
+
${db.symbolNoiseFor("gs")}
|
|
29
|
+
AND (der.end_line - der.start_line + 1) >= 3
|
|
30
|
+
AND (der.end_line - der.start_line + 1) <= ?
|
|
31
|
+
${scopeFilter}
|
|
32
|
+
ORDER BY d.relative_path`,
|
|
33
|
+
maxLoc
|
|
34
|
+
);
|
|
35
|
+
const results = [];
|
|
36
|
+
for (const sym of symbols) {
|
|
37
|
+
if (db.isIgnored(sym.relative_path)) continue;
|
|
38
|
+
const callees = getCalleeRowsForSymbol(db, {
|
|
39
|
+
documentId: sym.document_id,
|
|
40
|
+
startLine: sym.start_line,
|
|
41
|
+
endLine: sym.end_line,
|
|
42
|
+
symbolId: sym.id
|
|
43
|
+
});
|
|
44
|
+
const uniqueCallees = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const c of callees) {
|
|
46
|
+
if (!uniqueCallees.has(c.symbol)) {
|
|
47
|
+
uniqueCallees.set(c.symbol, { symbol: c.symbol, file: c.file });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (uniqueCallees.size !== 1) continue;
|
|
51
|
+
const [, callee] = [...uniqueCallees.entries()][0];
|
|
52
|
+
results.push({
|
|
53
|
+
symbol: sym.symbol,
|
|
54
|
+
shortName: shortenSymbol(sym.symbol),
|
|
55
|
+
file: sym.relative_path,
|
|
56
|
+
startLine: sym.start_line,
|
|
57
|
+
endLine: sym.end_line,
|
|
58
|
+
loc: sym.loc,
|
|
59
|
+
forwardsTo: callee.symbol,
|
|
60
|
+
forwardsToShort: shortenSymbol(callee.symbol),
|
|
61
|
+
forwardsToFile: callee.file
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
|
|
65
|
+
return results.slice(0, limit);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
passthroughCandidates
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=chunk-NDSQYIWT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/passthrough-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { getCalleeRowsForSymbol, testFileExclusionSql } from '../query-support.js';\nimport type { PassthroughCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find passthrough candidates: functions that just forward to one\n * other function.\n *\n * A function with exactly 1 callee and small LOC is likely a thin\n * wrapper that adds no value — it just passes arguments through to\n * the real implementation.\n */\nexport function passthroughCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): PassthroughCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // 1. Find all non-trivial symbols with definition ranges (>= 3 LOC)\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n loc: number;\n relative_path: string;\n }>(\n `SELECT\n gs.id,\n gs.symbol,\n der.document_id,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= 3\n AND (der.end_line - der.start_line + 1) <= ?\n ${scopeFilter}\n ORDER BY d.relative_path`,\n maxLoc,\n );\n\n const results: PassthroughCandidate[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n // 2. Count callees for this symbol\n const callees = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n // Deduplicate by symbol (same callee may appear in multiple chunks)\n const uniqueCallees = new Map<string, { symbol: string; file: string }>();\n for (const c of callees) {\n if (!uniqueCallees.has(c.symbol)) {\n uniqueCallees.set(c.symbol, { symbol: c.symbol, file: c.file });\n }\n }\n\n // 3. Passthrough = exactly 1 unique callee\n if (uniqueCallees.size !== 1) continue;\n\n const [, callee] = [...uniqueCallees.entries()][0]!;\n\n results.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n file: sym.relative_path,\n startLine: sym.start_line,\n endLine: sym.end_line,\n loc: sym.loc,\n forwardsTo: callee.symbol,\n forwardsToShort: shortenSymbol(callee.symbol),\n forwardsToFile: callee.file,\n });\n }\n\n results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));\n return results.slice(0, limit);\n}\n"],"mappings":";;;;;;;;;AAaO,SAAS,sBACd,IACA,MACwB;AACxB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,UAAU,GAAG;AAAA,IASjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA,IAEf;AAAA,EACF;AAEA,QAAM,UAAkC,CAAC;AAEzC,aAAW,OAAO,SAAS;AACzB,QAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAGrC,UAAM,UAAU,uBAAuB,IAAI;AAAA,MACzC,YAAY,IAAI;AAAA,MAChB,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,UAAU,IAAI;AAAA,IAChB,CAAC;AAGD,UAAM,gBAAgB,oBAAI,IAA8C;AACxE,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,cAAc,IAAI,EAAE,MAAM,GAAG;AAChC,sBAAc,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,CAAC;AAAA,MAChE;AAAA,IACF;AAGA,QAAI,cAAc,SAAS,EAAG;AAE9B,UAAM,CAAC,EAAE,MAAM,IAAI,CAAC,GAAG,cAAc,QAAQ,CAAC,EAAE,CAAC;AAEjD,YAAQ,KAAK;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,KAAK,IAAI;AAAA,MACT,YAAY,OAAO;AAAA,MACnB,iBAAiB,cAAc,OAAO,MAAM;AAAA,MAC5C,gBAAgB,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAEA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACpE,SAAO,QAAQ,MAAM,GAAG,KAAK;AAC/B;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
leafName
|
|
3
|
+
} from "./chunk-QOV2R2WT.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/methods.ts
|
|
6
|
+
function methods(db, className) {
|
|
7
|
+
const rows = db.all(
|
|
8
|
+
`SELECT der.start_line, der.end_line, gs.symbol
|
|
9
|
+
FROM global_symbols gs
|
|
10
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
11
|
+
WHERE gs.symbol LIKE ?
|
|
12
|
+
AND ${db.localSymbolPredicate}
|
|
13
|
+
AND gs.symbol LIKE '%().%'
|
|
14
|
+
${db.symbolNoise}
|
|
15
|
+
ORDER BY der.start_line`,
|
|
16
|
+
`%${className}#%`
|
|
17
|
+
);
|
|
18
|
+
return rows.map((r) => ({
|
|
19
|
+
startLine: r.start_line,
|
|
20
|
+
endLine: r.end_line,
|
|
21
|
+
name: leafName(r.symbol)
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
methods
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-NUZ4OMU3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/methods.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { MethodResult } from '../types.js';\nimport { leafName } from '../symbol-parser.js';\n\nexport function methods(db: ScipDatabase, className: string): MethodResult[] {\n const rows = db.all<{\n start_line: number;\n end_line: number;\n symbol: string;\n }>(\n `SELECT der.start_line, der.end_line, gs.symbol\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n AND gs.symbol LIKE '%().%'\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${className}#%`,\n );\n\n return rows.map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n name: leafName(r.symbol),\n }));\n}\n"],"mappings":";;;;;AAIO,SAAS,QAAQ,IAAkB,WAAmC;AAC3E,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA,YAIQ,GAAG,oBAAoB;AAAA;AAAA,QAE3B,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,SAAS;AAAA,EACf;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,MAAM,SAAS,EAAE,MAAM;AAAA,EACzB,EAAE;AACJ;","names":[]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/symbol-parser.ts
|
|
2
|
+
var SUFFIX_MAP = {
|
|
3
|
+
"/": "namespace",
|
|
4
|
+
"#": "type",
|
|
5
|
+
".": "term",
|
|
6
|
+
"[": "type-param",
|
|
7
|
+
":": "meta",
|
|
8
|
+
"!": "macro"
|
|
9
|
+
};
|
|
10
|
+
function parseSymbol(raw) {
|
|
11
|
+
if (raw.startsWith("local ")) {
|
|
12
|
+
return { kind: "local", id: raw.slice(6), raw };
|
|
13
|
+
}
|
|
14
|
+
const parts = raw.split(" ");
|
|
15
|
+
if (parts.length < 4) {
|
|
16
|
+
return {
|
|
17
|
+
scheme: parts[0] ?? "",
|
|
18
|
+
manager: parts[1] ?? "",
|
|
19
|
+
packageName: parts[2] ?? "",
|
|
20
|
+
version: "",
|
|
21
|
+
descriptors: [],
|
|
22
|
+
raw
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const scheme = parts[0];
|
|
26
|
+
const manager = parts[1];
|
|
27
|
+
let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);
|
|
28
|
+
let packageName;
|
|
29
|
+
if (restAfterManager.startsWith("`")) {
|
|
30
|
+
const closingTick = restAfterManager.indexOf("`", 1);
|
|
31
|
+
if (closingTick === -1) {
|
|
32
|
+
packageName = restAfterManager.slice(1);
|
|
33
|
+
restAfterManager = "";
|
|
34
|
+
} else {
|
|
35
|
+
packageName = restAfterManager.slice(1, closingTick);
|
|
36
|
+
restAfterManager = restAfterManager.slice(closingTick + 2);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
const spaceIdx = restAfterManager.indexOf(" ");
|
|
40
|
+
if (spaceIdx === -1) {
|
|
41
|
+
packageName = restAfterManager;
|
|
42
|
+
restAfterManager = "";
|
|
43
|
+
} else {
|
|
44
|
+
packageName = restAfterManager.slice(0, spaceIdx);
|
|
45
|
+
restAfterManager = restAfterManager.slice(spaceIdx + 1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let version;
|
|
49
|
+
const versionSpaceIdx = restAfterManager.indexOf(" ");
|
|
50
|
+
if (versionSpaceIdx === -1) {
|
|
51
|
+
version = restAfterManager;
|
|
52
|
+
restAfterManager = "";
|
|
53
|
+
} else {
|
|
54
|
+
version = restAfterManager.slice(0, versionSpaceIdx);
|
|
55
|
+
restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);
|
|
56
|
+
}
|
|
57
|
+
const descriptors = parseDescriptors(restAfterManager);
|
|
58
|
+
return { scheme, manager, packageName, version, descriptors, raw };
|
|
59
|
+
}
|
|
60
|
+
function parseDescriptors(input) {
|
|
61
|
+
const descriptors = [];
|
|
62
|
+
let i = 0;
|
|
63
|
+
while (i < input.length) {
|
|
64
|
+
if (input[i] === "[") {
|
|
65
|
+
const closeBracket = input.indexOf("]", i + 1);
|
|
66
|
+
if (closeBracket === -1) {
|
|
67
|
+
descriptors.push({ name: input.slice(i + 1), suffix: "type-param" });
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: "type-param" });
|
|
71
|
+
i = closeBracket + 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (input[i] === "(" && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]))) {
|
|
75
|
+
const closeParen = input.indexOf(")", i + 1);
|
|
76
|
+
if (closeParen !== -1 && input[closeParen + 1] !== ".") {
|
|
77
|
+
descriptors.push({ name: input.slice(i + 1, closeParen), suffix: "parameter" });
|
|
78
|
+
i = closeParen + 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let name;
|
|
83
|
+
if (input[i] === "`") {
|
|
84
|
+
const closingTick = input.indexOf("`", i + 1);
|
|
85
|
+
if (closingTick === -1) {
|
|
86
|
+
name = input.slice(i + 1);
|
|
87
|
+
i = input.length;
|
|
88
|
+
descriptors.push({ name, suffix: "term" });
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
name = input.slice(i + 1, closingTick);
|
|
92
|
+
i = closingTick + 1;
|
|
93
|
+
} else {
|
|
94
|
+
const start = i;
|
|
95
|
+
while (i < input.length && !isSuffixChar(input[i])) {
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
name = input.slice(start, i);
|
|
99
|
+
}
|
|
100
|
+
if (i >= input.length) {
|
|
101
|
+
if (name) descriptors.push({ name, suffix: "term" });
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
const char = input[i];
|
|
105
|
+
if (char === "(") {
|
|
106
|
+
const closeParen = input.indexOf(")", i + 1);
|
|
107
|
+
if (closeParen !== -1 && input[closeParen + 1] === ".") {
|
|
108
|
+
descriptors.push({ name, suffix: "method" });
|
|
109
|
+
i = closeParen + 2;
|
|
110
|
+
} else if (closeParen !== -1) {
|
|
111
|
+
descriptors.push({ name, suffix: "method" });
|
|
112
|
+
i = closeParen + 1;
|
|
113
|
+
} else {
|
|
114
|
+
descriptors.push({ name, suffix: "term" });
|
|
115
|
+
i++;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
const suffix = SUFFIX_MAP[char];
|
|
119
|
+
if (suffix) {
|
|
120
|
+
descriptors.push({ name, suffix });
|
|
121
|
+
i += 1;
|
|
122
|
+
} else {
|
|
123
|
+
i += 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return descriptors;
|
|
128
|
+
}
|
|
129
|
+
function isSuffixChar(c) {
|
|
130
|
+
return c === "/" || c === "#" || c === "." || c === "(" || c === "[" || c === ":" || c === "!";
|
|
131
|
+
}
|
|
132
|
+
function shortenSymbol(raw) {
|
|
133
|
+
const parsed = parseSymbol(raw);
|
|
134
|
+
if ("kind" in parsed && parsed.kind === "local") {
|
|
135
|
+
return `local:${parsed.id}`;
|
|
136
|
+
}
|
|
137
|
+
const sym = parsed;
|
|
138
|
+
if (sym.descriptors.length === 0) return sym.raw;
|
|
139
|
+
const parts = [];
|
|
140
|
+
for (const desc of sym.descriptors) {
|
|
141
|
+
let name = desc.name;
|
|
142
|
+
if (desc.suffix === "namespace") {
|
|
143
|
+
name = name.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "").replace(/\.(py|pyi)$/, "").replace(/\.(rs)$/, "").replace(/\.(java|scala|kt|kts)$/, "").replace(/\.(rb)$/, "").replace(/\.(go)$/, "").replace(/\.(cs|vb)$/, "").replace(/\.(dart)$/, "").replace(/\.(php)$/, "").replace(/\.(c|cc|cpp|cxx|h|hpp)$/, "");
|
|
144
|
+
}
|
|
145
|
+
if (!name) continue;
|
|
146
|
+
if (desc.suffix === "method") {
|
|
147
|
+
parts.push(`${name}()`);
|
|
148
|
+
} else {
|
|
149
|
+
parts.push(name);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return parts.join(":");
|
|
153
|
+
}
|
|
154
|
+
function leafName(raw) {
|
|
155
|
+
const parsed = parseSymbol(raw);
|
|
156
|
+
if ("kind" in parsed && parsed.kind === "local") {
|
|
157
|
+
return parsed.id;
|
|
158
|
+
}
|
|
159
|
+
const sym = parsed;
|
|
160
|
+
if (sym.descriptors.length === 0) return "";
|
|
161
|
+
const last = sym.descriptors[sym.descriptors.length - 1];
|
|
162
|
+
return last.name;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export {
|
|
166
|
+
parseSymbol,
|
|
167
|
+
shortenSymbol,
|
|
168
|
+
leafName
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=chunk-QOV2R2WT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/symbol-parser.ts"],"sourcesContent":["import type { ScipSymbol, ScipDescriptor, ScipLocalSymbol, DescriptorSuffix } from './types.js';\n\n/**\n * SCIP Symbol Grammar (from the SCIP spec):\n *\n * <symbol> ::= <scheme> ' ' <package> ' ' <descriptor>+ | 'local ' <local-id>\n * <package> ::= <manager> ' ' <package-name> ' ' <version> ' '\n * <descriptor> ::= <name> <suffix>\n *\n * Suffix characters:\n * / namespace\n * # type (class, interface, enum)\n * . term (variable, field, property)\n * (). method\n * [ type parameter\n * () parameter\n * : meta\n * ! macro\n *\n * Names may be backtick-escaped: `some.weird/name`\n */\n\nconst SUFFIX_MAP: Record<string, DescriptorSuffix> = {\n '/': 'namespace',\n '#': 'type',\n '.': 'term',\n '[': 'type-param',\n ':': 'meta',\n '!': 'macro',\n};\n\n/**\n * Parse a SCIP symbol string into its structured components.\n * Works for any SCIP-indexed language (TypeScript, Java, Rust, Python, etc.)\n */\nexport function parseSymbol(raw: string): ScipSymbol | ScipLocalSymbol {\n if (raw.startsWith('local ')) {\n return { kind: 'local', id: raw.slice(6), raw };\n }\n\n // Split: <scheme> <manager> <package-name> <version> <descriptors...>\n // The tricky part: package-name can contain spaces if backtick-escaped\n const parts = raw.split(' ');\n if (parts.length < 4) {\n // Malformed — return a best-effort parse\n return {\n scheme: parts[0] ?? '',\n manager: parts[1] ?? '',\n packageName: parts[2] ?? '',\n version: '',\n descriptors: [],\n raw,\n };\n }\n\n const scheme = parts[0]!;\n const manager = parts[1]!;\n\n // Package name and version: package name might be backtick-escaped\n // After scheme + manager, we need to find package + version + descriptor string\n let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);\n\n // Parse package name (may be backtick-escaped)\n let packageName: string;\n if (restAfterManager.startsWith('`')) {\n const closingTick = restAfterManager.indexOf('`', 1);\n if (closingTick === -1) {\n packageName = restAfterManager.slice(1);\n restAfterManager = '';\n } else {\n packageName = restAfterManager.slice(1, closingTick);\n restAfterManager = restAfterManager.slice(closingTick + 2); // skip ` and space\n }\n } else {\n const spaceIdx = restAfterManager.indexOf(' ');\n if (spaceIdx === -1) {\n packageName = restAfterManager;\n restAfterManager = '';\n } else {\n packageName = restAfterManager.slice(0, spaceIdx);\n restAfterManager = restAfterManager.slice(spaceIdx + 1);\n }\n }\n\n // Parse version\n let version: string;\n const versionSpaceIdx = restAfterManager.indexOf(' ');\n if (versionSpaceIdx === -1) {\n version = restAfterManager;\n restAfterManager = '';\n } else {\n version = restAfterManager.slice(0, versionSpaceIdx);\n restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);\n }\n\n // Parse descriptors from the remaining string\n const descriptors = parseDescriptors(restAfterManager);\n\n return { scheme, manager, packageName, version, descriptors, raw };\n}\n\n/**\n * Parse the descriptor chain from a SCIP symbol.\n *\n * SCIP descriptor grammar:\n * namespace: name/\n * type: name#\n * term: name.\n * method: name(disambiguator).\n * type-param: [name] (bracket-wrapped, prefix syntax)\n * parameter: (name) (paren-wrapped, prefix syntax)\n * meta: name:\n * macro: name!\n *\n * Names can be backtick-escaped: `some/name.with.dots`\n */\nfunction parseDescriptors(input: string): ScipDescriptor[] {\n const descriptors: ScipDescriptor[] = [];\n let i = 0;\n\n while (i < input.length) {\n // Type parameter: [name]\n if (input[i] === '[') {\n const closeBracket = input.indexOf(']', i + 1);\n if (closeBracket === -1) {\n descriptors.push({ name: input.slice(i + 1), suffix: 'type-param' });\n break;\n }\n descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: 'type-param' });\n i = closeBracket + 1;\n continue;\n }\n\n // Parameter: (name) — only when ( appears at descriptor start with no preceding name\n if (input[i] === '(' && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]!))) {\n const closeParen = input.indexOf(')', i + 1);\n if (closeParen !== -1 && input[closeParen + 1] !== '.') {\n // This is a parameter (name), not a method disambiguator\n descriptors.push({ name: input.slice(i + 1, closeParen), suffix: 'parameter' });\n i = closeParen + 1;\n continue;\n }\n }\n\n let name: string;\n\n // Backtick-escaped name\n if (input[i] === '`') {\n const closingTick = input.indexOf('`', i + 1);\n if (closingTick === -1) {\n name = input.slice(i + 1);\n i = input.length;\n descriptors.push({ name, suffix: 'term' });\n break;\n }\n name = input.slice(i + 1, closingTick);\n i = closingTick + 1;\n } else {\n // Read name until we hit a suffix character\n const start = i;\n while (i < input.length && !isSuffixChar(input[i]!)) {\n i++;\n }\n name = input.slice(start, i);\n }\n\n // Parse suffix after name\n if (i >= input.length) {\n if (name) descriptors.push({ name, suffix: 'term' });\n break;\n }\n\n const char = input[i]!;\n\n // Method: name(disambiguator).\n if (char === '(') {\n const closeParen = input.indexOf(')', i + 1);\n if (closeParen !== -1 && input[closeParen + 1] === '.') {\n descriptors.push({ name, suffix: 'method' });\n i = closeParen + 2; // skip past ).\n } else if (closeParen !== -1) {\n // Bare (disambiguator) without . — treat as method anyway (common in practice)\n descriptors.push({ name, suffix: 'method' });\n i = closeParen + 1;\n } else {\n descriptors.push({ name, suffix: 'term' });\n i++;\n }\n } else {\n const suffix = SUFFIX_MAP[char];\n if (suffix) {\n descriptors.push({ name, suffix });\n i += 1;\n } else {\n i += 1; // Unknown suffix — skip\n }\n }\n }\n\n return descriptors;\n}\n\nfunction isSuffixChar(c: string): boolean {\n return c === '/' || c === '#' || c === '.' || c === '(' || c === '[' || c === ':' || c === '!';\n}\n\n/**\n * Convert a parsed SCIP symbol to a short, human-readable name.\n * Language-agnostic: works for any SCIP-indexed language.\n *\n * Examples:\n * \"scip-typescript npm @vega/api 0.1.3 src/modules/auth/auth.service.ts/AuthService#login().\"\n * → \"auth.service:AuthService:login()\"\n *\n * \"scip-java maven com.example/mylib 1.0.0 com/example/MyClass#doStuff().\"\n * → \"MyClass:doStuff()\"\n *\n * \"rust-analyzer cargo my-crate 0.1.0 src/lib.rs/MyStruct#new().\"\n * → \"lib:MyStruct:new()\"\n */\nexport function shortenSymbol(raw: string): string {\n const parsed = parseSymbol(raw);\n if ('kind' in parsed && parsed.kind === 'local') {\n return `local:${parsed.id}`;\n }\n\n const sym = parsed as ScipSymbol;\n if (sym.descriptors.length === 0) return sym.raw;\n\n const parts: string[] = [];\n for (const desc of sym.descriptors) {\n // Strip file extensions from namespace descriptors (the file path parts)\n let name = desc.name;\n if (desc.suffix === 'namespace') {\n // Remove common file extensions\n name = name\n .replace(/\\.(ts|tsx|js|jsx|mjs|cjs)$/, '')\n .replace(/\\.(py|pyi)$/, '')\n .replace(/\\.(rs)$/, '')\n .replace(/\\.(java|scala|kt|kts)$/, '')\n .replace(/\\.(rb)$/, '')\n .replace(/\\.(go)$/, '')\n .replace(/\\.(cs|vb)$/, '')\n .replace(/\\.(dart)$/, '')\n .replace(/\\.(php)$/, '')\n .replace(/\\.(c|cc|cpp|cxx|h|hpp)$/, '');\n }\n\n // Skip empty names (can happen with trailing suffixes)\n if (!name) continue;\n\n // For methods, append () for clarity\n if (desc.suffix === 'method') {\n parts.push(`${name}()`);\n } else {\n parts.push(name);\n }\n }\n\n return parts.join(':');\n}\n\n/**\n * Extract just the leaf name from a SCIP symbol.\n * Useful when you only need the function/class/variable name without path context.\n */\nexport function leafName(raw: string): string {\n const parsed = parseSymbol(raw);\n if ('kind' in parsed && parsed.kind === 'local') {\n return parsed.id;\n }\n\n const sym = parsed as ScipSymbol;\n if (sym.descriptors.length === 0) return '';\n\n const last = sym.descriptors[sym.descriptors.length - 1]!;\n return last.name;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
shortenSymbol
|
|
3
|
+
} from "./chunk-QOV2R2WT.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/similar-signatures.ts
|
|
6
|
+
function similarSignatures(db, opts = {}) {
|
|
7
|
+
const { scope, minLoc = 1, limit } = opts;
|
|
8
|
+
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
9
|
+
const rows = db.all(
|
|
10
|
+
`SELECT
|
|
11
|
+
gs.symbol,
|
|
12
|
+
d.relative_path,
|
|
13
|
+
der.start_line,
|
|
14
|
+
der.end_line,
|
|
15
|
+
(der.end_line - der.start_line + 1) AS loc,
|
|
16
|
+
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
17
|
+
FROM global_symbols gs
|
|
18
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
19
|
+
JOIN documents d ON der.document_id = d.id
|
|
20
|
+
WHERE gs.documentation IS NOT NULL
|
|
21
|
+
AND gs.documentation != ''
|
|
22
|
+
AND INSTR(gs.documentation, '|') > 0
|
|
23
|
+
AND (der.end_line - der.start_line + 1) >= ?
|
|
24
|
+
${db.pathExclusionsFor("d")}
|
|
25
|
+
${db.symbolNoiseFor("gs")}
|
|
26
|
+
${scopeFilter}
|
|
27
|
+
ORDER BY d.relative_path, der.start_line`,
|
|
28
|
+
minLoc
|
|
29
|
+
);
|
|
30
|
+
const sigGroups = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const row of rows) {
|
|
32
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
33
|
+
const normalized = normalizeSignature(row.sig);
|
|
34
|
+
if (!normalized) continue;
|
|
35
|
+
const entry = {
|
|
36
|
+
symbol: row.symbol,
|
|
37
|
+
shortName: shortenSymbol(row.symbol),
|
|
38
|
+
file: row.relative_path,
|
|
39
|
+
startLine: row.start_line,
|
|
40
|
+
endLine: row.end_line,
|
|
41
|
+
loc: row.loc
|
|
42
|
+
};
|
|
43
|
+
const existing = sigGroups.get(normalized);
|
|
44
|
+
if (existing) {
|
|
45
|
+
existing.push(entry);
|
|
46
|
+
} else {
|
|
47
|
+
sigGroups.set(normalized, [entry]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const results = [];
|
|
51
|
+
for (const [signature, functions] of sigGroups) {
|
|
52
|
+
if (functions.length < 2) continue;
|
|
53
|
+
results.push({ signature, functions });
|
|
54
|
+
}
|
|
55
|
+
results.sort((a, b) => {
|
|
56
|
+
const sizeDiff = b.functions.length - a.functions.length;
|
|
57
|
+
if (sizeDiff !== 0) return sizeDiff;
|
|
58
|
+
const locA = a.functions.reduce((sum, f) => sum + f.loc, 0);
|
|
59
|
+
const locB = b.functions.reduce((sum, f) => sum + f.loc, 0);
|
|
60
|
+
return locB - locA;
|
|
61
|
+
});
|
|
62
|
+
return limit ? results.slice(0, limit) : results;
|
|
63
|
+
}
|
|
64
|
+
function normalizeSignature(raw) {
|
|
65
|
+
if (!raw || !raw.trim()) return null;
|
|
66
|
+
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();
|
|
67
|
+
const parenIdx = sig.indexOf("(");
|
|
68
|
+
if (parenIdx === -1) return null;
|
|
69
|
+
sig = sig.slice(parenIdx);
|
|
70
|
+
sig = sig.replace(/\s+/g, "").toLowerCase();
|
|
71
|
+
if (sig.length < 3) return null;
|
|
72
|
+
return sig;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
similarSignatures
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=chunk-SEFSL2GF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/similar-signatures.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SimilarSignatureGroup } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with near-identical type signatures (same parameter types\n * and return type) but different names. These are \"same shape\" functions\n * that may be doing similar work even if their internal implementation differs.\n *\n * The SCIP `documentation` field often contains the full type signature\n * after a `|` delimiter. We parse it, normalize it (strip the function name,\n * whitespace, and case), then group by normalized signature.\n *\n * Groups with 2+ functions = same-shape candidates.\n */\nexport function similarSignatures(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number; limit?: number } = {},\n): SimilarSignatureGroup[] {\n const { scope, minLoc = 1, limit } = opts;\n\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Get all function-level symbols with their documentation/signature strings.\n // We use the same signature extraction pattern as symbols.ts / trace.ts.\n // Filter to symbols that have a documentation field containing '|' (the sig delimiter)\n // and whose signature contains '(' (indicating a callable).\n const rows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n sig: string;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.documentation IS NOT NULL\n AND gs.documentation != ''\n AND INSTR(gs.documentation, '|') > 0\n AND (der.end_line - der.start_line + 1) >= ?\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line`,\n minLoc,\n );\n\n // Group by normalized signature\n const sigGroups = new Map<string, Array<{\n symbol: string;\n shortName: string;\n file: string;\n startLine: number;\n endLine: number;\n loc: number;\n }>>();\n\n for (const row of rows) {\n if (db.isIgnored(row.relative_path)) continue;\n\n const normalized = normalizeSignature(row.sig);\n if (!normalized) continue;\n\n const entry = {\n symbol: row.symbol,\n shortName: shortenSymbol(row.symbol),\n file: row.relative_path,\n startLine: row.start_line,\n endLine: row.end_line,\n loc: row.loc,\n };\n\n const existing = sigGroups.get(normalized);\n if (existing) {\n existing.push(entry);\n } else {\n sigGroups.set(normalized, [entry]);\n }\n }\n\n // Collect groups with 2+ functions\n const results: SimilarSignatureGroup[] = [];\n\n for (const [signature, functions] of sigGroups) {\n if (functions.length < 2) continue;\n\n results.push({ signature, functions });\n }\n\n // Sort by group size descending (largest groups = most duplication),\n // then by total LOC in the group\n results.sort((a, b) => {\n const sizeDiff = b.functions.length - a.functions.length;\n if (sizeDiff !== 0) return sizeDiff;\n const locA = a.functions.reduce((sum, f) => sum + f.loc, 0);\n const locB = b.functions.reduce((sum, f) => sum + f.loc, 0);\n return locB - locA;\n });\n\n return limit ? results.slice(0, limit) : results;\n}\n\n/**\n * Normalize a signature for comparison:\n * 1. Clean markdown fences and SCIP prefixes\n * 2. Strip everything before the first '(' (removes the function name)\n * 3. Strip whitespace and lowercase\n *\n * Returns null if the signature doesn't contain a callable form.\n */\nfunction normalizeSignature(raw: string): string | null {\n if (!raw || !raw.trim()) return null;\n\n // Clean markdown and SCIP decoration (same as cleanSignature)\n let sig = raw\n .replace(/^```\\w*\\s*/, '')\n .replace(/\\s*```$/, '')\n .replace(/^\\(method\\)\\s*/, '')\n .replace(/^\\(property\\)\\s*/, '')\n .replace(/^\\(function\\)\\s*/, '')\n .replace(/^\\(class\\)\\s*/, '')\n .replace(/^\\(interface\\)\\s*/, '')\n .replace(/^\\(enum\\)\\s*/, '')\n .replace(/^\\(type alias\\)\\s*/, '')\n .replace(/^\\(const\\)\\s*/, '')\n .replace(/^\\(var\\)\\s*/, '')\n .trim();\n\n // Find the first '(' — everything from there is the parameter/return signature\n const parenIdx = sig.indexOf('(');\n if (parenIdx === -1) return null;\n\n sig = sig.slice(parenIdx);\n\n // Normalize: strip all whitespace, lowercase\n sig = sig.replace(/\\s+/g, '').toLowerCase();\n\n // Must have meaningful content after normalization\n if (sig.length < 3) return null; // e.g. \"()\" alone is too generic\n\n return sig;\n}\n"],"mappings":";;;;;AAeO,SAAS,kBACd,IACA,OAA4D,CAAC,GACpC;AACzB,QAAM,EAAE,OAAO,SAAS,GAAG,MAAM,IAAI;AAErC,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAMtE,QAAM,OAAO,GAAG;AAAA,IAQd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA,IAEf;AAAA,EACF;AAGA,QAAM,YAAY,oBAAI,IAOlB;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAErC,UAAM,aAAa,mBAAmB,IAAI,GAAG;AAC7C,QAAI,CAAC,WAAY;AAEjB,UAAM,QAAQ;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,KAAK,IAAI;AAAA,IACX;AAEA,UAAM,WAAW,UAAU,IAAI,UAAU;AACzC,QAAI,UAAU;AACZ,eAAS,KAAK,KAAK;AAAA,IACrB,OAAO;AACL,gBAAU,IAAI,YAAY,CAAC,KAAK,CAAC;AAAA,IACnC;AAAA,EACF;AAGA,QAAM,UAAmC,CAAC;AAE1C,aAAW,CAAC,WAAW,SAAS,KAAK,WAAW;AAC9C,QAAI,UAAU,SAAS,EAAG;AAE1B,YAAQ,KAAK,EAAE,WAAW,UAAU,CAAC;AAAA,EACvC;AAIA,UAAQ,KAAK,CAAC,GAAG,MAAM;AACrB,UAAM,WAAW,EAAE,UAAU,SAAS,EAAE,UAAU;AAClD,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAM,OAAO,EAAE,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAC1D,UAAM,OAAO,EAAE,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAC1D,WAAO,OAAO;AAAA,EAChB,CAAC;AAED,SAAO,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAC3C;AAUA,SAAS,mBAAmB,KAA4B;AACtD,MAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAG,QAAO;AAGhC,MAAI,MAAM,IACP,QAAQ,cAAc,EAAE,EACxB,QAAQ,WAAW,EAAE,EACrB,QAAQ,kBAAkB,EAAE,EAC5B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,gBAAgB,EAAE,EAC1B,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,eAAe,EAAE,EACzB,KAAK;AAGR,QAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,MAAI,aAAa,GAAI,QAAO;AAE5B,QAAM,IAAI,MAAM,QAAQ;AAGxB,QAAM,IAAI,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAG1C,MAAI,IAAI,SAAS,EAAG,QAAO;AAE3B,SAAO;AACT;","names":[]}
|