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,418 @@
1
+ import { heuristicCallSyntax } from "./call-syntax.js";
2
+ import { childByType, collapseWs, locFromNode, namedChildren, sameNode, } from "./types.js";
3
+ // "My::App::log" -> { pkg: "My::App", name: "log" }
4
+ // "foo" -> { pkg: null, name: "foo" }
5
+ function splitQualified(text) {
6
+ const idx = text.lastIndexOf("::");
7
+ if (idx === -1)
8
+ return { pkg: null, name: text };
9
+ const pkg = text.slice(0, idx);
10
+ const name = text.slice(idx + 2);
11
+ return { pkg, name };
12
+ }
13
+ // ("Foo::Bar", "baz") -> "Foo.Bar.baz"
14
+ // (null, "bar") -> "bar"
15
+ function packageKey(packageName, name) {
16
+ if (packageName === null)
17
+ return name;
18
+ const normalizedPackage = packageName.replaceAll("::", ".");
19
+ return `${normalizedPackage}.${name}`;
20
+ }
21
+ function normalizePackage(name) {
22
+ return name === "main" ? null : name;
23
+ }
24
+ // ("Thing", "new") -> "new Thing" (constructor alias, skipped by entry inference)
25
+ // ("Thing", "run") -> "Thing.run"
26
+ function methodKey(packageName, name) {
27
+ if (packageName !== null && name === "new") {
28
+ return `new ${packageName.replaceAll("::", ".")}`;
29
+ }
30
+ return packageKey(packageName, name);
31
+ }
32
+ const SIGILS = new Map([
33
+ ["scalar", "$"],
34
+ ["array", "@"],
35
+ ["hash", "%"],
36
+ ]);
37
+ function varWithSigil(node) {
38
+ const sigil = SIGILS.get(node.type);
39
+ const name = childByType(node, "varname")?.text;
40
+ if (sigil === undefined || name === undefined)
41
+ return null;
42
+ return `${sigil}${name}`;
43
+ }
44
+ function isSelfLike(name) {
45
+ return name === "$self" || name === "$class";
46
+ }
47
+ // ($self, $x, @rest) nodes -> ["$x", "@rest"]
48
+ function paramNames(variables) {
49
+ return variables
50
+ .map((variable) => varWithSigil(variable))
51
+ .filter((name) => name !== null)
52
+ .filter((name) => !isSelfLike(name));
53
+ }
54
+ // signature ($x, $y = 1, @rest) -> ["$x", "$y", "@rest"]
55
+ function signatureParams(signature) {
56
+ return namedChildren(signature).map((parameter) => {
57
+ const variable = namedChildren(parameter).at(0);
58
+ if (variable === undefined)
59
+ return "_";
60
+ return varWithSigil(variable) ?? "_";
61
+ });
62
+ }
63
+ // `my <left> = <right>;` statement -> { left, right }
64
+ function assignmentOf(statement) {
65
+ if (statement.type !== "expression_statement")
66
+ return null;
67
+ const assignment = namedChildren(statement).at(0);
68
+ if (assignment?.type !== "assignment_expression")
69
+ return null;
70
+ const sides = namedChildren(assignment);
71
+ const left = sides.at(0);
72
+ const right = sides.at(1);
73
+ if (left?.type !== "variable_declaration" || right === undefined)
74
+ return null;
75
+ return { left, right };
76
+ }
77
+ // `shift`, `shift @_` -> true, `shift @queue` -> false
78
+ function shiftsArgs(right) {
79
+ if (right.type !== "func1op_call_expression")
80
+ return false;
81
+ if (right.child(0)?.type !== "shift")
82
+ return false;
83
+ const operand = namedChildren(right).at(0);
84
+ if (operand === undefined)
85
+ return true;
86
+ return (operand.type === "array" && childByType(operand, "varname")?.text === "_");
87
+ }
88
+ // [`my ($self, $x) = @_;`, ...] -> ["$x"]
89
+ // [`my $a = shift;`, `my $b = shift;`, ...] -> ["$a", "$b"]
90
+ function conventionalParams(statements) {
91
+ const names = [];
92
+ for (const statement of statements) {
93
+ if (statement.type === "comment")
94
+ continue;
95
+ const assignment = assignmentOf(statement);
96
+ if (assignment === null)
97
+ break;
98
+ const { left, right } = assignment;
99
+ const unpacksArgs = right.type === "array" && childByType(right, "varname")?.text === "_";
100
+ if (unpacksArgs) {
101
+ names.push(...paramNames(namedChildren(left)));
102
+ break;
103
+ }
104
+ if (!shiftsArgs(right))
105
+ break;
106
+ const declared = namedChildren(left).at(0);
107
+ if (declared !== undefined)
108
+ names.push(...paramNames([declared]));
109
+ }
110
+ return names;
111
+ }
112
+ function formatParams(names) {
113
+ return `(${names.join(", ")})`;
114
+ }
115
+ function getParamsLabel(node, body) {
116
+ const signature = childByType(node, "signature");
117
+ if (signature !== null) {
118
+ const names = signatureParams(signature).filter((n) => !isSelfLike(n));
119
+ return formatParams(names);
120
+ }
121
+ if (body === null)
122
+ return "()";
123
+ const names = conventionalParams(namedChildren(body));
124
+ return formatParams(names);
125
+ }
126
+ // ("foo", "Pkg") -> "Pkg.foo"
127
+ // ("Util::log", any) -> "Util.log"
128
+ function nameKey(text, packageName) {
129
+ const { pkg, name } = splitQualified(text);
130
+ if (pkg !== null)
131
+ return packageKey(normalizePackage(pkg), name);
132
+ return packageKey(packageName, name);
133
+ }
134
+ // foo() -> "foo" | "Pkg.foo"
135
+ // &foo() -> "foo"
136
+ function calleeKey(node, packageName) {
137
+ const fn = childByType(node, "function");
138
+ if (fn === null)
139
+ return null;
140
+ const text = childByType(fn, "varname")?.text ?? fn.text;
141
+ if (text === "" || text.startsWith("$"))
142
+ return null;
143
+ return nameKey(text, packageName);
144
+ }
145
+ // $self->m -> "Pkg.m"
146
+ // Foo::Bar->m -> "Foo.Bar.m"
147
+ function methodCallKey(node, packageName) {
148
+ const receiver = namedChildren(node).at(0);
149
+ const method = childByType(node, "method");
150
+ if (receiver === undefined || method === null)
151
+ return null;
152
+ const rawName = method.text;
153
+ if (rawName === "" || rawName.startsWith("$"))
154
+ return null; // computed: $obj->$m
155
+ if (rawName.startsWith("SUPER::")) {
156
+ return methodKey(packageName, rawName.slice("SUPER::".length));
157
+ }
158
+ const { pkg: methodPkg, name: prop } = splitQualified(rawName);
159
+ if (methodPkg !== null)
160
+ return methodKey(normalizePackage(methodPkg), prop);
161
+ switch (receiver.type) {
162
+ case "scalar": {
163
+ const name = childByType(receiver, "varname")?.text;
164
+ if (name === undefined)
165
+ return null;
166
+ if (name === "self" || name === "class") {
167
+ return methodKey(packageName, prop);
168
+ }
169
+ return `${name}.${prop}`;
170
+ }
171
+ case "bareword":
172
+ return methodKey(normalizePackage(receiver.text), prop);
173
+ default:
174
+ // __PACKAGE__ and chained/complex receivers: package-scope fallback
175
+ return methodKey(packageName, prop);
176
+ }
177
+ }
178
+ // `new Foo(1)`, `new Foo` target -> "Foo", else null
179
+ function constructedClass(target) {
180
+ let text = null;
181
+ if (target.type === "bareword")
182
+ text = target.text;
183
+ if (target.type === "function_call_expression") {
184
+ text = childByType(target, "function")?.text ?? null;
185
+ }
186
+ if (text === null || text === "" || text.startsWith("$"))
187
+ return null;
188
+ return text;
189
+ }
190
+ function keywordToken(node, keywords) {
191
+ const token = node.children.find((c) => !c.isNamed && keywords.includes(c.type));
192
+ return token?.type ?? null;
193
+ }
194
+ const NON_CONDITION_TYPES = new Set(["block", "elsif", "else", "comment"]);
195
+ function conditionOf(node) {
196
+ const condition = namedChildren(node).find((c) => !NON_CONDITION_TYPES.has(c.type));
197
+ return condition ?? null;
198
+ }
199
+ const TRY_KEYWORDS = new Set(["try", "catch", "finally"]);
200
+ function callStep(scope, key, node) {
201
+ return {
202
+ type: "call",
203
+ key,
204
+ ...locFromNode(scope.file, node),
205
+ syntax: heuristicCallSyntax(node, key),
206
+ };
207
+ }
208
+ function branchStep(scope, key, label, cond, anchor, children) {
209
+ const text = cond === null ? "" : collapseWs(cond.text);
210
+ return {
211
+ type: "branch",
212
+ key: text === "" ? key : `${key}:${text}`,
213
+ label: text === "" ? label : `${label} ${text}`,
214
+ ...locFromNode(scope.file, cond ?? anchor),
215
+ children,
216
+ };
217
+ }
218
+ function collectStatements(scope, statements) {
219
+ return statements.flatMap((statement) => collectNode(scope, statement));
220
+ }
221
+ function collectBlock(scope, block) {
222
+ if (block === null)
223
+ return [];
224
+ return collectStatements(scope, namedChildren(block));
225
+ }
226
+ // zero-arg paren-less call (`helper;`, `helper if $x;`) -> lone bareword
227
+ function collectExpression(scope, node) {
228
+ if (node.type === "bareword") {
229
+ return [callStep(scope, nameKey(node.text, scope.packageName), node)];
230
+ }
231
+ return collectNode(scope, node);
232
+ }
233
+ function collectNode(scope, node) {
234
+ switch (node.type) {
235
+ case "subroutine_declaration_statement":
236
+ case "method_declaration_statement":
237
+ case "anonymous_subroutine_expression":
238
+ return [];
239
+ case "conditional_statement":
240
+ return collectConditional(scope, node);
241
+ case "postfix_conditional_expression":
242
+ return collectPostfixConditional(scope, node);
243
+ case "try_statement":
244
+ return collectTryStatement(scope, node);
245
+ case "eval_expression":
246
+ return collectEval(scope, node);
247
+ case "function_call_expression":
248
+ case "ambiguous_function_call_expression":
249
+ return collectFunctionCall(scope, node);
250
+ case "method_call_expression":
251
+ return collectMethodCall(scope, node);
252
+ // map, grep, sort block: callback, not caller body
253
+ case "map_grep_expression":
254
+ case "sort_expression":
255
+ return namedChildren(node)
256
+ .filter((c) => c.type !== "block")
257
+ .flatMap((c) => collectNode(scope, c));
258
+ case "expression_statement":
259
+ return namedChildren(node).flatMap((c) => collectExpression(scope, c));
260
+ case "assignment_expression": {
261
+ const kids = namedChildren(node);
262
+ const right = kids.at(-1);
263
+ return kids.flatMap((c) => sameNode(c, right) ? collectExpression(scope, c) : collectNode(scope, c));
264
+ }
265
+ case "return_expression":
266
+ return namedChildren(node).flatMap((c) => collectExpression(scope, c));
267
+ default:
268
+ return collectStatements(scope, namedChildren(node));
269
+ }
270
+ }
271
+ function collectConditional(scope, node) {
272
+ const keyword = keywordToken(node, ["if", "unless"]) ?? "if";
273
+ const head = branchStep(scope, keyword, keyword, conditionOf(node), node, collectBlock(scope, childByType(node, "block")));
274
+ const clauses = namedChildren(node).flatMap((clause) => collectClause(scope, clause));
275
+ return [head, ...clauses];
276
+ }
277
+ // elsif chains nest in the CST
278
+ function collectClause(scope, clause) {
279
+ if (clause.type === "elsif") {
280
+ const head = branchStep(scope, "else-if", "elsif", conditionOf(clause), clause, collectBlock(scope, childByType(clause, "block")));
281
+ const nested = namedChildren(clause).flatMap((next) => collectClause(scope, next));
282
+ return [head, ...nested];
283
+ }
284
+ if (clause.type === "else") {
285
+ const body = collectBlock(scope, childByType(clause, "block"));
286
+ return [branchStep(scope, "else", "else", null, clause, body)];
287
+ }
288
+ return [];
289
+ }
290
+ function collectPostfixConditional(scope, node) {
291
+ const keyword = keywordToken(node, ["if", "unless"]) ?? "if";
292
+ const kids = namedChildren(node).filter((c) => c.type !== "comment");
293
+ const expression = kids.at(0);
294
+ const condition = kids.at(1) ?? null;
295
+ const children = expression === undefined ? [] : collectExpression(scope, expression);
296
+ return [branchStep(scope, keyword, keyword, condition, node, children)];
297
+ }
298
+ // keyword tokens precede blocks; catch var left out of keys (not rename-stable)
299
+ function collectTryStatement(scope, node) {
300
+ const children = node.children;
301
+ return children.flatMap((child, index) => {
302
+ if (child.type !== "block")
303
+ return [];
304
+ const keyword = children
305
+ .slice(0, index)
306
+ .reverse()
307
+ .find((c) => !c.isNamed && TRY_KEYWORDS.has(c.type));
308
+ const kind = keyword?.type ?? "try";
309
+ const body = collectStatements(scope, namedChildren(child));
310
+ return [branchStep(scope, kind, kind, null, keyword ?? node, body)];
311
+ });
312
+ }
313
+ // eval { ... } -> "try" branch; string eval (no block) -> no branch
314
+ function collectEval(scope, node) {
315
+ const block = childByType(node, "block");
316
+ if (block === null)
317
+ return collectStatements(scope, namedChildren(node));
318
+ const body = collectBlock(scope, block);
319
+ return [branchStep(scope, "try", "eval", null, node, body)];
320
+ }
321
+ function collectFunctionCall(scope, node) {
322
+ const fn = childByType(node, "function");
323
+ const io = childByType(node, "indirect_object");
324
+ const fnText = fn?.text;
325
+ // legacy indirect-object constructor (always paren-less): `new Foo(1)` -> `Foo->new(1)`
326
+ if (node.type === "ambiguous_function_call_expression" && fnText === "new") {
327
+ const target = namedChildren(node).find((c) => !sameNode(c, fn));
328
+ const cls = target === undefined ? null : constructedClass(target);
329
+ if (target !== undefined && cls !== null) {
330
+ const constructorArgSteps = target.type === "function_call_expression"
331
+ ? namedChildren(target)
332
+ .filter((c) => c.type !== "function")
333
+ .flatMap((c) => collectNode(scope, c))
334
+ : [];
335
+ return [
336
+ callStep(scope, methodKey(normalizePackage(cls), "new"), node),
337
+ ...constructorArgSteps,
338
+ ];
339
+ }
340
+ }
341
+ // indirect_object block (`helper { ... } 3`): callback, not caller body
342
+ const key = calleeKey(node, scope.packageName);
343
+ const argumentSteps = namedChildren(node)
344
+ .filter((c) => !sameNode(c, fn) && !sameNode(c, io))
345
+ .flatMap((c) => collectNode(scope, c));
346
+ if (key === null)
347
+ return argumentSteps;
348
+ return [callStep(scope, key, node), ...argumentSteps];
349
+ }
350
+ function collectMethodCall(scope, node) {
351
+ const method = childByType(node, "method");
352
+ const key = methodCallKey(node, scope.packageName);
353
+ const receiverAndArgSteps = namedChildren(node)
354
+ .filter((c) => !sameNode(c, method))
355
+ .flatMap((c) => collectNode(scope, c));
356
+ if (key === null)
357
+ return receiverAndArgSteps;
358
+ return [callStep(scope, key, node), ...receiverAndArgSteps];
359
+ }
360
+ // sub greet in Foo -> { key: "Foo.greet", label: "Foo.greet($name)", ... }
361
+ // sub new in Foo -> primary "Foo.new" plus constructor alias "new Foo"
362
+ function functionInfos(file, node, packageName) {
363
+ const nameNode = childByType(node, "bareword");
364
+ if (nameNode === null)
365
+ return [];
366
+ const body = childByType(node, "block");
367
+ const { pkg: inlinePkg, name } = splitQualified(nameNode.text);
368
+ const pkg = inlinePkg === null ? packageName : normalizePackage(inlinePkg);
369
+ const key = packageKey(pkg, name);
370
+ const isConstructor = pkg !== null && name === "new";
371
+ const labelBase = isConstructor ? methodKey(pkg, name) : key;
372
+ const info = {
373
+ key,
374
+ label: `${labelBase}${getParamsLabel(node, body)}`,
375
+ file,
376
+ steps: collectBlock({ file, packageName: pkg }, body),
377
+ exported: !name.startsWith("_"),
378
+ start: node.startIndex,
379
+ end: node.endIndex,
380
+ };
381
+ if (!isConstructor)
382
+ return [info];
383
+ return [info, { ...info, key: labelBase }];
384
+ }
385
+ // [`package Foo;`, `sub a`, `sub b`] -> [Foo.a, Foo.b]
386
+ // [`package Foo { sub a }`, `sub b`] -> [Foo.a, b]
387
+ function definitionsIn(file, statements, packageName) {
388
+ const out = [];
389
+ let currentPackage = packageName;
390
+ for (const statement of statements) {
391
+ if (statement.type === "package_statement" ||
392
+ statement.type === "class_statement") {
393
+ const declared = normalizePackage(childByType(statement, "package")?.text ?? null);
394
+ const block = childByType(statement, "block");
395
+ if (block === null) {
396
+ currentPackage = declared;
397
+ }
398
+ else {
399
+ out.push(...definitionsIn(file, namedChildren(block), declared));
400
+ }
401
+ continue;
402
+ }
403
+ if (statement.type === "subroutine_declaration_statement" ||
404
+ statement.type === "method_declaration_statement") {
405
+ out.push(...functionInfos(file, statement, currentPackage));
406
+ }
407
+ }
408
+ return out;
409
+ }
410
+ function extractFromTree(file, _source, tree) {
411
+ return definitionsIn(file, namedChildren(tree.rootNode), null);
412
+ }
413
+ export const perlExtractor = {
414
+ id: "perl",
415
+ extensions: [".pl", ".pm", ".t"],
416
+ grammarPackage: "tree-sitter-perl",
417
+ extract: extractFromTree,
418
+ };
@@ -0,0 +1,2 @@
1
+ import { type LanguageExtractor } from "./types.js";
2
+ export declare const phpExtractor: LanguageExtractor;