@orangepro/orangepro-mcp 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 +328 -0
- package/dist/local/agentWorkflow.js +81 -0
- package/dist/local/aiGraph/links.js +635 -0
- package/dist/local/analyze/analyzer.js +2129 -0
- package/dist/local/analyze/behaviorContracts.js +169 -0
- package/dist/local/analyze/boilerplate.js +42 -0
- package/dist/local/analyze/callGraph.js +458 -0
- package/dist/local/analyze/classify.js +219 -0
- package/dist/local/analyze/clustering.js +357 -0
- package/dist/local/analyze/confirm.js +2422 -0
- package/dist/local/analyze/coverage.js +518 -0
- package/dist/local/analyze/coverageArtifacts.js +607 -0
- package/dist/local/analyze/frameworks.js +115 -0
- package/dist/local/analyze/linkage/conventions.js +160 -0
- package/dist/local/analyze/parseCache.js +164 -0
- package/dist/local/analyze/selfAssert.js +53 -0
- package/dist/local/analyze/symbols.js +430 -0
- package/dist/local/analyze/testLayer.js +135 -0
- package/dist/local/analyze/treeSitter/engine.js +1253 -0
- package/dist/local/analyze/treeSitter/languages.js +101 -0
- package/dist/local/autoProve.js +620 -0
- package/dist/local/cli.js +1468 -0
- package/dist/local/cliArgs.js +112 -0
- package/dist/local/corpusScope.js +162 -0
- package/dist/local/enrich/csv.js +348 -0
- package/dist/local/enrich/index.js +43 -0
- package/dist/local/enrich/markdown.js +193 -0
- package/dist/local/explain/explain.js +91 -0
- package/dist/local/exportCli.js +26 -0
- package/dist/local/flows/flowWalker.js +215 -0
- package/dist/local/flows/llmFlowDiscovery.js +567 -0
- package/dist/local/freshness/changed.js +280 -0
- package/dist/local/freshness/manifest.js +35 -0
- package/dist/local/freshness/status.js +30 -0
- package/dist/local/gaps/gaps.js +114 -0
- package/dist/local/generate/buckets.js +73 -0
- package/dist/local/generate/compareJudge.js +124 -0
- package/dist/local/generate/compareReport.js +538 -0
- package/dist/local/generate/compareScore.js +105 -0
- package/dist/local/generate/deriveImports.js +91 -0
- package/dist/local/generate/generator.js +2586 -0
- package/dist/local/generate/prompt.js +144 -0
- package/dist/local/generate/promptV5.js +438 -0
- package/dist/local/generate/providers.js +400 -0
- package/dist/local/generate/runHints.js +304 -0
- package/dist/local/graph/citations.js +73 -0
- package/dist/local/graph/confirmable.js +72 -0
- package/dist/local/graph/factories.js +210 -0
- package/dist/local/graph/ontology.js +18 -0
- package/dist/local/interactive.js +53 -0
- package/dist/local/jobs/jobStore.js +80 -0
- package/dist/local/jobs/notify.js +29 -0
- package/dist/local/jobs/runner.js +75 -0
- package/dist/local/ledger.js +117 -0
- package/dist/local/localConfig.js +112 -0
- package/dist/local/mcp.js +548 -0
- package/dist/local/operations.js +1749 -0
- package/dist/local/pack/coverageReport.js +192 -0
- package/dist/local/pack/exporter.js +195 -0
- package/dist/local/pack/schema.js +128 -0
- package/dist/local/pack/summary.js +127 -0
- package/dist/local/pack/validate.js +25 -0
- package/dist/local/proofRunnability.js +366 -0
- package/dist/local/recipe/dbSqljs.js +255 -0
- package/dist/local/reprove/paths.js +13 -0
- package/dist/local/reprove/scoped.js +136 -0
- package/dist/local/resolve/barrelWalker.js +178 -0
- package/dist/local/resolve/exportIndex.js +270 -0
- package/dist/local/resolve/importGraph.js +347 -0
- package/dist/local/resolve/resolver.js +122 -0
- package/dist/local/resolve/resolverCache.js +117 -0
- package/dist/local/rtm.js +413 -0
- package/dist/local/score/coverage.js +99 -0
- package/dist/local/score/doctor.js +67 -0
- package/dist/local/score/risk.js +362 -0
- package/dist/local/score/score.js +182 -0
- package/dist/local/types.js +1 -0
- package/dist/local/util/hash.js +16 -0
- package/dist/local/util/ids.js +16 -0
- package/dist/local/util/progress.js +8 -0
- package/dist/local/util/redact.js +39 -0
- package/dist/local/util/time.js +1 -0
- package/dist/local/util/walk.js +174 -0
- package/dist/local/viz/behaviorReportData.js +367 -0
- package/dist/local/viz/behaviorReportHtml.js +664 -0
- package/dist/local/viz/d3.bundle.js +3 -0
- package/dist/local/viz/html.js +1152 -0
- package/dist/local/viz/payload.js +525 -0
- package/dist/local/workspace.js +99 -0
- package/docs/agent-workflow.md +167 -0
- package/docs/agents/claude-code.md +43 -0
- package/docs/agents/codex.md +52 -0
- package/docs/agents/cursor.md +39 -0
- package/docs/agents/opencode.md +43 -0
- package/docs/agents/vscode.md +34 -0
- package/docs/local-proof-kit.md +269 -0
- package/package.json +92 -0
- package/scripts/spikes/dynamic-proof-jest-reporter.cjs +66 -0
- package/scripts/spikes/dynamic-proof-mocha-reporter.cjs +105 -0
- package/scripts/spikes/dynamic-proof-spike.mjs +2335 -0
- package/scripts/spikes/dynamic-proof-vitest-reporter.mjs +81 -0
- package/scripts/spikes/failure-summary.mjs +29 -0
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-agnostic symbol extraction via tree-sitter (web-tree-sitter WASM — no
|
|
3
|
+
* native build, ships in the npm package). Grammars are LOADED ASYNC once
|
|
4
|
+
* (preloadTreeSitter), then extraction is SYNC so it drops into the synchronous
|
|
5
|
+
* analyzer walk. Replaces the fragile per-language regexes for non-TS languages.
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { Parser, Language } from "web-tree-sitter";
|
|
9
|
+
import { MAX_SYMBOLS_PER_FILE } from "../symbols.js";
|
|
10
|
+
import { TS_LANG_CONFIGS } from "./languages.js";
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
let initPromise = null;
|
|
13
|
+
const loaded = new Map();
|
|
14
|
+
const failed = new Set();
|
|
15
|
+
const parsers = new Map();
|
|
16
|
+
/** Resolve a grammar wasm shipped by the tree-sitter-wasms dependency. */
|
|
17
|
+
function grammarPath(wasm) {
|
|
18
|
+
return require.resolve(`tree-sitter-wasms/out/${wasm}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load the tree-sitter runtime + the grammars for `languages` (once; idempotent).
|
|
22
|
+
* Never throws. Returns which languages loaded vs FAILED so the caller/analyzer can
|
|
23
|
+
* surface a downgrade instead of silently serving shallow regex extraction.
|
|
24
|
+
*/
|
|
25
|
+
export async function preloadTreeSitter(languages) {
|
|
26
|
+
const requested = [...new Set(languages)].filter((l) => TS_LANG_CONFIGS[l]);
|
|
27
|
+
try {
|
|
28
|
+
if (!initPromise)
|
|
29
|
+
initPromise = Parser.init();
|
|
30
|
+
await initPromise;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Runtime wasm failed → every requested grammar is unavailable.
|
|
34
|
+
for (const language of requested)
|
|
35
|
+
if (!loaded.has(language))
|
|
36
|
+
failed.add(language);
|
|
37
|
+
return treeSitterStatus();
|
|
38
|
+
}
|
|
39
|
+
for (const language of requested) {
|
|
40
|
+
if (loaded.has(language))
|
|
41
|
+
continue;
|
|
42
|
+
try {
|
|
43
|
+
loaded.set(language, await Language.load(grammarPath(TS_LANG_CONFIGS[language].wasm)));
|
|
44
|
+
failed.delete(language);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
failed.add(language); // attempted + errored — distinct from "never preloaded"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return treeSitterStatus();
|
|
51
|
+
}
|
|
52
|
+
/** Snapshot of which grammars are ready vs failed (for analysis metadata / warnings). */
|
|
53
|
+
export function treeSitterStatus() {
|
|
54
|
+
return { loaded: [...loaded.keys()], failed: [...failed] };
|
|
55
|
+
}
|
|
56
|
+
/** True once a grammar for `language` is loaded and ready for sync extraction. */
|
|
57
|
+
export function treeSitterReady(language) {
|
|
58
|
+
return loaded.has(language);
|
|
59
|
+
}
|
|
60
|
+
/** True when a grammar load was ATTEMPTED and failed (vs simply never preloaded). */
|
|
61
|
+
export function treeSitterFailed(language) {
|
|
62
|
+
return failed.has(language);
|
|
63
|
+
}
|
|
64
|
+
/** Test-only: forget loaded grammars so a test can exercise the pre-preload (regex) path. */
|
|
65
|
+
export function __resetTreeSitterForTests() {
|
|
66
|
+
loaded.clear();
|
|
67
|
+
failed.clear();
|
|
68
|
+
parsers.clear();
|
|
69
|
+
}
|
|
70
|
+
function parserFor(language) {
|
|
71
|
+
const lang = loaded.get(language);
|
|
72
|
+
if (!lang)
|
|
73
|
+
return null;
|
|
74
|
+
let parser = parsers.get(language);
|
|
75
|
+
if (!parser) {
|
|
76
|
+
parser = new Parser();
|
|
77
|
+
parser.setLanguage(lang);
|
|
78
|
+
parsers.set(language, parser);
|
|
79
|
+
}
|
|
80
|
+
return parser;
|
|
81
|
+
}
|
|
82
|
+
// A trivial getter returns a bare field / `this.field` / `this`; never a call,
|
|
83
|
+
// arithmetic, or anything with behavior. Used to make boilerplate exclusion
|
|
84
|
+
// body-aware so `getOwner()` that calls a repository is NOT dropped.
|
|
85
|
+
const TRIVIAL_RETURN_TYPES = new Set(["identifier", "field_access", "this"]);
|
|
86
|
+
const RESERVED_SYMBOL_NAMES = new Set([
|
|
87
|
+
"auto",
|
|
88
|
+
"bool",
|
|
89
|
+
"case",
|
|
90
|
+
"char",
|
|
91
|
+
"class",
|
|
92
|
+
"const",
|
|
93
|
+
"default",
|
|
94
|
+
"define",
|
|
95
|
+
"do",
|
|
96
|
+
"double",
|
|
97
|
+
"else",
|
|
98
|
+
"enum",
|
|
99
|
+
"extern",
|
|
100
|
+
"float",
|
|
101
|
+
"for",
|
|
102
|
+
"goto",
|
|
103
|
+
"if",
|
|
104
|
+
"ifdef",
|
|
105
|
+
"ifndef",
|
|
106
|
+
"include",
|
|
107
|
+
"inline",
|
|
108
|
+
"int",
|
|
109
|
+
"long",
|
|
110
|
+
"namespace",
|
|
111
|
+
"private",
|
|
112
|
+
"protected",
|
|
113
|
+
"public",
|
|
114
|
+
"register",
|
|
115
|
+
"return",
|
|
116
|
+
"short",
|
|
117
|
+
"signed",
|
|
118
|
+
"sizeof",
|
|
119
|
+
"static",
|
|
120
|
+
"struct",
|
|
121
|
+
"switch",
|
|
122
|
+
"template",
|
|
123
|
+
"typename",
|
|
124
|
+
"typedef",
|
|
125
|
+
"union",
|
|
126
|
+
"unsigned",
|
|
127
|
+
"void",
|
|
128
|
+
"volatile",
|
|
129
|
+
"while"
|
|
130
|
+
]);
|
|
131
|
+
const FUNCTION_DECLARATOR_TYPES = new Set(["function_declarator"]);
|
|
132
|
+
/** Statement children of a block, ignoring comments. */
|
|
133
|
+
function blockStatements(block) {
|
|
134
|
+
const out = [];
|
|
135
|
+
for (let i = 0; i < block.namedChildCount; i++) {
|
|
136
|
+
const c = block.namedChild(i);
|
|
137
|
+
if (c && !/comment/.test(c.type))
|
|
138
|
+
out.push(c);
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* AST-proven trivial accessor: an empty body, a single bare-field `return`, or a
|
|
144
|
+
* single `this.field = param` assignment. Java-only for now (the motivating
|
|
145
|
+
* entity-heavy case); other languages return false so a name match alone never
|
|
146
|
+
* excludes a method. Abstract/interface methods (no body) are NOT trivial — they
|
|
147
|
+
* are contract surface and stay countable.
|
|
148
|
+
*/
|
|
149
|
+
function isTrivialAccessorBody(methodNode, language) {
|
|
150
|
+
if (language !== "java")
|
|
151
|
+
return false;
|
|
152
|
+
const body = methodNode.childForFieldName("body");
|
|
153
|
+
if (!body || body.type !== "block")
|
|
154
|
+
return false;
|
|
155
|
+
const stmts = blockStatements(body);
|
|
156
|
+
if (stmts.length === 0)
|
|
157
|
+
return true; // empty body
|
|
158
|
+
if (stmts.length !== 1)
|
|
159
|
+
return false;
|
|
160
|
+
const s = stmts[0];
|
|
161
|
+
if (s.type === "return_statement") {
|
|
162
|
+
const expr = s.namedChild(0);
|
|
163
|
+
return !expr || TRIVIAL_RETURN_TYPES.has(expr.type); // return this.field / return field / return;
|
|
164
|
+
}
|
|
165
|
+
if (s.type === "expression_statement") {
|
|
166
|
+
const assign = s.namedChild(0);
|
|
167
|
+
if (assign?.type !== "assignment_expression")
|
|
168
|
+
return false;
|
|
169
|
+
return assign.childForFieldName("right")?.type === "identifier"; // this.field = param
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function firstDescendantOfType(node, types) {
|
|
174
|
+
for (const child of namedChildren(node)) {
|
|
175
|
+
if (types.has(child.type))
|
|
176
|
+
return child;
|
|
177
|
+
const nested = firstDescendantOfType(child, types);
|
|
178
|
+
if (nested)
|
|
179
|
+
return nested;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
function hasAncestorType(node, type) {
|
|
184
|
+
let cur = node.parent;
|
|
185
|
+
while (cur) {
|
|
186
|
+
if (cur.type === type)
|
|
187
|
+
return true;
|
|
188
|
+
cur = cur.parent;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
function shouldEmitSymbol(node, language) {
|
|
193
|
+
if (language === "rust" && node.type === "function_signature_item") {
|
|
194
|
+
return hasAncestorType(node, "trait_item");
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function symbolName(node, cfg) {
|
|
199
|
+
if (node.type === "function_definition" && cfg.nameNodeTypes) {
|
|
200
|
+
const declarator = node.childForFieldName("declarator");
|
|
201
|
+
const functionDeclarator = declarator?.type === "function_declarator" ? declarator : declarator ? firstDescendantOfType(declarator, FUNCTION_DECLARATOR_TYPES) : null;
|
|
202
|
+
const fromDeclarator = functionDeclarator ? firstDescendantOfType(functionDeclarator, cfg.nameNodeTypes) : null;
|
|
203
|
+
return fromDeclarator?.text;
|
|
204
|
+
}
|
|
205
|
+
if (["struct_specifier", "union_specifier", "enum_specifier", "class_specifier"].includes(node.type)) {
|
|
206
|
+
if (!node.childForFieldName("body"))
|
|
207
|
+
return undefined;
|
|
208
|
+
return node.childForFieldName(cfg.nameField)?.text;
|
|
209
|
+
}
|
|
210
|
+
return node.childForFieldName(cfg.nameField)?.text ?? (cfg.nameNodeTypes ? firstDescendantOfType(node, cfg.nameNodeTypes)?.text : undefined);
|
|
211
|
+
}
|
|
212
|
+
function nodeLines(node) {
|
|
213
|
+
return { start_line: node.startPosition.row + 1, end_line: node.endPosition.row + 1 };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Extract class/function/method symbol metadata (names + a trivial-accessor flag —
|
|
217
|
+
* never source bodies) from a tree-sitter-supported language. Sync; returns [] when
|
|
218
|
+
* the grammar isn't loaded.
|
|
219
|
+
*/
|
|
220
|
+
export function extractTreeSitterSymbols(content, language) {
|
|
221
|
+
const cfg = TS_LANG_CONFIGS[language];
|
|
222
|
+
const parser = parserFor(language);
|
|
223
|
+
if (!cfg || !parser)
|
|
224
|
+
return { symbols: [], truncated: false };
|
|
225
|
+
const tree = parser.parse(content);
|
|
226
|
+
if (!tree)
|
|
227
|
+
return { symbols: [], truncated: false };
|
|
228
|
+
const byName = new Map();
|
|
229
|
+
let truncated = false;
|
|
230
|
+
const add = (sym) => {
|
|
231
|
+
const existing = byName.get(sym.name);
|
|
232
|
+
if (existing) {
|
|
233
|
+
// Prefer a class over a method/function of the same name (rare collisions).
|
|
234
|
+
if (existing.symbol_kind !== "class" && sym.symbol_kind === "class")
|
|
235
|
+
byName.set(sym.name, sym);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (byName.size >= MAX_SYMBOLS_PER_FILE) {
|
|
239
|
+
truncated = true;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
byName.set(sym.name, sym);
|
|
243
|
+
};
|
|
244
|
+
const walk = (node) => {
|
|
245
|
+
let kind = null;
|
|
246
|
+
if (cfg.classTypes.has(node.type))
|
|
247
|
+
kind = "class";
|
|
248
|
+
else if (cfg.methodTypes.has(node.type))
|
|
249
|
+
kind = "method";
|
|
250
|
+
else if (cfg.functionTypes.has(node.type))
|
|
251
|
+
kind = "function";
|
|
252
|
+
if (kind && shouldEmitSymbol(node, language)) {
|
|
253
|
+
const name = symbolName(node, cfg);
|
|
254
|
+
if (name && !RESERVED_SYMBOL_NAMES.has(name)) {
|
|
255
|
+
add(kind === "method"
|
|
256
|
+
? { name, symbol_kind: kind, trivial_accessor: isTrivialAccessorBody(node, language), ...nodeLines(node) }
|
|
257
|
+
: { name, symbol_kind: kind, ...nodeLines(node) });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
261
|
+
const child = node.child(i);
|
|
262
|
+
if (child)
|
|
263
|
+
walk(child);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
walk(tree.rootNode);
|
|
267
|
+
tree.delete();
|
|
268
|
+
return { symbols: [...byName.values()], truncated };
|
|
269
|
+
}
|
|
270
|
+
function namedChildren(node) {
|
|
271
|
+
const out = [];
|
|
272
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
273
|
+
const child = node.namedChild(i);
|
|
274
|
+
if (child)
|
|
275
|
+
out.push(child);
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
function stripQuotes(s) {
|
|
280
|
+
return s.replace(/^['"`<]/, "").replace(/['"`>]$/, "");
|
|
281
|
+
}
|
|
282
|
+
function lastDottedPart(s) {
|
|
283
|
+
const parts = s.split(/[./\\:]/).filter(Boolean);
|
|
284
|
+
return parts[parts.length - 1] || s;
|
|
285
|
+
}
|
|
286
|
+
function collectNames(node, out) {
|
|
287
|
+
if (!node)
|
|
288
|
+
return;
|
|
289
|
+
if (/^(identifier|package_identifier|field_identifier|simple_identifier)$/.test(node.type)) {
|
|
290
|
+
out.add(node.text);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
for (const child of namedChildren(node))
|
|
294
|
+
collectNames(child, out);
|
|
295
|
+
}
|
|
296
|
+
function localBindings(fn, language) {
|
|
297
|
+
const out = new Set();
|
|
298
|
+
const params = fn.childForFieldName("parameters") ?? (language === "kotlin" ? namedChildren(fn).find((n) => n.type === "function_value_parameters") : null);
|
|
299
|
+
if (params) {
|
|
300
|
+
if (language === "python")
|
|
301
|
+
collectNames(params, out);
|
|
302
|
+
else {
|
|
303
|
+
for (const child of namedChildren(params)) {
|
|
304
|
+
if (/parameter/.test(child.type))
|
|
305
|
+
collectNames(child.childForFieldName("name") ?? child, out);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const body = fn.childForFieldName("body") ?? (language === "kotlin" ? namedChildren(fn).find((n) => n.type === "function_body") : null);
|
|
310
|
+
const walk = (node) => {
|
|
311
|
+
if (language === "python" && node.type === "assignment")
|
|
312
|
+
collectNames(node.childForFieldName("left") ?? node.namedChild(0), out);
|
|
313
|
+
if (language === "python" && (node.type === "function_definition" || node.type === "class_definition"))
|
|
314
|
+
collectNames(node.childForFieldName("name"), out);
|
|
315
|
+
if (language === "python" && node.type === "for_statement")
|
|
316
|
+
collectNames(node.childForFieldName("left"), out);
|
|
317
|
+
if (language === "python" && node.type === "for_in_clause")
|
|
318
|
+
collectNames(node.childForFieldName("left"), out);
|
|
319
|
+
if (language === "python" && node.type === "except_clause")
|
|
320
|
+
collectNames(namedChildren(node).find((n) => n.type === "as_pattern") ?? null, out);
|
|
321
|
+
if (language === "python" && node.type === "lambda")
|
|
322
|
+
collectNames(node.childForFieldName("parameters"), out);
|
|
323
|
+
if (language === "go" && (node.type === "short_var_declaration" || node.type === "var_spec"))
|
|
324
|
+
collectNames(node.childForFieldName("name") ?? node.namedChild(0), out);
|
|
325
|
+
if (language === "go" && node.type === "range_clause")
|
|
326
|
+
collectNames(node.childForFieldName("left"), out);
|
|
327
|
+
if (language === "java" && node.type === "variable_declarator")
|
|
328
|
+
collectNames(node.childForFieldName("name"), out);
|
|
329
|
+
if (language === "java" && node.type === "catch_formal_parameter")
|
|
330
|
+
collectNames(node.childForFieldName("name"), out);
|
|
331
|
+
if (language === "java" && node.type === "enhanced_for_statement")
|
|
332
|
+
collectNames(node.childForFieldName("name"), out);
|
|
333
|
+
if (language === "rust" && node.type === "let_declaration")
|
|
334
|
+
collectNames(node.childForFieldName("pattern") ?? node.namedChild(0), out);
|
|
335
|
+
if (language === "rust" && node.type === "for_expression")
|
|
336
|
+
collectNames(node.childForFieldName("pattern"), out);
|
|
337
|
+
if (language === "rust" && node.type === "closure_expression")
|
|
338
|
+
collectNames(node.childForFieldName("parameters"), out);
|
|
339
|
+
if (language === "csharp" && node.type === "variable_declarator")
|
|
340
|
+
collectNames(node.childForFieldName("name") ?? node.namedChild(0), out);
|
|
341
|
+
if (language === "csharp" && node.type === "declaration_expression")
|
|
342
|
+
collectNames(node.childForFieldName("name"), out);
|
|
343
|
+
if (language === "csharp" && node.type === "catch_declaration")
|
|
344
|
+
collectNames(node.childForFieldName("name"), out);
|
|
345
|
+
if (language === "csharp" && node.type === "for_each_statement")
|
|
346
|
+
collectNames(node.childForFieldName("left"), out);
|
|
347
|
+
if (language === "csharp" && node.type === "lambda_expression") {
|
|
348
|
+
const lambdaParams = node.childForFieldName("parameters");
|
|
349
|
+
if (lambdaParams) {
|
|
350
|
+
for (const child of namedChildren(lambdaParams)) {
|
|
351
|
+
if (/parameter/.test(child.type))
|
|
352
|
+
collectNames(child.childForFieldName("name") ?? child, out);
|
|
353
|
+
else if (child.type === "identifier")
|
|
354
|
+
collectNames(child, out);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
const first = namedChildren(node)[0];
|
|
359
|
+
if (first?.type === "identifier")
|
|
360
|
+
collectNames(first, out);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (language === "kotlin" && node.type === "variable_declaration")
|
|
364
|
+
collectNames(node.childForFieldName("name") ?? node.namedChild(0), out);
|
|
365
|
+
if (language === "kotlin" && node.type === "catch_block")
|
|
366
|
+
collectNames(namedChildren(node).find((n) => n.type === "simple_identifier") ?? null, out);
|
|
367
|
+
for (const child of namedChildren(node))
|
|
368
|
+
walk(child);
|
|
369
|
+
};
|
|
370
|
+
if (body)
|
|
371
|
+
walk(body);
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
function functionName(node, language) {
|
|
375
|
+
const name = node.childForFieldName("name")?.text;
|
|
376
|
+
if (!name && language === "kotlin")
|
|
377
|
+
return namedChildren(node).find((n) => n.type === "simple_identifier")?.text;
|
|
378
|
+
if (!name)
|
|
379
|
+
return undefined;
|
|
380
|
+
if (language === "go" && node.type === "method_declaration")
|
|
381
|
+
return name;
|
|
382
|
+
return name;
|
|
383
|
+
}
|
|
384
|
+
function javaPackage(root) {
|
|
385
|
+
const pkg = namedChildren(root).find((n) => n.type === "package_declaration");
|
|
386
|
+
return pkg ? namedChildren(pkg).find((n) => n.type.endsWith("identifier"))?.text : undefined;
|
|
387
|
+
}
|
|
388
|
+
function goPackage(root) {
|
|
389
|
+
const pkg = namedChildren(root).find((n) => n.type === "package_clause");
|
|
390
|
+
return pkg ? namedChildren(pkg).find((n) => n.type === "package_identifier")?.text : undefined;
|
|
391
|
+
}
|
|
392
|
+
function kotlinPackage(root) {
|
|
393
|
+
const pkg = namedChildren(root).find((n) => n.type === "package_header");
|
|
394
|
+
return pkg ? namedChildren(pkg).find((n) => n.type === "identifier")?.text : undefined;
|
|
395
|
+
}
|
|
396
|
+
function phpNamespace(root) {
|
|
397
|
+
const ns = namedChildren(root).find((n) => n.type === "namespace_definition");
|
|
398
|
+
const name = ns?.childForFieldName("name") ?? namedChildren(ns ?? root).find((n) => n.type === "namespace_name");
|
|
399
|
+
return name?.text;
|
|
400
|
+
}
|
|
401
|
+
function csharpNamespace(root) {
|
|
402
|
+
const ns = namedChildren(root).find((n) => n.type === "file_scoped_namespace_declaration" || n.type === "namespace_declaration");
|
|
403
|
+
return ns?.childForFieldName("name")?.text;
|
|
404
|
+
}
|
|
405
|
+
function kotlinTopLevelSymbols(root) {
|
|
406
|
+
const out = new Set();
|
|
407
|
+
for (const child of namedChildren(root)) {
|
|
408
|
+
if (child.type === "function_declaration") {
|
|
409
|
+
const name = functionName(child, "kotlin");
|
|
410
|
+
if (name)
|
|
411
|
+
out.add(name);
|
|
412
|
+
}
|
|
413
|
+
else if (child.type === "class_declaration" || child.type === "object_declaration") {
|
|
414
|
+
const name = symbolName(child, TS_LANG_CONFIGS.kotlin);
|
|
415
|
+
if (name)
|
|
416
|
+
out.add(name);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return [...out].sort();
|
|
420
|
+
}
|
|
421
|
+
function extractImports(root, language) {
|
|
422
|
+
const imports = [];
|
|
423
|
+
const add = (binding) => {
|
|
424
|
+
if (!binding.local || !binding.module)
|
|
425
|
+
return;
|
|
426
|
+
imports.push(binding);
|
|
427
|
+
};
|
|
428
|
+
const walk = (node) => {
|
|
429
|
+
if (language === "java" && node.type === "import_declaration") {
|
|
430
|
+
const spec = namedChildren(node).find((n) => n.type.endsWith("identifier"))?.text;
|
|
431
|
+
if (spec && !spec.endsWith(".*"))
|
|
432
|
+
add({ local: lastDottedPart(spec), module: spec, imported: lastDottedPart(spec), kind: "named" });
|
|
433
|
+
}
|
|
434
|
+
else if (language === "python" && node.type === "import_statement") {
|
|
435
|
+
for (const child of namedChildren(node)) {
|
|
436
|
+
const name = child.childForFieldName("name")?.text ?? child.text;
|
|
437
|
+
const alias = child.childForFieldName("alias")?.text;
|
|
438
|
+
if (name)
|
|
439
|
+
add({ local: alias || lastDottedPart(name), module: name, kind: "module" });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
else if (language === "python" && node.type === "import_from_statement") {
|
|
443
|
+
const kids = namedChildren(node);
|
|
444
|
+
const moduleNode = kids.find((n) => n.type === "dotted_name" || n.type === "relative_import");
|
|
445
|
+
const module = moduleNode?.text;
|
|
446
|
+
if (module) {
|
|
447
|
+
if (/\bimport\s+\*/.test(node.text))
|
|
448
|
+
add({ local: "*", module, imported: "*", kind: "named" });
|
|
449
|
+
for (const child of kids) {
|
|
450
|
+
if (child === moduleNode)
|
|
451
|
+
continue;
|
|
452
|
+
const imported = child.childForFieldName("name")?.text ?? (child.type === "identifier" || child.type === "dotted_name" ? child.text : undefined);
|
|
453
|
+
const alias = child.childForFieldName("alias")?.text;
|
|
454
|
+
if (imported === "*")
|
|
455
|
+
add({ local: "*", module, imported, kind: "named" });
|
|
456
|
+
else if (imported)
|
|
457
|
+
add({ local: alias || imported, module, imported, kind: "named" });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else if (language === "go" && node.type === "import_spec") {
|
|
462
|
+
const raw = node.childForFieldName("path")?.text;
|
|
463
|
+
const module = raw ? stripQuotes(raw) : undefined;
|
|
464
|
+
const alias = node.childForFieldName("name")?.text;
|
|
465
|
+
if (module && alias !== "_" && alias !== ".")
|
|
466
|
+
add({ local: alias || lastDottedPart(module), module, kind: "module" });
|
|
467
|
+
}
|
|
468
|
+
else if (language === "ruby" && node.type === "call") {
|
|
469
|
+
const method = node.childForFieldName("method")?.text;
|
|
470
|
+
if (method === "require_relative") {
|
|
471
|
+
const str = namedChildren(node).find((n) => n.type === "argument_list")?.descendantsOfType("string_content")[0]?.text;
|
|
472
|
+
if (str)
|
|
473
|
+
add({ local: lastDottedPart(str), module: `./${str}`, kind: "module" });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else if (language === "kotlin" && node.type === "import_header") {
|
|
477
|
+
const spec = namedChildren(node).find((n) => n.type === "identifier")?.text;
|
|
478
|
+
if (spec && !spec.endsWith(".*"))
|
|
479
|
+
add({ local: lastDottedPart(spec), module: spec, imported: lastDottedPart(spec), kind: "named" });
|
|
480
|
+
}
|
|
481
|
+
else if (language === "rust" && node.type === "mod_item") {
|
|
482
|
+
const name = node.childForFieldName("name")?.text;
|
|
483
|
+
if (name && !node.childForFieldName("body"))
|
|
484
|
+
add({ local: name, module: `./${name}`, kind: "module" });
|
|
485
|
+
}
|
|
486
|
+
else if (language === "rust" && node.type === "use_declaration") {
|
|
487
|
+
const arg = node.childForFieldName("argument");
|
|
488
|
+
if (arg && (arg.type === "scoped_identifier" || arg.type === "identifier")) {
|
|
489
|
+
const module = arg.text.replace(/::/g, ".");
|
|
490
|
+
const imported = lastDottedPart(module);
|
|
491
|
+
add({ local: imported, module, imported, kind: "named" });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else if (language === "php" && node.type === "namespace_use_clause") {
|
|
495
|
+
const spec = namedChildren(node).find((n) => n.type === "qualified_name" || n.type === "namespace_name")?.text;
|
|
496
|
+
if (spec)
|
|
497
|
+
add({ local: lastDottedPart(spec), module: spec, imported: lastDottedPart(spec), kind: "named" });
|
|
498
|
+
}
|
|
499
|
+
else if (language === "csharp" && node.type === "using_directive") {
|
|
500
|
+
const spec = namedChildren(node).find((n) => n.type === "qualified_name" || n.type === "identifier")?.text;
|
|
501
|
+
if (spec)
|
|
502
|
+
add({ local: lastDottedPart(spec), module: spec, kind: "module" });
|
|
503
|
+
}
|
|
504
|
+
else if ((language === "c" || language === "cpp") && node.type === "preproc_include") {
|
|
505
|
+
const raw = node.childForFieldName("path")?.text;
|
|
506
|
+
if (raw && !raw.startsWith("<")) {
|
|
507
|
+
const module = stripQuotes(raw);
|
|
508
|
+
add({ local: lastDottedPart(module), module, kind: "module" });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
for (const child of namedChildren(node))
|
|
512
|
+
walk(child);
|
|
513
|
+
};
|
|
514
|
+
walk(root);
|
|
515
|
+
return imports;
|
|
516
|
+
}
|
|
517
|
+
function callParts(node, language) {
|
|
518
|
+
if (language === "java" && node.type === "method_invocation") {
|
|
519
|
+
const nameNode = node.childForFieldName("name");
|
|
520
|
+
const name = nameNode?.text;
|
|
521
|
+
if (!name)
|
|
522
|
+
return null;
|
|
523
|
+
const kids = namedChildren(node);
|
|
524
|
+
const qualifier = kids[0] !== nameNode && kids[0]?.type !== "argument_list" ? kids[0]?.text : undefined;
|
|
525
|
+
return { callee: name, qualifier, via: qualifier ? "qualified" : "free" };
|
|
526
|
+
}
|
|
527
|
+
if (language === "python" && node.type === "call") {
|
|
528
|
+
const fn = node.childForFieldName("function");
|
|
529
|
+
if (!fn)
|
|
530
|
+
return null;
|
|
531
|
+
if (fn.type === "identifier")
|
|
532
|
+
return { callee: fn.text, via: "free" };
|
|
533
|
+
if (fn.type === "attribute") {
|
|
534
|
+
const kids = namedChildren(fn);
|
|
535
|
+
const callee = kids[kids.length - 1]?.text;
|
|
536
|
+
const qualifier = kids.slice(0, -1).map((n) => n.text).join(".");
|
|
537
|
+
return callee ? { callee, qualifier, via: "qualified" } : null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (language === "go" && node.type === "call_expression") {
|
|
541
|
+
const fn = node.childForFieldName("function");
|
|
542
|
+
if (!fn)
|
|
543
|
+
return null;
|
|
544
|
+
if (fn.type === "identifier")
|
|
545
|
+
return { callee: fn.text, via: "free" };
|
|
546
|
+
if (fn.type === "selector_expression") {
|
|
547
|
+
const kids = namedChildren(fn);
|
|
548
|
+
const callee = kids[kids.length - 1]?.text;
|
|
549
|
+
const qualifier = kids.slice(0, -1).map((n) => n.text).join(".");
|
|
550
|
+
return callee ? { callee, qualifier, via: "qualified" } : null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (language === "rust" && node.type === "call_expression") {
|
|
554
|
+
const fn = node.childForFieldName("function");
|
|
555
|
+
if (!fn)
|
|
556
|
+
return null;
|
|
557
|
+
if (fn.type === "identifier")
|
|
558
|
+
return { callee: fn.text, via: "free" };
|
|
559
|
+
if (fn.type === "scoped_identifier") {
|
|
560
|
+
const callee = fn.childForFieldName("name")?.text;
|
|
561
|
+
const qualifier = fn.childForFieldName("path")?.text.replace(/::/g, ".");
|
|
562
|
+
return callee && qualifier ? { callee, qualifier, via: "qualified" } : null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (language === "kotlin" && node.type === "call_expression") {
|
|
566
|
+
const fn = namedChildren(node).find((n) => n.type !== "call_suffix");
|
|
567
|
+
if (!fn)
|
|
568
|
+
return null;
|
|
569
|
+
if (fn.type === "simple_identifier")
|
|
570
|
+
return { callee: fn.text, via: "free" };
|
|
571
|
+
if (fn.type === "navigation_expression") {
|
|
572
|
+
const kids = namedChildren(fn);
|
|
573
|
+
const suffix = kids[kids.length - 1];
|
|
574
|
+
const suffixKids = suffix?.type === "navigation_suffix" ? namedChildren(suffix) : [];
|
|
575
|
+
const callee = suffixKids[suffixKids.length - 1]?.text;
|
|
576
|
+
const qualifier = kids.slice(0, -1).map((n) => n.text).join(".");
|
|
577
|
+
return callee && qualifier ? { callee, qualifier, via: "qualified" } : null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (language === "php") {
|
|
581
|
+
if (node.type === "function_call_expression") {
|
|
582
|
+
const fn = node.childForFieldName("function");
|
|
583
|
+
return fn?.text ? { callee: fn.text, via: "free" } : null;
|
|
584
|
+
}
|
|
585
|
+
if (node.type === "scoped_call_expression") {
|
|
586
|
+
const qualifier = node.childForFieldName("scope")?.text;
|
|
587
|
+
const callee = node.childForFieldName("name")?.text;
|
|
588
|
+
return callee && qualifier ? { callee, qualifier, via: "qualified" } : null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (language === "csharp" && node.type === "invocation_expression") {
|
|
592
|
+
const fn = node.childForFieldName("function");
|
|
593
|
+
if (!fn)
|
|
594
|
+
return null;
|
|
595
|
+
if (fn.type === "identifier")
|
|
596
|
+
return { callee: fn.text, via: "free" };
|
|
597
|
+
if (fn.type === "member_access_expression") {
|
|
598
|
+
const kids = namedChildren(fn);
|
|
599
|
+
const callee = fn.childForFieldName("name")?.text ?? kids[kids.length - 1]?.text;
|
|
600
|
+
const qualifier = (fn.childForFieldName("expression")?.text ?? kids.slice(0, -1).map((n) => n.text).join(".")).replace(/\?$/, "");
|
|
601
|
+
return callee && qualifier ? { callee, qualifier, via: "qualified" } : null;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
const GO_TEST_FAIL_METHODS = new Set(["Error", "Errorf", "Fatal", "Fatalf", "Fail", "FailNow"]);
|
|
607
|
+
const GO_TESTIFY_ASSERT_MODULE = /^github\.com\/stretchr\/testify\/(?:v\d+\/)?(?:assert|require)$/;
|
|
608
|
+
const GO_TESTIFY_SUITE_MODULE = /^github\.com\/stretchr\/testify\/(?:v\d+\/)?suite$/;
|
|
609
|
+
const GO_ASSERT_METHODS = new Set([
|
|
610
|
+
"Contains",
|
|
611
|
+
"Equal",
|
|
612
|
+
"Error",
|
|
613
|
+
"ErrorIs",
|
|
614
|
+
"False",
|
|
615
|
+
"Len",
|
|
616
|
+
"Nil",
|
|
617
|
+
"NoError",
|
|
618
|
+
"NotContains",
|
|
619
|
+
"NotEqual",
|
|
620
|
+
"NotNil",
|
|
621
|
+
"NotZero",
|
|
622
|
+
"True",
|
|
623
|
+
"Zero"
|
|
624
|
+
]);
|
|
625
|
+
function selectorParts(node) {
|
|
626
|
+
if (node.type !== "selector_expression")
|
|
627
|
+
return null;
|
|
628
|
+
const kids = namedChildren(node);
|
|
629
|
+
const name = kids[kids.length - 1]?.text;
|
|
630
|
+
const qualifier = kids.slice(0, -1).map((n) => n.text).join(".");
|
|
631
|
+
return qualifier && name ? { qualifier, name } : null;
|
|
632
|
+
}
|
|
633
|
+
function callFunctionSelector(node) {
|
|
634
|
+
if (node.type !== "call_expression")
|
|
635
|
+
return null;
|
|
636
|
+
const fn = node.childForFieldName("function");
|
|
637
|
+
return fn ? selectorParts(fn) : null;
|
|
638
|
+
}
|
|
639
|
+
function containsIdentifier(node, names) {
|
|
640
|
+
if (!node)
|
|
641
|
+
return false;
|
|
642
|
+
if ((node.type === "identifier" || node.type === "package_identifier") && names.has(node.text))
|
|
643
|
+
return true;
|
|
644
|
+
return namedChildren(node).some((child) => containsIdentifier(child, names));
|
|
645
|
+
}
|
|
646
|
+
function goTestingParamNames(fn) {
|
|
647
|
+
const out = new Set();
|
|
648
|
+
const params = fn.childForFieldName("parameters");
|
|
649
|
+
collectGoTestingParamNames(params, out);
|
|
650
|
+
return out;
|
|
651
|
+
}
|
|
652
|
+
function collectGoTestingParamNames(params, out) {
|
|
653
|
+
if (!params)
|
|
654
|
+
return;
|
|
655
|
+
for (const child of namedChildren(params)) {
|
|
656
|
+
if (!/parameter/.test(child.type))
|
|
657
|
+
continue;
|
|
658
|
+
const type = child.childForFieldName("type")?.text ?? "";
|
|
659
|
+
if (/testing\.[TB]\b/.test(type)) {
|
|
660
|
+
const name = child.childForFieldName("name")?.text;
|
|
661
|
+
if (name)
|
|
662
|
+
out.add(name);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function goAssertLocals(imports) {
|
|
667
|
+
const out = new Set();
|
|
668
|
+
for (const i of imports) {
|
|
669
|
+
if (GO_TESTIFY_ASSERT_MODULE.test(i.module))
|
|
670
|
+
out.add(i.local);
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
function goSuiteLocals(imports) {
|
|
675
|
+
const out = new Set();
|
|
676
|
+
for (const i of imports) {
|
|
677
|
+
if (GO_TESTIFY_SUITE_MODULE.test(i.module))
|
|
678
|
+
out.add(i.local);
|
|
679
|
+
}
|
|
680
|
+
return out;
|
|
681
|
+
}
|
|
682
|
+
function goCanonicalSuiteTypes(root, suiteLocals) {
|
|
683
|
+
const out = new Set();
|
|
684
|
+
const isSuiteType = (node) => {
|
|
685
|
+
if (!node)
|
|
686
|
+
return false;
|
|
687
|
+
const text = node.text.replace(/^\*/, "");
|
|
688
|
+
return [...suiteLocals].some((local) => text === `${local}.Suite`);
|
|
689
|
+
};
|
|
690
|
+
const walk = (node) => {
|
|
691
|
+
if (node.type === "type_spec") {
|
|
692
|
+
const name = node.childForFieldName("name")?.text;
|
|
693
|
+
const typeNode = node.childForFieldName("type");
|
|
694
|
+
if (name && typeNode?.type === "struct_type") {
|
|
695
|
+
const embedsSuite = typeNode
|
|
696
|
+
.descendantsOfType("field_declaration")
|
|
697
|
+
.some((field) => Boolean(field && isSuiteType(field.childForFieldName("type"))));
|
|
698
|
+
if (embedsSuite)
|
|
699
|
+
out.add(name);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
for (const child of namedChildren(node))
|
|
703
|
+
walk(child);
|
|
704
|
+
};
|
|
705
|
+
walk(root);
|
|
706
|
+
return out;
|
|
707
|
+
}
|
|
708
|
+
function goDotAssertMethods(root) {
|
|
709
|
+
const out = new Set();
|
|
710
|
+
const walk = (node) => {
|
|
711
|
+
if (node.type === "import_spec") {
|
|
712
|
+
const raw = node.childForFieldName("path")?.text;
|
|
713
|
+
const module = raw ? stripQuotes(raw) : undefined;
|
|
714
|
+
const alias = node.childForFieldName("name")?.text;
|
|
715
|
+
if (alias === "." && module && GO_TESTIFY_ASSERT_MODULE.test(module)) {
|
|
716
|
+
for (const method of GO_ASSERT_METHODS)
|
|
717
|
+
out.add(method);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
for (const child of namedChildren(node))
|
|
721
|
+
walk(child);
|
|
722
|
+
};
|
|
723
|
+
walk(root);
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
726
|
+
function callExpressions(node) {
|
|
727
|
+
if (!node)
|
|
728
|
+
return [];
|
|
729
|
+
const out = [];
|
|
730
|
+
const walk = (cur) => {
|
|
731
|
+
if (cur.type === "call_expression")
|
|
732
|
+
out.push(cur);
|
|
733
|
+
for (const child of namedChildren(cur))
|
|
734
|
+
walk(child);
|
|
735
|
+
};
|
|
736
|
+
walk(node);
|
|
737
|
+
return out;
|
|
738
|
+
}
|
|
739
|
+
function goProductCallsIn(node) {
|
|
740
|
+
return callExpressions(node).map((call) => callParts(call, "go")).filter((p) => Boolean(p));
|
|
741
|
+
}
|
|
742
|
+
function singleGoProductCallIn(node) {
|
|
743
|
+
const calls = goProductCallsIn(node);
|
|
744
|
+
return calls.length === 1 ? calls : [];
|
|
745
|
+
}
|
|
746
|
+
function hasGoTestingFailure(node, testingParams) {
|
|
747
|
+
return callExpressions(node).some((call) => {
|
|
748
|
+
const sel = callFunctionSelector(call);
|
|
749
|
+
return Boolean(sel && testingParams.has(sel.qualifier) && GO_TEST_FAIL_METHODS.has(sel.name));
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
function goAssertionCalls(node, assertLocals, dotAssertMethods, shadowed) {
|
|
753
|
+
return callExpressions(node).filter((call) => {
|
|
754
|
+
const sel = callFunctionSelector(call);
|
|
755
|
+
if (sel)
|
|
756
|
+
return !shadowed.has(sel.qualifier) && assertLocals.has(sel.qualifier) && GO_ASSERT_METHODS.has(sel.name);
|
|
757
|
+
const fn = call.childForFieldName("function");
|
|
758
|
+
return Boolean(fn?.type === "identifier" && !shadowed.has(fn.text) && dotAssertMethods.has(fn.text));
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
function goAssertionSubject(assertion, testingParams) {
|
|
762
|
+
const sel = callFunctionSelector(assertion);
|
|
763
|
+
const name = sel?.name ?? assertion.childForFieldName("function")?.text;
|
|
764
|
+
const args = namedChildren(assertion.childForFieldName("arguments") ?? assertion).filter((n) => n.type !== "comment");
|
|
765
|
+
if (!name || args.length < 2)
|
|
766
|
+
return null;
|
|
767
|
+
if (!testingParams.has(args[0]?.text ?? ""))
|
|
768
|
+
return null;
|
|
769
|
+
if (name === "Equal" || name === "NotEqual")
|
|
770
|
+
return args[2] ?? null;
|
|
771
|
+
return args[1] ?? null;
|
|
772
|
+
}
|
|
773
|
+
function goSuiteReceiver(fn, suiteTypes) {
|
|
774
|
+
const receiver = fn.childForFieldName("receiver");
|
|
775
|
+
const param = namedChildren(receiver ?? fn).find((n) => n.type === "parameter_declaration");
|
|
776
|
+
const name = param?.childForFieldName("name")?.text;
|
|
777
|
+
const type = param?.childForFieldName("type")?.text.replace(/^\*/, "");
|
|
778
|
+
return name && type && suiteTypes.has(type) ? name : null;
|
|
779
|
+
}
|
|
780
|
+
function goSuiteAssertionCalls(node, receiver, shadowed) {
|
|
781
|
+
if (shadowed.has(receiver))
|
|
782
|
+
return [];
|
|
783
|
+
return callExpressions(node).filter((call) => {
|
|
784
|
+
const fn = call.childForFieldName("function");
|
|
785
|
+
const sel = fn ? selectorParts(fn) : null;
|
|
786
|
+
if (!sel || !GO_ASSERT_METHODS.has(sel.name))
|
|
787
|
+
return false;
|
|
788
|
+
if (sel.qualifier === receiver)
|
|
789
|
+
return true;
|
|
790
|
+
const operand = fn?.childForFieldName("operand");
|
|
791
|
+
const access = operand?.type === "call_expression" ? callFunctionSelector(operand) : null;
|
|
792
|
+
return Boolean(access && access.qualifier === receiver && (access.name === "Require" || access.name === "Assert"));
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
function goSuiteAssertionSubject(assertion) {
|
|
796
|
+
const sel = callFunctionSelector(assertion);
|
|
797
|
+
const args = namedChildren(assertion.childForFieldName("arguments") ?? assertion).filter((n) => n.type !== "comment");
|
|
798
|
+
if (!sel?.name || args.length < 1)
|
|
799
|
+
return null;
|
|
800
|
+
if (sel.name === "Equal" || sel.name === "NotEqual")
|
|
801
|
+
return args[1] ?? null;
|
|
802
|
+
return args[0] ?? null;
|
|
803
|
+
}
|
|
804
|
+
function goSubtestBody(stmt, testingParams) {
|
|
805
|
+
const call = callExpressions(stmt).find((c) => {
|
|
806
|
+
const sel = callFunctionSelector(c);
|
|
807
|
+
return Boolean(sel && testingParams.has(sel.qualifier) && sel.name === "Run");
|
|
808
|
+
});
|
|
809
|
+
if (!call)
|
|
810
|
+
return null;
|
|
811
|
+
const fn = namedChildren(call.childForFieldName("arguments") ?? call).find((n) => n.type === "func_literal");
|
|
812
|
+
const body = fn?.childForFieldName("body");
|
|
813
|
+
if (!fn || !body)
|
|
814
|
+
return null;
|
|
815
|
+
const nextTestingParams = new Set(testingParams);
|
|
816
|
+
collectGoTestingParamNames(fn.childForFieldName("parameters"), nextTestingParams);
|
|
817
|
+
return nextTestingParams.size > testingParams.size ? { body, testingParams: nextTestingParams } : null;
|
|
818
|
+
}
|
|
819
|
+
function goShortVarCalls(stmt) {
|
|
820
|
+
if (stmt.type !== "short_var_declaration")
|
|
821
|
+
return null;
|
|
822
|
+
const names = new Set();
|
|
823
|
+
collectNames(stmt.childForFieldName("left") ?? stmt.namedChild(0), names);
|
|
824
|
+
const calls = singleGoProductCallIn(stmt.childForFieldName("right") ?? namedChildren(stmt)[1] ?? null);
|
|
825
|
+
return names.size && calls.length ? { names, calls } : null;
|
|
826
|
+
}
|
|
827
|
+
function extractGoProofCalls(root, imports) {
|
|
828
|
+
const out = [];
|
|
829
|
+
const seen = new Set();
|
|
830
|
+
const assertLocals = goAssertLocals(imports);
|
|
831
|
+
const dotAssertMethods = goDotAssertMethods(root);
|
|
832
|
+
const suiteTypes = goCanonicalSuiteTypes(root, goSuiteLocals(imports));
|
|
833
|
+
const add = (testName, shadowed, assertion, calls) => {
|
|
834
|
+
for (const c of calls) {
|
|
835
|
+
const key = `${testName}|${c.qualifier ?? ""}|${c.callee}|${assertion}`;
|
|
836
|
+
if (seen.has(key))
|
|
837
|
+
continue;
|
|
838
|
+
seen.add(key);
|
|
839
|
+
out.push({ caller: testName, testName, ...c, shadowed: [...shadowed], assertion });
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
const processBlock = (block, testName, testingParams, shadowed) => {
|
|
843
|
+
for (const stmt of blockStatements(block)) {
|
|
844
|
+
for (const assertion of goAssertionCalls(stmt, assertLocals, dotAssertMethods, shadowed)) {
|
|
845
|
+
add(testName, shadowed, "assert_helper", singleGoProductCallIn(goAssertionSubject(assertion, testingParams)));
|
|
846
|
+
}
|
|
847
|
+
if (stmt.type === "if_statement" && hasGoTestingFailure(stmt.childForFieldName("consequence"), testingParams)) {
|
|
848
|
+
const condition = stmt.childForFieldName("condition");
|
|
849
|
+
add(testName, shadowed, "testing_fail", singleGoProductCallIn(condition));
|
|
850
|
+
const init = stmt.childForFieldName("initializer");
|
|
851
|
+
const initCalls = init ? goShortVarCalls(init) : null;
|
|
852
|
+
if (initCalls && containsIdentifier(condition, initCalls.names))
|
|
853
|
+
add(testName, shadowed, "testing_fail", initCalls.calls);
|
|
854
|
+
}
|
|
855
|
+
for (const child of namedChildren(stmt)) {
|
|
856
|
+
if (child.type === "block")
|
|
857
|
+
processBlock(child, testName, testingParams, shadowed);
|
|
858
|
+
}
|
|
859
|
+
const subtest = goSubtestBody(stmt, testingParams);
|
|
860
|
+
if (subtest)
|
|
861
|
+
processBlock(subtest.body, testName, subtest.testingParams, shadowed);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
const processSuiteBlock = (block, testName, receiver, shadowed) => {
|
|
865
|
+
for (const stmt of blockStatements(block)) {
|
|
866
|
+
for (const assertion of goSuiteAssertionCalls(stmt, receiver, shadowed)) {
|
|
867
|
+
add(testName, shadowed, "assert_helper", singleGoProductCallIn(goSuiteAssertionSubject(assertion)));
|
|
868
|
+
}
|
|
869
|
+
for (const child of namedChildren(stmt)) {
|
|
870
|
+
if (child.type === "block")
|
|
871
|
+
processSuiteBlock(child, testName, receiver, shadowed);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
for (const child of namedChildren(root)) {
|
|
876
|
+
if (child.type === "method_declaration") {
|
|
877
|
+
const name = functionName(child, "go");
|
|
878
|
+
if (!name || !/^Test[A-Z0-9_]/.test(name))
|
|
879
|
+
continue;
|
|
880
|
+
const receiver = goSuiteReceiver(child, suiteTypes);
|
|
881
|
+
const body = child.childForFieldName("body");
|
|
882
|
+
if (!receiver || !body)
|
|
883
|
+
continue;
|
|
884
|
+
processSuiteBlock(body, name, receiver, localBindings(child, "go"));
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
if (child.type !== "function_declaration")
|
|
888
|
+
continue;
|
|
889
|
+
const name = functionName(child, "go");
|
|
890
|
+
if (!name || !/^Test[A-Z0-9_]/.test(name))
|
|
891
|
+
continue;
|
|
892
|
+
const testingParams = goTestingParamNames(child);
|
|
893
|
+
const body = child.childForFieldName("body");
|
|
894
|
+
if (!testingParams.size || !body)
|
|
895
|
+
continue;
|
|
896
|
+
processBlock(body, name, testingParams, localBindings(child, "go"));
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
const JAVA_JUNIT_ASSERT_CLASSES = {
|
|
901
|
+
junit4: "org.junit.Assert",
|
|
902
|
+
junit5: "org.junit.jupiter.api.Assertions"
|
|
903
|
+
};
|
|
904
|
+
const JAVA_JUNIT_TEST_ANNOTATIONS = new Set(["org.junit.Test", "org.junit.jupiter.api.Test"]);
|
|
905
|
+
const JAVA_ASSERT_ACTUAL_ARG = new Set([
|
|
906
|
+
"assertArrayEquals",
|
|
907
|
+
"assertEquals",
|
|
908
|
+
"assertIterableEquals",
|
|
909
|
+
"assertLinesMatch",
|
|
910
|
+
"assertNotEquals",
|
|
911
|
+
"assertNotSame",
|
|
912
|
+
"assertSame"
|
|
913
|
+
]);
|
|
914
|
+
const JAVA_ASSERT_SUBJECT_ARG = new Set(["assertFalse", "assertNotNull", "assertNull", "assertTrue"]);
|
|
915
|
+
const JAVA_ASSERT_METHODS = new Set([...JAVA_ASSERT_ACTUAL_ARG, ...JAVA_ASSERT_SUBJECT_ARG]);
|
|
916
|
+
function addJavaAssertionImport(out, name, assertion) {
|
|
917
|
+
if (!JAVA_ASSERT_METHODS.has(name))
|
|
918
|
+
return;
|
|
919
|
+
let set = out.get(name);
|
|
920
|
+
if (!set)
|
|
921
|
+
out.set(name, (set = new Set()));
|
|
922
|
+
set.add(assertion);
|
|
923
|
+
}
|
|
924
|
+
function javaJunitAssertImports(root) {
|
|
925
|
+
const out = new Map();
|
|
926
|
+
for (const node of root.descendantsOfType("import_declaration")) {
|
|
927
|
+
if (!node)
|
|
928
|
+
continue;
|
|
929
|
+
if (!/^import\s+static\b/.test(node.text))
|
|
930
|
+
continue;
|
|
931
|
+
const spec = namedChildren(node).find((n) => n.type.endsWith("identifier"))?.text;
|
|
932
|
+
if (!spec)
|
|
933
|
+
continue;
|
|
934
|
+
const star = namedChildren(node).some((n) => n.type === "asterisk");
|
|
935
|
+
for (const [assertion, owner] of Object.entries(JAVA_JUNIT_ASSERT_CLASSES)) {
|
|
936
|
+
if (star && spec === owner) {
|
|
937
|
+
for (const method of JAVA_ASSERT_METHODS)
|
|
938
|
+
addJavaAssertionImport(out, method, assertion);
|
|
939
|
+
}
|
|
940
|
+
else if (!star && spec.startsWith(`${owner}.`)) {
|
|
941
|
+
addJavaAssertionImport(out, lastDottedPart(spec), assertion);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return out;
|
|
946
|
+
}
|
|
947
|
+
function javaJunitTestAnnotationLocals(imports) {
|
|
948
|
+
const out = new Set();
|
|
949
|
+
for (const i of imports) {
|
|
950
|
+
if (JAVA_JUNIT_TEST_ANNOTATIONS.has(i.module))
|
|
951
|
+
out.add(i.local);
|
|
952
|
+
}
|
|
953
|
+
return out;
|
|
954
|
+
}
|
|
955
|
+
function annotationName(node) {
|
|
956
|
+
if (node.type !== "marker_annotation" && node.type !== "annotation")
|
|
957
|
+
return undefined;
|
|
958
|
+
return node.childForFieldName("name")?.text ?? namedChildren(node)[0]?.text;
|
|
959
|
+
}
|
|
960
|
+
function javaAnnotationNames(method) {
|
|
961
|
+
const modifiers = namedChildren(method).find((n) => n.type === "modifiers");
|
|
962
|
+
if (!modifiers)
|
|
963
|
+
return [];
|
|
964
|
+
const out = [];
|
|
965
|
+
const walk = (node) => {
|
|
966
|
+
const name = annotationName(node);
|
|
967
|
+
if (name)
|
|
968
|
+
out.push(name);
|
|
969
|
+
for (const child of namedChildren(node))
|
|
970
|
+
walk(child);
|
|
971
|
+
};
|
|
972
|
+
walk(modifiers);
|
|
973
|
+
return out;
|
|
974
|
+
}
|
|
975
|
+
function isJavaJunitTestMethod(method, testAnnotationLocals) {
|
|
976
|
+
return javaAnnotationNames(method).some((name) => JAVA_JUNIT_TEST_ANNOTATIONS.has(name) || testAnnotationLocals.has(name));
|
|
977
|
+
}
|
|
978
|
+
function javaDeclaredMethodNames(root) {
|
|
979
|
+
const out = new Set();
|
|
980
|
+
for (const method of root.descendantsOfType("method_declaration")) {
|
|
981
|
+
if (!method)
|
|
982
|
+
continue;
|
|
983
|
+
const name = functionName(method, "java");
|
|
984
|
+
if (name)
|
|
985
|
+
out.add(name);
|
|
986
|
+
}
|
|
987
|
+
return out;
|
|
988
|
+
}
|
|
989
|
+
function javaClassInfos(root) {
|
|
990
|
+
const out = [];
|
|
991
|
+
const walk = (node) => {
|
|
992
|
+
if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "enum_declaration" || node.type === "record_declaration") {
|
|
993
|
+
const name = node.childForFieldName("name")?.text;
|
|
994
|
+
const body = node.childForFieldName("body");
|
|
995
|
+
const methods = new Set();
|
|
996
|
+
if (body) {
|
|
997
|
+
for (const child of namedChildren(body)) {
|
|
998
|
+
if (child.type !== "method_declaration")
|
|
999
|
+
continue;
|
|
1000
|
+
const method = functionName(child, "java");
|
|
1001
|
+
if (method)
|
|
1002
|
+
methods.add(method);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (name)
|
|
1006
|
+
out.push({ name, methods: [...methods].sort() });
|
|
1007
|
+
}
|
|
1008
|
+
for (const child of namedChildren(node))
|
|
1009
|
+
walk(child);
|
|
1010
|
+
};
|
|
1011
|
+
walk(root);
|
|
1012
|
+
return out;
|
|
1013
|
+
}
|
|
1014
|
+
function hasAncestorTypeBefore(node, stop, type) {
|
|
1015
|
+
let cur = node.parent;
|
|
1016
|
+
while (cur && cur.id !== stop.id) {
|
|
1017
|
+
if (cur.type === type)
|
|
1018
|
+
return true;
|
|
1019
|
+
cur = cur.parent;
|
|
1020
|
+
}
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
function javaAssertionCalls(node, assertImports, shadowed) {
|
|
1024
|
+
return node
|
|
1025
|
+
.descendantsOfType("method_invocation")
|
|
1026
|
+
.filter((call) => Boolean(call))
|
|
1027
|
+
.filter((call) => !hasAncestorTypeBefore(call, node, "lambda_expression"))
|
|
1028
|
+
.map((call) => {
|
|
1029
|
+
const name = call.childForFieldName("name")?.text;
|
|
1030
|
+
const object = call.childForFieldName("object");
|
|
1031
|
+
const assertions = name && !object && !shadowed.has(name) ? assertImports.get(name) : undefined;
|
|
1032
|
+
return assertions ? { call, assertions } : null;
|
|
1033
|
+
})
|
|
1034
|
+
.filter((v) => Boolean(v));
|
|
1035
|
+
}
|
|
1036
|
+
function javaAssertionSubject(call, assertions) {
|
|
1037
|
+
const name = call.childForFieldName("name")?.text;
|
|
1038
|
+
const args = namedChildren(call.childForFieldName("arguments") ?? call).filter((n) => n.type !== "comment");
|
|
1039
|
+
if (!name)
|
|
1040
|
+
return null;
|
|
1041
|
+
if (JAVA_ASSERT_ACTUAL_ARG.has(name)) {
|
|
1042
|
+
if (args.length === 2)
|
|
1043
|
+
return args[1] ?? null;
|
|
1044
|
+
if (args.length === 3 && assertions.size === 1 && assertions.has("junit5"))
|
|
1045
|
+
return args[1] ?? null;
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
if (JAVA_ASSERT_SUBJECT_ARG.has(name)) {
|
|
1049
|
+
if (args.length === 1)
|
|
1050
|
+
return args[0] ?? null;
|
|
1051
|
+
if (args.length === 2 && assertions.size === 1 && assertions.has("junit5"))
|
|
1052
|
+
return args[0] ?? null;
|
|
1053
|
+
}
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
function javaCallLikeCount(node) {
|
|
1057
|
+
if (!node)
|
|
1058
|
+
return 0;
|
|
1059
|
+
let count = node.type === "method_invocation" || node.type === "object_creation_expression" ? 1 : 0;
|
|
1060
|
+
for (const child of namedChildren(node))
|
|
1061
|
+
count += javaCallLikeCount(child);
|
|
1062
|
+
return count;
|
|
1063
|
+
}
|
|
1064
|
+
function simpleJavaClassName(text) {
|
|
1065
|
+
return text && /^[A-Za-z_$][\w$]*$/.test(text) ? text : null;
|
|
1066
|
+
}
|
|
1067
|
+
function javaObjectCreationClassName(node) {
|
|
1068
|
+
if (node.type !== "object_creation_expression")
|
|
1069
|
+
return null;
|
|
1070
|
+
if (namedChildren(node).some((n) => n.type === "class_body"))
|
|
1071
|
+
return null;
|
|
1072
|
+
return simpleJavaClassName(node.childForFieldName("type")?.text);
|
|
1073
|
+
}
|
|
1074
|
+
function javaDirectProofTarget(subject) {
|
|
1075
|
+
if (!subject || javaCallLikeCount(subject) !== 1)
|
|
1076
|
+
return null;
|
|
1077
|
+
if (subject.type === "object_creation_expression") {
|
|
1078
|
+
const className = javaObjectCreationClassName(subject);
|
|
1079
|
+
return className ? { className, callee: className, target_kind: "constructor" } : null;
|
|
1080
|
+
}
|
|
1081
|
+
if (subject.type !== "method_invocation")
|
|
1082
|
+
return null;
|
|
1083
|
+
const callee = subject.childForFieldName("name")?.text;
|
|
1084
|
+
const object = subject.childForFieldName("object");
|
|
1085
|
+
const className = object?.type === "identifier" ? simpleJavaClassName(object.text) : null;
|
|
1086
|
+
return callee && className ? { className, callee, target_kind: "method" } : null;
|
|
1087
|
+
}
|
|
1088
|
+
function processJavaNestedBlocks(block, testName, shadowed, processBlock) {
|
|
1089
|
+
const walk = (node) => {
|
|
1090
|
+
if (node.type === "lambda_expression")
|
|
1091
|
+
return;
|
|
1092
|
+
for (const child of namedChildren(node)) {
|
|
1093
|
+
if (child.type === "block")
|
|
1094
|
+
processBlock(child, testName, shadowed);
|
|
1095
|
+
else
|
|
1096
|
+
walk(child);
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
walk(block);
|
|
1100
|
+
}
|
|
1101
|
+
function extractJavaProofCalls(root, imports) {
|
|
1102
|
+
const out = [];
|
|
1103
|
+
const seen = new Set();
|
|
1104
|
+
const assertImports = javaJunitAssertImports(root);
|
|
1105
|
+
if (assertImports.size === 0)
|
|
1106
|
+
return out;
|
|
1107
|
+
const testAnnotationLocals = javaJunitTestAnnotationLocals(imports);
|
|
1108
|
+
const declaredMethods = javaDeclaredMethodNames(root);
|
|
1109
|
+
const add = (testName, shadowed, assertions, target) => {
|
|
1110
|
+
if (!target || shadowed.has(target.className))
|
|
1111
|
+
return;
|
|
1112
|
+
const assertion = assertions.has("junit5") ? "junit5" : "junit4";
|
|
1113
|
+
const key = `${testName}|${target.target_kind}|${target.className}|${target.callee}|${assertion}`;
|
|
1114
|
+
if (seen.has(key))
|
|
1115
|
+
return;
|
|
1116
|
+
seen.add(key);
|
|
1117
|
+
out.push({ testName, ...target, assertion, shadowed: [...shadowed] });
|
|
1118
|
+
};
|
|
1119
|
+
const processBlock = (block, testName, shadowed) => {
|
|
1120
|
+
for (const stmt of blockStatements(block)) {
|
|
1121
|
+
for (const assertion of javaAssertionCalls(stmt, assertImports, shadowed)) {
|
|
1122
|
+
add(testName, shadowed, assertion.assertions, javaDirectProofTarget(javaAssertionSubject(assertion.call, assertion.assertions)));
|
|
1123
|
+
}
|
|
1124
|
+
processJavaNestedBlocks(stmt, testName, shadowed, processBlock);
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
for (const method of root.descendantsOfType("method_declaration")) {
|
|
1128
|
+
if (!method)
|
|
1129
|
+
continue;
|
|
1130
|
+
if (!isJavaJunitTestMethod(method, testAnnotationLocals))
|
|
1131
|
+
continue;
|
|
1132
|
+
const name = functionName(method, "java");
|
|
1133
|
+
const body = method.childForFieldName("body");
|
|
1134
|
+
if (!name || !body)
|
|
1135
|
+
continue;
|
|
1136
|
+
const shadowed = localBindings(method, "java");
|
|
1137
|
+
for (const declared of declaredMethods) {
|
|
1138
|
+
shadowed.add(declared);
|
|
1139
|
+
}
|
|
1140
|
+
processBlock(body, name, shadowed);
|
|
1141
|
+
}
|
|
1142
|
+
return out;
|
|
1143
|
+
}
|
|
1144
|
+
function directPythonAssertCall(assertion) {
|
|
1145
|
+
const subject = namedChildren(assertion)[0];
|
|
1146
|
+
if (!subject)
|
|
1147
|
+
return null;
|
|
1148
|
+
const actual = subject.type === "comparison_operator" ? singlePythonComparisonCall(subject) : subject;
|
|
1149
|
+
if (actual?.type !== "call")
|
|
1150
|
+
return null;
|
|
1151
|
+
return callParts(actual, "python");
|
|
1152
|
+
}
|
|
1153
|
+
function singlePythonComparisonCall(comparison) {
|
|
1154
|
+
const calls = namedChildren(comparison).filter((child) => child.type === "call");
|
|
1155
|
+
return calls.length === 1 ? calls[0] ?? null : null;
|
|
1156
|
+
}
|
|
1157
|
+
function extractPythonProofCalls(root) {
|
|
1158
|
+
const out = [];
|
|
1159
|
+
const seen = new Set();
|
|
1160
|
+
const add = (testName, shadowed, call) => {
|
|
1161
|
+
if (!call)
|
|
1162
|
+
return;
|
|
1163
|
+
const key = `${testName}|${call.qualifier ?? ""}|${call.callee}`;
|
|
1164
|
+
if (seen.has(key))
|
|
1165
|
+
return;
|
|
1166
|
+
seen.add(key);
|
|
1167
|
+
out.push({ caller: testName, testName, ...call, shadowed: [...shadowed], assertion: "pytest_assert" });
|
|
1168
|
+
};
|
|
1169
|
+
const processBlock = (block, testName, shadowed) => {
|
|
1170
|
+
for (const stmt of blockStatements(block)) {
|
|
1171
|
+
if (stmt.type === "assert_statement")
|
|
1172
|
+
add(testName, shadowed, directPythonAssertCall(stmt));
|
|
1173
|
+
if (stmt.type === "function_definition" || stmt.type === "class_definition" || stmt.type === "lambda")
|
|
1174
|
+
continue;
|
|
1175
|
+
for (const child of namedChildren(stmt)) {
|
|
1176
|
+
if (child.type === "block")
|
|
1177
|
+
processBlock(child, testName, shadowed);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
const walk = (node, insideFunction) => {
|
|
1182
|
+
if (node.type === "function_definition") {
|
|
1183
|
+
const name = functionName(node, "python");
|
|
1184
|
+
const body = node.childForFieldName("body");
|
|
1185
|
+
if (name && /^test_/.test(name) && body)
|
|
1186
|
+
processBlock(body, name, localBindings(node, "python"));
|
|
1187
|
+
if (insideFunction)
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
for (const child of namedChildren(node))
|
|
1191
|
+
walk(child, insideFunction || node.type === "function_definition");
|
|
1192
|
+
};
|
|
1193
|
+
walk(root, false);
|
|
1194
|
+
return out;
|
|
1195
|
+
}
|
|
1196
|
+
export function extractTreeSitterStructure(content, language) {
|
|
1197
|
+
const parser = parserFor(language);
|
|
1198
|
+
if (!parser)
|
|
1199
|
+
return { imports: [], calls: [] };
|
|
1200
|
+
const tree = parser.parse(content);
|
|
1201
|
+
if (!tree)
|
|
1202
|
+
return { imports: [], calls: [] };
|
|
1203
|
+
const root = tree.rootNode;
|
|
1204
|
+
const calls = [];
|
|
1205
|
+
const isCallableNode = (node) => (language === "java" && node.type === "method_declaration") ||
|
|
1206
|
+
(language === "python" && node.type === "function_definition") ||
|
|
1207
|
+
(language === "go" && (node.type === "function_declaration" || node.type === "method_declaration")) ||
|
|
1208
|
+
(language === "kotlin" && node.type === "function_declaration") ||
|
|
1209
|
+
(language === "rust" && node.type === "function_item") ||
|
|
1210
|
+
(language === "php" && (node.type === "function_definition" || node.type === "method_declaration")) ||
|
|
1211
|
+
(language === "csharp" && node.type === "method_declaration");
|
|
1212
|
+
const visit = (node, caller, shadowed, insideFunction) => {
|
|
1213
|
+
let nextCaller = caller;
|
|
1214
|
+
let nextShadowed = shadowed;
|
|
1215
|
+
let nextInsideFunction = insideFunction;
|
|
1216
|
+
if (isCallableNode(node)) {
|
|
1217
|
+
if (insideFunction) {
|
|
1218
|
+
nextCaller = null; // nested function body: calls belong to an un-emitted local symbol
|
|
1219
|
+
nextShadowed = new Set();
|
|
1220
|
+
nextInsideFunction = true;
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
const name = functionName(node, language);
|
|
1224
|
+
if (name) {
|
|
1225
|
+
nextCaller = name;
|
|
1226
|
+
nextShadowed = localBindings(node, language);
|
|
1227
|
+
nextInsideFunction = true;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
const parts = callParts(node, language);
|
|
1232
|
+
if (nextCaller && parts) {
|
|
1233
|
+
calls.push({ caller: nextCaller, ...parts, shadowed: [...nextShadowed] });
|
|
1234
|
+
}
|
|
1235
|
+
for (const child of namedChildren(node))
|
|
1236
|
+
visit(child, nextCaller, nextShadowed, nextInsideFunction);
|
|
1237
|
+
};
|
|
1238
|
+
visit(root, null, new Set(), false);
|
|
1239
|
+
const imports = extractImports(root, language);
|
|
1240
|
+
const result = {
|
|
1241
|
+
...(language === "java" ? { packageName: javaPackage(root), javaClasses: javaClassInfos(root), javaProofCalls: extractJavaProofCalls(root, imports) } : {}),
|
|
1242
|
+
...(language === "go" ? { packageName: goPackage(root) } : {}),
|
|
1243
|
+
...(language === "kotlin" ? { packageName: kotlinPackage(root), topLevelSymbols: kotlinTopLevelSymbols(root) } : {}),
|
|
1244
|
+
...(language === "php" ? { moduleName: phpNamespace(root) } : {}),
|
|
1245
|
+
...(language === "csharp" ? { moduleName: csharpNamespace(root) } : {}),
|
|
1246
|
+
imports,
|
|
1247
|
+
calls,
|
|
1248
|
+
...(language === "go" ? { goProofCalls: extractGoProofCalls(root, imports) } : {}),
|
|
1249
|
+
...(language === "python" ? { pythonProofCalls: extractPythonProofCalls(root) } : {})
|
|
1250
|
+
};
|
|
1251
|
+
tree.delete();
|
|
1252
|
+
return result;
|
|
1253
|
+
}
|