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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +259 -0
  3. package/dist/calltree.d.ts +47 -0
  4. package/dist/calltree.js +296 -0
  5. package/dist/cli.d.ts +57 -0
  6. package/dist/cli.js +340 -0
  7. package/dist/diff.d.ts +7 -0
  8. package/dist/diff.js +114 -0
  9. package/dist/extract.d.ts +26 -0
  10. package/dist/extract.js +152 -0
  11. package/dist/git.d.ts +40 -0
  12. package/dist/git.js +288 -0
  13. package/dist/index.d.ts +9 -0
  14. package/dist/index.js +8 -0
  15. package/dist/infer.d.ts +21 -0
  16. package/dist/infer.js +189 -0
  17. package/dist/languages/bash.d.ts +2 -0
  18. package/dist/languages/bash.js +208 -0
  19. package/dist/languages/c.d.ts +2 -0
  20. package/dist/languages/c.js +218 -0
  21. package/dist/languages/call-syntax.d.ts +125 -0
  22. package/dist/languages/call-syntax.js +997 -0
  23. package/dist/languages/cpp.d.ts +2 -0
  24. package/dist/languages/cpp.js +321 -0
  25. package/dist/languages/csharp.d.ts +2 -0
  26. package/dist/languages/csharp.js +324 -0
  27. package/dist/languages/elixir.d.ts +2 -0
  28. package/dist/languages/elixir.js +331 -0
  29. package/dist/languages/go.d.ts +2 -0
  30. package/dist/languages/go.js +299 -0
  31. package/dist/languages/grammars.d.ts +50 -0
  32. package/dist/languages/grammars.js +351 -0
  33. package/dist/languages/haskell.d.ts +2 -0
  34. package/dist/languages/haskell.js +250 -0
  35. package/dist/languages/java.d.ts +2 -0
  36. package/dist/languages/java.js +351 -0
  37. package/dist/languages/javascript.d.ts +4 -0
  38. package/dist/languages/javascript.js +648 -0
  39. package/dist/languages/kotlin.d.ts +2 -0
  40. package/dist/languages/kotlin.js +368 -0
  41. package/dist/languages/lua.d.ts +2 -0
  42. package/dist/languages/lua.js +212 -0
  43. package/dist/languages/ocaml.d.ts +2 -0
  44. package/dist/languages/ocaml.js +291 -0
  45. package/dist/languages/perl.d.ts +2 -0
  46. package/dist/languages/perl.js +418 -0
  47. package/dist/languages/php.d.ts +2 -0
  48. package/dist/languages/php.js +397 -0
  49. package/dist/languages/python.d.ts +2 -0
  50. package/dist/languages/python.js +376 -0
  51. package/dist/languages/registry.d.ts +7 -0
  52. package/dist/languages/registry.js +69 -0
  53. package/dist/languages/ruby.d.ts +2 -0
  54. package/dist/languages/ruby.js +391 -0
  55. package/dist/languages/rust.d.ts +2 -0
  56. package/dist/languages/rust.js +261 -0
  57. package/dist/languages/scala.d.ts +2 -0
  58. package/dist/languages/scala.js +307 -0
  59. package/dist/languages/solidity.d.ts +2 -0
  60. package/dist/languages/solidity.js +240 -0
  61. package/dist/languages/swift.d.ts +2 -0
  62. package/dist/languages/swift.js +268 -0
  63. package/dist/languages/types.d.ts +36 -0
  64. package/dist/languages/types.js +74 -0
  65. package/dist/languages/typescript-contracts.d.ts +57 -0
  66. package/dist/languages/typescript-contracts.js +528 -0
  67. package/dist/languages/typescript-dispatch.d.ts +68 -0
  68. package/dist/languages/typescript-dispatch.js +710 -0
  69. package/dist/languages/typescript.d.ts +4 -0
  70. package/dist/languages/typescript.js +722 -0
  71. package/dist/languages/zig.d.ts +2 -0
  72. package/dist/languages/zig.js +243 -0
  73. package/dist/loc.d.ts +17 -0
  74. package/dist/loc.js +34 -0
  75. package/dist/reach.d.ts +17 -0
  76. package/dist/reach.js +65 -0
  77. package/dist/render.d.ts +18 -0
  78. package/dist/render.js +83 -0
  79. package/dist/review/brand.d.ts +8 -0
  80. package/dist/review/brand.js +25 -0
  81. package/dist/review/call-context.d.ts +27 -0
  82. package/dist/review/call-context.js +446 -0
  83. package/dist/review/call-flow-html.d.ts +32 -0
  84. package/dist/review/call-flow-html.js +1870 -0
  85. package/dist/review/call-flow-nav.d.ts +151 -0
  86. package/dist/review/call-flow-nav.js +317 -0
  87. package/dist/review/call-flow.d.ts +47 -0
  88. package/dist/review/call-flow.js +229 -0
  89. package/dist/review/change-facts.d.ts +69 -0
  90. package/dist/review/change-facts.js +729 -0
  91. package/dist/review/cli.d.ts +2 -0
  92. package/dist/review/cli.js +50 -0
  93. package/dist/review/connected-analysis.d.ts +100 -0
  94. package/dist/review/connected-analysis.js +163 -0
  95. package/dist/review/connected-html.d.ts +17 -0
  96. package/dist/review/connected-html.js +2853 -0
  97. package/dist/review/connected.d.ts +23 -0
  98. package/dist/review/connected.js +141 -0
  99. package/dist/review/escape-html.d.ts +2 -0
  100. package/dist/review/escape-html.js +9 -0
  101. package/dist/review/evidence-html.d.ts +21 -0
  102. package/dist/review/evidence-html.js +521 -0
  103. package/dist/review/evidence-syntax.d.ts +132 -0
  104. package/dist/review/evidence-syntax.js +478 -0
  105. package/dist/review/evidence-types.d.ts +62 -0
  106. package/dist/review/evidence-types.js +1 -0
  107. package/dist/review/evidence.d.ts +31 -0
  108. package/dist/review/evidence.js +1603 -0
  109. package/dist/review/file-role.d.ts +9 -0
  110. package/dist/review/file-role.js +29 -0
  111. package/dist/review/github.d.ts +204 -0
  112. package/dist/review/github.js +1245 -0
  113. package/dist/review/history.d.ts +101 -0
  114. package/dist/review/history.js +412 -0
  115. package/dist/review/html.d.ts +34 -0
  116. package/dist/review/html.js +1104 -0
  117. package/dist/review/input.d.ts +10 -0
  118. package/dist/review/input.js +113 -0
  119. package/dist/review/intent.d.ts +4 -0
  120. package/dist/review/intent.js +75 -0
  121. package/dist/review/mcp-cli.d.ts +2 -0
  122. package/dist/review/mcp-cli.js +25 -0
  123. package/dist/review/mcp.d.ts +12 -0
  124. package/dist/review/mcp.js +414 -0
  125. package/dist/review/module-resolution.d.ts +2 -0
  126. package/dist/review/module-resolution.js +86 -0
  127. package/dist/review/palette.d.ts +7 -0
  128. package/dist/review/palette.js +104 -0
  129. package/dist/review/pipeline.d.ts +77 -0
  130. package/dist/review/pipeline.js +227 -0
  131. package/dist/review/pr-input.d.ts +19 -0
  132. package/dist/review/pr-input.js +130 -0
  133. package/dist/review/questions.d.ts +201 -0
  134. package/dist/review/questions.js +174 -0
  135. package/dist/review/reference-check.d.ts +7 -0
  136. package/dist/review/reference-check.js +733 -0
  137. package/dist/review/report-pages.d.ts +109 -0
  138. package/dist/review/report-pages.js +328 -0
  139. package/dist/review/service.d.ts +23 -0
  140. package/dist/review/service.js +198 -0
  141. package/dist/review/setup.d.ts +112 -0
  142. package/dist/review/setup.js +549 -0
  143. package/dist/review/source.d.ts +26 -0
  144. package/dist/review/source.js +276 -0
  145. package/dist/review/toml.d.ts +38 -0
  146. package/dist/review/toml.js +565 -0
  147. package/dist/review/types.d.ts +179 -0
  148. package/dist/review/types.js +1 -0
  149. package/dist/run.d.ts +49 -0
  150. package/dist/run.js +311 -0
  151. package/dist/types.d.ts +366 -0
  152. package/dist/types.js +83 -0
  153. package/package.json +88 -0
  154. 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,2 @@
1
+ import { type LanguageExtractor } from "./types.js";
2
+ export declare const goExtractor: LanguageExtractor;
@@ -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;