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,331 @@
|
|
|
1
|
+
import { heuristicCallSyntax, parameterText, textOnlyParams, } from "./call-syntax.js";
|
|
2
|
+
import { childByType, collapseWs, locFromNode, namedChildren, } from "./types.js";
|
|
3
|
+
function isDefKw(name) {
|
|
4
|
+
return name === "def" || name === "defp" || name === "defmacro" || name === "defmacrop";
|
|
5
|
+
}
|
|
6
|
+
function isPrivateDef(name) {
|
|
7
|
+
return name === "defp" || name === "defmacrop";
|
|
8
|
+
}
|
|
9
|
+
function getParamsLabel(args) {
|
|
10
|
+
if (!args)
|
|
11
|
+
return "()";
|
|
12
|
+
// def foo(a, b) → arguments node under the name call
|
|
13
|
+
const names = [];
|
|
14
|
+
for (const c of namedChildren(args)) {
|
|
15
|
+
if (c.type === "identifier")
|
|
16
|
+
names.push(c.text);
|
|
17
|
+
else if (c.type === "keywords")
|
|
18
|
+
continue;
|
|
19
|
+
else
|
|
20
|
+
names.push("_");
|
|
21
|
+
}
|
|
22
|
+
return names.length === 0 ? "()" : `(${names.join(", ")})`;
|
|
23
|
+
}
|
|
24
|
+
/** Extract def name + params from `def`/`defp` call arguments. */
|
|
25
|
+
function defNameAndParams(args) {
|
|
26
|
+
if (!args)
|
|
27
|
+
return null;
|
|
28
|
+
const kids = namedChildren(args);
|
|
29
|
+
const head = kids[0] ?? null;
|
|
30
|
+
if (!head)
|
|
31
|
+
return null;
|
|
32
|
+
if (head.type === "identifier") {
|
|
33
|
+
return { name: head.text, params: null };
|
|
34
|
+
}
|
|
35
|
+
if (head.type === "call") {
|
|
36
|
+
const id = childByType(head, "identifier");
|
|
37
|
+
if (!id)
|
|
38
|
+
return null;
|
|
39
|
+
return { name: id.text, params: childByType(head, "arguments") };
|
|
40
|
+
}
|
|
41
|
+
// def foo(%{}), do: ... — pattern head
|
|
42
|
+
if (head.type === "binary_operator") {
|
|
43
|
+
const left = head.namedChild(0);
|
|
44
|
+
if (left?.type === "identifier")
|
|
45
|
+
return { name: left.text, params: null };
|
|
46
|
+
if (left?.type === "call") {
|
|
47
|
+
const id = childByType(left, "identifier");
|
|
48
|
+
if (id)
|
|
49
|
+
return { name: id.text, params: childByType(left, "arguments") };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function calleeKey(node, moduleName, hasExplicitArgs) {
|
|
55
|
+
if (node.type === "identifier") {
|
|
56
|
+
// Bare call inside a module → Module.fun (local call)
|
|
57
|
+
if (moduleName)
|
|
58
|
+
return `${moduleName}.${node.text}`;
|
|
59
|
+
return node.text;
|
|
60
|
+
}
|
|
61
|
+
if (node.type === "dot") {
|
|
62
|
+
const left = node.namedChild(0);
|
|
63
|
+
const right = node.namedChild(1);
|
|
64
|
+
if (!right)
|
|
65
|
+
return null;
|
|
66
|
+
const prop = right.text;
|
|
67
|
+
if (!left)
|
|
68
|
+
return prop;
|
|
69
|
+
if (left.type === "alias") {
|
|
70
|
+
// Foo.Bar.baz → use full alias text
|
|
71
|
+
return `${left.text}.${prop}`;
|
|
72
|
+
}
|
|
73
|
+
if (left.type === "identifier") {
|
|
74
|
+
// options.session_id with no args is field-ish remote call noise — skip
|
|
75
|
+
if (!hasExplicitArgs && left.text[0] && left.text[0] === left.text[0].toLowerCase()) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return `${left.text}.${prop}`;
|
|
79
|
+
}
|
|
80
|
+
// get_services().boot() — left is call; keep receiver.prop when possible
|
|
81
|
+
if (left.type === "call") {
|
|
82
|
+
return prop;
|
|
83
|
+
}
|
|
84
|
+
if (moduleName)
|
|
85
|
+
return `${moduleName}.${prop}`;
|
|
86
|
+
return prop;
|
|
87
|
+
}
|
|
88
|
+
// access_call opts[:ok] — not a callable we track
|
|
89
|
+
if (node.type === "access_call")
|
|
90
|
+
return null;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function bodyOfDef(call) {
|
|
94
|
+
const doBlock = childByType(call, "do_block");
|
|
95
|
+
if (doBlock)
|
|
96
|
+
return doBlock;
|
|
97
|
+
// def foo, do: expr → keywords pair
|
|
98
|
+
const args = childByType(call, "arguments");
|
|
99
|
+
const keywords = args ? childByType(args, "keywords") : null;
|
|
100
|
+
if (keywords) {
|
|
101
|
+
const pair = childByType(keywords, "pair");
|
|
102
|
+
if (pair) {
|
|
103
|
+
// keyword + value; value is last named child
|
|
104
|
+
const kids = namedChildren(pair);
|
|
105
|
+
return kids[kids.length - 1] ?? null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function collectBody(file, body, moduleName) {
|
|
111
|
+
if (!body)
|
|
112
|
+
return [];
|
|
113
|
+
return collectStatements(file, namedChildren(body), moduleName);
|
|
114
|
+
}
|
|
115
|
+
function collectStatements(file, statements, moduleName) {
|
|
116
|
+
const steps = [];
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
const addCall = (key, node) => {
|
|
119
|
+
const mark = `${key}:${node.startIndex}`;
|
|
120
|
+
if (seen.has(mark))
|
|
121
|
+
return;
|
|
122
|
+
seen.add(mark);
|
|
123
|
+
const step = { type: "call", key, ...locFromNode(file, node) };
|
|
124
|
+
step.syntax = heuristicCallSyntax(node, key);
|
|
125
|
+
steps.push(step);
|
|
126
|
+
};
|
|
127
|
+
const walk = (node) => {
|
|
128
|
+
// Nested defs / anonymous fn — do not attribute to outer
|
|
129
|
+
if (node.type === "anonymous_function")
|
|
130
|
+
return;
|
|
131
|
+
if (node.type === "call") {
|
|
132
|
+
const head = childByType(node, "identifier");
|
|
133
|
+
if (head && (isDefKw(head.text) || head.text === "fn" || head.text === "defmodule")) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (head && head.text === "if") {
|
|
137
|
+
const args = childByType(node, "arguments");
|
|
138
|
+
const cond = args?.namedChild(0) ?? null;
|
|
139
|
+
const condText = cond ? collapseWs(cond.text) : "";
|
|
140
|
+
const doBlock = childByType(node, "do_block");
|
|
141
|
+
const thenKids = doBlock
|
|
142
|
+
? namedChildren(doBlock).filter((c) => c.type !== "else_block")
|
|
143
|
+
: [];
|
|
144
|
+
const elseBlock = doBlock ? childByType(doBlock, "else_block") : null;
|
|
145
|
+
steps.push({
|
|
146
|
+
type: "branch",
|
|
147
|
+
key: condText ? `if:${condText}` : "if",
|
|
148
|
+
label: condText ? `if ${condText}` : "if",
|
|
149
|
+
...locFromNode(file, cond ?? node),
|
|
150
|
+
children: collectStatements(file, thenKids, moduleName),
|
|
151
|
+
});
|
|
152
|
+
if (elseBlock) {
|
|
153
|
+
steps.push({
|
|
154
|
+
type: "branch",
|
|
155
|
+
key: "else",
|
|
156
|
+
label: "else",
|
|
157
|
+
...locFromNode(file, elseBlock),
|
|
158
|
+
children: collectBody(file, elseBlock, moduleName),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// cond may contain calls — walk it
|
|
162
|
+
if (cond)
|
|
163
|
+
walk(cond);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (head && head.text === "case") {
|
|
167
|
+
const doBlock = childByType(node, "do_block");
|
|
168
|
+
for (const clause of doBlock ? namedChildren(doBlock) : []) {
|
|
169
|
+
if (clause.type !== "stab_clause")
|
|
170
|
+
continue;
|
|
171
|
+
const pattern = childByType(clause, "arguments");
|
|
172
|
+
const text = pattern ? collapseWs(pattern.text) : "";
|
|
173
|
+
const body = childByType(clause, "body");
|
|
174
|
+
steps.push({
|
|
175
|
+
type: "branch",
|
|
176
|
+
key: text ? `case:${text}` : "case",
|
|
177
|
+
label: text ? `case ${text}` : "case",
|
|
178
|
+
...locFromNode(file, pattern ?? clause),
|
|
179
|
+
children: body ? collectBody(file, body, moduleName) : [],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (head && head.text === "cond") {
|
|
185
|
+
const doBlock = childByType(node, "do_block");
|
|
186
|
+
for (const clause of doBlock ? namedChildren(doBlock) : []) {
|
|
187
|
+
if (clause.type !== "stab_clause")
|
|
188
|
+
continue;
|
|
189
|
+
const pattern = childByType(clause, "arguments");
|
|
190
|
+
const text = pattern ? collapseWs(pattern.text) : "";
|
|
191
|
+
const body = childByType(clause, "body");
|
|
192
|
+
steps.push({
|
|
193
|
+
type: "branch",
|
|
194
|
+
key: text ? `cond:${text}` : "cond",
|
|
195
|
+
label: text ? `cond ${text}` : "cond",
|
|
196
|
+
...locFromNode(file, pattern ?? clause),
|
|
197
|
+
children: body ? collectBody(file, body, moduleName) : [],
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (head && head.text === "try") {
|
|
203
|
+
const doBlock = childByType(node, "do_block");
|
|
204
|
+
const tryKids = doBlock
|
|
205
|
+
? namedChildren(doBlock).filter((c) => c.type !== "rescue_block" &&
|
|
206
|
+
c.type !== "catch_block" &&
|
|
207
|
+
c.type !== "after_block" &&
|
|
208
|
+
c.type !== "else_block")
|
|
209
|
+
: [];
|
|
210
|
+
steps.push({
|
|
211
|
+
type: "branch",
|
|
212
|
+
key: "try",
|
|
213
|
+
label: "try",
|
|
214
|
+
...locFromNode(file, node),
|
|
215
|
+
children: collectStatements(file, tryKids, moduleName),
|
|
216
|
+
});
|
|
217
|
+
const rescue = doBlock ? childByType(doBlock, "rescue_block") : null;
|
|
218
|
+
if (rescue) {
|
|
219
|
+
for (const clause of namedChildren(rescue)) {
|
|
220
|
+
if (clause.type !== "stab_clause")
|
|
221
|
+
continue;
|
|
222
|
+
const pattern = childByType(clause, "arguments");
|
|
223
|
+
const text = pattern ? collapseWs(pattern.text) : "";
|
|
224
|
+
const body = childByType(clause, "body");
|
|
225
|
+
steps.push({
|
|
226
|
+
type: "branch",
|
|
227
|
+
key: text ? `rescue:${text}` : "rescue",
|
|
228
|
+
label: text ? `rescue ${text}` : "rescue",
|
|
229
|
+
...locFromNode(file, pattern ?? clause),
|
|
230
|
+
children: body ? collectBody(file, body, moduleName) : [],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const after = doBlock ? childByType(doBlock, "after_block") : null;
|
|
235
|
+
if (after) {
|
|
236
|
+
steps.push({
|
|
237
|
+
type: "branch",
|
|
238
|
+
key: "after",
|
|
239
|
+
label: "after",
|
|
240
|
+
...locFromNode(file, after),
|
|
241
|
+
children: collectBody(file, after, moduleName),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Regular call — callee is first named child (identifier or dot)
|
|
247
|
+
const callee = node.namedChild(0);
|
|
248
|
+
const args = childByType(node, "arguments");
|
|
249
|
+
// Explicit args node present (even empty `()`) vs bare `foo.bar`
|
|
250
|
+
const hasExplicitArgs = args !== null;
|
|
251
|
+
if (callee && callee.type !== "do_block") {
|
|
252
|
+
const key = calleeKey(callee, moduleName, hasExplicitArgs);
|
|
253
|
+
if (key)
|
|
254
|
+
addCall(key, node);
|
|
255
|
+
}
|
|
256
|
+
// Walk args for nested calls, but skip field-ish dots without further calls
|
|
257
|
+
if (args)
|
|
258
|
+
walk(args);
|
|
259
|
+
const doBlock = childByType(node, "do_block");
|
|
260
|
+
if (doBlock)
|
|
261
|
+
walk(doBlock);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
for (const child of namedChildren(node))
|
|
265
|
+
walk(child);
|
|
266
|
+
};
|
|
267
|
+
for (const stmt of statements)
|
|
268
|
+
walk(stmt);
|
|
269
|
+
return steps;
|
|
270
|
+
}
|
|
271
|
+
function handleDef(file, call, moduleName, functions) {
|
|
272
|
+
const kw = childByType(call, "identifier")?.text ?? "def";
|
|
273
|
+
const args = childByType(call, "arguments");
|
|
274
|
+
const parsed = defNameAndParams(args);
|
|
275
|
+
if (!parsed)
|
|
276
|
+
return;
|
|
277
|
+
const { name, params } = parsed;
|
|
278
|
+
const body = bodyOfDef(call);
|
|
279
|
+
const key = moduleName ? `${moduleName}.${name}` : name;
|
|
280
|
+
const exported = !isPrivateDef(kw);
|
|
281
|
+
functions.push({
|
|
282
|
+
key,
|
|
283
|
+
label: `${key}${getParamsLabel(params)}`,
|
|
284
|
+
// Elixir uses `arguments` for a declared list as well as a call's.
|
|
285
|
+
params: textOnlyParams(parameterText(params)),
|
|
286
|
+
file,
|
|
287
|
+
steps: collectBody(file, body, moduleName),
|
|
288
|
+
exported,
|
|
289
|
+
start: call.startIndex,
|
|
290
|
+
end: call.endIndex,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function handleDefmodule(file, call, functions) {
|
|
294
|
+
const args = childByType(call, "arguments");
|
|
295
|
+
const alias = args ? childByType(args, "alias") : null;
|
|
296
|
+
const moduleName = alias?.text ?? null;
|
|
297
|
+
if (!moduleName)
|
|
298
|
+
return;
|
|
299
|
+
const body = childByType(call, "do_block");
|
|
300
|
+
if (!body)
|
|
301
|
+
return;
|
|
302
|
+
for (const stmt of namedChildren(body)) {
|
|
303
|
+
if (stmt.type !== "call")
|
|
304
|
+
continue;
|
|
305
|
+
const kw = childByType(stmt, "identifier")?.text;
|
|
306
|
+
if (kw && isDefKw(kw)) {
|
|
307
|
+
handleDef(file, stmt, moduleName, functions);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function extractFromTree(file, _source, tree) {
|
|
312
|
+
const functions = [];
|
|
313
|
+
for (const stmt of namedChildren(tree.rootNode)) {
|
|
314
|
+
if (stmt.type !== "call")
|
|
315
|
+
continue;
|
|
316
|
+
const kw = childByType(stmt, "identifier")?.text;
|
|
317
|
+
if (kw === "defmodule") {
|
|
318
|
+
handleDefmodule(file, stmt, functions);
|
|
319
|
+
}
|
|
320
|
+
else if (kw && isDefKw(kw)) {
|
|
321
|
+
handleDef(file, stmt, null, functions);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return functions;
|
|
325
|
+
}
|
|
326
|
+
export const elixirExtractor = {
|
|
327
|
+
id: "elixir",
|
|
328
|
+
extensions: [".ex", ".exs"],
|
|
329
|
+
grammarPackage: "tree-sitter-elixir",
|
|
330
|
+
extract: extractFromTree,
|
|
331
|
+
};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { heuristicCallSyntax } from "./call-syntax.js";
|
|
2
|
+
import { childByType, declaredParamsOf, collapseWs, locFromNode, namedChildren, sameNode, } from "./types.js";
|
|
3
|
+
function isExported(name) {
|
|
4
|
+
const ch = name[0];
|
|
5
|
+
return ch !== undefined && ch === ch.toUpperCase() && ch !== ch.toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
function getParamsLabel(params) {
|
|
8
|
+
if (!params || params.type !== "parameter_list")
|
|
9
|
+
return "()";
|
|
10
|
+
const names = [];
|
|
11
|
+
for (const p of namedChildren(params)) {
|
|
12
|
+
if (p.type !== "parameter_declaration")
|
|
13
|
+
continue;
|
|
14
|
+
const ids = namedChildren(p).filter((c) => c.type === "identifier");
|
|
15
|
+
if (ids.length === 0)
|
|
16
|
+
names.push("_");
|
|
17
|
+
else
|
|
18
|
+
for (const id of ids)
|
|
19
|
+
names.push(id.text);
|
|
20
|
+
}
|
|
21
|
+
return names.length === 0 ? "()" : `(${names.join(", ")})`;
|
|
22
|
+
}
|
|
23
|
+
function calleeKey(node, receiverType, receiverName) {
|
|
24
|
+
if (node.type === "identifier") {
|
|
25
|
+
// NewThing() → new Thing (constructor alias)
|
|
26
|
+
if (node.text.startsWith("New") && node.text.length > 3) {
|
|
27
|
+
const typeName = node.text.slice(3);
|
|
28
|
+
if (isExported(typeName))
|
|
29
|
+
return `new ${typeName}`;
|
|
30
|
+
}
|
|
31
|
+
return node.text;
|
|
32
|
+
}
|
|
33
|
+
if (node.type === "selector_expression") {
|
|
34
|
+
const object = node.namedChild(0);
|
|
35
|
+
const field = childByType(node, "field_identifier");
|
|
36
|
+
if (!object || !field)
|
|
37
|
+
return null;
|
|
38
|
+
const prop = field.text;
|
|
39
|
+
if (object.type === "identifier") {
|
|
40
|
+
const objName = object.text;
|
|
41
|
+
if (receiverType && receiverName && objName === receiverName) {
|
|
42
|
+
return `${receiverType}.${prop}`;
|
|
43
|
+
}
|
|
44
|
+
return `${objName}.${prop}`;
|
|
45
|
+
}
|
|
46
|
+
return `${collapseWs(object.text)}.${prop}`;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function statementsOf(node) {
|
|
51
|
+
if (node.type === "block") {
|
|
52
|
+
const list = childByType(node, "statement_list");
|
|
53
|
+
return list ? namedChildren(list) : namedChildren(node);
|
|
54
|
+
}
|
|
55
|
+
if (node.type === "statement_list")
|
|
56
|
+
return namedChildren(node);
|
|
57
|
+
return [node];
|
|
58
|
+
}
|
|
59
|
+
function collectIf(file, node, receiverType, receiverName, steps, asElseIf = false) {
|
|
60
|
+
const kids = namedChildren(node);
|
|
61
|
+
const consequent = kids.find((c) => c.type === "block") ?? null;
|
|
62
|
+
const alt = kids.find((c) => !sameNode(c, consequent) && (c.type === "block" || c.type === "if_statement")) ??
|
|
63
|
+
null;
|
|
64
|
+
const before = consequent
|
|
65
|
+
? kids.slice(0, kids.indexOf(consequent))
|
|
66
|
+
: kids;
|
|
67
|
+
const condNode = before[before.length - 1] ?? null;
|
|
68
|
+
const condText = condNode ? collapseWs(condNode.text) : "";
|
|
69
|
+
const kind = asElseIf ? "else-if" : "if";
|
|
70
|
+
const labelKind = asElseIf ? "else if" : "if";
|
|
71
|
+
steps.push({
|
|
72
|
+
type: "branch",
|
|
73
|
+
key: condText ? `${kind}:${condText}` : kind,
|
|
74
|
+
label: condText ? `${labelKind} ${condText}` : labelKind,
|
|
75
|
+
...locFromNode(file, condNode ?? node),
|
|
76
|
+
children: consequent
|
|
77
|
+
? collectStatements(file, statementsOf(consequent), receiverType, receiverName)
|
|
78
|
+
: [],
|
|
79
|
+
});
|
|
80
|
+
if (!alt)
|
|
81
|
+
return;
|
|
82
|
+
if (alt.type === "if_statement") {
|
|
83
|
+
collectIf(file, alt, receiverType, receiverName, steps, true);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
steps.push({
|
|
87
|
+
type: "branch",
|
|
88
|
+
key: "else",
|
|
89
|
+
label: "else",
|
|
90
|
+
...locFromNode(file, alt),
|
|
91
|
+
children: collectStatements(file, statementsOf(alt), receiverType, receiverName),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function collectStatements(file, statements, receiverType, receiverName) {
|
|
96
|
+
const steps = [];
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
const addCall = (key, node, children) => {
|
|
99
|
+
const mark = `${key}:${node.startIndex}`;
|
|
100
|
+
if (seen.has(mark))
|
|
101
|
+
return;
|
|
102
|
+
seen.add(mark);
|
|
103
|
+
const step = {
|
|
104
|
+
type: "call",
|
|
105
|
+
key,
|
|
106
|
+
...locFromNode(file, node),
|
|
107
|
+
children,
|
|
108
|
+
};
|
|
109
|
+
step.syntax = heuristicCallSyntax(node, key);
|
|
110
|
+
steps.push(step);
|
|
111
|
+
};
|
|
112
|
+
const walk = (node) => {
|
|
113
|
+
if (node.type === "function_declaration" ||
|
|
114
|
+
node.type === "method_declaration" ||
|
|
115
|
+
node.type === "func_literal") {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (node.type === "if_statement") {
|
|
119
|
+
collectIf(file, node, receiverType, receiverName, steps, false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (node.type === "defer_statement") {
|
|
123
|
+
steps.push({
|
|
124
|
+
type: "branch",
|
|
125
|
+
key: "defer",
|
|
126
|
+
label: "defer",
|
|
127
|
+
...locFromNode(file, node),
|
|
128
|
+
children: collectStatements(file, namedChildren(node), receiverType, receiverName),
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (node.type === "expression_switch_statement" ||
|
|
133
|
+
node.type === "type_switch_statement") {
|
|
134
|
+
const kids = namedChildren(node);
|
|
135
|
+
const subject = kids.find((c) => c.type !== "expression_case" &&
|
|
136
|
+
c.type !== "type_case" &&
|
|
137
|
+
c.type !== "default_case") ?? null;
|
|
138
|
+
const subjectText = subject ? collapseWs(subject.text) : "";
|
|
139
|
+
for (const clause of kids) {
|
|
140
|
+
if (clause.type === "expression_case" || clause.type === "type_case") {
|
|
141
|
+
const expr = childByType(clause, "expression_list") ??
|
|
142
|
+
childByType(clause, "type_list") ??
|
|
143
|
+
namedChildren(clause).find((c) => c.type !== "statement_list") ??
|
|
144
|
+
null;
|
|
145
|
+
const text = expr ? collapseWs(expr.text) : "";
|
|
146
|
+
const list = childByType(clause, "statement_list");
|
|
147
|
+
steps.push({
|
|
148
|
+
type: "branch",
|
|
149
|
+
key: text ? `case:${text}` : "case",
|
|
150
|
+
label: text
|
|
151
|
+
? `case ${text}`
|
|
152
|
+
: subjectText
|
|
153
|
+
? `case ${subjectText}`
|
|
154
|
+
: "case",
|
|
155
|
+
...locFromNode(file, expr ?? clause),
|
|
156
|
+
children: list
|
|
157
|
+
? collectStatements(file, namedChildren(list), receiverType, receiverName)
|
|
158
|
+
: [],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (clause.type === "default_case") {
|
|
162
|
+
const list = childByType(clause, "statement_list");
|
|
163
|
+
steps.push({
|
|
164
|
+
type: "branch",
|
|
165
|
+
key: "default",
|
|
166
|
+
label: "default",
|
|
167
|
+
...locFromNode(file, clause),
|
|
168
|
+
children: list
|
|
169
|
+
? collectStatements(file, namedChildren(list), receiverType, receiverName)
|
|
170
|
+
: [],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (node.type === "call_expression") {
|
|
177
|
+
const callee = node.namedChild(0);
|
|
178
|
+
const args = childByType(node, "argument_list");
|
|
179
|
+
const inlineChildren = args
|
|
180
|
+
? namedChildren(args).flatMap((arg) => {
|
|
181
|
+
if (arg.type !== "func_literal")
|
|
182
|
+
return [];
|
|
183
|
+
const body = childByType(arg, "block");
|
|
184
|
+
return body
|
|
185
|
+
? collectStatements(file, statementsOf(body), receiverType, receiverName)
|
|
186
|
+
: [];
|
|
187
|
+
})
|
|
188
|
+
: [];
|
|
189
|
+
if (callee && callee.type !== "func_literal") {
|
|
190
|
+
const key = calleeKey(callee, receiverType, receiverName);
|
|
191
|
+
if (key)
|
|
192
|
+
addCall(key, node, inlineChildren);
|
|
193
|
+
}
|
|
194
|
+
// Do not walk into func_literal callees/bodies
|
|
195
|
+
for (const child of namedChildren(node)) {
|
|
196
|
+
if (child.type === "func_literal")
|
|
197
|
+
continue;
|
|
198
|
+
if (sameNode(child, callee))
|
|
199
|
+
continue;
|
|
200
|
+
walk(child);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
for (const child of namedChildren(node))
|
|
205
|
+
walk(child);
|
|
206
|
+
};
|
|
207
|
+
for (const stmt of statements)
|
|
208
|
+
walk(stmt);
|
|
209
|
+
return steps;
|
|
210
|
+
}
|
|
211
|
+
function receiverTypeName(method) {
|
|
212
|
+
const recv = method.namedChild(0);
|
|
213
|
+
if (!recv || recv.type !== "parameter_list")
|
|
214
|
+
return null;
|
|
215
|
+
const decl = childByType(recv, "parameter_declaration");
|
|
216
|
+
if (!decl)
|
|
217
|
+
return null;
|
|
218
|
+
const pointer = childByType(decl, "pointer_type");
|
|
219
|
+
const typeId = childByType(pointer ?? decl, "type_identifier") ??
|
|
220
|
+
childByType(decl, "type_identifier");
|
|
221
|
+
return typeId?.text ?? null;
|
|
222
|
+
}
|
|
223
|
+
function receiverVariableName(method) {
|
|
224
|
+
const recv = method.namedChild(0);
|
|
225
|
+
if (!recv || recv.type !== "parameter_list")
|
|
226
|
+
return null;
|
|
227
|
+
const decl = childByType(recv, "parameter_declaration");
|
|
228
|
+
return decl ? childByType(decl, "identifier")?.text ?? null : null;
|
|
229
|
+
}
|
|
230
|
+
function handleFunction(file, node, functions) {
|
|
231
|
+
const name = childByType(node, "identifier")?.text ?? null;
|
|
232
|
+
if (!name)
|
|
233
|
+
return;
|
|
234
|
+
const params = namedChildren(node).find((c) => c.type === "parameter_list") ?? null;
|
|
235
|
+
const body = childByType(node, "block");
|
|
236
|
+
const info = {
|
|
237
|
+
key: name,
|
|
238
|
+
label: `${name}${getParamsLabel(params)}`,
|
|
239
|
+
params: declaredParamsOf(node),
|
|
240
|
+
file,
|
|
241
|
+
steps: body ? collectStatements(file, statementsOf(body), null, null) : [],
|
|
242
|
+
exported: isExported(name),
|
|
243
|
+
start: node.startIndex,
|
|
244
|
+
end: node.endIndex,
|
|
245
|
+
};
|
|
246
|
+
functions.push(info);
|
|
247
|
+
// NewThing → constructor alias `new Thing`
|
|
248
|
+
if (name.startsWith("New") && name.length > 3) {
|
|
249
|
+
const typeName = name.slice(3);
|
|
250
|
+
if (isExported(typeName)) {
|
|
251
|
+
functions.push({
|
|
252
|
+
...info,
|
|
253
|
+
key: `new ${typeName}`,
|
|
254
|
+
label: `${typeName}()`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function handleMethod(file, node, functions) {
|
|
260
|
+
const typeName = receiverTypeName(node);
|
|
261
|
+
const receiverName = receiverVariableName(node);
|
|
262
|
+
const name = childByType(node, "field_identifier")?.text ?? null;
|
|
263
|
+
if (!typeName || !name)
|
|
264
|
+
return;
|
|
265
|
+
const paramLists = namedChildren(node).filter((c) => c.type === "parameter_list");
|
|
266
|
+
const params = paramLists[1] ?? paramLists[0] ?? null;
|
|
267
|
+
const body = childByType(node, "block");
|
|
268
|
+
const key = `${typeName}.${name}`;
|
|
269
|
+
functions.push({
|
|
270
|
+
key,
|
|
271
|
+
label: `${key}${getParamsLabel(params)}`,
|
|
272
|
+
params: declaredParamsOf(node),
|
|
273
|
+
file,
|
|
274
|
+
steps: body
|
|
275
|
+
? collectStatements(file, statementsOf(body), typeName, receiverName)
|
|
276
|
+
: [],
|
|
277
|
+
exported: isExported(name),
|
|
278
|
+
start: node.startIndex,
|
|
279
|
+
end: node.endIndex,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function extractFromTree(file, _source, tree) {
|
|
283
|
+
const functions = [];
|
|
284
|
+
for (const stmt of namedChildren(tree.rootNode)) {
|
|
285
|
+
if (stmt.type === "function_declaration") {
|
|
286
|
+
handleFunction(file, stmt, functions);
|
|
287
|
+
}
|
|
288
|
+
else if (stmt.type === "method_declaration") {
|
|
289
|
+
handleMethod(file, stmt, functions);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return functions;
|
|
293
|
+
}
|
|
294
|
+
export const goExtractor = {
|
|
295
|
+
id: "go",
|
|
296
|
+
extensions: [".go"],
|
|
297
|
+
grammarPackage: "tree-sitter-go",
|
|
298
|
+
extract: extractFromTree,
|
|
299
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loaded tree-sitter grammar package surface.
|
|
3
|
+
* Named exports (`typescript`, `tsx`, …) are looked up by `resolveLanguage`.
|
|
4
|
+
*/
|
|
5
|
+
export type GrammarModule = {
|
|
6
|
+
language?: GrammarModule;
|
|
7
|
+
typescript?: GrammarModule;
|
|
8
|
+
tsx?: GrammarModule;
|
|
9
|
+
[exportName: string]: GrammarModule | undefined;
|
|
10
|
+
};
|
|
11
|
+
/** Runtime grammar handle passed to `parser.setLanguage`. */
|
|
12
|
+
export type GrammarLanguage = GrammarModule;
|
|
13
|
+
/** On-disk cache of npm-installed tree-sitter grammar packages. */
|
|
14
|
+
export declare function grammarCacheDir(): string;
|
|
15
|
+
/**
|
|
16
|
+
* The cache's own package.json, which also allows the grammar's install
|
|
17
|
+
* script: npm 12 blocks dependency install scripts unless `allowScripts`
|
|
18
|
+
* names them, and rejects `--allow-scripts` in a project install like this
|
|
19
|
+
* one. Earlier npm ignores the field. A cache written before this keeps its
|
|
20
|
+
* other fields and gains the entry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function ensureCachePackageJson(cacheDir: string, npmPackage: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Absolute path to one of npm's JS entry points (`npm-cli.js`, `npx-cli.js`),
|
|
25
|
+
* or undefined when npm is not installed beside Node.
|
|
26
|
+
*/
|
|
27
|
+
export declare function npmCliPath(cli: string, directories?: readonly string[]): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* On Windows, npm.cmd is a shell script, not an executable. Run npm's JS entry
|
|
30
|
+
* with Node instead: cache paths (including spaces and percent signs) remain
|
|
31
|
+
* literal argv values rather than being interpreted by cmd.exe.
|
|
32
|
+
*/
|
|
33
|
+
export declare function npmSpawnSpec(args: string[], platform?: NodeJS.Platform): {
|
|
34
|
+
file: string;
|
|
35
|
+
args: string[];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Path of this platform's prebuild when the file exists but targets another
|
|
39
|
+
* CPU (e.g. x86-64 bytes shipped as linux-arm64). Null when the prebuild is
|
|
40
|
+
* absent, unreadable, or correct: only a confirmed mismatch is reported.
|
|
41
|
+
* Exported for tests and diagnostics.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mislabeledPrebuild(packageRoot: string): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Install an npm grammar package into the shared cache if missing, then require it.
|
|
46
|
+
* Reuses the cache across CLI invocations.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadGrammarPackage(npmPackage: string): GrammarModule;
|
|
49
|
+
/** Resolve the value to pass to parser.setLanguage. */
|
|
50
|
+
export declare function resolveLanguage(mod: GrammarModule, exportName?: string): GrammarLanguage;
|