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,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
|
+
};
|