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,391 @@
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 !== "method_parameters")
5
+ return "()";
6
+ const names = [];
7
+ for (const p of namedChildren(params)) {
8
+ if (p.type === "identifier" || p.type === "optional_parameter") {
9
+ const id = p.type === "identifier" ? p : childByType(p, "identifier");
10
+ names.push(id?.text ?? "_");
11
+ continue;
12
+ }
13
+ if (p.type === "splat_parameter" ||
14
+ p.type === "hash_splat_parameter" ||
15
+ p.type === "block_parameter") {
16
+ const id = childByType(p, "identifier");
17
+ const prefix = p.type === "hash_splat_parameter"
18
+ ? "**"
19
+ : p.type === "block_parameter"
20
+ ? "&"
21
+ : "*";
22
+ names.push(id ? `${prefix}${id.text}` : prefix);
23
+ continue;
24
+ }
25
+ if (p.type === "keyword_parameter") {
26
+ const id = childByType(p, "identifier");
27
+ names.push(id ? `${id.text}:` : "_:");
28
+ continue;
29
+ }
30
+ names.push("_");
31
+ }
32
+ return names.length === 0 ? "()" : `(${names.join(", ")})`;
33
+ }
34
+ function callKey(node, className) {
35
+ // call → [receiver,] identifier/constant, [argument_list|!]
36
+ const kids = namedChildren(node).filter((c) => c.type !== "argument_list" &&
37
+ c.type !== "block" &&
38
+ c.type !== "do_block");
39
+ if (kids.length === 0)
40
+ return null;
41
+ if (kids.length === 1) {
42
+ const only = kids[0];
43
+ if (only.type === "identifier") {
44
+ return className ? `${className}.${only.text}` : only.text;
45
+ }
46
+ return null;
47
+ }
48
+ const method = kids[kids.length - 1];
49
+ const receiver = kids[0];
50
+ if (method.type !== "identifier")
51
+ return null;
52
+ const prop = method.text;
53
+ if (receiver.type === "self" && className) {
54
+ return `${className}.${prop}`;
55
+ }
56
+ if (receiver.type === "constant") {
57
+ if (prop === "new")
58
+ return `new ${receiver.text}`;
59
+ return `${receiver.text}.${prop}`;
60
+ }
61
+ if (receiver.type === "identifier") {
62
+ return `${receiver.text}.${prop}`;
63
+ }
64
+ if (className)
65
+ return `${className}.${prop}`;
66
+ return prop;
67
+ }
68
+ /**
69
+ * Bare identifier used as a call (Ruby paren-less style).
70
+ * Only treat as call when it appears in statement/expression call position.
71
+ */
72
+ function bareCallKey(node, className) {
73
+ if (node.type !== "identifier")
74
+ return null;
75
+ const name = node.text;
76
+ // Skip assignment targets handled elsewhere; keywords-ish
77
+ if (name === "self" || name === "super") {
78
+ return className && name === "super" ? className : null;
79
+ }
80
+ return className ? `${className}.${name}` : name;
81
+ }
82
+ function collectBody(file, body, className) {
83
+ if (!body)
84
+ return [];
85
+ if (body.type === "body_statement" || body.type === "then" || body.type === "else") {
86
+ return collectStatements(file, namedChildren(body), className);
87
+ }
88
+ return collectStatements(file, [body], className);
89
+ }
90
+ function collectStatements(file, statements, className) {
91
+ const steps = [];
92
+ const seen = new Set();
93
+ const addCall = (key, node) => {
94
+ const mark = `${key}:${node.startIndex}`;
95
+ if (seen.has(mark))
96
+ return;
97
+ seen.add(mark);
98
+ const step = { type: "call", key, ...locFromNode(file, node) };
99
+ step.syntax = heuristicCallSyntax(node, key);
100
+ steps.push(step);
101
+ };
102
+ const walk = (node, asStatement) => {
103
+ if (node.type === "method" ||
104
+ node.type === "singleton_method" ||
105
+ node.type === "class" ||
106
+ node.type === "module" ||
107
+ node.type === "lambda" ||
108
+ node.type === "block" ||
109
+ node.type === "do_block") {
110
+ return;
111
+ }
112
+ if (node.type === "if" || node.type === "unless") {
113
+ const kids = namedChildren(node);
114
+ const cond = kids.find((c) => c.type !== "then" &&
115
+ c.type !== "else" &&
116
+ c.type !== "elsif") ?? null;
117
+ const thenNode = childByType(node, "then");
118
+ const condText = cond ? collapseWs(cond.text) : "";
119
+ const kind = node.type; // if | unless
120
+ steps.push({
121
+ type: "branch",
122
+ key: condText ? `${kind}:${condText}` : kind,
123
+ label: condText ? `${kind} ${condText}` : kind,
124
+ ...locFromNode(file, cond ?? node),
125
+ children: collectBody(file, thenNode, className),
126
+ });
127
+ for (const clause of kids) {
128
+ if (clause.type === "elsif") {
129
+ const elsifKids = namedChildren(clause);
130
+ const elsifCond = elsifKids.find((c) => c.type !== "then" && c.type !== "else") ??
131
+ null;
132
+ const text = elsifCond ? collapseWs(elsifCond.text) : "";
133
+ steps.push({
134
+ type: "branch",
135
+ key: text ? `else-if:${text}` : "else-if",
136
+ label: text ? `elsif ${text}` : "elsif",
137
+ ...locFromNode(file, elsifCond ?? clause),
138
+ children: collectBody(file, childByType(clause, "then"), className),
139
+ });
140
+ // nested else under elsif
141
+ const nestedElse = childByType(clause, "else");
142
+ if (nestedElse) {
143
+ steps.push({
144
+ type: "branch",
145
+ key: "else",
146
+ label: "else",
147
+ ...locFromNode(file, nestedElse),
148
+ children: collectBody(file, nestedElse, className),
149
+ });
150
+ }
151
+ }
152
+ if (clause.type === "else") {
153
+ steps.push({
154
+ type: "branch",
155
+ key: "else",
156
+ label: "else",
157
+ ...locFromNode(file, clause),
158
+ children: collectBody(file, clause, className),
159
+ });
160
+ }
161
+ }
162
+ return;
163
+ }
164
+ if (node.type === "begin") {
165
+ // Body statements are direct children before rescue/ensure/else
166
+ const bodyStmts = namedChildren(node).filter((c) => c.type !== "rescue" &&
167
+ c.type !== "ensure" &&
168
+ c.type !== "else");
169
+ steps.push({
170
+ type: "branch",
171
+ key: "try",
172
+ label: "begin",
173
+ ...locFromNode(file, node),
174
+ children: collectStatements(file, bodyStmts, className),
175
+ });
176
+ for (const clause of namedChildren(node)) {
177
+ if (clause.type === "rescue") {
178
+ const exceptions = childByType(clause, "exceptions");
179
+ const text = exceptions ? collapseWs(exceptions.text) : "";
180
+ const thenNode = childByType(clause, "then");
181
+ steps.push({
182
+ type: "branch",
183
+ key: text ? `rescue:${text}` : "rescue",
184
+ label: text ? `rescue ${text}` : "rescue",
185
+ ...locFromNode(file, exceptions ?? clause),
186
+ children: thenNode
187
+ ? collectBody(file, thenNode, className)
188
+ : collectStatements(file, namedChildren(clause).filter((c) => c.type !== "exceptions" && c.type !== "exception_variable"), className),
189
+ });
190
+ }
191
+ if (clause.type === "else") {
192
+ steps.push({
193
+ type: "branch",
194
+ key: "else",
195
+ label: "else",
196
+ ...locFromNode(file, clause),
197
+ children: collectBody(file, clause, className),
198
+ });
199
+ }
200
+ if (clause.type === "ensure") {
201
+ steps.push({
202
+ type: "branch",
203
+ key: "ensure",
204
+ label: "ensure",
205
+ ...locFromNode(file, clause),
206
+ children: collectStatements(file, namedChildren(clause), className),
207
+ });
208
+ }
209
+ }
210
+ return;
211
+ }
212
+ if (node.type === "case") {
213
+ for (const clause of namedChildren(node)) {
214
+ if (clause.type === "when") {
215
+ const pattern = childByType(clause, "pattern") ??
216
+ namedChildren(clause).find((c) => c.type !== "then") ??
217
+ null;
218
+ const text = pattern ? collapseWs(pattern.text) : "";
219
+ steps.push({
220
+ type: "branch",
221
+ key: text ? `when:${text}` : "when",
222
+ label: text ? `when ${text}` : "when",
223
+ ...locFromNode(file, pattern ?? clause),
224
+ children: collectBody(file, childByType(clause, "then"), className),
225
+ });
226
+ }
227
+ if (clause.type === "else") {
228
+ steps.push({
229
+ type: "branch",
230
+ key: "else",
231
+ label: "else",
232
+ ...locFromNode(file, clause),
233
+ children: collectBody(file, clause, className),
234
+ });
235
+ }
236
+ }
237
+ return;
238
+ }
239
+ if (node.type === "call") {
240
+ // Ruby models `obj.attr` as `call` even without args. Count those only
241
+ // at statement level; always count calls that pass an argument_list.
242
+ const hasArgs = childByType(node, "argument_list") !== null;
243
+ const hasBlock = childByType(node, "block") !== null ||
244
+ childByType(node, "do_block") !== null;
245
+ const kids = namedChildren(node).filter((c) => c.type !== "argument_list" &&
246
+ c.type !== "block" &&
247
+ c.type !== "do_block");
248
+ const isReceiverCall = kids.length >= 2;
249
+ // `lambda { ... }` / `proc { ... }` are closures, not real callees
250
+ const bareName = kids.length === 1 && kids[0].type === "identifier"
251
+ ? kids[0].text
252
+ : null;
253
+ const isClosureCtor = hasBlock && (bareName === "lambda" || bareName === "proc");
254
+ if (!isClosureCtor &&
255
+ (asStatement || hasArgs || !isReceiverCall)) {
256
+ const key = callKey(node, className);
257
+ if (key)
258
+ addCall(key, node);
259
+ }
260
+ // Nested invocations inside arguments (not bare attr reads)
261
+ const args = childByType(node, "argument_list");
262
+ if (args) {
263
+ for (const child of namedChildren(args))
264
+ walk(child, false);
265
+ }
266
+ return;
267
+ }
268
+ if (node.type === "assignment") {
269
+ // RHS may be bare call identifier
270
+ const kids = namedChildren(node);
271
+ const rhs = kids[1] ?? null;
272
+ if (rhs) {
273
+ if (rhs.type === "identifier") {
274
+ // Assignment RHS is a free call, never a receiver method.
275
+ const key = bareCallKey(rhs, null);
276
+ if (key)
277
+ addCall(key, rhs);
278
+ }
279
+ else {
280
+ walk(rhs, false);
281
+ }
282
+ }
283
+ return;
284
+ }
285
+ // Statement-level bare identifier → method call
286
+ if (node.type === "identifier" && asStatement) {
287
+ const key = bareCallKey(node, className);
288
+ if (key)
289
+ addCall(key, node);
290
+ return;
291
+ }
292
+ for (const child of namedChildren(node)) {
293
+ walk(child, false);
294
+ }
295
+ };
296
+ for (const stmt of statements)
297
+ walk(stmt, true);
298
+ return steps;
299
+ }
300
+ function handleMethod(file, node, className, functions) {
301
+ const name = childByType(node, "identifier")?.text ?? null;
302
+ if (!name)
303
+ return;
304
+ const params = childByType(node, "method_parameters");
305
+ const body = childByType(node, "body_statement");
306
+ const isInit = className !== null && name === "initialize";
307
+ const key = className
308
+ ? isInit
309
+ ? `${className}.initialize`
310
+ : `${className}.${name}`
311
+ : name;
312
+ const labelBase = isInit ? `${className}` : key;
313
+ const info = {
314
+ key,
315
+ label: `${labelBase}${getParamsLabel(params)}`,
316
+ params: declaredParamsOf(node),
317
+ file,
318
+ steps: collectBody(file, body, className),
319
+ exported: !name.startsWith("_"),
320
+ start: node.startIndex,
321
+ end: node.endIndex,
322
+ };
323
+ functions.push(info);
324
+ if (isInit && className) {
325
+ functions.push({
326
+ ...info,
327
+ key: `new ${className}`,
328
+ label: `${className}${getParamsLabel(params)}`,
329
+ params: declaredParamsOf(node),
330
+ });
331
+ }
332
+ }
333
+ function handleSingletonMethod(file, node, className, functions) {
334
+ // def self.bar / def Foo.bar
335
+ const name = childByType(node, "identifier")?.text ?? null;
336
+ if (!name)
337
+ return;
338
+ const params = childByType(node, "method_parameters");
339
+ const body = childByType(node, "body_statement");
340
+ const key = className ? `${className}.${name}` : name;
341
+ functions.push({
342
+ key,
343
+ label: `${key}${getParamsLabel(params)}`,
344
+ params: declaredParamsOf(node),
345
+ file,
346
+ steps: collectBody(file, body, className),
347
+ exported: !name.startsWith("_"),
348
+ start: node.startIndex,
349
+ end: node.endIndex,
350
+ });
351
+ }
352
+ function handleClass(file, node, functions) {
353
+ const className = childByType(node, "constant")?.text ?? null;
354
+ if (!className)
355
+ return;
356
+ const body = childByType(node, "body_statement");
357
+ if (!body)
358
+ return;
359
+ for (const stmt of namedChildren(body)) {
360
+ if (stmt.type === "method") {
361
+ handleMethod(file, stmt, className, functions);
362
+ }
363
+ else if (stmt.type === "singleton_method") {
364
+ handleSingletonMethod(file, stmt, className, functions);
365
+ }
366
+ else if (stmt.type === "class") {
367
+ handleClass(file, stmt, functions);
368
+ }
369
+ }
370
+ }
371
+ function extractFromTree(file, _source, tree) {
372
+ const functions = [];
373
+ for (const stmt of namedChildren(tree.rootNode)) {
374
+ if (stmt.type === "method") {
375
+ handleMethod(file, stmt, null, functions);
376
+ }
377
+ else if (stmt.type === "singleton_method") {
378
+ handleSingletonMethod(file, stmt, null, functions);
379
+ }
380
+ else if (stmt.type === "class") {
381
+ handleClass(file, stmt, functions);
382
+ }
383
+ }
384
+ return functions;
385
+ }
386
+ export const rubyExtractor = {
387
+ id: "ruby",
388
+ extensions: [".rb"],
389
+ grammarPackage: "tree-sitter-ruby",
390
+ extract: extractFromTree,
391
+ };
@@ -0,0 +1,2 @@
1
+ import { type LanguageExtractor } from "./types.js";
2
+ export declare const rustExtractor: LanguageExtractor;
@@ -0,0 +1,261 @@
1
+ import { heuristicCallSyntax } from "./call-syntax.js";
2
+ import { childByType, declaredParamsOf, collapseWs, locFromNode, namedChildren, sameNode, } from "./types.js";
3
+ function isPublic(node) {
4
+ return childByType(node, "visibility_modifier") !== null;
5
+ }
6
+ function getParamsLabel(params) {
7
+ if (!params || params.type !== "parameters")
8
+ return "()";
9
+ const names = [];
10
+ for (const p of namedChildren(params)) {
11
+ if (p.type === "self_parameter") {
12
+ names.push("self");
13
+ continue;
14
+ }
15
+ if (p.type === "parameter") {
16
+ const ident = namedChildren(p).find((c) => c.type === "identifier") ?? null;
17
+ names.push(ident?.text ?? "_");
18
+ continue;
19
+ }
20
+ if (p.type === "variadic_parameter") {
21
+ names.push("...");
22
+ }
23
+ }
24
+ return names.length === 0 ? "()" : `(${names.join(", ")})`;
25
+ }
26
+ function calleeKey(node, typeName) {
27
+ if (node.type === "identifier")
28
+ return node.text;
29
+ if (node.type === "field_expression") {
30
+ const object = node.namedChild(0);
31
+ const field = childByType(node, "field_identifier");
32
+ if (!object || !field)
33
+ return null;
34
+ const prop = field.text;
35
+ if (object.type === "self" && typeName) {
36
+ return `${typeName}.${prop}`;
37
+ }
38
+ if (object.type === "identifier") {
39
+ const objName = object.text;
40
+ if (typeName &&
41
+ objName[0] &&
42
+ objName[0] === objName[0].toLowerCase()) {
43
+ return `${typeName}.${prop}`;
44
+ }
45
+ return `${objName}.${prop}`;
46
+ }
47
+ if (typeName)
48
+ return `${typeName}.${prop}`;
49
+ return prop;
50
+ }
51
+ if (node.type === "scoped_identifier") {
52
+ // Foo::new / Foo::bar — last two identifier-like parts
53
+ const parts = namedChildren(node).filter((c) => c.type === "identifier" ||
54
+ c.type === "type_identifier" ||
55
+ c.type === "scoped_identifier");
56
+ // Flatten: take text of last child as name, preceding type_identifier/identifier
57
+ const nameNode = parts.at(-1);
58
+ if (!nameNode)
59
+ return null;
60
+ const name = nameNode.text;
61
+ // Prefer a type_identifier sibling before the name
62
+ const typeNode = namedChildren(node).find((c) => c.type === "type_identifier") ??
63
+ namedChildren(node).find((c) => c.type === "identifier" && !sameNode(c, nameNode)) ??
64
+ null;
65
+ if (typeNode) {
66
+ if (name === "new")
67
+ return `new ${typeNode.text}`;
68
+ return `${typeNode.text}.${name}`;
69
+ }
70
+ return name;
71
+ }
72
+ return null;
73
+ }
74
+ function statementsOf(node) {
75
+ if (node.type === "block")
76
+ return namedChildren(node);
77
+ return [node];
78
+ }
79
+ function collectStatements(file, statements, typeName) {
80
+ const steps = [];
81
+ const seen = new Set();
82
+ const addCall = (key, node) => {
83
+ const mark = `${key}:${node.startIndex}`;
84
+ if (seen.has(mark))
85
+ return;
86
+ seen.add(mark);
87
+ const step = { type: "call", key, ...locFromNode(file, node) };
88
+ step.syntax = heuristicCallSyntax(node, key);
89
+ steps.push(step);
90
+ };
91
+ const pushIfChain = (node, asElseIf) => {
92
+ const kids = namedChildren(node);
93
+ const cond = kids.find((c) => c.type !== "block" && c.type !== "else_clause") ?? null;
94
+ const thenBlock = kids.find((c) => c.type === "block") ?? null;
95
+ const elseClause = childByType(node, "else_clause");
96
+ const condText = cond ? collapseWs(cond.text) : "";
97
+ const kind = asElseIf ? "else-if" : "if";
98
+ const labelKind = asElseIf ? "else if" : "if";
99
+ steps.push({
100
+ type: "branch",
101
+ key: condText ? `${kind}:${condText}` : kind,
102
+ label: condText ? `${labelKind} ${condText}` : labelKind,
103
+ ...locFromNode(file, cond ?? node),
104
+ children: thenBlock
105
+ ? collectStatements(file, statementsOf(thenBlock), typeName)
106
+ : [],
107
+ });
108
+ if (!elseClause)
109
+ return;
110
+ const elseInner = elseClause.namedChild(0);
111
+ if (!elseInner)
112
+ return;
113
+ if (elseInner.type === "if_expression") {
114
+ pushIfChain(elseInner, true);
115
+ return;
116
+ }
117
+ steps.push({
118
+ type: "branch",
119
+ key: "else",
120
+ label: "else",
121
+ ...locFromNode(file, elseClause),
122
+ children: collectStatements(file, statementsOf(elseInner), typeName),
123
+ });
124
+ };
125
+ const walk = (node) => {
126
+ if (node.type === "function_item" ||
127
+ node.type === "closure_expression" ||
128
+ node.type === "impl_item") {
129
+ return;
130
+ }
131
+ if (node.type === "if_expression") {
132
+ pushIfChain(node, false);
133
+ return;
134
+ }
135
+ if (node.type === "match_expression") {
136
+ const block = childByType(node, "match_block");
137
+ for (const arm of block ? namedChildren(block) : []) {
138
+ if (arm.type !== "match_arm")
139
+ continue;
140
+ const pattern = childByType(arm, "match_pattern") ??
141
+ namedChildren(arm).find((c) => c.type !== "block" &&
142
+ c.type !== "call_expression" &&
143
+ c.type !== "identifier" &&
144
+ c.type !== "if_expression" &&
145
+ c.type !== "match_expression" &&
146
+ c.type !== "closure_expression") ??
147
+ null;
148
+ // Body is the last named child that isn't the pattern / attribute
149
+ const kids = namedChildren(arm).filter((c) => c.type !== "attribute_item");
150
+ const body = kids.length > 1 ? kids[kids.length - 1] : null;
151
+ const text = pattern ? collapseWs(pattern.text) : "";
152
+ steps.push({
153
+ type: "branch",
154
+ key: text ? `case:${text}` : "case",
155
+ label: text ? `case ${text}` : "case",
156
+ ...locFromNode(file, pattern ?? arm),
157
+ children: body
158
+ ? collectStatements(file, statementsOf(body), typeName)
159
+ : [],
160
+ });
161
+ }
162
+ return;
163
+ }
164
+ if (node.type === "call_expression") {
165
+ const callee = node.namedChild(0);
166
+ if (callee) {
167
+ const key = calleeKey(callee, typeName);
168
+ if (key)
169
+ addCall(key, node);
170
+ }
171
+ }
172
+ for (const child of namedChildren(node))
173
+ walk(child);
174
+ };
175
+ for (const stmt of statements)
176
+ walk(stmt);
177
+ return steps;
178
+ }
179
+ function handleFunctionItem(file, node, typeName, functions) {
180
+ const name = childByType(node, "identifier")?.text ?? null;
181
+ if (!name)
182
+ return;
183
+ const params = childByType(node, "parameters");
184
+ const body = childByType(node, "block");
185
+ const isNew = typeName !== null && name === "new";
186
+ const key = typeName
187
+ ? isNew
188
+ ? `${typeName}.new`
189
+ : `${typeName}.${name}`
190
+ : name;
191
+ const labelBase = isNew ? `new ${typeName}` : key;
192
+ const exported = isPublic(node) || !name.startsWith("_");
193
+ const info = {
194
+ key,
195
+ label: `${labelBase}${getParamsLabel(params)}`,
196
+ params: declaredParamsOf(node),
197
+ file,
198
+ steps: body ? collectStatements(file, statementsOf(body), typeName) : [],
199
+ exported,
200
+ start: node.startIndex,
201
+ end: node.endIndex,
202
+ };
203
+ functions.push(info);
204
+ if (isNew && typeName) {
205
+ functions.push({
206
+ ...info,
207
+ key: `new ${typeName}`,
208
+ label: `new ${typeName}${getParamsLabel(params)}`,
209
+ params: declaredParamsOf(node),
210
+ });
211
+ }
212
+ }
213
+ function handleImpl(file, node, functions) {
214
+ const typeIds = namedChildren(node).filter((c) => c.type === "type_identifier");
215
+ // `impl Foo` → one type_identifier; `impl Trait for Foo` → last is the type
216
+ const typeName = typeIds.at(-1)?.text ?? null;
217
+ if (!typeName)
218
+ return;
219
+ const list = childByType(node, "declaration_list");
220
+ if (!list)
221
+ return;
222
+ for (const item of namedChildren(list)) {
223
+ if (item.type === "function_item") {
224
+ handleFunctionItem(file, item, typeName, functions);
225
+ }
226
+ }
227
+ }
228
+ function handleModule(file, node, functions) {
229
+ const list = childByType(node, "declaration_list");
230
+ if (!list)
231
+ return;
232
+ for (const item of namedChildren(list)) {
233
+ visit(file, item, functions);
234
+ }
235
+ }
236
+ function visit(file, node, functions) {
237
+ if (node.type === "function_item") {
238
+ handleFunctionItem(file, node, null, functions);
239
+ return;
240
+ }
241
+ if (node.type === "impl_item") {
242
+ handleImpl(file, node, functions);
243
+ return;
244
+ }
245
+ if (node.type === "mod_item") {
246
+ handleModule(file, node, functions);
247
+ }
248
+ }
249
+ function extractFromTree(file, _source, tree) {
250
+ const functions = [];
251
+ for (const stmt of namedChildren(tree.rootNode)) {
252
+ visit(file, stmt, functions);
253
+ }
254
+ return functions;
255
+ }
256
+ export const rustExtractor = {
257
+ id: "rust",
258
+ extensions: [".rs"],
259
+ grammarPackage: "tree-sitter-rust",
260
+ extract: extractFromTree,
261
+ };
@@ -0,0 +1,2 @@
1
+ import { type LanguageExtractor } from "./types.js";
2
+ export declare const scalaExtractor: LanguageExtractor;