diffninja 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/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/calltree.d.ts +47 -0
- package/dist/calltree.js +296 -0
- package/dist/cli.d.ts +57 -0
- package/dist/cli.js +340 -0
- package/dist/diff.d.ts +7 -0
- package/dist/diff.js +114 -0
- package/dist/extract.d.ts +26 -0
- package/dist/extract.js +152 -0
- package/dist/git.d.ts +40 -0
- package/dist/git.js +288 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/infer.d.ts +21 -0
- package/dist/infer.js +189 -0
- package/dist/languages/bash.d.ts +2 -0
- package/dist/languages/bash.js +208 -0
- package/dist/languages/c.d.ts +2 -0
- package/dist/languages/c.js +218 -0
- package/dist/languages/call-syntax.d.ts +125 -0
- package/dist/languages/call-syntax.js +997 -0
- package/dist/languages/cpp.d.ts +2 -0
- package/dist/languages/cpp.js +321 -0
- package/dist/languages/csharp.d.ts +2 -0
- package/dist/languages/csharp.js +324 -0
- package/dist/languages/elixir.d.ts +2 -0
- package/dist/languages/elixir.js +331 -0
- package/dist/languages/go.d.ts +2 -0
- package/dist/languages/go.js +299 -0
- package/dist/languages/grammars.d.ts +50 -0
- package/dist/languages/grammars.js +351 -0
- package/dist/languages/haskell.d.ts +2 -0
- package/dist/languages/haskell.js +250 -0
- package/dist/languages/java.d.ts +2 -0
- package/dist/languages/java.js +351 -0
- package/dist/languages/javascript.d.ts +4 -0
- package/dist/languages/javascript.js +648 -0
- package/dist/languages/kotlin.d.ts +2 -0
- package/dist/languages/kotlin.js +368 -0
- package/dist/languages/lua.d.ts +2 -0
- package/dist/languages/lua.js +212 -0
- package/dist/languages/ocaml.d.ts +2 -0
- package/dist/languages/ocaml.js +291 -0
- package/dist/languages/perl.d.ts +2 -0
- package/dist/languages/perl.js +418 -0
- package/dist/languages/php.d.ts +2 -0
- package/dist/languages/php.js +397 -0
- package/dist/languages/python.d.ts +2 -0
- package/dist/languages/python.js +376 -0
- package/dist/languages/registry.d.ts +7 -0
- package/dist/languages/registry.js +69 -0
- package/dist/languages/ruby.d.ts +2 -0
- package/dist/languages/ruby.js +391 -0
- package/dist/languages/rust.d.ts +2 -0
- package/dist/languages/rust.js +261 -0
- package/dist/languages/scala.d.ts +2 -0
- package/dist/languages/scala.js +307 -0
- package/dist/languages/solidity.d.ts +2 -0
- package/dist/languages/solidity.js +240 -0
- package/dist/languages/swift.d.ts +2 -0
- package/dist/languages/swift.js +268 -0
- package/dist/languages/types.d.ts +36 -0
- package/dist/languages/types.js +74 -0
- package/dist/languages/typescript-contracts.d.ts +57 -0
- package/dist/languages/typescript-contracts.js +528 -0
- package/dist/languages/typescript-dispatch.d.ts +68 -0
- package/dist/languages/typescript-dispatch.js +710 -0
- package/dist/languages/typescript.d.ts +4 -0
- package/dist/languages/typescript.js +722 -0
- package/dist/languages/zig.d.ts +2 -0
- package/dist/languages/zig.js +243 -0
- package/dist/loc.d.ts +17 -0
- package/dist/loc.js +34 -0
- package/dist/reach.d.ts +17 -0
- package/dist/reach.js +65 -0
- package/dist/render.d.ts +18 -0
- package/dist/render.js +83 -0
- package/dist/review/brand.d.ts +8 -0
- package/dist/review/brand.js +25 -0
- package/dist/review/call-context.d.ts +27 -0
- package/dist/review/call-context.js +446 -0
- package/dist/review/call-flow-html.d.ts +32 -0
- package/dist/review/call-flow-html.js +1870 -0
- package/dist/review/call-flow-nav.d.ts +151 -0
- package/dist/review/call-flow-nav.js +317 -0
- package/dist/review/call-flow.d.ts +47 -0
- package/dist/review/call-flow.js +229 -0
- package/dist/review/change-facts.d.ts +69 -0
- package/dist/review/change-facts.js +729 -0
- package/dist/review/cli.d.ts +2 -0
- package/dist/review/cli.js +50 -0
- package/dist/review/connected-analysis.d.ts +100 -0
- package/dist/review/connected-analysis.js +163 -0
- package/dist/review/connected-html.d.ts +17 -0
- package/dist/review/connected-html.js +2853 -0
- package/dist/review/connected.d.ts +23 -0
- package/dist/review/connected.js +141 -0
- package/dist/review/escape-html.d.ts +2 -0
- package/dist/review/escape-html.js +9 -0
- package/dist/review/evidence-html.d.ts +21 -0
- package/dist/review/evidence-html.js +521 -0
- package/dist/review/evidence-syntax.d.ts +132 -0
- package/dist/review/evidence-syntax.js +478 -0
- package/dist/review/evidence-types.d.ts +62 -0
- package/dist/review/evidence-types.js +1 -0
- package/dist/review/evidence.d.ts +31 -0
- package/dist/review/evidence.js +1603 -0
- package/dist/review/file-role.d.ts +9 -0
- package/dist/review/file-role.js +29 -0
- package/dist/review/github.d.ts +204 -0
- package/dist/review/github.js +1245 -0
- package/dist/review/history.d.ts +101 -0
- package/dist/review/history.js +412 -0
- package/dist/review/html.d.ts +34 -0
- package/dist/review/html.js +1104 -0
- package/dist/review/input.d.ts +10 -0
- package/dist/review/input.js +113 -0
- package/dist/review/intent.d.ts +4 -0
- package/dist/review/intent.js +75 -0
- package/dist/review/mcp-cli.d.ts +2 -0
- package/dist/review/mcp-cli.js +25 -0
- package/dist/review/mcp.d.ts +12 -0
- package/dist/review/mcp.js +414 -0
- package/dist/review/module-resolution.d.ts +2 -0
- package/dist/review/module-resolution.js +86 -0
- package/dist/review/palette.d.ts +7 -0
- package/dist/review/palette.js +104 -0
- package/dist/review/pipeline.d.ts +77 -0
- package/dist/review/pipeline.js +227 -0
- package/dist/review/pr-input.d.ts +19 -0
- package/dist/review/pr-input.js +130 -0
- package/dist/review/questions.d.ts +201 -0
- package/dist/review/questions.js +174 -0
- package/dist/review/reference-check.d.ts +7 -0
- package/dist/review/reference-check.js +733 -0
- package/dist/review/report-pages.d.ts +109 -0
- package/dist/review/report-pages.js +328 -0
- package/dist/review/service.d.ts +23 -0
- package/dist/review/service.js +198 -0
- package/dist/review/setup.d.ts +112 -0
- package/dist/review/setup.js +549 -0
- package/dist/review/source.d.ts +26 -0
- package/dist/review/source.js +276 -0
- package/dist/review/toml.d.ts +38 -0
- package/dist/review/toml.js +565 -0
- package/dist/review/types.d.ts +179 -0
- package/dist/review/types.js +1 -0
- package/dist/run.d.ts +49 -0
- package/dist/run.js +311 -0
- package/dist/types.d.ts +366 -0
- package/dist/types.js +83 -0
- package/package.json +88 -0
- package/scripts/ensure-native-grammar.mjs +188 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { heuristicCallSyntax } from "./call-syntax.js";
|
|
2
|
+
import { childByType, declaredParamsOf, collapseWs, locFromNode, namedChildren, } from "./types.js";
|
|
3
|
+
function isPrivate(node) {
|
|
4
|
+
const mods = childByType(node, "modifiers");
|
|
5
|
+
if (!mods)
|
|
6
|
+
return false;
|
|
7
|
+
return /\bprivate\b/.test(mods.text);
|
|
8
|
+
}
|
|
9
|
+
function isLikelyTypeName(name) {
|
|
10
|
+
const c = name[0];
|
|
11
|
+
return c !== undefined && c === c.toUpperCase() && c !== c.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
function getParamsLabel(params) {
|
|
14
|
+
if (!params || params.type !== "function_value_parameters")
|
|
15
|
+
return "()";
|
|
16
|
+
const names = [];
|
|
17
|
+
for (const p of namedChildren(params)) {
|
|
18
|
+
if (p.type === "parameter") {
|
|
19
|
+
const id = childByType(p, "simple_identifier");
|
|
20
|
+
names.push(id?.text ?? "_");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return names.length === 0 ? "()" : `(${names.join(", ")})`;
|
|
24
|
+
}
|
|
25
|
+
function navigationProp(nav) {
|
|
26
|
+
const suffix = childByType(nav, "navigation_suffix");
|
|
27
|
+
const id = suffix ? childByType(suffix, "simple_identifier") : null;
|
|
28
|
+
return id?.text ?? null;
|
|
29
|
+
}
|
|
30
|
+
function calleeKey(node, className) {
|
|
31
|
+
if (node.type === "simple_identifier") {
|
|
32
|
+
if (isLikelyTypeName(node.text))
|
|
33
|
+
return `new ${node.text}`;
|
|
34
|
+
return node.text;
|
|
35
|
+
}
|
|
36
|
+
if (node.type === "navigation_expression") {
|
|
37
|
+
const object = node.namedChild(0);
|
|
38
|
+
const prop = navigationProp(node);
|
|
39
|
+
if (!object || !prop)
|
|
40
|
+
return null;
|
|
41
|
+
if (object.type === "this_expression" && className) {
|
|
42
|
+
return `${className}.${prop}`;
|
|
43
|
+
}
|
|
44
|
+
if (object.type === "simple_identifier") {
|
|
45
|
+
return `${object.text}.${prop}`;
|
|
46
|
+
}
|
|
47
|
+
if (className)
|
|
48
|
+
return `${className}.${prop}`;
|
|
49
|
+
return prop;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function statementsOf(body) {
|
|
54
|
+
if (!body)
|
|
55
|
+
return [];
|
|
56
|
+
if (body.type === "function_body" || body.type === "control_structure_body") {
|
|
57
|
+
const stmts = childByType(body, "statements");
|
|
58
|
+
if (stmts)
|
|
59
|
+
return namedChildren(stmts);
|
|
60
|
+
// empty `{}` body — or a bare nested if_expression
|
|
61
|
+
return namedChildren(body).filter((c) => c.type !== "statements");
|
|
62
|
+
}
|
|
63
|
+
if (body.type === "statements")
|
|
64
|
+
return namedChildren(body);
|
|
65
|
+
return [body];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Body statements of a lambda/anonymous-function argument. Kotlin higher-order
|
|
69
|
+
* calls (`runBlocking { ... }`, `use { ... }`) execute these as part of the
|
|
70
|
+
* enclosing call flow, so they nest as call-site children of the receiving call.
|
|
71
|
+
*/
|
|
72
|
+
function lambdaBodyStatements(lambda) {
|
|
73
|
+
if (lambda.type === "anonymous_function") {
|
|
74
|
+
return statementsOf(childByType(lambda, "function_body"));
|
|
75
|
+
}
|
|
76
|
+
const stmts = childByType(lambda, "statements");
|
|
77
|
+
return stmts ? namedChildren(stmts) : [];
|
|
78
|
+
}
|
|
79
|
+
function collectStatements(file, statements, className) {
|
|
80
|
+
const steps = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
const addCall = (key, node, children) => {
|
|
83
|
+
const mark = `${key}:${node.startIndex}`;
|
|
84
|
+
if (seen.has(mark))
|
|
85
|
+
return;
|
|
86
|
+
seen.add(mark);
|
|
87
|
+
const step = {
|
|
88
|
+
type: "call",
|
|
89
|
+
key,
|
|
90
|
+
...locFromNode(file, node),
|
|
91
|
+
};
|
|
92
|
+
if (children && children.length > 0) {
|
|
93
|
+
step.children = children;
|
|
94
|
+
}
|
|
95
|
+
step.syntax = heuristicCallSyntax(node, key);
|
|
96
|
+
steps.push(step);
|
|
97
|
+
};
|
|
98
|
+
// Lambda arguments of THIS call only — lambdas under a nested call belong to it.
|
|
99
|
+
const findLambdaArgs = (node, into) => {
|
|
100
|
+
if (node.type === "call_expression")
|
|
101
|
+
return;
|
|
102
|
+
if (node.type === "lambda_literal" || node.type === "anonymous_function") {
|
|
103
|
+
into.push(node);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const child of namedChildren(node))
|
|
107
|
+
findLambdaArgs(child, into);
|
|
108
|
+
};
|
|
109
|
+
const pushIfChain = (node, asElseIf) => {
|
|
110
|
+
const kids = namedChildren(node);
|
|
111
|
+
const bodies = kids.filter((c) => c.type === "control_structure_body");
|
|
112
|
+
const cond = kids.find((c) => c.type !== "control_structure_body") ?? null;
|
|
113
|
+
const condText = cond ? collapseWs(cond.text) : "";
|
|
114
|
+
const kind = asElseIf ? "else-if" : "if";
|
|
115
|
+
const labelKind = asElseIf ? "else if" : "if";
|
|
116
|
+
steps.push({
|
|
117
|
+
type: "branch",
|
|
118
|
+
key: condText ? `${kind}:${condText}` : kind,
|
|
119
|
+
label: condText ? `${labelKind} ${condText}` : labelKind,
|
|
120
|
+
...locFromNode(file, cond ?? node),
|
|
121
|
+
children: collectStatements(file, statementsOf(bodies[0] ?? null), className),
|
|
122
|
+
});
|
|
123
|
+
if (!bodies[1])
|
|
124
|
+
return;
|
|
125
|
+
const elseStmts = statementsOf(bodies[1]);
|
|
126
|
+
if (elseStmts.length === 1 && elseStmts[0].type === "if_expression") {
|
|
127
|
+
pushIfChain(elseStmts[0], true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
steps.push({
|
|
131
|
+
type: "branch",
|
|
132
|
+
key: "else",
|
|
133
|
+
label: "else",
|
|
134
|
+
...locFromNode(file, bodies[1]),
|
|
135
|
+
children: collectStatements(file, elseStmts, className),
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const walk = (node) => {
|
|
139
|
+
if (node.type === "function_declaration" ||
|
|
140
|
+
node.type === "class_declaration" ||
|
|
141
|
+
node.type === "object_declaration" ||
|
|
142
|
+
node.type === "companion_object" ||
|
|
143
|
+
node.type === "anonymous_function" ||
|
|
144
|
+
node.type === "lambda_literal" ||
|
|
145
|
+
node.type === "secondary_constructor" ||
|
|
146
|
+
node.type === "anonymous_initializer") {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (node.type === "if_expression") {
|
|
150
|
+
pushIfChain(node, false);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (node.type === "try_expression") {
|
|
154
|
+
const tryStmts = childByType(node, "statements");
|
|
155
|
+
steps.push({
|
|
156
|
+
type: "branch",
|
|
157
|
+
key: "try",
|
|
158
|
+
label: "try",
|
|
159
|
+
...locFromNode(file, node),
|
|
160
|
+
children: tryStmts
|
|
161
|
+
? collectStatements(file, namedChildren(tryStmts), className)
|
|
162
|
+
: [],
|
|
163
|
+
});
|
|
164
|
+
for (const clause of namedChildren(node)) {
|
|
165
|
+
if (clause.type === "catch_block") {
|
|
166
|
+
const type = childByType(clause, "user_type") ??
|
|
167
|
+
namedChildren(clause).find((c) => c.type !== "simple_identifier" && c.type !== "statements") ??
|
|
168
|
+
null;
|
|
169
|
+
const text = type ? collapseWs(type.text) : "";
|
|
170
|
+
const body = childByType(clause, "statements");
|
|
171
|
+
steps.push({
|
|
172
|
+
type: "branch",
|
|
173
|
+
key: text ? `catch:${text}` : "catch",
|
|
174
|
+
label: text ? `catch ${text}` : "catch",
|
|
175
|
+
...locFromNode(file, type ?? clause),
|
|
176
|
+
children: body
|
|
177
|
+
? collectStatements(file, namedChildren(body), className)
|
|
178
|
+
: [],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (clause.type === "finally_block") {
|
|
182
|
+
const body = childByType(clause, "statements");
|
|
183
|
+
steps.push({
|
|
184
|
+
type: "branch",
|
|
185
|
+
key: "finally",
|
|
186
|
+
label: "finally",
|
|
187
|
+
...locFromNode(file, clause),
|
|
188
|
+
children: body
|
|
189
|
+
? collectStatements(file, namedChildren(body), className)
|
|
190
|
+
: [],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (node.type === "when_expression") {
|
|
197
|
+
for (const entry of namedChildren(node)) {
|
|
198
|
+
if (entry.type !== "when_entry")
|
|
199
|
+
continue;
|
|
200
|
+
const cond = childByType(entry, "when_condition");
|
|
201
|
+
const body = childByType(entry, "control_structure_body");
|
|
202
|
+
if (cond) {
|
|
203
|
+
const text = collapseWs(cond.text);
|
|
204
|
+
steps.push({
|
|
205
|
+
type: "branch",
|
|
206
|
+
key: text ? `case:${text}` : "case",
|
|
207
|
+
label: text ? `case ${text}` : "case",
|
|
208
|
+
...locFromNode(file, cond),
|
|
209
|
+
children: collectStatements(file, statementsOf(body), className),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
steps.push({
|
|
214
|
+
type: "branch",
|
|
215
|
+
key: "else",
|
|
216
|
+
label: "else",
|
|
217
|
+
...locFromNode(file, entry),
|
|
218
|
+
children: collectStatements(file, statementsOf(body), className),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (node.type === "call_expression") {
|
|
225
|
+
// `f(args) { lambda }` parses as call_expression(call_expression(f, args), lambda):
|
|
226
|
+
// unwrap to the innermost callee, keeping every suffix level's arguments.
|
|
227
|
+
const argSubtrees = namedChildren(node).slice(1);
|
|
228
|
+
let callee = node.namedChild(0);
|
|
229
|
+
while (callee?.type === "call_expression") {
|
|
230
|
+
argSubtrees.push(...namedChildren(callee).slice(1));
|
|
231
|
+
callee = callee.namedChild(0);
|
|
232
|
+
}
|
|
233
|
+
if (callee) {
|
|
234
|
+
const key = calleeKey(callee, className);
|
|
235
|
+
if (key) {
|
|
236
|
+
const lambdas = [];
|
|
237
|
+
for (const subtree of argSubtrees)
|
|
238
|
+
findLambdaArgs(subtree, lambdas);
|
|
239
|
+
const children = lambdas.flatMap((lambda) => collectStatements(file, lambdaBodyStatements(lambda), className));
|
|
240
|
+
addCall(key, node, children);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Nested calls in arguments stay siblings; lambda bodies were claimed
|
|
244
|
+
// above and `walk` still skips lambda nodes, so nothing double-counts.
|
|
245
|
+
for (const subtree of argSubtrees)
|
|
246
|
+
walk(subtree);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
for (const child of namedChildren(node))
|
|
250
|
+
walk(child);
|
|
251
|
+
};
|
|
252
|
+
for (const stmt of statements)
|
|
253
|
+
walk(stmt);
|
|
254
|
+
return steps;
|
|
255
|
+
}
|
|
256
|
+
function handleFunction(file, node, className, functions) {
|
|
257
|
+
const name = childByType(node, "simple_identifier")?.text ?? null;
|
|
258
|
+
if (!name)
|
|
259
|
+
return;
|
|
260
|
+
const params = childByType(node, "function_value_parameters");
|
|
261
|
+
const body = childByType(node, "function_body");
|
|
262
|
+
const key = className ? `${className}.${name}` : name;
|
|
263
|
+
functions.push({
|
|
264
|
+
key,
|
|
265
|
+
label: `${key}${getParamsLabel(params)}`,
|
|
266
|
+
params: declaredParamsOf(node),
|
|
267
|
+
file,
|
|
268
|
+
steps: collectStatements(file, statementsOf(body), className),
|
|
269
|
+
exported: !isPrivate(node),
|
|
270
|
+
start: node.startIndex,
|
|
271
|
+
end: node.endIndex,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function handleSecondaryConstructor(file, node, className, functions) {
|
|
275
|
+
const params = childByType(node, "function_value_parameters");
|
|
276
|
+
const stmts = childByType(node, "statements");
|
|
277
|
+
const info = {
|
|
278
|
+
key: `${className}.constructor`,
|
|
279
|
+
label: `${className}${getParamsLabel(params)}`,
|
|
280
|
+
params: declaredParamsOf(node),
|
|
281
|
+
file,
|
|
282
|
+
steps: collectStatements(file, stmts ? namedChildren(stmts) : [], className),
|
|
283
|
+
exported: !isPrivate(node),
|
|
284
|
+
start: node.startIndex,
|
|
285
|
+
end: node.endIndex,
|
|
286
|
+
};
|
|
287
|
+
functions.push(info);
|
|
288
|
+
functions.push({ ...info, key: `new ${className}` });
|
|
289
|
+
}
|
|
290
|
+
function handleInitBlock(file, node, className, functions) {
|
|
291
|
+
const stmts = childByType(node, "statements");
|
|
292
|
+
const info = {
|
|
293
|
+
key: `${className}.init`,
|
|
294
|
+
label: `${className}()`,
|
|
295
|
+
file,
|
|
296
|
+
steps: collectStatements(file, stmts ? namedChildren(stmts) : [], className),
|
|
297
|
+
exported: true,
|
|
298
|
+
start: node.startIndex,
|
|
299
|
+
end: node.endIndex,
|
|
300
|
+
};
|
|
301
|
+
functions.push(info);
|
|
302
|
+
functions.push({ ...info, key: `new ${className}` });
|
|
303
|
+
}
|
|
304
|
+
function handleClass(file, node, functions) {
|
|
305
|
+
const className = childByType(node, "type_identifier")?.text ?? null;
|
|
306
|
+
if (!className)
|
|
307
|
+
return;
|
|
308
|
+
const body = childByType(node, "class_body");
|
|
309
|
+
if (!body)
|
|
310
|
+
return;
|
|
311
|
+
for (const element of namedChildren(body)) {
|
|
312
|
+
if (element.type === "function_declaration") {
|
|
313
|
+
handleFunction(file, element, className, functions);
|
|
314
|
+
}
|
|
315
|
+
else if (element.type === "secondary_constructor") {
|
|
316
|
+
handleSecondaryConstructor(file, element, className, functions);
|
|
317
|
+
}
|
|
318
|
+
else if (element.type === "anonymous_initializer") {
|
|
319
|
+
handleInitBlock(file, element, className, functions);
|
|
320
|
+
}
|
|
321
|
+
else if (element.type === "class_declaration") {
|
|
322
|
+
handleClass(file, element, functions);
|
|
323
|
+
}
|
|
324
|
+
else if (element.type === "companion_object" ||
|
|
325
|
+
element.type === "object_declaration") {
|
|
326
|
+
handleObjectLike(file, element, className, functions);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function handleObjectLike(file, node, parentClass, functions) {
|
|
331
|
+
// companion object methods are called as Class.method — attribute to parent
|
|
332
|
+
// (or the object name for top-level `object Foo`).
|
|
333
|
+
const objectName = childByType(node, "type_identifier")?.text ??
|
|
334
|
+
childByType(node, "simple_identifier")?.text ??
|
|
335
|
+
null;
|
|
336
|
+
const typeName = node.type === "companion_object"
|
|
337
|
+
? (parentClass ?? objectName)
|
|
338
|
+
: (objectName ?? parentClass);
|
|
339
|
+
const body = childByType(node, "class_body");
|
|
340
|
+
if (!body || !typeName)
|
|
341
|
+
return;
|
|
342
|
+
for (const element of namedChildren(body)) {
|
|
343
|
+
if (element.type === "function_declaration") {
|
|
344
|
+
handleFunction(file, element, typeName, functions);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function extractFromTree(file, _source, tree) {
|
|
349
|
+
const functions = [];
|
|
350
|
+
for (const stmt of namedChildren(tree.rootNode)) {
|
|
351
|
+
if (stmt.type === "function_declaration") {
|
|
352
|
+
handleFunction(file, stmt, null, functions);
|
|
353
|
+
}
|
|
354
|
+
else if (stmt.type === "class_declaration") {
|
|
355
|
+
handleClass(file, stmt, functions);
|
|
356
|
+
}
|
|
357
|
+
else if (stmt.type === "object_declaration") {
|
|
358
|
+
handleObjectLike(file, stmt, null, functions);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return functions;
|
|
362
|
+
}
|
|
363
|
+
export const kotlinExtractor = {
|
|
364
|
+
id: "kotlin",
|
|
365
|
+
extensions: [".kt", ".kts"],
|
|
366
|
+
grammarPackage: "tree-sitter-kotlin",
|
|
367
|
+
extract: extractFromTree,
|
|
368
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { heuristicCallSyntax } from "./call-syntax.js";
|
|
2
|
+
import { childByType, declaredParamsOf, collapseWs, locFromNode, namedChildren, } from "./types.js";
|
|
3
|
+
function getParamsLabel(params) {
|
|
4
|
+
if (!params || params.type !== "parameters")
|
|
5
|
+
return "()";
|
|
6
|
+
const names = [];
|
|
7
|
+
for (const p of namedChildren(params)) {
|
|
8
|
+
if (p.type === "identifier")
|
|
9
|
+
names.push(p.text);
|
|
10
|
+
else if (p.type === "vararg_expression")
|
|
11
|
+
names.push("...");
|
|
12
|
+
}
|
|
13
|
+
return names.length === 0 ? "()" : `(${names.join(", ")})`;
|
|
14
|
+
}
|
|
15
|
+
function calleeKey(node, typeName) {
|
|
16
|
+
if (node.type === "identifier")
|
|
17
|
+
return node.text;
|
|
18
|
+
if (node.type === "method_index_expression" ||
|
|
19
|
+
node.type === "dot_index_expression") {
|
|
20
|
+
const object = node.namedChild(0);
|
|
21
|
+
const field = node.namedChild(1);
|
|
22
|
+
if (!object || !field || field.type !== "identifier")
|
|
23
|
+
return null;
|
|
24
|
+
const prop = field.text;
|
|
25
|
+
if (object.type === "identifier") {
|
|
26
|
+
if (object.text === "self" && typeName) {
|
|
27
|
+
return `${typeName}.${prop}`;
|
|
28
|
+
}
|
|
29
|
+
return `${object.text}.${prop}`;
|
|
30
|
+
}
|
|
31
|
+
if (typeName)
|
|
32
|
+
return `${typeName}.${prop}`;
|
|
33
|
+
return prop;
|
|
34
|
+
}
|
|
35
|
+
// Ignore computed: obj[key]()
|
|
36
|
+
if (node.type === "bracket_index_expression")
|
|
37
|
+
return null;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function statementsOf(body) {
|
|
41
|
+
if (!body)
|
|
42
|
+
return [];
|
|
43
|
+
if (body.type === "block")
|
|
44
|
+
return namedChildren(body);
|
|
45
|
+
return [body];
|
|
46
|
+
}
|
|
47
|
+
function collectStatements(file, statements, typeName) {
|
|
48
|
+
const steps = [];
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
const addCall = (key, node) => {
|
|
51
|
+
const mark = `${key}:${node.startIndex}`;
|
|
52
|
+
if (seen.has(mark))
|
|
53
|
+
return;
|
|
54
|
+
seen.add(mark);
|
|
55
|
+
const step = { type: "call", key, ...locFromNode(file, node) };
|
|
56
|
+
step.syntax = heuristicCallSyntax(node, key);
|
|
57
|
+
steps.push(step);
|
|
58
|
+
};
|
|
59
|
+
const walk = (node) => {
|
|
60
|
+
if (node.type === "function_declaration" ||
|
|
61
|
+
node.type === "function_definition") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (node.type === "if_statement") {
|
|
65
|
+
const kids = namedChildren(node);
|
|
66
|
+
const thenBlock = childByType(node, "block");
|
|
67
|
+
const cond = kids.find((c) => c.type !== "block" &&
|
|
68
|
+
c.type !== "else_statement" &&
|
|
69
|
+
c.type !== "elseif_statement") ?? null;
|
|
70
|
+
const condText = cond ? collapseWs(cond.text) : "";
|
|
71
|
+
steps.push({
|
|
72
|
+
type: "branch",
|
|
73
|
+
key: condText ? `if:${condText}` : "if",
|
|
74
|
+
label: condText ? `if ${condText}` : "if",
|
|
75
|
+
...locFromNode(file, cond ?? node),
|
|
76
|
+
children: collectStatements(file, statementsOf(thenBlock), typeName),
|
|
77
|
+
});
|
|
78
|
+
for (const clause of kids) {
|
|
79
|
+
if (clause.type === "elseif_statement") {
|
|
80
|
+
const elseifCond = namedChildren(clause).find((c) => c.type !== "block") ?? null;
|
|
81
|
+
const text = elseifCond ? collapseWs(elseifCond.text) : "";
|
|
82
|
+
steps.push({
|
|
83
|
+
type: "branch",
|
|
84
|
+
key: text ? `else-if:${text}` : "else-if",
|
|
85
|
+
label: text ? `elseif ${text}` : "elseif",
|
|
86
|
+
...locFromNode(file, elseifCond ?? clause),
|
|
87
|
+
children: collectStatements(file, statementsOf(childByType(clause, "block")), typeName),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (clause.type === "else_statement") {
|
|
91
|
+
steps.push({
|
|
92
|
+
type: "branch",
|
|
93
|
+
key: "else",
|
|
94
|
+
label: "else",
|
|
95
|
+
...locFromNode(file, clause),
|
|
96
|
+
children: collectStatements(file, statementsOf(childByType(clause, "block")), typeName),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (node.type === "function_call") {
|
|
103
|
+
const callee = node.namedChild(0);
|
|
104
|
+
if (callee) {
|
|
105
|
+
const key = calleeKey(callee, typeName);
|
|
106
|
+
if (key)
|
|
107
|
+
addCall(key, node);
|
|
108
|
+
}
|
|
109
|
+
for (const child of namedChildren(node).slice(1))
|
|
110
|
+
walk(child);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const child of namedChildren(node))
|
|
114
|
+
walk(child);
|
|
115
|
+
};
|
|
116
|
+
for (const stmt of statements)
|
|
117
|
+
walk(stmt);
|
|
118
|
+
return steps;
|
|
119
|
+
}
|
|
120
|
+
function functionName(node) {
|
|
121
|
+
const method = childByType(node, "method_index_expression");
|
|
122
|
+
if (method) {
|
|
123
|
+
const typeId = method.namedChild(0);
|
|
124
|
+
const nameId = method.namedChild(1);
|
|
125
|
+
if (typeId?.type === "identifier" && nameId?.type === "identifier") {
|
|
126
|
+
return { typeName: typeId.text, name: nameId.text };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const dotted = childByType(node, "dot_index_expression");
|
|
130
|
+
if (dotted) {
|
|
131
|
+
const typeId = dotted.namedChild(0);
|
|
132
|
+
const nameId = dotted.namedChild(1);
|
|
133
|
+
if (typeId?.type === "identifier" && nameId?.type === "identifier") {
|
|
134
|
+
return { typeName: typeId.text, name: nameId.text };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const id = childByType(node, "identifier");
|
|
138
|
+
if (id)
|
|
139
|
+
return { typeName: null, name: id.text };
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function isLocalFunction(node) {
|
|
143
|
+
// `local function foo` — grammar may expose `local` as an unnamed child
|
|
144
|
+
// or via a leading "local" token in text.
|
|
145
|
+
if (node.text.trimStart().startsWith("local "))
|
|
146
|
+
return true;
|
|
147
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
148
|
+
const c = node.child(i);
|
|
149
|
+
if (c && !c.isNamed && c.text === "local")
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
function handleFunction(file, node, functions) {
|
|
155
|
+
const parsed = functionName(node);
|
|
156
|
+
if (!parsed)
|
|
157
|
+
return;
|
|
158
|
+
const { typeName, name } = parsed;
|
|
159
|
+
const params = childByType(node, "parameters");
|
|
160
|
+
const body = childByType(node, "block");
|
|
161
|
+
const key = typeName ? `${typeName}.${name}` : name;
|
|
162
|
+
const local = isLocalFunction(node);
|
|
163
|
+
functions.push({
|
|
164
|
+
key,
|
|
165
|
+
label: `${key}${getParamsLabel(params)}`,
|
|
166
|
+
params: declaredParamsOf(node),
|
|
167
|
+
file,
|
|
168
|
+
steps: collectStatements(file, statementsOf(body), typeName),
|
|
169
|
+
exported: !local,
|
|
170
|
+
start: node.startIndex,
|
|
171
|
+
end: node.endIndex,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function extractFromTree(file, _source, tree) {
|
|
175
|
+
const functions = [];
|
|
176
|
+
const visit = (node) => {
|
|
177
|
+
if (node.type === "function_declaration") {
|
|
178
|
+
handleFunction(file, node, functions);
|
|
179
|
+
// Still walk into body for nested function_declaration indexing
|
|
180
|
+
const body = childByType(node, "block");
|
|
181
|
+
if (body) {
|
|
182
|
+
for (const child of namedChildren(body)) {
|
|
183
|
+
if (child.type === "function_declaration" ||
|
|
184
|
+
child.type === "variable_declaration" ||
|
|
185
|
+
child.type === "assignment_statement") {
|
|
186
|
+
visit(child);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// local foo = function() ... end
|
|
193
|
+
if (node.type === "function_definition" &&
|
|
194
|
+
node.parent &&
|
|
195
|
+
(node.parent.type === "variable_declaration" ||
|
|
196
|
+
node.parent.type === "assignment_statement" ||
|
|
197
|
+
node.parent.type === "expression_list")) {
|
|
198
|
+
// Prefer named binding from surrounding assignment when possible
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
for (const child of namedChildren(node))
|
|
202
|
+
visit(child);
|
|
203
|
+
};
|
|
204
|
+
visit(tree.rootNode);
|
|
205
|
+
return functions;
|
|
206
|
+
}
|
|
207
|
+
export const luaExtractor = {
|
|
208
|
+
id: "lua",
|
|
209
|
+
extensions: [".lua"],
|
|
210
|
+
grammarPackage: "@tree-sitter-grammars/tree-sitter-lua",
|
|
211
|
+
extract: extractFromTree,
|
|
212
|
+
};
|