clast-ai 0.1.2 → 0.2.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/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +11 -9
- package/README.md +447 -401
- package/bench/ab/onair-concept.json +22 -0
- package/bench/ab/onair-deadcode.json +7 -0
- package/bench/ab/onair-hard.json +32 -0
- package/bench/ab/onair-impact.json +17 -0
- package/bench/ab/onair-refactors.json +18 -0
- package/bench/ab/onair-survey.json +17 -0
- package/bench/ab/onair-tasks.json +20 -0
- package/bench/ab/onair.json +52 -0
- package/bench/ab/vscode-concept.json +22 -0
- package/bench/ab/vscode-hard.json +32 -0
- package/bench/ab/vscode-impact.json +17 -0
- package/bench/ab/vscode-refactors.json +18 -0
- package/bench/ab/vscode-tasks.json +20 -0
- package/bench/ab/vscode.json +52 -0
- package/bench/ab.mjs +213 -0
- package/bench/bench.mjs +98 -0
- package/bench/resolution.mjs +75 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +257 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -2
- package/dist/config/defaults.js +38 -31
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +26 -14
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +73 -13
- package/dist/db/index.js +481 -39
- package/dist/db/index.js.map +1 -1
- package/dist/engine.d.ts +15 -0
- package/dist/engine.js +28 -0
- package/dist/engine.js.map +1 -0
- package/dist/indexer/imports.d.ts +6 -0
- package/dist/indexer/imports.js +254 -0
- package/dist/indexer/imports.js.map +1 -0
- package/dist/indexer/index.d.ts +63 -0
- package/dist/indexer/index.js +360 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/process.d.ts +22 -0
- package/dist/indexer/process.js +42 -0
- package/dist/indexer/process.js.map +1 -0
- package/dist/indexer/scan.d.ts +43 -0
- package/dist/indexer/scan.js +223 -0
- package/dist/indexer/scan.js.map +1 -0
- package/dist/indexer/worker.d.ts +1 -0
- package/dist/indexer/worker.js +23 -0
- package/dist/indexer/worker.js.map +1 -0
- package/dist/llm/comment-generator.d.ts +17 -19
- package/dist/llm/comment-generator.js +46 -80
- package/dist/llm/comment-generator.js.map +1 -1
- package/dist/llm/index.d.ts +0 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/prompts.d.ts +0 -1
- package/dist/llm/prompts.js +16 -16
- package/dist/parser/extractor.d.ts +21 -16
- package/dist/parser/extractor.js +700 -228
- package/dist/parser/extractor.js.map +1 -1
- package/dist/parser/index.d.ts +17 -21
- package/dist/parser/index.js +115 -171
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/langs/c.d.ts +3 -0
- package/dist/parser/langs/c.js +879 -0
- package/dist/parser/langs/c.js.map +1 -0
- package/dist/parser/langs/csharp.d.ts +2 -0
- package/dist/parser/langs/csharp.js +548 -0
- package/dist/parser/langs/csharp.js.map +1 -0
- package/dist/parser/langs/dart.d.ts +2 -0
- package/dist/parser/langs/dart.js +893 -0
- package/dist/parser/langs/dart.js.map +1 -0
- package/dist/parser/langs/go.d.ts +2 -0
- package/dist/parser/langs/go.js +455 -0
- package/dist/parser/langs/go.js.map +1 -0
- package/dist/parser/langs/java.d.ts +2 -0
- package/dist/parser/langs/java.js +390 -0
- package/dist/parser/langs/java.js.map +1 -0
- package/dist/parser/langs/php.d.ts +2 -0
- package/dist/parser/langs/php.js +486 -0
- package/dist/parser/langs/php.js.map +1 -0
- package/dist/parser/langs/python.d.ts +2 -0
- package/dist/parser/langs/python.js +216 -0
- package/dist/parser/langs/python.js.map +1 -0
- package/dist/parser/langs/ruby.d.ts +2 -0
- package/dist/parser/langs/ruby.js +602 -0
- package/dist/parser/langs/ruby.js.map +1 -0
- package/dist/parser/langs/rust.d.ts +2 -0
- package/dist/parser/langs/rust.js +931 -0
- package/dist/parser/langs/rust.js.map +1 -0
- package/dist/parser/langs/swift.d.ts +2 -0
- package/dist/parser/langs/swift.js +616 -0
- package/dist/parser/langs/swift.js.map +1 -0
- package/dist/parser/langs/typescript.d.ts +4 -0
- package/dist/parser/langs/typescript.js +535 -0
- package/dist/parser/langs/typescript.js.map +1 -0
- package/dist/parser/registry.d.ts +16 -0
- package/dist/parser/registry.js +87 -0
- package/dist/parser/registry.js.map +1 -0
- package/dist/parser/returns.d.ts +9 -0
- package/dist/parser/returns.js +152 -0
- package/dist/parser/returns.js.map +1 -0
- package/dist/parser/spec.d.ts +165 -0
- package/dist/parser/spec.js +188 -0
- package/dist/parser/spec.js.map +1 -0
- package/dist/query/arity.d.ts +27 -0
- package/dist/query/arity.js +187 -0
- package/dist/query/arity.js.map +1 -0
- package/dist/query/callgraph.d.ts +113 -0
- package/dist/query/callgraph.js +332 -0
- package/dist/query/callgraph.js.map +1 -0
- package/dist/query/format.d.ts +36 -0
- package/dist/query/format.js +136 -0
- package/dist/query/format.js.map +1 -0
- package/dist/query/hierarchy.d.ts +44 -0
- package/dist/query/hierarchy.js +441 -0
- package/dist/query/hierarchy.js.map +1 -0
- package/dist/query/resolve.d.ts +265 -0
- package/dist/query/resolve.js +1957 -0
- package/dist/query/resolve.js.map +1 -0
- package/dist/query/search.d.ts +35 -0
- package/dist/query/search.js +379 -0
- package/dist/query/search.js.map +1 -0
- package/dist/query/words.d.ts +30 -0
- package/dist/query/words.js +201 -0
- package/dist/query/words.js.map +1 -0
- package/dist/server/index.d.ts +1 -3
- package/dist/server/index.js +313 -73
- package/dist/server/index.js.map +1 -1
- package/dist/tools/context.d.ts +35 -0
- package/dist/tools/context.js +79 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/explore.d.ts +51 -0
- package/dist/tools/explore.js +779 -0
- package/dist/tools/explore.js.map +1 -0
- package/dist/tools/impact.d.ts +21 -0
- package/dist/tools/impact.js +743 -0
- package/dist/tools/impact.js.map +1 -0
- package/dist/tools/search.d.ts +59 -0
- package/dist/tools/search.js +843 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/stats.d.ts +26 -0
- package/dist/tools/stats.js +604 -0
- package/dist/tools/stats.js.map +1 -0
- package/dist/types.d.ts +95 -40
- package/dist/types.js +9 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher/index.d.ts +18 -16
- package/dist/watcher/index.js +94 -77
- package/dist/watcher/index.js.map +1 -1
- package/package.json +21 -24
- package/plugin/server.mjs +50 -0
- package/skills/audit/SKILL.md +37 -0
- package/skills/deadcode/SKILL.md +33 -0
- package/skills/explore/SKILL.md +54 -0
- package/skills/impact/SKILL.md +24 -0
- package/skills/status/SKILL.md +11 -0
- package/src/cli.ts +256 -0
- package/src/config/defaults.ts +50 -32
- package/src/config/index.ts +23 -14
- package/src/db/index.ts +540 -43
- package/src/engine.ts +30 -0
- package/src/indexer/imports.ts +241 -0
- package/src/indexer/index.ts +369 -0
- package/src/indexer/process.ts +63 -0
- package/src/indexer/scan.ts +226 -0
- package/src/indexer/worker.ts +24 -0
- package/src/llm/comment-generator.ts +52 -103
- package/src/llm/index.ts +80 -80
- package/src/llm/prompts.ts +95 -95
- package/src/parser/extractor.ts +643 -256
- package/src/parser/index.ts +105 -205
- package/src/parser/langs/c.ts +860 -0
- package/src/parser/langs/csharp.ts +519 -0
- package/src/parser/langs/dart.ts +820 -0
- package/src/parser/langs/go.ts +437 -0
- package/src/parser/langs/java.ts +361 -0
- package/src/parser/langs/php.ts +450 -0
- package/src/parser/langs/python.ts +206 -0
- package/src/parser/langs/ruby.ts +554 -0
- package/src/parser/langs/rust.ts +904 -0
- package/src/parser/langs/swift.ts +590 -0
- package/src/parser/langs/typescript.ts +499 -0
- package/src/parser/registry.ts +88 -0
- package/src/parser/returns.ts +137 -0
- package/src/parser/spec.ts +309 -0
- package/src/query/arity.ts +173 -0
- package/src/query/callgraph.ts +473 -0
- package/src/query/format.ts +136 -0
- package/src/query/hierarchy.ts +447 -0
- package/src/query/resolve.ts +1972 -0
- package/src/query/search.ts +430 -0
- package/src/query/words.ts +183 -0
- package/src/server/index.ts +390 -79
- package/src/tools/context.ts +82 -0
- package/src/tools/explore.ts +860 -0
- package/src/tools/impact.ts +748 -0
- package/src/tools/search.ts +886 -0
- package/src/tools/stats.ts +633 -0
- package/src/types.ts +113 -45
- package/src/version.ts +2 -0
- package/src/watcher/index.ts +82 -88
- package/tsconfig.json +7 -4
- package/.mcp.json +0 -8
- package/commands/clast-status.md +0 -9
- package/dist/config/defaults.d.ts.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/queries.d.ts +0 -44
- package/dist/db/queries.d.ts.map +0 -1
- package/dist/db/queries.js +0 -251
- package/dist/db/queries.js.map +0 -1
- package/dist/db/schema.d.ts +0 -3
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js +0 -94
- package/dist/db/schema.js.map +0 -1
- package/dist/db/types.d.ts +0 -44
- package/dist/db/types.d.ts.map +0 -1
- package/dist/db/types.js +0 -2
- package/dist/db/types.js.map +0 -1
- package/dist/llm/comment-generator.d.ts.map +0 -1
- package/dist/llm/index.d.ts.map +0 -1
- package/dist/llm/prompts.d.ts.map +0 -1
- package/dist/parser/call-resolver.d.ts +0 -15
- package/dist/parser/call-resolver.d.ts.map +0 -1
- package/dist/parser/call-resolver.js +0 -16
- package/dist/parser/call-resolver.js.map +0 -1
- package/dist/parser/extractor.d.ts.map +0 -1
- package/dist/parser/index.d.ts.map +0 -1
- package/dist/parser/languages.d.ts +0 -11
- package/dist/parser/languages.d.ts.map +0 -1
- package/dist/parser/languages.js +0 -74
- package/dist/parser/languages.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/register-tools.d.ts +0 -7
- package/dist/server/register-tools.d.ts.map +0 -1
- package/dist/server/register-tools.js +0 -17
- package/dist/server/register-tools.js.map +0 -1
- package/dist/server/tools/clast-call-graph.d.ts +0 -4
- package/dist/server/tools/clast-call-graph.d.ts.map +0 -1
- package/dist/server/tools/clast-call-graph.js +0 -70
- package/dist/server/tools/clast-call-graph.js.map +0 -1
- package/dist/server/tools/clast-comment.d.ts +0 -5
- package/dist/server/tools/clast-comment.d.ts.map +0 -1
- package/dist/server/tools/clast-comment.js +0 -64
- package/dist/server/tools/clast-comment.js.map +0 -1
- package/dist/server/tools/clast-file-summary.d.ts +0 -4
- package/dist/server/tools/clast-file-summary.d.ts.map +0 -1
- package/dist/server/tools/clast-file-summary.js +0 -42
- package/dist/server/tools/clast-file-summary.js.map +0 -1
- package/dist/server/tools/clast-get-context.d.ts +0 -4
- package/dist/server/tools/clast-get-context.d.ts.map +0 -1
- package/dist/server/tools/clast-get-context.js +0 -80
- package/dist/server/tools/clast-get-context.js.map +0 -1
- package/dist/server/tools/clast-reindex.d.ts +0 -5
- package/dist/server/tools/clast-reindex.d.ts.map +0 -1
- package/dist/server/tools/clast-reindex.js +0 -36
- package/dist/server/tools/clast-reindex.js.map +0 -1
- package/dist/server/tools/clast-search.d.ts +0 -4
- package/dist/server/tools/clast-search.d.ts.map +0 -1
- package/dist/server/tools/clast-search.js +0 -29
- package/dist/server/tools/clast-search.js.map +0 -1
- package/dist/server/tools/clast-status.d.ts +0 -5
- package/dist/server/tools/clast-status.d.ts.map +0 -1
- package/dist/server/tools/clast-status.js +0 -23
- package/dist/server/tools/clast-status.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/watcher/gitignore.d.ts +0 -9
- package/dist/watcher/gitignore.d.ts.map +0 -1
- package/dist/watcher/gitignore.js +0 -38
- package/dist/watcher/gitignore.js.map +0 -1
- package/dist/watcher/index.d.ts.map +0 -1
- package/skills/clast-explore/SKILL.md +0 -41
- package/src/db/queries.ts +0 -332
- package/src/db/schema.ts +0 -106
- package/src/db/types.ts +0 -47
- package/src/parser/call-resolver.ts +0 -17
- package/src/parser/languages.ts +0 -86
- package/src/server/register-tools.ts +0 -30
- package/src/server/tools/clast-call-graph.ts +0 -82
- package/src/server/tools/clast-comment.ts +0 -76
- package/src/server/tools/clast-file-summary.ts +0 -59
- package/src/server/tools/clast-get-context.ts +0 -97
- package/src/server/tools/clast-reindex.ts +0 -52
- package/src/server/tools/clast-search.ts +0 -38
- package/src/server/tools/clast-status.ts +0 -37
- package/src/watcher/gitignore.ts +0 -42
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** Languages whose signatures are checked. Others (PHP, Ruby, JavaScript, Swift, Dart) accept any count. */
|
|
2
|
+
export const ARITY_LANGS = new Set(['java', 'csharp', 'go', 'c', 'cpp', 'typescript', 'tsx', 'python', 'rust']);
|
|
3
|
+
function escapeRe(s) {
|
|
4
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
5
|
+
}
|
|
6
|
+
/** Index of the `)` matching the `(` at `open`, or -1. Skips string literals. */
|
|
7
|
+
function closing(sig, open) {
|
|
8
|
+
let depth = 0;
|
|
9
|
+
for (let i = open; i < sig.length; i++) {
|
|
10
|
+
const ch = sig[i];
|
|
11
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
12
|
+
const end = sig.indexOf(ch, i + 1);
|
|
13
|
+
if (end === -1)
|
|
14
|
+
return -1;
|
|
15
|
+
i = end;
|
|
16
|
+
}
|
|
17
|
+
else if (ch === '(')
|
|
18
|
+
depth++;
|
|
19
|
+
else if (ch === ')' && --depth === 0)
|
|
20
|
+
return i;
|
|
21
|
+
}
|
|
22
|
+
return -1;
|
|
23
|
+
}
|
|
24
|
+
/** Split a parameter list on top-level commas. */
|
|
25
|
+
function splitParams(list) {
|
|
26
|
+
const out = [];
|
|
27
|
+
let depth = 0;
|
|
28
|
+
let start = 0;
|
|
29
|
+
for (let i = 0; i < list.length; i++) {
|
|
30
|
+
const ch = list[i];
|
|
31
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
32
|
+
const end = list.indexOf(ch, i + 1);
|
|
33
|
+
if (end === -1)
|
|
34
|
+
break;
|
|
35
|
+
i = end;
|
|
36
|
+
}
|
|
37
|
+
else if (ch === '(' || ch === '[' || ch === '{' || ch === '<')
|
|
38
|
+
depth++;
|
|
39
|
+
else if (ch === ')' || ch === ']' || ch === '}')
|
|
40
|
+
depth--;
|
|
41
|
+
else if (ch === '>') {
|
|
42
|
+
// Not the arrow of `=>` / `->`.
|
|
43
|
+
const prev = list[i - 1];
|
|
44
|
+
if (prev !== '=' && prev !== '-')
|
|
45
|
+
depth--;
|
|
46
|
+
}
|
|
47
|
+
else if (ch === ',' && depth <= 0) {
|
|
48
|
+
out.push(list.slice(start, i));
|
|
49
|
+
start = i + 1;
|
|
50
|
+
depth = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
out.push(list.slice(start));
|
|
54
|
+
return out.map(p => p.trim()).filter(Boolean);
|
|
55
|
+
}
|
|
56
|
+
/** True when the parameter has a default value (`x = 1`, `x: int = 1`) at its top level. */
|
|
57
|
+
function hasDefault(param) {
|
|
58
|
+
let depth = 0;
|
|
59
|
+
for (let i = 0; i < param.length; i++) {
|
|
60
|
+
const ch = param[i];
|
|
61
|
+
if (ch === '(' || ch === '[' || ch === '{' || ch === '<')
|
|
62
|
+
depth++;
|
|
63
|
+
else if (ch === ')' || ch === ']' || ch === '}' || (ch === '>' && param[i - 1] !== '=' && param[i - 1] !== '-'))
|
|
64
|
+
depth--;
|
|
65
|
+
else if (ch === '=' && depth <= 0) {
|
|
66
|
+
const next = param[i + 1];
|
|
67
|
+
const prev = param[i - 1];
|
|
68
|
+
if (next !== '=' && next !== '>' && prev !== '=' && prev !== '!' && prev !== '<' && prev !== '>')
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
export function paramRange(signature, name, lang) {
|
|
75
|
+
if (!ARITY_LANGS.has(lang) || !signature || !name)
|
|
76
|
+
return null;
|
|
77
|
+
let sig = signature;
|
|
78
|
+
if (lang === 'go') {
|
|
79
|
+
// func (r *T) Name(...): skip the receiver.
|
|
80
|
+
const recv = /^func\s*\(/.exec(sig);
|
|
81
|
+
if (recv) {
|
|
82
|
+
const end = closing(sig, recv[0].length - 1);
|
|
83
|
+
if (end === -1)
|
|
84
|
+
return null;
|
|
85
|
+
sig = sig.slice(end + 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const m = new RegExp('(?:^|[^\\w$])' + escapeRe(name) + '\\s*(?=[<\\[(])').exec(sig);
|
|
89
|
+
if (!m)
|
|
90
|
+
return null;
|
|
91
|
+
let i = m.index + m[0].length;
|
|
92
|
+
// Type parameters: <T, U> or Go [T any].
|
|
93
|
+
if (sig[i] === '<' || sig[i] === '[') {
|
|
94
|
+
const open = sig[i];
|
|
95
|
+
const close = open === '<' ? '>' : ']';
|
|
96
|
+
let depth = 0;
|
|
97
|
+
for (; i < sig.length; i++) {
|
|
98
|
+
if (sig[i] === open)
|
|
99
|
+
depth++;
|
|
100
|
+
else if (sig[i] === close && sig[i - 1] !== '=' && sig[i - 1] !== '-' && --depth === 0) {
|
|
101
|
+
i++;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
while (sig[i] === ' ')
|
|
106
|
+
i++;
|
|
107
|
+
}
|
|
108
|
+
if (sig[i] !== '(')
|
|
109
|
+
return null;
|
|
110
|
+
const end = closing(sig, i);
|
|
111
|
+
if (end === -1)
|
|
112
|
+
return null; // truncated signature
|
|
113
|
+
let params = splitParams(sig.slice(i + 1, end));
|
|
114
|
+
if (lang === 'c' || lang === 'cpp') {
|
|
115
|
+
if (params.length === 1 && params[0] === 'void')
|
|
116
|
+
params = [];
|
|
117
|
+
// `int f()` in C says nothing about the parameters.
|
|
118
|
+
else if (params.length === 0 && lang === 'c')
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (lang === 'python') {
|
|
122
|
+
if (params.length && /^(self|cls)\b/.test(params[0]))
|
|
123
|
+
params = params.slice(1);
|
|
124
|
+
}
|
|
125
|
+
else if (lang === 'rust') {
|
|
126
|
+
if (params.length && /^(&\s*('\w+\s+)?)?(mut\s+)?self\b/.test(params[0]))
|
|
127
|
+
params = params.slice(1);
|
|
128
|
+
}
|
|
129
|
+
else if (lang === 'typescript' || lang === 'tsx') {
|
|
130
|
+
if (params.length && /^this\s*:/.test(params[0]))
|
|
131
|
+
params = params.slice(1);
|
|
132
|
+
}
|
|
133
|
+
let min = 0;
|
|
134
|
+
let max = 0;
|
|
135
|
+
let variadic = false;
|
|
136
|
+
let ext = false;
|
|
137
|
+
let voidable = false;
|
|
138
|
+
for (let k = 0; k < params.length; k++) {
|
|
139
|
+
const p = params[k];
|
|
140
|
+
if (lang === 'python') {
|
|
141
|
+
if (p === '*' || p === '/')
|
|
142
|
+
continue; // keyword-only / positional-only markers
|
|
143
|
+
if (p.startsWith('*')) {
|
|
144
|
+
variadic = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (p.includes('...') || (lang === 'csharp' && /^(\[[^\]]*\]\s*)*params\s/.test(p))) {
|
|
149
|
+
variadic = true;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (lang === 'csharp' && k === 0 && /^(\[[^\]]*\]\s*)*this\s/.test(p))
|
|
153
|
+
ext = true;
|
|
154
|
+
max++;
|
|
155
|
+
const optional = hasDefault(p) ||
|
|
156
|
+
((lang === 'typescript' || lang === 'tsx') && /^[\w$]+\?\s*(:|$)/.test(p));
|
|
157
|
+
if (!optional) {
|
|
158
|
+
min++;
|
|
159
|
+
voidable = (lang === 'typescript' || lang === 'tsx') && /:\s*(?:[A-Z]|T[A-Z]\w*)\s*$/.test(p);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { min, max: variadic ? Infinity : max, ext, voidable };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Can a call with `argc` arguments be a call of a function with this range?
|
|
166
|
+
* `unbound` marks calls written through a type name (`Base.save(self, x)`,
|
|
167
|
+
* `Type::method(value, x)`), which pass the receiver as an argument.
|
|
168
|
+
*/
|
|
169
|
+
export function arityFits(range, argc, lang, unbound) {
|
|
170
|
+
if (!range || argc < 0)
|
|
171
|
+
return true;
|
|
172
|
+
if (argc >= range.min && argc <= range.max)
|
|
173
|
+
return true;
|
|
174
|
+
// `emitter.fire()` on an Emitter<void>: a parameter of type T can be left out when T is void.
|
|
175
|
+
if (range.voidable && argc === range.min - 1)
|
|
176
|
+
return true;
|
|
177
|
+
// Extension method called as a member: the receiver fills the `this` parameter.
|
|
178
|
+
if (range.ext && argc + 1 >= range.min && argc + 1 <= range.max)
|
|
179
|
+
return true;
|
|
180
|
+
if (unbound && (lang === 'python' || lang === 'rust') && argc - 1 >= range.min && argc - 1 <= range.max)
|
|
181
|
+
return true;
|
|
182
|
+
// Go: f(g()) forwards all results of g.
|
|
183
|
+
if (lang === 'go' && argc === 1 && range.min > 1)
|
|
184
|
+
return true;
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=arity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arity.js","sourceRoot":"","sources":["../../src/query/arity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ClastDb } from '../db/index.js';
|
|
2
|
+
import { type SymbolRow } from '../types.js';
|
|
3
|
+
import { type Resolver } from './resolve.js';
|
|
4
|
+
export interface CallerEdge {
|
|
5
|
+
/** Calling symbol, or null for a call at file level. */
|
|
6
|
+
caller: SymbolRow | null;
|
|
7
|
+
path: string;
|
|
8
|
+
line: number;
|
|
9
|
+
count: number;
|
|
10
|
+
/** False when name-based resolution could not rule out another target. */
|
|
11
|
+
certain: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* True for call sites whose receiver type is unknown and that could not be
|
|
14
|
+
* attributed to any definition: they MAY call the target (same method
|
|
15
|
+
* name), but there is no evidence either way.
|
|
16
|
+
*/
|
|
17
|
+
weak?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CalleeEdge {
|
|
20
|
+
name: string;
|
|
21
|
+
line: number;
|
|
22
|
+
count: number;
|
|
23
|
+
receiver: string | null;
|
|
24
|
+
/** Resolved definition, or null for external / unknown callees. */
|
|
25
|
+
target: SymbolRow | null;
|
|
26
|
+
certain: boolean;
|
|
27
|
+
/** Number of other plausible definitions. */
|
|
28
|
+
alternatives: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The definition that best stands for a symbol's family member with the same
|
|
32
|
+
* qualified name: the function body rather than its prototype, the
|
|
33
|
+
* out-of-line C++ method rather than the in-class declaration.
|
|
34
|
+
*/
|
|
35
|
+
export declare function representative(db: ClastDb, resolver: Resolver, sym: SymbolRow): SymbolRow;
|
|
36
|
+
/**
|
|
37
|
+
* Who calls `target`? Call sites are matched by name and then filtered by
|
|
38
|
+
* resolving each site back to its most plausible definition.
|
|
39
|
+
*/
|
|
40
|
+
export interface CallerQuery {
|
|
41
|
+
/** Only call sites in files whose path contains this text (pushed down to SQL). */
|
|
42
|
+
pathContains?: string;
|
|
43
|
+
/** Examine fewer call sites (summaries that only show the first few callers). */
|
|
44
|
+
quick?: boolean;
|
|
45
|
+
/** Other overloads of the target whose call sites count too (a bare name means "any overload"). */
|
|
46
|
+
overloads?: SymbolRow[];
|
|
47
|
+
}
|
|
48
|
+
/** Set by callersOf when a name has more call sites than were examined. */
|
|
49
|
+
export interface CallerStats {
|
|
50
|
+
examined: number;
|
|
51
|
+
total: number;
|
|
52
|
+
}
|
|
53
|
+
export declare function callersOf(db: ClastDb, resolver: Resolver, target: SymbolRow, query?: CallerQuery, stats?: CallerStats): CallerEdge[];
|
|
54
|
+
/** What does `sym` call? For containers, the calls of all direct members. */
|
|
55
|
+
export declare function calleesOf(db: ClastDb, resolver: Resolver, sym: SymbolRow): CalleeEdge[];
|
|
56
|
+
/** One entry per calling symbol (call sites inside the same caller are merged). */
|
|
57
|
+
export interface CallerGroup {
|
|
58
|
+
caller: SymbolRow | null;
|
|
59
|
+
path: string;
|
|
60
|
+
/** First call line inside the caller. */
|
|
61
|
+
line: number;
|
|
62
|
+
/** Number of call sites in this caller. */
|
|
63
|
+
sites: number;
|
|
64
|
+
certain: boolean;
|
|
65
|
+
/** All call sites in this caller are unattributed (receiver type unknown). */
|
|
66
|
+
weak: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare function groupCallers(edges: CallerEdge[]): CallerGroup[];
|
|
69
|
+
/**
|
|
70
|
+
* Call sites of a name that has no definition in the project (an external
|
|
71
|
+
* library function, a dynamically defined method).
|
|
72
|
+
*/
|
|
73
|
+
export declare function externalCallSites(db: ClastDb, name: string, limit?: number): CallerEdge[];
|
|
74
|
+
export interface GraphNode {
|
|
75
|
+
sym: SymbolRow | null;
|
|
76
|
+
/** For callers: where the call happens. For callees: call line in the parent. */
|
|
77
|
+
path: string;
|
|
78
|
+
line: number;
|
|
79
|
+
certain: boolean;
|
|
80
|
+
/** Call sites merged into this node. */
|
|
81
|
+
sites: number;
|
|
82
|
+
/** Unattributed call site (receiver type unknown). */
|
|
83
|
+
weak?: boolean;
|
|
84
|
+
label?: string;
|
|
85
|
+
children: GraphNode[];
|
|
86
|
+
/** Set when this symbol was already expanded elsewhere in the tree. */
|
|
87
|
+
repeated?: boolean;
|
|
88
|
+
/** Children omitted because of the node budget. */
|
|
89
|
+
omitted?: number;
|
|
90
|
+
/** Set when the node has too many edges to expand at this depth. */
|
|
91
|
+
hub?: number;
|
|
92
|
+
}
|
|
93
|
+
export interface GraphOptions {
|
|
94
|
+
depth: number;
|
|
95
|
+
/** Total node budget. */
|
|
96
|
+
budget: number;
|
|
97
|
+
includeTests: boolean;
|
|
98
|
+
/** Only edges whose file matches this path filter. */
|
|
99
|
+
path?: (path: string) => boolean;
|
|
100
|
+
/** Text every call-site path must contain (lets the database do the first cut for `within`). */
|
|
101
|
+
pathContains?: string;
|
|
102
|
+
/** Filled in for the root symbol: how many call sites of the name exist vs were examined. */
|
|
103
|
+
stats?: CallerStats;
|
|
104
|
+
/** Other overloads of the root whose callers are included. */
|
|
105
|
+
overloads?: SymbolRow[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Breadth-first expansion of callers or callees, bounded by a total node
|
|
109
|
+
* budget so the output stays small. Hubs (symbols with dozens of callers)
|
|
110
|
+
* are reported but not expanded beyond the first level: their fan-out would
|
|
111
|
+
* drown the rest of the tree.
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildGraph(db: ClastDb, resolver: Resolver, root: SymbolRow, direction: 'callers' | 'callees', opts: GraphOptions): GraphNode;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { CONTAINER_KINDS } from '../types.js';
|
|
2
|
+
import { isTestPath } from './format.js';
|
|
3
|
+
import { goInterfaceMethods } from './hierarchy.js';
|
|
4
|
+
import { langFamily } from './resolve.js';
|
|
5
|
+
const MAX_CALL_ROWS = 4000;
|
|
6
|
+
const MAX_NEAR_ROWS = 20000;
|
|
7
|
+
const QUICK_NEAR_ROWS = 5000;
|
|
8
|
+
const QUICK_FAR_ROWS = 1500;
|
|
9
|
+
const MAX_WEAK = 1500;
|
|
10
|
+
/**
|
|
11
|
+
* The definition that best stands for a symbol's family member with the same
|
|
12
|
+
* qualified name: the function body rather than its prototype, the
|
|
13
|
+
* out-of-line C++ method rather than the in-class declaration.
|
|
14
|
+
*/
|
|
15
|
+
export function representative(db, resolver, sym) {
|
|
16
|
+
// Only C-family code separates declarations from definitions across files.
|
|
17
|
+
if (langFamily(sym.lang) !== 'c' && sym.kind !== 'declaration')
|
|
18
|
+
return sym;
|
|
19
|
+
let best = sym;
|
|
20
|
+
let bestScore = (sym.kind === 'declaration' ? 0 : 1000) + (sym.end_line - sym.start_line);
|
|
21
|
+
for (const id of resolver.familyOf(sym)) {
|
|
22
|
+
if (id === sym.id)
|
|
23
|
+
continue;
|
|
24
|
+
const other = db.symbolById(id);
|
|
25
|
+
// The same member in another file (header vs source); overloads in the same file are different functions.
|
|
26
|
+
if (!other || other.qname !== sym.qname || (other.file_id === sym.file_id && sym.kind !== 'declaration'))
|
|
27
|
+
continue;
|
|
28
|
+
const score = (other.kind === 'declaration' ? 0 : 1000) + (other.end_line - other.start_line);
|
|
29
|
+
if (score > bestScore) {
|
|
30
|
+
best = other;
|
|
31
|
+
bestScore = score;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return best;
|
|
35
|
+
}
|
|
36
|
+
/** Ids of the symbol itself plus (for containers) its direct members. */
|
|
37
|
+
function bodyIds(db, sym) {
|
|
38
|
+
if (CONTAINER_KINDS.has(sym.kind)) {
|
|
39
|
+
const members = db.all(`SELECT id FROM symbols WHERE parent_id = ?`, sym.id);
|
|
40
|
+
return [sym.id, ...members.map(m => m.id)];
|
|
41
|
+
}
|
|
42
|
+
// A function's body includes the closures and helpers nested inside it.
|
|
43
|
+
const ids = [sym.id];
|
|
44
|
+
let frontier = [sym.id];
|
|
45
|
+
for (let depth = 0; depth < 4 && frontier.length && ids.length < 200; depth++) {
|
|
46
|
+
const next = [];
|
|
47
|
+
for (const id of frontier) {
|
|
48
|
+
for (const row of db.all(`SELECT id FROM symbols WHERE parent_id = ? AND kind IN ('function', 'method')`, id)) {
|
|
49
|
+
ids.push(row.id);
|
|
50
|
+
next.push(row.id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
frontier = next;
|
|
54
|
+
}
|
|
55
|
+
return ids;
|
|
56
|
+
}
|
|
57
|
+
export function callersOf(db, resolver, target, query = {}, stats) {
|
|
58
|
+
const nameId = db.nameId(target.name, false);
|
|
59
|
+
if (nameId === undefined)
|
|
60
|
+
return [];
|
|
61
|
+
const cols = `c.rowid AS id, c.file_id, c.caller_id, c.line, c.receiver, c.rtype, c.via, c.argc, c.count, f.path, f.lang`;
|
|
62
|
+
const pathClause = query.pathContains ? ` AND f.path LIKE ? ESCAPE '\\'` : '';
|
|
63
|
+
const pathArg = query.pathContains ? ['%' + query.pathContains.replace(/[\\%_]/g, c => '\\' + c) + '%'] : [];
|
|
64
|
+
// Very common names (`dispose`, `push`, `get`) have more call sites than can be examined.
|
|
65
|
+
// Sites that can see the target - its own file and the files that import it - always go first.
|
|
66
|
+
const near = db.all(`SELECT ${cols} FROM calls c JOIN files f ON f.id = c.file_id
|
|
67
|
+
WHERE c.name_id = ? AND (c.file_id = ? OR c.file_id IN (SELECT file_id FROM imports WHERE resolved_id = ?))${pathClause}
|
|
68
|
+
LIMIT ${query.quick ? QUICK_NEAR_ROWS : MAX_NEAR_ROWS}`, nameId, target.file_id, target.file_id, ...pathArg);
|
|
69
|
+
const seenRows = new Set(near.map(r => r.id));
|
|
70
|
+
const far = db
|
|
71
|
+
.all(`SELECT ${cols} FROM calls c JOIN files f ON f.id = c.file_id
|
|
72
|
+
WHERE c.name_id = ?${pathClause} LIMIT ${MAX_CALL_ROWS + MAX_NEAR_ROWS}`, nameId, ...pathArg)
|
|
73
|
+
.filter(r => !seenRows.has(r.id))
|
|
74
|
+
.slice(0, query.quick ? QUICK_FAR_ROWS : MAX_CALL_ROWS);
|
|
75
|
+
const rows = [...near, ...far];
|
|
76
|
+
if (stats) {
|
|
77
|
+
stats.examined = rows.length;
|
|
78
|
+
stats.total = rows.length < (query.quick ? QUICK_FAR_ROWS : MAX_CALL_ROWS)
|
|
79
|
+
? rows.length
|
|
80
|
+
: db.get(`SELECT COUNT(*) AS n FROM calls WHERE name_id = ?`, nameId)?.n ?? rows.length;
|
|
81
|
+
}
|
|
82
|
+
let family = resolver.familyOf(target);
|
|
83
|
+
// A constructor is called by naming its class: `new Foo(...)` resolves to the class.
|
|
84
|
+
if (target.parent_id !== null && target.kind === 'method') {
|
|
85
|
+
const parent = db.symbolById(target.parent_id);
|
|
86
|
+
if (parent && parent.name === target.name && CONTAINER_KINDS.has(parent.kind))
|
|
87
|
+
family = new Set([...family, parent.id]);
|
|
88
|
+
}
|
|
89
|
+
const arityTargets = query.overloads?.length ? [target, ...query.overloads] : [target];
|
|
90
|
+
/** Go interface methods the target satisfies structurally: calls through them are possible, not certain. */
|
|
91
|
+
let structural = null;
|
|
92
|
+
if (target.lang === 'go' && target.kind === 'method') {
|
|
93
|
+
// Go interfaces are satisfied structurally: a call through an interface this method's
|
|
94
|
+
// type implements may reach it.
|
|
95
|
+
const viaInterface = goInterfaceMethods(db, target);
|
|
96
|
+
if (viaInterface.length) {
|
|
97
|
+
family = new Set([...family, ...viaInterface]);
|
|
98
|
+
structural = new Set(viaInterface);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const edges = [];
|
|
102
|
+
const weak = [];
|
|
103
|
+
const targetIsMember = target.kind === 'method' || target.qname.includes('.');
|
|
104
|
+
// `get_or_create`, `maybeAutoCommit`: a name like that on an unknown receiver is worth listing as possible
|
|
105
|
+
// even from files that never mention the class.
|
|
106
|
+
const distinctive = target.name.length >= 8 && /[a-z0-9][A-Z_]|_[a-z]/.test(target.name);
|
|
107
|
+
// `@property def count` / `get count()`: read as an attribute, so `.count(...)` calls elsewhere are something else.
|
|
108
|
+
const propertyLike = /^@(cached_)?property\b|^(public |private |protected |static |override )*get \w/.test(target.signature);
|
|
109
|
+
const callerCache = new Map();
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
let caller = null;
|
|
112
|
+
if (row.caller_id !== null) {
|
|
113
|
+
if (!callerCache.has(row.caller_id))
|
|
114
|
+
callerCache.set(row.caller_id, db.symbolById(row.caller_id));
|
|
115
|
+
caller = callerCache.get(row.caller_id) ?? null;
|
|
116
|
+
}
|
|
117
|
+
const site = {
|
|
118
|
+
fileId: row.file_id, path: row.path, lang: row.lang, receiver: row.receiver, rtype: row.rtype, via: row.via,
|
|
119
|
+
argc: row.argc, caller,
|
|
120
|
+
};
|
|
121
|
+
// A call with another number of arguments is a call of another overload (or of something else).
|
|
122
|
+
if (!arityTargets.some(t => resolver.arityFits(t, site)))
|
|
123
|
+
continue;
|
|
124
|
+
// A symbol "calling itself" by name from inside its own definition line is recursion: keep it.
|
|
125
|
+
const res = resolver.resolve(target.name, site);
|
|
126
|
+
if (res.candidates.length === 0) {
|
|
127
|
+
// Nothing could be attributed. If the receiver's type is simply unknown,
|
|
128
|
+
// keep the site as a possible caller instead of silently dropping it.
|
|
129
|
+
if (targetIsMember && !propertyLike && weak.length < MAX_WEAK && (distinctive || resolver.canSee(row.file_id, target)) && resolver.isOpaqueReceiver(site) && langFamily(row.lang) === langFamily(target.lang) && !(isTestPath(target.path) && !isTestPath(row.path))) {
|
|
130
|
+
weak.push({ caller, path: row.path, line: row.line, count: row.count, certain: false, weak: true });
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const top = res.candidates[0];
|
|
135
|
+
let certain;
|
|
136
|
+
if (family.has(top.sym.id)) {
|
|
137
|
+
// Resolved to the target or to its interface/override/overload twin.
|
|
138
|
+
certain = res.confident;
|
|
139
|
+
// Any type with these methods satisfies a Go interface: a call through it may reach the
|
|
140
|
+
// target, but equally one of the other implementers.
|
|
141
|
+
if (structural && structural.has(top.sym.id))
|
|
142
|
+
certain = false;
|
|
143
|
+
// Bound to an override in a subclass: that call does not run the target.
|
|
144
|
+
if (top.sym.id !== target.id && resolver.overriddenBelow(target, top.sym)) {
|
|
145
|
+
if (certain)
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Bound to a supertype's declaration, but on a receiver that cannot be the target's class
|
|
149
|
+
// (a sibling subclass, or an explicit super call).
|
|
150
|
+
if (certain && top.sym.id !== target.id && !resolver.reaches(target, top.sym, site))
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const mine = res.candidates.find(c => family.has(c.sym.id));
|
|
155
|
+
// Keep near-ties as uncertain edges; drop sites that clearly mean another definition.
|
|
156
|
+
if (!mine || top.score - mine.score > 10)
|
|
157
|
+
continue;
|
|
158
|
+
// The site binds to another copy of this definition (a vendored fork) that its file can see better.
|
|
159
|
+
if (top.sym.qname === target.qname && top.sym.kind === target.kind && top.sym.file_id !== target.file_id &&
|
|
160
|
+
top.score - mine.score >= 4)
|
|
161
|
+
continue;
|
|
162
|
+
certain = false;
|
|
163
|
+
}
|
|
164
|
+
edges.push({ caller, path: row.path, line: row.line, count: row.count, certain });
|
|
165
|
+
}
|
|
166
|
+
// Call sites that use a local alias of the target: `import { X as Y }`, default imports, require bindings.
|
|
167
|
+
for (const use of resolver.aliasesOf(target)) {
|
|
168
|
+
const aliasId = db.nameId(use.alias, false);
|
|
169
|
+
if (aliasId === undefined)
|
|
170
|
+
continue;
|
|
171
|
+
const aliasRows = db.all(`SELECT c.file_id, c.caller_id, c.line, c.receiver, c.rtype, c.via, c.count, f.path, f.lang
|
|
172
|
+
FROM calls c JOIN files f ON f.id = c.file_id
|
|
173
|
+
WHERE c.name_id = ? AND c.file_id = ? AND c.receiver IS NULL`, aliasId, use.fileId);
|
|
174
|
+
for (const row of aliasRows) {
|
|
175
|
+
const caller = row.caller_id !== null ? db.symbolById(row.caller_id) ?? null : null;
|
|
176
|
+
edges.push({ caller, path: row.path, line: row.line, count: row.count, certain: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Unattributed sites only matter when they are not already explained.
|
|
180
|
+
const known = new Set(edges.map(e => e.path + ':' + e.line));
|
|
181
|
+
for (const w of weak)
|
|
182
|
+
if (!known.has(w.path + ':' + w.line))
|
|
183
|
+
edges.push(w);
|
|
184
|
+
// Production code first, then resolved before name-only, then by location.
|
|
185
|
+
edges.sort((a, b) => Number(isTestPath(a.path)) - Number(isTestPath(b.path)) ||
|
|
186
|
+
Number(b.certain) - Number(a.certain) ||
|
|
187
|
+
Number(a.weak ?? false) - Number(b.weak ?? false) ||
|
|
188
|
+
a.path.localeCompare(b.path) ||
|
|
189
|
+
a.line - b.line);
|
|
190
|
+
return edges;
|
|
191
|
+
}
|
|
192
|
+
/** What does `sym` call? For containers, the calls of all direct members. */
|
|
193
|
+
export function calleesOf(db, resolver, sym) {
|
|
194
|
+
const ids = bodyIds(db, sym);
|
|
195
|
+
const byName = new Map();
|
|
196
|
+
for (const id of ids) {
|
|
197
|
+
const caller = id === sym.id ? sym : db.symbolById(id) ?? sym;
|
|
198
|
+
const rows = db.all(`SELECT n.name, c.line, c.receiver, c.rtype, c.via, c.argc, c.count
|
|
199
|
+
FROM calls c JOIN names n ON n.id = c.name_id
|
|
200
|
+
WHERE c.caller_id = ? ORDER BY c.line`, id);
|
|
201
|
+
for (const row of rows) {
|
|
202
|
+
const res = resolver.resolve(row.name, {
|
|
203
|
+
fileId: sym.file_id, path: sym.path, lang: sym.lang, receiver: row.receiver, rtype: row.rtype, via: row.via,
|
|
204
|
+
argc: row.argc, caller,
|
|
205
|
+
});
|
|
206
|
+
const first = res.candidates[0];
|
|
207
|
+
// Follow the call to the implementation, not to a prototype / in-class declaration.
|
|
208
|
+
const top = first ? { ...first, sym: representative(db, resolver, first.sym) } : undefined;
|
|
209
|
+
const key = row.name + '\0' + (top ? top.sym.id : '');
|
|
210
|
+
const existing = byName.get(key);
|
|
211
|
+
if (existing) {
|
|
212
|
+
existing.count += row.count;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
byName.set(key, {
|
|
216
|
+
name: row.name,
|
|
217
|
+
line: row.line,
|
|
218
|
+
count: row.count,
|
|
219
|
+
receiver: row.receiver,
|
|
220
|
+
target: top ? top.sym : null,
|
|
221
|
+
certain: res.confident,
|
|
222
|
+
alternatives: top
|
|
223
|
+
? Math.max(0, res.candidates.length - 1)
|
|
224
|
+
// Unresolved, but the project does define methods with this name: not "external".
|
|
225
|
+
: resolver.isOpaqueReceiver({
|
|
226
|
+
fileId: sym.file_id, path: sym.path, lang: sym.lang, receiver: row.receiver, rtype: row.rtype, via: row.via, caller,
|
|
227
|
+
})
|
|
228
|
+
? resolver.candidates(row.name).filter(c => langFamily(c.lang) === langFamily(sym.lang)).length
|
|
229
|
+
: 0,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return [...byName.values()].sort((a, b) => a.line - b.line);
|
|
234
|
+
}
|
|
235
|
+
export function groupCallers(edges) {
|
|
236
|
+
const groups = new Map();
|
|
237
|
+
for (const e of edges) {
|
|
238
|
+
const key = e.caller ? 's' + e.caller.id : 'f' + e.path;
|
|
239
|
+
const g = groups.get(key);
|
|
240
|
+
if (g) {
|
|
241
|
+
g.sites += e.count;
|
|
242
|
+
if (e.certain)
|
|
243
|
+
g.certain = true;
|
|
244
|
+
if (!e.weak)
|
|
245
|
+
g.weak = false;
|
|
246
|
+
if (e.line < g.line)
|
|
247
|
+
g.line = e.line;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
groups.set(key, {
|
|
251
|
+
caller: e.caller, path: e.path, line: e.line, sites: e.count, certain: e.certain, weak: e.weak ?? false,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return [...groups.values()];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Call sites of a name that has no definition in the project (an external
|
|
259
|
+
* library function, a dynamically defined method).
|
|
260
|
+
*/
|
|
261
|
+
export function externalCallSites(db, name, limit = 2000) {
|
|
262
|
+
const nameId = db.nameId(name, false);
|
|
263
|
+
if (nameId === undefined)
|
|
264
|
+
return [];
|
|
265
|
+
const rows = db.all(`SELECT c.file_id, c.caller_id, c.line, c.receiver, c.rtype, c.via, c.count, f.path, f.lang
|
|
266
|
+
FROM calls c JOIN files f ON f.id = c.file_id WHERE c.name_id = ? LIMIT ?`, nameId, limit);
|
|
267
|
+
const edges = rows.map(row => ({
|
|
268
|
+
caller: row.caller_id !== null ? db.symbolById(row.caller_id) ?? null : null,
|
|
269
|
+
path: row.path, line: row.line, count: row.count, certain: false,
|
|
270
|
+
}));
|
|
271
|
+
edges.sort((a, b) => Number(isTestPath(a.path)) - Number(isTestPath(b.path)) || a.path.localeCompare(b.path) || a.line - b.line);
|
|
272
|
+
return edges;
|
|
273
|
+
}
|
|
274
|
+
/** Symbols with more direct edges than this are not expanded below the first level. */
|
|
275
|
+
const HUB_LIMIT = 30;
|
|
276
|
+
/**
|
|
277
|
+
* Breadth-first expansion of callers or callees, bounded by a total node
|
|
278
|
+
* budget so the output stays small. Hubs (symbols with dozens of callers)
|
|
279
|
+
* are reported but not expanded beyond the first level: their fan-out would
|
|
280
|
+
* drown the rest of the tree.
|
|
281
|
+
*/
|
|
282
|
+
export function buildGraph(db, resolver, root, direction, opts) {
|
|
283
|
+
const rootNode = { sym: root, path: root.path, line: root.start_line, certain: true, sites: 1, children: [] };
|
|
284
|
+
const expanded = new Set([root.id]);
|
|
285
|
+
let frontier = [rootNode];
|
|
286
|
+
let remaining = opts.budget;
|
|
287
|
+
for (let level = 0; level < opts.depth && frontier.length && remaining > 0; level++) {
|
|
288
|
+
const next = [];
|
|
289
|
+
// Split the remaining budget fairly across this level's nodes.
|
|
290
|
+
const perNode = level === 0 ? remaining : Math.max(3, Math.ceil(remaining / frontier.length));
|
|
291
|
+
for (const node of frontier) {
|
|
292
|
+
if (!node.sym || remaining <= 0)
|
|
293
|
+
continue;
|
|
294
|
+
let children;
|
|
295
|
+
if (direction === 'callers') {
|
|
296
|
+
children = groupCallers(callersOf(db, resolver, node.sym, { pathContains: opts.pathContains, overloads: level === 0 ? opts.overloads : undefined }, level === 0 ? opts.stats : undefined))
|
|
297
|
+
.filter(g => (opts.includeTests || !isTestPath(g.path)) && (!opts.path || opts.path(g.path)))
|
|
298
|
+
.map(g => ({
|
|
299
|
+
sym: g.caller, path: g.path, line: g.line, certain: g.certain, sites: g.sites, weak: g.weak,
|
|
300
|
+
label: g.caller ? undefined : '<file level>', children: [],
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
children = calleesOf(db, resolver, node.sym)
|
|
305
|
+
.filter(e => e.target !== null && (opts.includeTests || !isTestPath(e.target.path)))
|
|
306
|
+
.map(e => ({ sym: e.target, path: e.target.path, line: e.line, certain: e.certain, sites: e.count, children: [] }));
|
|
307
|
+
}
|
|
308
|
+
if (level > 0 && children.length > HUB_LIMIT) {
|
|
309
|
+
node.hub = children.length;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const take = Math.min(children.length, perNode, remaining);
|
|
313
|
+
node.omitted = children.length - take;
|
|
314
|
+
node.children = children.slice(0, take);
|
|
315
|
+
remaining -= take;
|
|
316
|
+
for (const child of node.children) {
|
|
317
|
+
// Only confirmed edges are worth following further.
|
|
318
|
+
if (!child.sym || child.weak || (direction === 'callers' && !child.certain))
|
|
319
|
+
continue;
|
|
320
|
+
if (expanded.has(child.sym.id)) {
|
|
321
|
+
child.repeated = true;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
expanded.add(child.sym.id);
|
|
325
|
+
next.push(child);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
frontier = next;
|
|
329
|
+
}
|
|
330
|
+
return rootNode;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=callgraph.js.map
|