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,268 @@
|
|
|
1
|
+
import { heuristicCallSyntax } from "./call-syntax.js";
|
|
2
|
+
import { childByType, collapseWs, locFromNode, namedChildren, } from "./types.js";
|
|
3
|
+
function isPrivate(node) {
|
|
4
|
+
const mods = childByType(node, "modifiers");
|
|
5
|
+
if (!mods)
|
|
6
|
+
return false;
|
|
7
|
+
return /\bprivate\b|\bfileprivate\b/.test(mods.text);
|
|
8
|
+
}
|
|
9
|
+
function isLikelyTypeName(name) {
|
|
10
|
+
const c = name[0];
|
|
11
|
+
return c !== undefined && c === c.toUpperCase() && c !== c.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
function getParamsLabel(node) {
|
|
14
|
+
const params = namedChildren(node).filter((c) => c.type === "parameter");
|
|
15
|
+
if (params.length === 0)
|
|
16
|
+
return "()";
|
|
17
|
+
const names = [];
|
|
18
|
+
for (const p of params) {
|
|
19
|
+
// Prefer external/local simple_identifier; last id is usually the local name
|
|
20
|
+
const ids = namedChildren(p).filter((c) => c.type === "simple_identifier");
|
|
21
|
+
names.push(ids.at(-1)?.text ?? "_");
|
|
22
|
+
}
|
|
23
|
+
return `(${names.join(", ")})`;
|
|
24
|
+
}
|
|
25
|
+
function navigationProp(nav) {
|
|
26
|
+
const suffix = childByType(nav, "navigation_suffix");
|
|
27
|
+
const id = suffix ? childByType(suffix, "simple_identifier") : null;
|
|
28
|
+
return id?.text ?? null;
|
|
29
|
+
}
|
|
30
|
+
function calleeKey(node, className) {
|
|
31
|
+
if (node.type === "simple_identifier") {
|
|
32
|
+
if (isLikelyTypeName(node.text))
|
|
33
|
+
return `new ${node.text}`;
|
|
34
|
+
return node.text;
|
|
35
|
+
}
|
|
36
|
+
if (node.type === "navigation_expression") {
|
|
37
|
+
const object = node.namedChild(0);
|
|
38
|
+
const prop = navigationProp(node);
|
|
39
|
+
if (!object || !prop)
|
|
40
|
+
return null;
|
|
41
|
+
if (object.type === "self_expression" && className) {
|
|
42
|
+
return `${className}.${prop}`;
|
|
43
|
+
}
|
|
44
|
+
if (object.type === "simple_identifier") {
|
|
45
|
+
return `${object.text}.${prop}`;
|
|
46
|
+
}
|
|
47
|
+
if (className)
|
|
48
|
+
return `${className}.${prop}`;
|
|
49
|
+
return prop;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function statementsOf(body) {
|
|
54
|
+
if (!body)
|
|
55
|
+
return [];
|
|
56
|
+
if (body.type === "function_body") {
|
|
57
|
+
const stmts = childByType(body, "statements");
|
|
58
|
+
return stmts ? namedChildren(stmts) : [];
|
|
59
|
+
}
|
|
60
|
+
if (body.type === "statements")
|
|
61
|
+
return namedChildren(body);
|
|
62
|
+
return [body];
|
|
63
|
+
}
|
|
64
|
+
function collectStatements(file, statements, className) {
|
|
65
|
+
const steps = [];
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const addCall = (key, node) => {
|
|
68
|
+
const mark = `${key}:${node.startIndex}`;
|
|
69
|
+
if (seen.has(mark))
|
|
70
|
+
return;
|
|
71
|
+
seen.add(mark);
|
|
72
|
+
const step = { type: "call", key, ...locFromNode(file, node) };
|
|
73
|
+
step.syntax = heuristicCallSyntax(node, key);
|
|
74
|
+
steps.push(step);
|
|
75
|
+
};
|
|
76
|
+
const pushIfChain = (node, asElseIf) => {
|
|
77
|
+
const kids = namedChildren(node);
|
|
78
|
+
const stmtBlocks = kids.filter((c) => c.type === "statements");
|
|
79
|
+
const nestedIf = kids.find((c) => c.type === "if_statement") ?? null;
|
|
80
|
+
const cond = kids.find((c) => c.type !== "statements" &&
|
|
81
|
+
c.type !== "else" &&
|
|
82
|
+
c.type !== "if_statement") ?? null;
|
|
83
|
+
const condText = cond ? collapseWs(cond.text) : "";
|
|
84
|
+
const kind = asElseIf ? "else-if" : "if";
|
|
85
|
+
const labelKind = asElseIf ? "else if" : "if";
|
|
86
|
+
steps.push({
|
|
87
|
+
type: "branch",
|
|
88
|
+
key: condText ? `${kind}:${condText}` : kind,
|
|
89
|
+
label: condText ? `${labelKind} ${condText}` : labelKind,
|
|
90
|
+
...locFromNode(file, cond ?? node),
|
|
91
|
+
children: collectStatements(file, statementsOf(stmtBlocks[0] ?? null), className),
|
|
92
|
+
});
|
|
93
|
+
if (nestedIf) {
|
|
94
|
+
pushIfChain(nestedIf, true);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (stmtBlocks[1]) {
|
|
98
|
+
steps.push({
|
|
99
|
+
type: "branch",
|
|
100
|
+
key: "else",
|
|
101
|
+
label: "else",
|
|
102
|
+
...locFromNode(file, stmtBlocks[1]),
|
|
103
|
+
children: collectStatements(file, statementsOf(stmtBlocks[1]), className),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const walk = (node) => {
|
|
108
|
+
if (node.type === "function_declaration" ||
|
|
109
|
+
node.type === "init_declaration" ||
|
|
110
|
+
node.type === "class_declaration" ||
|
|
111
|
+
node.type === "lambda_literal") {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (node.type === "if_statement") {
|
|
115
|
+
pushIfChain(node, false);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (node.type === "do_statement") {
|
|
119
|
+
const doBody = childByType(node, "statements");
|
|
120
|
+
steps.push({
|
|
121
|
+
type: "branch",
|
|
122
|
+
key: "do",
|
|
123
|
+
label: "do",
|
|
124
|
+
...locFromNode(file, node),
|
|
125
|
+
children: doBody
|
|
126
|
+
? collectStatements(file, namedChildren(doBody), className)
|
|
127
|
+
: [],
|
|
128
|
+
});
|
|
129
|
+
for (const clause of namedChildren(node)) {
|
|
130
|
+
if (clause.type === "catch_block") {
|
|
131
|
+
const body = childByType(clause, "statements");
|
|
132
|
+
steps.push({
|
|
133
|
+
type: "branch",
|
|
134
|
+
key: "catch",
|
|
135
|
+
label: "catch",
|
|
136
|
+
...locFromNode(file, clause),
|
|
137
|
+
children: body
|
|
138
|
+
? collectStatements(file, namedChildren(body), className)
|
|
139
|
+
: [],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (node.type === "switch_statement") {
|
|
146
|
+
for (const entry of namedChildren(node)) {
|
|
147
|
+
if (entry.type !== "switch_entry")
|
|
148
|
+
continue;
|
|
149
|
+
const pattern = childByType(entry, "switch_pattern");
|
|
150
|
+
const isDefault = namedChildren(entry).some((c) => c.type === "default_keyword");
|
|
151
|
+
const body = childByType(entry, "statements");
|
|
152
|
+
if (isDefault || !pattern) {
|
|
153
|
+
steps.push({
|
|
154
|
+
type: "branch",
|
|
155
|
+
key: "default",
|
|
156
|
+
label: "default",
|
|
157
|
+
...locFromNode(file, entry),
|
|
158
|
+
children: body
|
|
159
|
+
? collectStatements(file, namedChildren(body), className)
|
|
160
|
+
: [],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const text = collapseWs(pattern.text);
|
|
165
|
+
steps.push({
|
|
166
|
+
type: "branch",
|
|
167
|
+
key: text ? `case:${text}` : "case",
|
|
168
|
+
label: text ? `case ${text}` : "case",
|
|
169
|
+
...locFromNode(file, pattern),
|
|
170
|
+
children: body
|
|
171
|
+
? collectStatements(file, namedChildren(body), className)
|
|
172
|
+
: [],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (node.type === "call_expression") {
|
|
179
|
+
const callee = node.namedChild(0);
|
|
180
|
+
if (callee) {
|
|
181
|
+
const key = calleeKey(callee, className);
|
|
182
|
+
if (key)
|
|
183
|
+
addCall(key, node);
|
|
184
|
+
}
|
|
185
|
+
for (const child of namedChildren(node).slice(1))
|
|
186
|
+
walk(child);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Unwrap try openIt() → still see the call
|
|
190
|
+
if (node.type === "try_expression") {
|
|
191
|
+
for (const child of namedChildren(node))
|
|
192
|
+
walk(child);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
for (const child of namedChildren(node))
|
|
196
|
+
walk(child);
|
|
197
|
+
};
|
|
198
|
+
for (const stmt of statements)
|
|
199
|
+
walk(stmt);
|
|
200
|
+
return steps;
|
|
201
|
+
}
|
|
202
|
+
function handleFunction(file, node, className, functions) {
|
|
203
|
+
const name = childByType(node, "simple_identifier")?.text ?? null;
|
|
204
|
+
if (!name)
|
|
205
|
+
return;
|
|
206
|
+
const body = childByType(node, "function_body");
|
|
207
|
+
const key = className ? `${className}.${name}` : name;
|
|
208
|
+
functions.push({
|
|
209
|
+
key,
|
|
210
|
+
label: `${key}${getParamsLabel(node)}`,
|
|
211
|
+
file,
|
|
212
|
+
steps: collectStatements(file, statementsOf(body), className),
|
|
213
|
+
exported: !isPrivate(node),
|
|
214
|
+
start: node.startIndex,
|
|
215
|
+
end: node.endIndex,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function handleInit(file, node, className, functions) {
|
|
219
|
+
const body = childByType(node, "function_body");
|
|
220
|
+
const info = {
|
|
221
|
+
key: `${className}.init`,
|
|
222
|
+
label: `${className}${getParamsLabel(node)}`,
|
|
223
|
+
file,
|
|
224
|
+
steps: collectStatements(file, statementsOf(body), className),
|
|
225
|
+
exported: !isPrivate(node),
|
|
226
|
+
start: node.startIndex,
|
|
227
|
+
end: node.endIndex,
|
|
228
|
+
};
|
|
229
|
+
functions.push(info);
|
|
230
|
+
functions.push({ ...info, key: `new ${className}` });
|
|
231
|
+
}
|
|
232
|
+
function handleClass(file, node, functions) {
|
|
233
|
+
const className = childByType(node, "type_identifier")?.text ?? null;
|
|
234
|
+
if (!className)
|
|
235
|
+
return;
|
|
236
|
+
const body = childByType(node, "class_body");
|
|
237
|
+
if (!body)
|
|
238
|
+
return;
|
|
239
|
+
for (const element of namedChildren(body)) {
|
|
240
|
+
if (element.type === "function_declaration") {
|
|
241
|
+
handleFunction(file, element, className, functions);
|
|
242
|
+
}
|
|
243
|
+
else if (element.type === "init_declaration") {
|
|
244
|
+
handleInit(file, element, className, functions);
|
|
245
|
+
}
|
|
246
|
+
else if (element.type === "class_declaration") {
|
|
247
|
+
handleClass(file, element, functions);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function extractFromTree(file, _source, tree) {
|
|
252
|
+
const functions = [];
|
|
253
|
+
for (const stmt of namedChildren(tree.rootNode)) {
|
|
254
|
+
if (stmt.type === "function_declaration") {
|
|
255
|
+
handleFunction(file, stmt, null, functions);
|
|
256
|
+
}
|
|
257
|
+
else if (stmt.type === "class_declaration") {
|
|
258
|
+
handleClass(file, stmt, functions);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return functions;
|
|
262
|
+
}
|
|
263
|
+
export const swiftExtractor = {
|
|
264
|
+
id: "swift",
|
|
265
|
+
extensions: [".swift"],
|
|
266
|
+
grammarPackage: "tree-sitter-swift",
|
|
267
|
+
extract: extractFromTree,
|
|
268
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type Parser from "tree-sitter";
|
|
2
|
+
import type { DeclaredParams, FunctionInfo, SourceLoc } from "../types.js";
|
|
3
|
+
export type SyntaxNode = Parser.SyntaxNode;
|
|
4
|
+
export type Tree = Parser.Tree;
|
|
5
|
+
export interface LanguageExtractor {
|
|
6
|
+
/** Stable id, e.g. "typescript" | "python" | "go" */
|
|
7
|
+
id: string;
|
|
8
|
+
/** File extensions including dot, lowercase */
|
|
9
|
+
extensions: string[];
|
|
10
|
+
/** npm package providing the tree-sitter grammar */
|
|
11
|
+
grammarPackage: string;
|
|
12
|
+
/** Named export on the grammar package, if any (e.g. "typescript", "tsx") */
|
|
13
|
+
grammarExport?: string;
|
|
14
|
+
extract(file: string, source: string, tree: Tree): FunctionInfo[];
|
|
15
|
+
}
|
|
16
|
+
export declare function namedChildren(node: SyntaxNode): SyntaxNode[];
|
|
17
|
+
/**
|
|
18
|
+
* Whether two handles name the same syntax node. The native binding may return
|
|
19
|
+
* a fresh JS object for a node on each access, and whether it does depends on
|
|
20
|
+
* garbage collection, so `===` on nodes gives different answers from run to run.
|
|
21
|
+
* A node's id is stable within its tree.
|
|
22
|
+
*/
|
|
23
|
+
export declare function sameNode(a: SyntaxNode | null | undefined, b: SyntaxNode | null | undefined): boolean;
|
|
24
|
+
export declare function childByType(node: SyntaxNode, type: string): SyntaxNode | null;
|
|
25
|
+
export declare function collapseWs(text: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Call-site / branch span for a syntax node.
|
|
28
|
+
* Uses tree-sitter 0-based rows → 1-based display lines.
|
|
29
|
+
*/
|
|
30
|
+
export declare function locFromNode(file: string, node: SyntaxNode): SourceLoc;
|
|
31
|
+
/**
|
|
32
|
+
* Declared parameter list of a definition, read from the AST: the verbatim
|
|
33
|
+
* list text, with no slots. Text alone keeps argument binding `unknown`, which
|
|
34
|
+
* is what the extractors that do not build lexical scopes can honestly claim.
|
|
35
|
+
*/
|
|
36
|
+
export declare function declaredParamsOf(node: SyntaxNode): DeclaredParams | undefined;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { parameterText, textOnlyParams } from "./call-syntax.js";
|
|
2
|
+
export function namedChildren(node) {
|
|
3
|
+
const out = [];
|
|
4
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5
|
+
const child = node.namedChild(i);
|
|
6
|
+
if (child)
|
|
7
|
+
out.push(child);
|
|
8
|
+
}
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether two handles name the same syntax node. The native binding may return
|
|
13
|
+
* a fresh JS object for a node on each access, and whether it does depends on
|
|
14
|
+
* garbage collection, so `===` on nodes gives different answers from run to run.
|
|
15
|
+
* A node's id is stable within its tree.
|
|
16
|
+
*/
|
|
17
|
+
export function sameNode(a, b) {
|
|
18
|
+
return a !== null && a !== undefined && b !== null && b !== undefined && a.id === b.id;
|
|
19
|
+
}
|
|
20
|
+
export function childByType(node, type) {
|
|
21
|
+
return namedChildren(node).find((c) => c.type === type) ?? null;
|
|
22
|
+
}
|
|
23
|
+
export function collapseWs(text) {
|
|
24
|
+
return text.replace(/\s+/g, " ").trim();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Call-site / branch span for a syntax node.
|
|
28
|
+
* Uses tree-sitter 0-based rows → 1-based display lines.
|
|
29
|
+
*/
|
|
30
|
+
export function locFromNode(file, node) {
|
|
31
|
+
const line = node.startPosition.row + 1;
|
|
32
|
+
const endLine = node.endPosition.row + 1;
|
|
33
|
+
return endLine > line ? { file, line, endLine } : { file, line };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parameter-list containers the grammars declare. A list is a direct child of
|
|
37
|
+
* the definition node, or of its declarator (`int callee(int param);`).
|
|
38
|
+
*/
|
|
39
|
+
const PARAMETER_CONTAINERS = {
|
|
40
|
+
formal_parameters: true,
|
|
41
|
+
parameters: true,
|
|
42
|
+
parameter_list: true,
|
|
43
|
+
method_parameters: true,
|
|
44
|
+
function_value_parameters: true,
|
|
45
|
+
lambda_parameters: true,
|
|
46
|
+
parameter_clause: true,
|
|
47
|
+
};
|
|
48
|
+
/** Nodes that hold a parameter list one level below the definition. */
|
|
49
|
+
const PARAMETER_DECLARATORS = {
|
|
50
|
+
function_declarator: true,
|
|
51
|
+
declarator: true,
|
|
52
|
+
method_declarator: true,
|
|
53
|
+
call_signature: true,
|
|
54
|
+
function_declarator_body: true,
|
|
55
|
+
};
|
|
56
|
+
function namedChildrenTyped(node, types) {
|
|
57
|
+
return namedChildren(node).filter((child) => types[child.type] === true);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Declared parameter list of a definition, read from the AST: the verbatim
|
|
61
|
+
* list text, with no slots. Text alone keeps argument binding `unknown`, which
|
|
62
|
+
* is what the extractors that do not build lexical scopes can honestly claim.
|
|
63
|
+
*/
|
|
64
|
+
export function declaredParamsOf(node) {
|
|
65
|
+
const direct = namedChildrenTyped(node, PARAMETER_CONTAINERS)[0];
|
|
66
|
+
if (direct)
|
|
67
|
+
return textOnlyParams(parameterText(direct));
|
|
68
|
+
for (const declarator of namedChildrenTyped(node, PARAMETER_DECLARATORS)) {
|
|
69
|
+
const nested = namedChildrenTyped(declarator, PARAMETER_CONTAINERS)[0];
|
|
70
|
+
if (nested)
|
|
71
|
+
return textOnlyParams(parameterText(nested));
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript / TSX type contracts for review context.
|
|
3
|
+
*
|
|
4
|
+
* An interface, type alias, or enum is syntactic evidence about a shape, not a
|
|
5
|
+
* callable: the entries this helper adds carry `review.kind`, no steps, and the
|
|
6
|
+
* whole declaration span, and the extractor keeps them out of the callable
|
|
7
|
+
* index. What they *do* carry is `review.references`: the type names written in
|
|
8
|
+
* a definition's signature, body, or declaration — `Promise<Foo>`,
|
|
9
|
+
* `x as Bar`, `interface A extends B`, `foo<Baz>()`, `import("m").Q` — each
|
|
10
|
+
* with the module specifier and original name it was written through.
|
|
11
|
+
*
|
|
12
|
+
* {@link resolveTypeContracts} turns those references into snapshot-local
|
|
13
|
+
* edges. Relative imports bind to snapshot paths; a bare or aliased module
|
|
14
|
+
* path binds only when one snapshot file's path suffix matches it. Unresolved
|
|
15
|
+
* imports stay unresolved, even when another file has a unique same-named
|
|
16
|
+
* declaration. Unimported names prefer the same file, then a unique snapshot
|
|
17
|
+
* candidate. These are syntactic candidates, not TypeScript symbol resolution.
|
|
18
|
+
*
|
|
19
|
+
* Every step is derived from the source and the snapshot declarations, so the
|
|
20
|
+
* same input always yields the same edges.
|
|
21
|
+
*
|
|
22
|
+
* Deliberate limits: only module-level declarations are extracted; references
|
|
23
|
+
* in module-level value code, class field annotations, and heritage clauses
|
|
24
|
+
* belong to no definition and are dropped; re-export/barrel hops are not
|
|
25
|
+
* followed; a default import is matched by its local binding name; a
|
|
26
|
+
* declaration that names itself is not an edge. External types absent from
|
|
27
|
+
* the snapshot remain unresolved.
|
|
28
|
+
*/
|
|
29
|
+
import type Parser from "tree-sitter";
|
|
30
|
+
import type { FunctionInfo } from "../types.js";
|
|
31
|
+
type Tree = Parser.Tree;
|
|
32
|
+
/**
|
|
33
|
+
* Record the type contracts of one file: append a non-callable definition per
|
|
34
|
+
* interface / type alias / enum, then attach each written reference to the
|
|
35
|
+
* innermost definition (callable or contract) whose span contains it. A
|
|
36
|
+
* reference in module-level code that belongs to no definition is left out
|
|
37
|
+
* rather than credited to a sibling.
|
|
38
|
+
*/
|
|
39
|
+
export declare function extractTypeContracts(file: string, tree: Tree, functions: FunctionInfo[]): void;
|
|
40
|
+
/** One `owner -> target` type-contract relation for the context graph. */
|
|
41
|
+
export interface TypeContractEdge {
|
|
42
|
+
owner: FunctionInfo;
|
|
43
|
+
target: FunctionInfo;
|
|
44
|
+
evidence: string;
|
|
45
|
+
kind: "contract";
|
|
46
|
+
/** 1-based line of the written reference, not of either definition. */
|
|
47
|
+
line: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve one reference against the snapshot. A relative specifier must name a
|
|
51
|
+
* snapshot file (exact binding); a bare or aliased specifier binds only when
|
|
52
|
+
* exactly one snapshot file's path ends with its tail; a name written without a
|
|
53
|
+
* module binds to the file's own declaration. Only a name no module explains
|
|
54
|
+
* may fall back to a globally unique declaration.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveTypeContracts(functions: readonly FunctionInfo[]): TypeContractEdge[];
|
|
57
|
+
export {};
|