circle-ir 3.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 +15 -0
- package/README.md +200 -0
- package/configs/sinks/code_injection.yaml +672 -0
- package/configs/sinks/command.yaml +917 -0
- package/configs/sinks/deserialization.yaml +105 -0
- package/configs/sinks/ldap.yaml +136 -0
- package/configs/sinks/nodejs.json +629 -0
- package/configs/sinks/path.yaml +715 -0
- package/configs/sinks/python.json +501 -0
- package/configs/sinks/rust.json +339 -0
- package/configs/sinks/sql.yaml +233 -0
- package/configs/sinks/ssrf.yaml +160 -0
- package/configs/sinks/xpath.yaml +121 -0
- package/configs/sinks/xss.yaml +727 -0
- package/configs/sources/db_sources.yaml +90 -0
- package/configs/sources/env_sources.yaml +94 -0
- package/configs/sources/express.json +197 -0
- package/configs/sources/file_sources.yaml +164 -0
- package/configs/sources/http_sources.yaml +379 -0
- package/configs/sources/io_sources.yaml +519 -0
- package/configs/sources/network_sources.yaml +99 -0
- package/configs/sources/python.json +230 -0
- package/configs/sources/rust.json +286 -0
- package/configs/sources/spring.yaml +70 -0
- package/dist/analysis/advisory-db.d.ts +86 -0
- package/dist/analysis/advisory-db.js +104 -0
- package/dist/analysis/advisory-db.js.map +1 -0
- package/dist/analysis/cargo-parser.d.ts +42 -0
- package/dist/analysis/cargo-parser.js +102 -0
- package/dist/analysis/cargo-parser.js.map +1 -0
- package/dist/analysis/config-loader.d.ts +37 -0
- package/dist/analysis/config-loader.js +1561 -0
- package/dist/analysis/config-loader.js.map +1 -0
- package/dist/analysis/constant-propagation/ast-utils.d.ts +25 -0
- package/dist/analysis/constant-propagation/ast-utils.js +34 -0
- package/dist/analysis/constant-propagation/ast-utils.js.map +1 -0
- package/dist/analysis/constant-propagation/evaluator.d.ts +32 -0
- package/dist/analysis/constant-propagation/evaluator.js +296 -0
- package/dist/analysis/constant-propagation/evaluator.js.map +1 -0
- package/dist/analysis/constant-propagation/index.d.ts +62 -0
- package/dist/analysis/constant-propagation/index.js +152 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -0
- package/dist/analysis/constant-propagation/patterns.d.ts +8 -0
- package/dist/analysis/constant-propagation/patterns.js +126 -0
- package/dist/analysis/constant-propagation/patterns.js.map +1 -0
- package/dist/analysis/constant-propagation/propagator.d.ts +180 -0
- package/dist/analysis/constant-propagation/propagator.js +1985 -0
- package/dist/analysis/constant-propagation/propagator.js.map +1 -0
- package/dist/analysis/constant-propagation/types.d.ts +63 -0
- package/dist/analysis/constant-propagation/types.js +5 -0
- package/dist/analysis/constant-propagation/types.js.map +1 -0
- package/dist/analysis/constant-propagation.d.ts +9 -0
- package/dist/analysis/constant-propagation.js +18 -0
- package/dist/analysis/constant-propagation.js.map +1 -0
- package/dist/analysis/dependency-scanner.d.ts +79 -0
- package/dist/analysis/dependency-scanner.js +122 -0
- package/dist/analysis/dependency-scanner.js.map +1 -0
- package/dist/analysis/dfg-verifier.d.ts +116 -0
- package/dist/analysis/dfg-verifier.js +399 -0
- package/dist/analysis/dfg-verifier.js.map +1 -0
- package/dist/analysis/findings.d.ts +11 -0
- package/dist/analysis/findings.js +228 -0
- package/dist/analysis/findings.js.map +1 -0
- package/dist/analysis/index.d.ts +16 -0
- package/dist/analysis/index.js +18 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/interprocedural.d.ts +99 -0
- package/dist/analysis/interprocedural.js +526 -0
- package/dist/analysis/interprocedural.js.map +1 -0
- package/dist/analysis/path-finder.d.ts +133 -0
- package/dist/analysis/path-finder.js +354 -0
- package/dist/analysis/path-finder.js.map +1 -0
- package/dist/analysis/rules.d.ts +75 -0
- package/dist/analysis/rules.js +332 -0
- package/dist/analysis/rules.js.map +1 -0
- package/dist/analysis/semver.d.ts +27 -0
- package/dist/analysis/semver.js +127 -0
- package/dist/analysis/semver.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +15 -0
- package/dist/analysis/taint-matcher.js +634 -0
- package/dist/analysis/taint-matcher.js.map +1 -0
- package/dist/analysis/taint-propagation.d.ts +67 -0
- package/dist/analysis/taint-propagation.js +298 -0
- package/dist/analysis/taint-propagation.js.map +1 -0
- package/dist/analysis/unresolved.d.ts +14 -0
- package/dist/analysis/unresolved.js +202 -0
- package/dist/analysis/unresolved.js.map +1 -0
- package/dist/analyzer.d.ts +43 -0
- package/dist/analyzer.js +1010 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/browser/circle-ir.js +16576 -0
- package/dist/browser.d.ts +38 -0
- package/dist/browser.js +38 -0
- package/dist/browser.js.map +1 -0
- package/dist/core/circle-ir-core.cjs +13626 -0
- package/dist/core/circle-ir-core.d.ts +59 -0
- package/dist/core/circle-ir-core.js +13591 -0
- package/dist/core/extractors/calls.d.ts +13 -0
- package/dist/core/extractors/calls.js +1429 -0
- package/dist/core/extractors/calls.js.map +1 -0
- package/dist/core/extractors/cfg.d.ts +9 -0
- package/dist/core/extractors/cfg.js +519 -0
- package/dist/core/extractors/cfg.js.map +1 -0
- package/dist/core/extractors/dfg.d.ts +12 -0
- package/dist/core/extractors/dfg.js +1081 -0
- package/dist/core/extractors/dfg.js.map +1 -0
- package/dist/core/extractors/exports.d.ts +14 -0
- package/dist/core/extractors/exports.js +80 -0
- package/dist/core/extractors/exports.js.map +1 -0
- package/dist/core/extractors/imports.d.ts +9 -0
- package/dist/core/extractors/imports.js +739 -0
- package/dist/core/extractors/imports.js.map +1 -0
- package/dist/core/extractors/index.d.ts +10 -0
- package/dist/core/extractors/index.js +11 -0
- package/dist/core/extractors/index.js.map +1 -0
- package/dist/core/extractors/meta.d.ts +10 -0
- package/dist/core/extractors/meta.js +109 -0
- package/dist/core/extractors/meta.js.map +1 -0
- package/dist/core/extractors/types.d.ts +10 -0
- package/dist/core/extractors/types.js +1479 -0
- package/dist/core/extractors/types.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/parser.d.ts +84 -0
- package/dist/core/parser.js +250 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core-lib.d.ts +59 -0
- package/dist/core-lib.js +62 -0
- package/dist/core-lib.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +14 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/plugins/base.d.ts +44 -0
- package/dist/languages/plugins/base.js +82 -0
- package/dist/languages/plugins/base.js.map +1 -0
- package/dist/languages/plugins/index.d.ts +14 -0
- package/dist/languages/plugins/index.js +25 -0
- package/dist/languages/plugins/index.js.map +1 -0
- package/dist/languages/plugins/java.d.ts +49 -0
- package/dist/languages/plugins/java.js +402 -0
- package/dist/languages/plugins/java.js.map +1 -0
- package/dist/languages/plugins/javascript.d.ts +48 -0
- package/dist/languages/plugins/javascript.js +445 -0
- package/dist/languages/plugins/javascript.js.map +1 -0
- package/dist/languages/plugins/python.d.ts +47 -0
- package/dist/languages/plugins/python.js +480 -0
- package/dist/languages/plugins/python.js.map +1 -0
- package/dist/languages/plugins/rust.d.ts +47 -0
- package/dist/languages/plugins/rust.js +405 -0
- package/dist/languages/plugins/rust.js.map +1 -0
- package/dist/languages/registry.d.ts +30 -0
- package/dist/languages/registry.js +80 -0
- package/dist/languages/registry.js.map +1 -0
- package/dist/languages/types.d.ts +184 -0
- package/dist/languages/types.js +8 -0
- package/dist/languages/types.js.map +1 -0
- package/dist/resolution/cross-file.d.ts +146 -0
- package/dist/resolution/cross-file.js +439 -0
- package/dist/resolution/cross-file.js.map +1 -0
- package/dist/resolution/index.d.ts +12 -0
- package/dist/resolution/index.js +10 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/symbol-table.d.ts +136 -0
- package/dist/resolution/symbol-table.js +336 -0
- package/dist/resolution/symbol-table.js.map +1 -0
- package/dist/resolution/type-hierarchy.d.ts +124 -0
- package/dist/resolution/type-hierarchy.js +515 -0
- package/dist/resolution/type-hierarchy.js.map +1 -0
- package/dist/types/config.d.ts +45 -0
- package/dist/types/config.js +5 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +392 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +85 -0
- package/dist/utils/logger.js +198 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/docs/SPEC.md +1021 -0
- package/examples/browser-example.html +610 -0
- package/examples/node-example.ts +215 -0
- package/package.json +107 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
|
@@ -0,0 +1,1429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call extractor - extracts method invocations
|
|
3
|
+
*/
|
|
4
|
+
import { findNodes, findAncestor, getNodeText, getNodesFromCache } from '../parser.js';
|
|
5
|
+
/**
|
|
6
|
+
* Detect if the tree is JavaScript/TypeScript based on specific node patterns.
|
|
7
|
+
* We check for the presence of call_expression nodes (JS) vs method_invocation (Java).
|
|
8
|
+
*/
|
|
9
|
+
function detectLanguageFromTree(tree, cache) {
|
|
10
|
+
// Check for Rust-specific nodes
|
|
11
|
+
const rustStructs = getNodesFromCache(tree.rootNode, 'struct_item', cache);
|
|
12
|
+
const rustImpls = getNodesFromCache(tree.rootNode, 'impl_item', cache);
|
|
13
|
+
const rustFunctions = getNodesFromCache(tree.rootNode, 'function_item', cache);
|
|
14
|
+
const rustUseDecls = getNodesFromCache(tree.rootNode, 'use_declaration', cache);
|
|
15
|
+
// Check for Python-specific nodes
|
|
16
|
+
const pythonCalls = getNodesFromCache(tree.rootNode, 'call', cache);
|
|
17
|
+
const pythonClasses = getNodesFromCache(tree.rootNode, 'class_definition', cache);
|
|
18
|
+
const pythonFunctions = getNodesFromCache(tree.rootNode, 'function_definition', cache);
|
|
19
|
+
// Check for JavaScript-specific nodes
|
|
20
|
+
const callExpressions = getNodesFromCache(tree.rootNode, 'call_expression', cache);
|
|
21
|
+
const arrowFunctions = getNodesFromCache(tree.rootNode, 'arrow_function', cache);
|
|
22
|
+
// Check for Java-specific nodes
|
|
23
|
+
const methodInvocations = getNodesFromCache(tree.rootNode, 'method_invocation', cache);
|
|
24
|
+
const classDeclarations = getNodesFromCache(tree.rootNode, 'class_declaration', cache);
|
|
25
|
+
// Count indicators for each language
|
|
26
|
+
const rustIndicators = rustStructs.length + rustImpls.length + rustFunctions.length + rustUseDecls.length;
|
|
27
|
+
const pythonIndicators = pythonCalls.length + pythonClasses.length + pythonFunctions.length;
|
|
28
|
+
const jsIndicators = callExpressions.length + arrowFunctions.length;
|
|
29
|
+
const javaIndicators = methodInvocations.length + classDeclarations.length;
|
|
30
|
+
// Rust detection: has Rust-specific nodes
|
|
31
|
+
if (rustIndicators > 0 && (rustStructs.length > 0 || rustImpls.length > 0 || rustFunctions.length > 0)) {
|
|
32
|
+
return 'rust';
|
|
33
|
+
}
|
|
34
|
+
// Python detection: has 'call' nodes (not 'call_expression') and python class/function defs
|
|
35
|
+
if (pythonCalls.length > 0 && (pythonClasses.length > 0 || pythonFunctions.length > 0) && methodInvocations.length === 0 && callExpressions.length === 0) {
|
|
36
|
+
return 'python';
|
|
37
|
+
}
|
|
38
|
+
// If we have Java indicators and no/fewer JS indicators, it's Java
|
|
39
|
+
// Java class declarations are a strong indicator
|
|
40
|
+
if (classDeclarations.length > 0 && methodInvocations.length > 0) {
|
|
41
|
+
return 'java';
|
|
42
|
+
}
|
|
43
|
+
// If we have call_expression but no method_invocation, it's JavaScript
|
|
44
|
+
if (callExpressions.length > 0 && methodInvocations.length === 0) {
|
|
45
|
+
return 'javascript';
|
|
46
|
+
}
|
|
47
|
+
// Default to Java for backwards compatibility
|
|
48
|
+
return javaIndicators >= jsIndicators ? 'java' : 'javascript';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract all method calls from the tree.
|
|
52
|
+
* @param tree The parsed AST tree
|
|
53
|
+
* @param cache Optional node cache for performance
|
|
54
|
+
* @param language Optional language hint ('java' | 'javascript' | 'typescript' | 'python' | 'rust')
|
|
55
|
+
*/
|
|
56
|
+
export function extractCalls(tree, cache, language) {
|
|
57
|
+
const calls = [];
|
|
58
|
+
// Use language hint if provided, otherwise detect from tree
|
|
59
|
+
const detectedLanguage = language ?? detectLanguageFromTree(tree, cache);
|
|
60
|
+
const isJavaScript = detectedLanguage === 'javascript' || detectedLanguage === 'typescript';
|
|
61
|
+
const isPython = detectedLanguage === 'python';
|
|
62
|
+
const isRust = detectedLanguage === 'rust';
|
|
63
|
+
if (isRust) {
|
|
64
|
+
return extractRustCalls(tree, cache);
|
|
65
|
+
}
|
|
66
|
+
if (isPython) {
|
|
67
|
+
return extractPythonCalls(tree, cache);
|
|
68
|
+
}
|
|
69
|
+
if (isJavaScript) {
|
|
70
|
+
return extractJavaScriptCalls(tree, cache);
|
|
71
|
+
}
|
|
72
|
+
// Build resolution context for Java
|
|
73
|
+
const context = buildResolutionContext(tree, cache);
|
|
74
|
+
// Find all method invocations
|
|
75
|
+
const invocations = getNodesFromCache(tree.rootNode, 'method_invocation', cache);
|
|
76
|
+
for (const inv of invocations) {
|
|
77
|
+
calls.push(extractCallInfo(inv, context));
|
|
78
|
+
}
|
|
79
|
+
// Find object creation expressions (constructor calls)
|
|
80
|
+
const objectCreations = getNodesFromCache(tree.rootNode, 'object_creation_expression', cache);
|
|
81
|
+
for (const creation of objectCreations) {
|
|
82
|
+
const callInfo = extractObjectCreation(creation, context);
|
|
83
|
+
if (callInfo) {
|
|
84
|
+
calls.push(callInfo);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return calls;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extract all function/method calls from a JavaScript/TypeScript tree.
|
|
91
|
+
*/
|
|
92
|
+
function extractJavaScriptCalls(tree, cache) {
|
|
93
|
+
const calls = [];
|
|
94
|
+
// Build JS resolution context
|
|
95
|
+
const context = buildJSResolutionContext(tree, cache);
|
|
96
|
+
// Find all call expressions (function/method calls)
|
|
97
|
+
const callExpressions = getNodesFromCache(tree.rootNode, 'call_expression', cache);
|
|
98
|
+
for (const call of callExpressions) {
|
|
99
|
+
const callInfo = extractJSCallInfo(call, context);
|
|
100
|
+
if (callInfo) {
|
|
101
|
+
calls.push(callInfo);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Find new expressions (constructor calls)
|
|
105
|
+
const newExpressions = getNodesFromCache(tree.rootNode, 'new_expression', cache);
|
|
106
|
+
for (const newExpr of newExpressions) {
|
|
107
|
+
const callInfo = extractJSNewExpression(newExpr, context);
|
|
108
|
+
if (callInfo) {
|
|
109
|
+
calls.push(callInfo);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return calls;
|
|
113
|
+
}
|
|
114
|
+
function buildJSResolutionContext(tree, cache) {
|
|
115
|
+
const context = {
|
|
116
|
+
functionNames: new Set(),
|
|
117
|
+
variableTypes: new Map(),
|
|
118
|
+
imports: new Map(),
|
|
119
|
+
};
|
|
120
|
+
// Collect function names
|
|
121
|
+
const functions = getNodesFromCache(tree.rootNode, 'function_declaration', cache);
|
|
122
|
+
for (const func of functions) {
|
|
123
|
+
const nameNode = func.childForFieldName('name');
|
|
124
|
+
if (nameNode) {
|
|
125
|
+
context.functionNames.add(getNodeText(nameNode));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Collect arrow functions assigned to variables
|
|
129
|
+
const varDecls = getNodesFromCache(tree.rootNode, 'variable_declaration', cache);
|
|
130
|
+
const lexDecls = getNodesFromCache(tree.rootNode, 'lexical_declaration', cache);
|
|
131
|
+
for (const decl of [...varDecls, ...lexDecls]) {
|
|
132
|
+
const declarators = findNodes(decl, 'variable_declarator');
|
|
133
|
+
for (const declarator of declarators) {
|
|
134
|
+
const nameNode = declarator.childForFieldName('name');
|
|
135
|
+
const valueNode = declarator.childForFieldName('value');
|
|
136
|
+
if (nameNode && valueNode) {
|
|
137
|
+
const name = getNodeText(nameNode);
|
|
138
|
+
if (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression') {
|
|
139
|
+
context.functionNames.add(name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Collect imports
|
|
145
|
+
const imports = getNodesFromCache(tree.rootNode, 'import_statement', cache);
|
|
146
|
+
for (const imp of imports) {
|
|
147
|
+
const text = getNodeText(imp);
|
|
148
|
+
// Simple pattern matching for common import formats
|
|
149
|
+
// import x from 'module' or const x = require('module')
|
|
150
|
+
const fromMatch = text.match(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
151
|
+
if (fromMatch) {
|
|
152
|
+
context.imports.set(fromMatch[1], fromMatch[2]);
|
|
153
|
+
}
|
|
154
|
+
const destructMatch = text.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
|
|
155
|
+
if (destructMatch) {
|
|
156
|
+
const names = destructMatch[1].split(',').map(n => n.trim().split(/\s+as\s+/).pop().trim());
|
|
157
|
+
for (const name of names) {
|
|
158
|
+
context.imports.set(name, destructMatch[2]);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return context;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Extract call information from a JS call_expression node.
|
|
166
|
+
*/
|
|
167
|
+
function extractJSCallInfo(node, context) {
|
|
168
|
+
const functionNode = node.childForFieldName('function');
|
|
169
|
+
if (!functionNode)
|
|
170
|
+
return null;
|
|
171
|
+
let methodName;
|
|
172
|
+
let receiver = null;
|
|
173
|
+
if (functionNode.type === 'member_expression') {
|
|
174
|
+
// Method call: obj.method() or obj.prop.method()
|
|
175
|
+
const objectNode = functionNode.childForFieldName('object');
|
|
176
|
+
const propertyNode = functionNode.childForFieldName('property');
|
|
177
|
+
if (!propertyNode)
|
|
178
|
+
return null;
|
|
179
|
+
methodName = getNodeText(propertyNode);
|
|
180
|
+
receiver = objectNode ? getNodeText(objectNode) : null;
|
|
181
|
+
}
|
|
182
|
+
else if (functionNode.type === 'identifier') {
|
|
183
|
+
// Direct function call: func()
|
|
184
|
+
methodName = getNodeText(functionNode);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// Complex expression like (getFunc())()
|
|
188
|
+
methodName = getNodeText(functionNode);
|
|
189
|
+
}
|
|
190
|
+
// Get arguments
|
|
191
|
+
const argsNode = node.childForFieldName('arguments');
|
|
192
|
+
const args = argsNode ? extractJSArguments(argsNode) : [];
|
|
193
|
+
// Find enclosing function
|
|
194
|
+
const enclosingFunc = findJSEnclosingFunction(node);
|
|
195
|
+
// Resolve the call
|
|
196
|
+
const { resolved, resolution } = resolveJSCall(methodName, receiver, context);
|
|
197
|
+
return {
|
|
198
|
+
method_name: methodName,
|
|
199
|
+
receiver,
|
|
200
|
+
arguments: args,
|
|
201
|
+
location: {
|
|
202
|
+
line: node.startPosition.row + 1,
|
|
203
|
+
column: node.startPosition.column,
|
|
204
|
+
},
|
|
205
|
+
in_method: enclosingFunc,
|
|
206
|
+
resolved,
|
|
207
|
+
resolution,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extract call information from a JS new_expression (constructor call).
|
|
212
|
+
*/
|
|
213
|
+
function extractJSNewExpression(node, context) {
|
|
214
|
+
const constructorNode = node.childForFieldName('constructor');
|
|
215
|
+
if (!constructorNode)
|
|
216
|
+
return null;
|
|
217
|
+
const typeName = getNodeText(constructorNode);
|
|
218
|
+
// Get arguments
|
|
219
|
+
const argsNode = node.childForFieldName('arguments');
|
|
220
|
+
const args = argsNode ? extractJSArguments(argsNode) : [];
|
|
221
|
+
// Find enclosing function
|
|
222
|
+
const enclosingFunc = findJSEnclosingFunction(node);
|
|
223
|
+
return {
|
|
224
|
+
method_name: typeName,
|
|
225
|
+
receiver: null,
|
|
226
|
+
arguments: args,
|
|
227
|
+
location: {
|
|
228
|
+
line: node.startPosition.row + 1,
|
|
229
|
+
column: node.startPosition.column,
|
|
230
|
+
},
|
|
231
|
+
in_method: enclosingFunc,
|
|
232
|
+
resolved: true,
|
|
233
|
+
resolution: {
|
|
234
|
+
status: 'resolved',
|
|
235
|
+
target: `${typeName}.<init>`,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Extract arguments from a JS arguments node.
|
|
241
|
+
*/
|
|
242
|
+
function extractJSArguments(argsNode) {
|
|
243
|
+
const args = [];
|
|
244
|
+
let position = 0;
|
|
245
|
+
for (let i = 0; i < argsNode.childCount; i++) {
|
|
246
|
+
const child = argsNode.child(i);
|
|
247
|
+
if (!child)
|
|
248
|
+
continue;
|
|
249
|
+
// Skip parentheses and commas
|
|
250
|
+
if (child.type === '(' || child.type === ')' || child.type === ',') {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const expression = getNodeText(child);
|
|
254
|
+
const { variable, literal } = analyzeJSArgument(child);
|
|
255
|
+
args.push({
|
|
256
|
+
position,
|
|
257
|
+
expression,
|
|
258
|
+
variable,
|
|
259
|
+
literal,
|
|
260
|
+
});
|
|
261
|
+
position++;
|
|
262
|
+
}
|
|
263
|
+
return args;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Analyze a JS argument to extract variable name or literal value.
|
|
267
|
+
*/
|
|
268
|
+
function analyzeJSArgument(node) {
|
|
269
|
+
// Check if it's a simple identifier (variable reference)
|
|
270
|
+
if (node.type === 'identifier') {
|
|
271
|
+
return { variable: getNodeText(node), literal: null };
|
|
272
|
+
}
|
|
273
|
+
// Check if it's a member expression (e.g., req.params.id)
|
|
274
|
+
if (node.type === 'member_expression') {
|
|
275
|
+
return { variable: getNodeText(node), literal: null };
|
|
276
|
+
}
|
|
277
|
+
// Check if it's a literal
|
|
278
|
+
if (isJSLiteral(node)) {
|
|
279
|
+
return { variable: null, literal: extractJSLiteralValue(node) };
|
|
280
|
+
}
|
|
281
|
+
// For complex expressions, try to find the primary variable
|
|
282
|
+
const identifier = findPrimaryIdentifier(node);
|
|
283
|
+
if (identifier) {
|
|
284
|
+
return { variable: identifier, literal: null };
|
|
285
|
+
}
|
|
286
|
+
return { variable: null, literal: null };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Check if a node represents a JS literal value.
|
|
290
|
+
*/
|
|
291
|
+
function isJSLiteral(node) {
|
|
292
|
+
const literalTypes = new Set([
|
|
293
|
+
'string',
|
|
294
|
+
'template_string',
|
|
295
|
+
'number',
|
|
296
|
+
'true',
|
|
297
|
+
'false',
|
|
298
|
+
'null',
|
|
299
|
+
'undefined',
|
|
300
|
+
]);
|
|
301
|
+
return literalTypes.has(node.type);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Extract the value from a JS literal node.
|
|
305
|
+
*/
|
|
306
|
+
function extractJSLiteralValue(node) {
|
|
307
|
+
const text = getNodeText(node);
|
|
308
|
+
// Remove quotes from string literals
|
|
309
|
+
if (node.type === 'string') {
|
|
310
|
+
return text.slice(1, -1);
|
|
311
|
+
}
|
|
312
|
+
return text;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Find the name of the enclosing function in JS.
|
|
316
|
+
*/
|
|
317
|
+
function findJSEnclosingFunction(node) {
|
|
318
|
+
let current = node.parent;
|
|
319
|
+
while (current) {
|
|
320
|
+
if (current.type === 'function_declaration') {
|
|
321
|
+
const nameNode = current.childForFieldName('name');
|
|
322
|
+
if (nameNode) {
|
|
323
|
+
return getNodeText(nameNode);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (current.type === 'method_definition') {
|
|
327
|
+
const nameNode = current.childForFieldName('name');
|
|
328
|
+
if (nameNode) {
|
|
329
|
+
return getNodeText(nameNode);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (current.type === 'variable_declarator') {
|
|
333
|
+
// Arrow function assigned to variable
|
|
334
|
+
const valueNode = current.childForFieldName('value');
|
|
335
|
+
if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
|
|
336
|
+
const nameNode = current.childForFieldName('name');
|
|
337
|
+
if (nameNode) {
|
|
338
|
+
return getNodeText(nameNode);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Express route handler pattern: app.get('/path', (req, res) => { ... })
|
|
343
|
+
if (current.type === 'arrow_function' || current.type === 'function_expression') {
|
|
344
|
+
const parent = current.parent;
|
|
345
|
+
if (parent?.type === 'arguments') {
|
|
346
|
+
const callExpr = parent.parent;
|
|
347
|
+
if (callExpr?.type === 'call_expression') {
|
|
348
|
+
const funcNode = callExpr.childForFieldName('function');
|
|
349
|
+
if (funcNode?.type === 'member_expression') {
|
|
350
|
+
const propNode = funcNode.childForFieldName('property');
|
|
351
|
+
if (propNode) {
|
|
352
|
+
// Return route handler name like "get" or "post"
|
|
353
|
+
return getNodeText(propNode) + '_handler';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
current = current.parent;
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Resolve a JS function/method call to determine its target.
|
|
365
|
+
*/
|
|
366
|
+
function resolveJSCall(methodName, receiver, context) {
|
|
367
|
+
// No receiver - might be a local function or global
|
|
368
|
+
if (!receiver) {
|
|
369
|
+
if (context.functionNames.has(methodName)) {
|
|
370
|
+
return {
|
|
371
|
+
resolved: true,
|
|
372
|
+
resolution: {
|
|
373
|
+
status: 'resolved',
|
|
374
|
+
target: methodName,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
// Built-in globals
|
|
379
|
+
const builtins = new Set(['eval', 'setTimeout', 'setInterval', 'fetch', 'require']);
|
|
380
|
+
if (builtins.has(methodName)) {
|
|
381
|
+
return {
|
|
382
|
+
resolved: true,
|
|
383
|
+
resolution: {
|
|
384
|
+
status: 'resolved',
|
|
385
|
+
target: methodName,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
resolved: false,
|
|
391
|
+
resolution: {
|
|
392
|
+
status: 'external_method',
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
// Check common JS/Node.js patterns
|
|
397
|
+
const jsTypeMappings = inferJSTypeFromReceiver(receiver);
|
|
398
|
+
if (jsTypeMappings) {
|
|
399
|
+
return {
|
|
400
|
+
resolved: true,
|
|
401
|
+
resolution: {
|
|
402
|
+
status: 'resolved',
|
|
403
|
+
target: `${jsTypeMappings}.${methodName}`,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
// Check if receiver is an imported module
|
|
408
|
+
const baseReceiver = receiver.split('.')[0];
|
|
409
|
+
const moduleName = context.imports.get(baseReceiver);
|
|
410
|
+
if (moduleName) {
|
|
411
|
+
return {
|
|
412
|
+
resolved: true,
|
|
413
|
+
resolution: {
|
|
414
|
+
status: 'resolved',
|
|
415
|
+
target: `${moduleName}.${methodName}`,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
resolved: false,
|
|
421
|
+
resolution: {
|
|
422
|
+
status: 'external_method',
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Infer type from JS receiver name patterns.
|
|
428
|
+
*/
|
|
429
|
+
function inferJSTypeFromReceiver(receiver) {
|
|
430
|
+
const patterns = {
|
|
431
|
+
// Express
|
|
432
|
+
req: 'Request',
|
|
433
|
+
request: 'Request',
|
|
434
|
+
res: 'Response',
|
|
435
|
+
response: 'Response',
|
|
436
|
+
app: 'Express',
|
|
437
|
+
router: 'Router',
|
|
438
|
+
// Node.js built-ins
|
|
439
|
+
fs: 'fs',
|
|
440
|
+
path: 'path',
|
|
441
|
+
http: 'http',
|
|
442
|
+
https: 'https',
|
|
443
|
+
child_process: 'child_process',
|
|
444
|
+
crypto: 'crypto',
|
|
445
|
+
os: 'os',
|
|
446
|
+
// Database
|
|
447
|
+
db: 'Connection',
|
|
448
|
+
connection: 'Connection',
|
|
449
|
+
pool: 'Pool',
|
|
450
|
+
client: 'Client',
|
|
451
|
+
// Common patterns
|
|
452
|
+
console: 'console',
|
|
453
|
+
process: 'process',
|
|
454
|
+
Buffer: 'Buffer',
|
|
455
|
+
JSON: 'JSON',
|
|
456
|
+
Math: 'Math',
|
|
457
|
+
Object: 'Object',
|
|
458
|
+
Array: 'Array',
|
|
459
|
+
String: 'String',
|
|
460
|
+
Promise: 'Promise',
|
|
461
|
+
};
|
|
462
|
+
// Check exact match first
|
|
463
|
+
if (patterns[receiver]) {
|
|
464
|
+
return patterns[receiver];
|
|
465
|
+
}
|
|
466
|
+
// Check base of member expression (e.g., req.params -> req)
|
|
467
|
+
const base = receiver.split('.')[0];
|
|
468
|
+
if (patterns[base]) {
|
|
469
|
+
return patterns[base];
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Build context for method resolution.
|
|
475
|
+
*/
|
|
476
|
+
function buildResolutionContext(tree, cache) {
|
|
477
|
+
const context = {
|
|
478
|
+
className: null,
|
|
479
|
+
methodNames: new Set(),
|
|
480
|
+
fieldTypes: new Map(),
|
|
481
|
+
localVarTypes: new Map(),
|
|
482
|
+
imports: new Set(),
|
|
483
|
+
};
|
|
484
|
+
// Find class name
|
|
485
|
+
const classes = getNodesFromCache(tree.rootNode, 'class_declaration', cache);
|
|
486
|
+
if (classes.length > 0) {
|
|
487
|
+
const nameNode = classes[0].childForFieldName('name');
|
|
488
|
+
if (nameNode) {
|
|
489
|
+
context.className = getNodeText(nameNode);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Collect method names from the class
|
|
493
|
+
const methods = getNodesFromCache(tree.rootNode, 'method_declaration', cache);
|
|
494
|
+
for (const method of methods) {
|
|
495
|
+
const nameNode = method.childForFieldName('name');
|
|
496
|
+
if (nameNode) {
|
|
497
|
+
context.methodNames.add(getNodeText(nameNode));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Collect field types
|
|
501
|
+
const fields = getNodesFromCache(tree.rootNode, 'field_declaration', cache);
|
|
502
|
+
for (const field of fields) {
|
|
503
|
+
const typeNode = field.childForFieldName('type');
|
|
504
|
+
const declarators = findNodes(field, 'variable_declarator');
|
|
505
|
+
if (typeNode) {
|
|
506
|
+
const typeName = getNodeText(typeNode);
|
|
507
|
+
for (const decl of declarators) {
|
|
508
|
+
const nameNode = decl.childForFieldName('name');
|
|
509
|
+
if (nameNode) {
|
|
510
|
+
context.fieldTypes.set(getNodeText(nameNode), typeName);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Collect local variable types from all method bodies
|
|
516
|
+
const localVarDecls = getNodesFromCache(tree.rootNode, 'local_variable_declaration', cache);
|
|
517
|
+
for (const decl of localVarDecls) {
|
|
518
|
+
const typeNode = decl.childForFieldName('type');
|
|
519
|
+
const declarators = findNodes(decl, 'variable_declarator');
|
|
520
|
+
if (typeNode) {
|
|
521
|
+
const typeName = getNodeText(typeNode);
|
|
522
|
+
for (const declarator of declarators) {
|
|
523
|
+
const nameNode = declarator.childForFieldName('name');
|
|
524
|
+
if (nameNode) {
|
|
525
|
+
context.localVarTypes.set(getNodeText(nameNode), typeName);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// Collect imports
|
|
531
|
+
const imports = getNodesFromCache(tree.rootNode, 'import_declaration', cache);
|
|
532
|
+
for (const imp of imports) {
|
|
533
|
+
const text = getNodeText(imp);
|
|
534
|
+
// Extract class name from import (last part)
|
|
535
|
+
const match = text.match(/import\s+(?:static\s+)?([a-zA-Z0-9_.]+)/);
|
|
536
|
+
if (match) {
|
|
537
|
+
const parts = match[1].split('.');
|
|
538
|
+
context.imports.add(parts[parts.length - 1]);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return context;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Extract call information from a method_invocation node.
|
|
545
|
+
*/
|
|
546
|
+
function extractCallInfo(node, context) {
|
|
547
|
+
// Get method name
|
|
548
|
+
const nameNode = node.childForFieldName('name');
|
|
549
|
+
const methodName = nameNode ? getNodeText(nameNode) : 'unknown';
|
|
550
|
+
// Get receiver (object the method is called on)
|
|
551
|
+
const objectNode = node.childForFieldName('object');
|
|
552
|
+
const receiver = objectNode ? getNodeText(objectNode) : null;
|
|
553
|
+
// Get arguments
|
|
554
|
+
const argsNode = node.childForFieldName('arguments');
|
|
555
|
+
const args = argsNode ? extractArguments(argsNode) : [];
|
|
556
|
+
// Find enclosing method
|
|
557
|
+
const enclosingMethod = findEnclosingMethod(node);
|
|
558
|
+
// Resolve the call
|
|
559
|
+
const { resolved, resolution } = resolveMethodCall(methodName, receiver, context);
|
|
560
|
+
return {
|
|
561
|
+
method_name: methodName,
|
|
562
|
+
receiver,
|
|
563
|
+
arguments: args,
|
|
564
|
+
location: {
|
|
565
|
+
line: node.startPosition.row + 1,
|
|
566
|
+
column: node.startPosition.column,
|
|
567
|
+
},
|
|
568
|
+
in_method: enclosingMethod,
|
|
569
|
+
resolved,
|
|
570
|
+
resolution,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Extract call information from an object_creation_expression (new ...).
|
|
575
|
+
*/
|
|
576
|
+
function extractObjectCreation(node, context) {
|
|
577
|
+
// Get the type being instantiated
|
|
578
|
+
const typeNode = node.childForFieldName('type');
|
|
579
|
+
if (!typeNode)
|
|
580
|
+
return null;
|
|
581
|
+
const typeName = getNodeText(typeNode);
|
|
582
|
+
// Get arguments
|
|
583
|
+
const argsNode = node.childForFieldName('arguments');
|
|
584
|
+
const args = argsNode ? extractArguments(argsNode) : [];
|
|
585
|
+
// Find enclosing method
|
|
586
|
+
const enclosingMethod = findEnclosingMethod(node);
|
|
587
|
+
// Constructor calls are always resolved (we know the class)
|
|
588
|
+
const resolution = {
|
|
589
|
+
status: 'resolved',
|
|
590
|
+
target: `${typeName}.<init>`,
|
|
591
|
+
};
|
|
592
|
+
return {
|
|
593
|
+
method_name: typeName, // Constructor name is the class name
|
|
594
|
+
receiver: null,
|
|
595
|
+
arguments: args,
|
|
596
|
+
location: {
|
|
597
|
+
line: node.startPosition.row + 1,
|
|
598
|
+
column: node.startPosition.column,
|
|
599
|
+
},
|
|
600
|
+
in_method: enclosingMethod,
|
|
601
|
+
resolved: true,
|
|
602
|
+
resolution,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Extract arguments from an argument_list node.
|
|
607
|
+
*/
|
|
608
|
+
function extractArguments(argsNode) {
|
|
609
|
+
const args = [];
|
|
610
|
+
let position = 0;
|
|
611
|
+
for (let i = 0; i < argsNode.childCount; i++) {
|
|
612
|
+
const child = argsNode.child(i);
|
|
613
|
+
if (!child)
|
|
614
|
+
continue;
|
|
615
|
+
// Skip parentheses and commas
|
|
616
|
+
if (child.type === '(' || child.type === ')' || child.type === ',') {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const expression = getNodeText(child);
|
|
620
|
+
const { variable, literal } = analyzeArgument(child);
|
|
621
|
+
args.push({
|
|
622
|
+
position,
|
|
623
|
+
expression,
|
|
624
|
+
variable,
|
|
625
|
+
literal,
|
|
626
|
+
});
|
|
627
|
+
position++;
|
|
628
|
+
}
|
|
629
|
+
return args;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Analyze an argument to extract variable name or literal value.
|
|
633
|
+
*/
|
|
634
|
+
function analyzeArgument(node) {
|
|
635
|
+
// Check if it's a simple identifier (variable reference)
|
|
636
|
+
if (node.type === 'identifier') {
|
|
637
|
+
return { variable: getNodeText(node), literal: null };
|
|
638
|
+
}
|
|
639
|
+
// Check if it's a literal
|
|
640
|
+
if (isLiteral(node)) {
|
|
641
|
+
return { variable: null, literal: extractLiteralValue(node) };
|
|
642
|
+
}
|
|
643
|
+
// Check for field access (e.g., this.field or obj.field)
|
|
644
|
+
if (node.type === 'field_access') {
|
|
645
|
+
const field = node.childForFieldName('field');
|
|
646
|
+
if (field) {
|
|
647
|
+
return { variable: getNodeText(field), literal: null };
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// For complex expressions, try to find the primary variable
|
|
651
|
+
const identifier = findPrimaryIdentifier(node);
|
|
652
|
+
if (identifier) {
|
|
653
|
+
return { variable: identifier, literal: null };
|
|
654
|
+
}
|
|
655
|
+
return { variable: null, literal: null };
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Check if a node represents a literal value.
|
|
659
|
+
*/
|
|
660
|
+
function isLiteral(node) {
|
|
661
|
+
const literalTypes = new Set([
|
|
662
|
+
'string_literal',
|
|
663
|
+
'character_literal',
|
|
664
|
+
'decimal_integer_literal',
|
|
665
|
+
'hex_integer_literal',
|
|
666
|
+
'octal_integer_literal',
|
|
667
|
+
'binary_integer_literal',
|
|
668
|
+
'decimal_floating_point_literal',
|
|
669
|
+
'hex_floating_point_literal',
|
|
670
|
+
'true',
|
|
671
|
+
'false',
|
|
672
|
+
'null_literal',
|
|
673
|
+
]);
|
|
674
|
+
return literalTypes.has(node.type);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Extract the value from a literal node.
|
|
678
|
+
*/
|
|
679
|
+
function extractLiteralValue(node) {
|
|
680
|
+
const text = getNodeText(node);
|
|
681
|
+
// Remove quotes from string literals
|
|
682
|
+
if (node.type === 'string_literal') {
|
|
683
|
+
return text.slice(1, -1);
|
|
684
|
+
}
|
|
685
|
+
// Remove quotes from character literals
|
|
686
|
+
if (node.type === 'character_literal') {
|
|
687
|
+
return text.slice(1, -1);
|
|
688
|
+
}
|
|
689
|
+
return text;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Find the primary identifier in a complex expression.
|
|
693
|
+
*/
|
|
694
|
+
function findPrimaryIdentifier(node) {
|
|
695
|
+
// Direct identifier
|
|
696
|
+
if (node.type === 'identifier') {
|
|
697
|
+
return getNodeText(node);
|
|
698
|
+
}
|
|
699
|
+
// Search children
|
|
700
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
701
|
+
const child = node.child(i);
|
|
702
|
+
if (child?.type === 'identifier') {
|
|
703
|
+
return getNodeText(child);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// Recursive search for first identifier
|
|
707
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
708
|
+
const child = node.child(i);
|
|
709
|
+
if (child) {
|
|
710
|
+
const result = findPrimaryIdentifier(child);
|
|
711
|
+
if (result)
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Find the name of the enclosing method.
|
|
719
|
+
*/
|
|
720
|
+
function findEnclosingMethod(node) {
|
|
721
|
+
const methodDecl = findAncestor(node, 'method_declaration');
|
|
722
|
+
if (methodDecl) {
|
|
723
|
+
const nameNode = methodDecl.childForFieldName('name');
|
|
724
|
+
if (nameNode) {
|
|
725
|
+
return getNodeText(nameNode);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const constructorDecl = findAncestor(node, 'constructor_declaration');
|
|
729
|
+
if (constructorDecl) {
|
|
730
|
+
const nameNode = constructorDecl.childForFieldName('name');
|
|
731
|
+
if (nameNode) {
|
|
732
|
+
return getNodeText(nameNode);
|
|
733
|
+
}
|
|
734
|
+
return '<init>';
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Resolve a method call to determine its target.
|
|
740
|
+
*/
|
|
741
|
+
function resolveMethodCall(methodName, receiver, context) {
|
|
742
|
+
// No receiver - might be this.method() or static import
|
|
743
|
+
if (!receiver) {
|
|
744
|
+
// Check if it's a method in the current class
|
|
745
|
+
if (context.methodNames.has(methodName)) {
|
|
746
|
+
return {
|
|
747
|
+
resolved: true,
|
|
748
|
+
resolution: {
|
|
749
|
+
status: 'resolved',
|
|
750
|
+
target: context.className ? `${context.className}.${methodName}` : methodName,
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
// Unresolved - could be a static import or inherited method
|
|
755
|
+
return {
|
|
756
|
+
resolved: false,
|
|
757
|
+
resolution: {
|
|
758
|
+
status: 'external_method',
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
// 'this' receiver - definitely in current class
|
|
763
|
+
if (receiver === 'this') {
|
|
764
|
+
return {
|
|
765
|
+
resolved: true,
|
|
766
|
+
resolution: {
|
|
767
|
+
status: 'resolved',
|
|
768
|
+
target: context.className ? `${context.className}.${methodName}` : methodName,
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
// 'super' receiver - parent class method
|
|
773
|
+
if (receiver === 'super') {
|
|
774
|
+
return {
|
|
775
|
+
resolved: false,
|
|
776
|
+
resolution: {
|
|
777
|
+
status: 'external_method',
|
|
778
|
+
},
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
// Check if receiver is a field with known type
|
|
782
|
+
const fieldType = context.fieldTypes.get(receiver);
|
|
783
|
+
if (fieldType) {
|
|
784
|
+
// Check if it's likely an interface (common patterns)
|
|
785
|
+
if (isLikelyInterface(fieldType)) {
|
|
786
|
+
return {
|
|
787
|
+
resolved: false,
|
|
788
|
+
resolution: {
|
|
789
|
+
status: 'interface_method',
|
|
790
|
+
candidates: [`${fieldType}.${methodName}`],
|
|
791
|
+
},
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
resolved: true,
|
|
796
|
+
resolution: {
|
|
797
|
+
status: 'resolved',
|
|
798
|
+
target: `${fieldType}.${methodName}`,
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
// Check if receiver is a local variable with known type
|
|
803
|
+
const localVarType = context.localVarTypes.get(receiver);
|
|
804
|
+
if (localVarType) {
|
|
805
|
+
// Check if it's likely an interface (common patterns)
|
|
806
|
+
if (isLikelyInterface(localVarType)) {
|
|
807
|
+
return {
|
|
808
|
+
resolved: false,
|
|
809
|
+
resolution: {
|
|
810
|
+
status: 'interface_method',
|
|
811
|
+
candidates: [`${localVarType}.${methodName}`],
|
|
812
|
+
},
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
return {
|
|
816
|
+
resolved: true,
|
|
817
|
+
resolution: {
|
|
818
|
+
status: 'resolved',
|
|
819
|
+
target: `${localVarType}.${methodName}`,
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
// Check if receiver looks like a class name (starts with uppercase)
|
|
824
|
+
if (receiver[0] === receiver[0].toUpperCase() && /^[A-Z]/.test(receiver)) {
|
|
825
|
+
// Static method call
|
|
826
|
+
return {
|
|
827
|
+
resolved: true,
|
|
828
|
+
resolution: {
|
|
829
|
+
status: 'resolved',
|
|
830
|
+
target: `${receiver}.${methodName}`,
|
|
831
|
+
},
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
// Check common external types by receiver name patterns
|
|
835
|
+
const externalType = inferTypeFromReceiverName(receiver);
|
|
836
|
+
if (externalType) {
|
|
837
|
+
return {
|
|
838
|
+
resolved: true,
|
|
839
|
+
resolution: {
|
|
840
|
+
status: 'resolved',
|
|
841
|
+
target: `${externalType}.${methodName}`,
|
|
842
|
+
},
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
// Can't resolve - unknown receiver
|
|
846
|
+
return {
|
|
847
|
+
resolved: false,
|
|
848
|
+
resolution: {
|
|
849
|
+
status: 'external_method',
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Check if a type name is likely an interface.
|
|
855
|
+
*/
|
|
856
|
+
function isLikelyInterface(typeName) {
|
|
857
|
+
const interfacePatterns = [
|
|
858
|
+
/^I[A-Z]/, // IService, IRepository
|
|
859
|
+
/Service$/,
|
|
860
|
+
/Repository$/,
|
|
861
|
+
/Dao$/,
|
|
862
|
+
/Manager$/,
|
|
863
|
+
/Handler$/,
|
|
864
|
+
/Listener$/,
|
|
865
|
+
/Callback$/,
|
|
866
|
+
/Provider$/,
|
|
867
|
+
/Factory$/,
|
|
868
|
+
];
|
|
869
|
+
return interfacePatterns.some(pattern => pattern.test(typeName));
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Infer type from common receiver naming patterns.
|
|
873
|
+
*/
|
|
874
|
+
function inferTypeFromReceiverName(receiver) {
|
|
875
|
+
const patterns = {
|
|
876
|
+
request: 'HttpServletRequest',
|
|
877
|
+
req: 'HttpServletRequest',
|
|
878
|
+
response: 'HttpServletResponse',
|
|
879
|
+
resp: 'HttpServletResponse',
|
|
880
|
+
session: 'HttpSession',
|
|
881
|
+
stmt: 'Statement',
|
|
882
|
+
ps: 'PreparedStatement',
|
|
883
|
+
conn: 'Connection',
|
|
884
|
+
connection: 'Connection',
|
|
885
|
+
em: 'EntityManager',
|
|
886
|
+
rs: 'ResultSet',
|
|
887
|
+
runtime: 'Runtime',
|
|
888
|
+
out: 'PrintStream',
|
|
889
|
+
err: 'PrintStream',
|
|
890
|
+
writer: 'PrintWriter',
|
|
891
|
+
pw: 'PrintWriter',
|
|
892
|
+
bw: 'BufferedWriter',
|
|
893
|
+
};
|
|
894
|
+
const lowerReceiver = receiver.toLowerCase();
|
|
895
|
+
return patterns[lowerReceiver] ?? null;
|
|
896
|
+
}
|
|
897
|
+
// =============================================================================
|
|
898
|
+
// Python Call Extraction
|
|
899
|
+
// =============================================================================
|
|
900
|
+
/**
|
|
901
|
+
* Extract all function/method calls from a Python tree.
|
|
902
|
+
*/
|
|
903
|
+
function extractPythonCalls(tree, cache) {
|
|
904
|
+
const calls = [];
|
|
905
|
+
// Build Python resolution context
|
|
906
|
+
const context = buildPythonResolutionContext(tree, cache);
|
|
907
|
+
// Find all call nodes
|
|
908
|
+
const callNodes = getNodesFromCache(tree.rootNode, 'call', cache);
|
|
909
|
+
for (const callNode of callNodes) {
|
|
910
|
+
const callInfo = extractPythonCallInfo(callNode, context);
|
|
911
|
+
if (callInfo) {
|
|
912
|
+
calls.push(callInfo);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return calls;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Build context for Python method resolution.
|
|
919
|
+
*/
|
|
920
|
+
function buildPythonResolutionContext(tree, cache) {
|
|
921
|
+
const context = {
|
|
922
|
+
functionNames: new Set(),
|
|
923
|
+
classNames: new Set(),
|
|
924
|
+
imports: new Map(),
|
|
925
|
+
variableTypes: new Map(),
|
|
926
|
+
};
|
|
927
|
+
// Collect function names
|
|
928
|
+
const functions = getNodesFromCache(tree.rootNode, 'function_definition', cache);
|
|
929
|
+
for (const func of functions) {
|
|
930
|
+
const nameNode = func.childForFieldName('name');
|
|
931
|
+
if (nameNode) {
|
|
932
|
+
context.functionNames.add(getNodeText(nameNode));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
// Collect class names
|
|
936
|
+
const classes = getNodesFromCache(tree.rootNode, 'class_definition', cache);
|
|
937
|
+
for (const cls of classes) {
|
|
938
|
+
const nameNode = cls.childForFieldName('name');
|
|
939
|
+
if (nameNode) {
|
|
940
|
+
context.classNames.add(getNodeText(nameNode));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
// Collect imports
|
|
944
|
+
const importStatements = getNodesFromCache(tree.rootNode, 'import_statement', cache);
|
|
945
|
+
for (const stmt of importStatements) {
|
|
946
|
+
// import os, sys
|
|
947
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
948
|
+
const child = stmt.child(i);
|
|
949
|
+
if (child?.type === 'dotted_name') {
|
|
950
|
+
const name = getNodeText(child);
|
|
951
|
+
const parts = name.split('.');
|
|
952
|
+
context.imports.set(parts[parts.length - 1], name);
|
|
953
|
+
}
|
|
954
|
+
else if (child?.type === 'aliased_import') {
|
|
955
|
+
const nameNode = child.childForFieldName('name');
|
|
956
|
+
const aliasNode = child.childForFieldName('alias');
|
|
957
|
+
if (nameNode && aliasNode) {
|
|
958
|
+
context.imports.set(getNodeText(aliasNode), getNodeText(nameNode));
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const importFromStatements = getNodesFromCache(tree.rootNode, 'import_from_statement', cache);
|
|
964
|
+
for (const stmt of importFromStatements) {
|
|
965
|
+
const moduleNode = stmt.childForFieldName('module_name');
|
|
966
|
+
const moduleName = moduleNode ? getNodeText(moduleNode) : '';
|
|
967
|
+
// from module import name1, name2
|
|
968
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
969
|
+
const child = stmt.child(i);
|
|
970
|
+
if (child?.type === 'dotted_name' && child !== moduleNode) {
|
|
971
|
+
context.imports.set(getNodeText(child), moduleName);
|
|
972
|
+
}
|
|
973
|
+
else if (child?.type === 'aliased_import') {
|
|
974
|
+
const nameNode = child.childForFieldName('name');
|
|
975
|
+
const aliasNode = child.childForFieldName('alias');
|
|
976
|
+
if (nameNode) {
|
|
977
|
+
const alias = aliasNode ? getNodeText(aliasNode) : getNodeText(nameNode);
|
|
978
|
+
context.imports.set(alias, moduleName);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return context;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Extract call information from a Python call node.
|
|
987
|
+
*/
|
|
988
|
+
function extractPythonCallInfo(node, context) {
|
|
989
|
+
const funcNode = node.childForFieldName('function');
|
|
990
|
+
if (!funcNode)
|
|
991
|
+
return null;
|
|
992
|
+
let methodName;
|
|
993
|
+
let receiver = null;
|
|
994
|
+
if (funcNode.type === 'attribute') {
|
|
995
|
+
// Method call: obj.method()
|
|
996
|
+
const objNode = funcNode.childForFieldName('object');
|
|
997
|
+
const attrNode = funcNode.childForFieldName('attribute');
|
|
998
|
+
receiver = objNode ? getNodeText(objNode) : null;
|
|
999
|
+
methodName = attrNode ? getNodeText(attrNode) : 'unknown';
|
|
1000
|
+
}
|
|
1001
|
+
else if (funcNode.type === 'identifier') {
|
|
1002
|
+
// Function call: func()
|
|
1003
|
+
methodName = getNodeText(funcNode);
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
// Complex expression: (get_func())()
|
|
1007
|
+
methodName = getNodeText(funcNode);
|
|
1008
|
+
}
|
|
1009
|
+
// Extract arguments
|
|
1010
|
+
const argsNode = node.childForFieldName('arguments');
|
|
1011
|
+
const args = argsNode ? extractPythonArguments(argsNode) : [];
|
|
1012
|
+
// Find enclosing method
|
|
1013
|
+
const inMethod = findPythonEnclosingMethod(node);
|
|
1014
|
+
// Resolve the call
|
|
1015
|
+
const resolution = resolvePythonCall(methodName, receiver, context);
|
|
1016
|
+
return {
|
|
1017
|
+
method_name: methodName,
|
|
1018
|
+
receiver,
|
|
1019
|
+
arguments: args,
|
|
1020
|
+
location: {
|
|
1021
|
+
line: node.startPosition.row + 1,
|
|
1022
|
+
column: node.startPosition.column,
|
|
1023
|
+
},
|
|
1024
|
+
in_method: inMethod,
|
|
1025
|
+
resolved: resolution.status === 'resolved',
|
|
1026
|
+
resolution,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Extract Python call arguments.
|
|
1031
|
+
*/
|
|
1032
|
+
function extractPythonArguments(node) {
|
|
1033
|
+
const args = [];
|
|
1034
|
+
let position = 0;
|
|
1035
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1036
|
+
const child = node.child(i);
|
|
1037
|
+
if (!child)
|
|
1038
|
+
continue;
|
|
1039
|
+
// Skip punctuation
|
|
1040
|
+
if (child.type === ',' || child.type === '(' || child.type === ')')
|
|
1041
|
+
continue;
|
|
1042
|
+
if (child.type === 'keyword_argument') {
|
|
1043
|
+
// Named argument: key=value
|
|
1044
|
+
const keyNode = child.childForFieldName('name');
|
|
1045
|
+
const valueNode = child.childForFieldName('value');
|
|
1046
|
+
const key = keyNode ? getNodeText(keyNode) : null;
|
|
1047
|
+
const value = valueNode ? getNodeText(valueNode) : '';
|
|
1048
|
+
const literal = valueNode ? extractPythonLiteral(valueNode) : null;
|
|
1049
|
+
const variable = valueNode?.type === 'identifier' ? getNodeText(valueNode) : null;
|
|
1050
|
+
args.push({
|
|
1051
|
+
position: position++,
|
|
1052
|
+
expression: key ? `${key}=${value}` : value,
|
|
1053
|
+
variable,
|
|
1054
|
+
literal,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
// Positional argument
|
|
1059
|
+
const expression = getNodeText(child);
|
|
1060
|
+
const literal = extractPythonLiteral(child);
|
|
1061
|
+
const variable = child.type === 'identifier' ? expression : null;
|
|
1062
|
+
args.push({
|
|
1063
|
+
position: position++,
|
|
1064
|
+
expression,
|
|
1065
|
+
variable,
|
|
1066
|
+
literal,
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return args;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Extract literal value from a Python node.
|
|
1074
|
+
*/
|
|
1075
|
+
function extractPythonLiteral(node) {
|
|
1076
|
+
const literalTypes = ['string', 'integer', 'float', 'true', 'false', 'none'];
|
|
1077
|
+
if (literalTypes.includes(node.type)) {
|
|
1078
|
+
const text = getNodeText(node);
|
|
1079
|
+
// Remove quotes from strings
|
|
1080
|
+
if (node.type === 'string') {
|
|
1081
|
+
return text.replace(/^['"]|['"]$/g, '').replace(/^f['"]|['"]$/g, '');
|
|
1082
|
+
}
|
|
1083
|
+
return text;
|
|
1084
|
+
}
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Find the enclosing method/function for a Python call.
|
|
1089
|
+
*/
|
|
1090
|
+
function findPythonEnclosingMethod(node) {
|
|
1091
|
+
let current = node.parent;
|
|
1092
|
+
while (current) {
|
|
1093
|
+
if (current.type === 'function_definition') {
|
|
1094
|
+
const nameNode = current.childForFieldName('name');
|
|
1095
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
1096
|
+
}
|
|
1097
|
+
current = current.parent;
|
|
1098
|
+
}
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Resolve a Python call to its target.
|
|
1103
|
+
*/
|
|
1104
|
+
function resolvePythonCall(methodName, receiver, context) {
|
|
1105
|
+
// Check if it's a known function in the current module
|
|
1106
|
+
if (!receiver && context.functionNames.has(methodName)) {
|
|
1107
|
+
return {
|
|
1108
|
+
status: 'resolved',
|
|
1109
|
+
target: methodName,
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
// Check if it's a class constructor
|
|
1113
|
+
if (!receiver && context.classNames.has(methodName)) {
|
|
1114
|
+
return {
|
|
1115
|
+
status: 'resolved',
|
|
1116
|
+
target: `${methodName}.__init__`,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
// Check if the receiver is an imported module
|
|
1120
|
+
if (receiver && context.imports.has(receiver)) {
|
|
1121
|
+
return {
|
|
1122
|
+
status: 'resolved',
|
|
1123
|
+
target: `${context.imports.get(receiver)}.${methodName}`,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
// Check if it's a direct import
|
|
1127
|
+
if (!receiver && context.imports.has(methodName)) {
|
|
1128
|
+
return {
|
|
1129
|
+
status: 'resolved',
|
|
1130
|
+
target: `${context.imports.get(methodName)}.${methodName}`,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
// Common Python built-ins
|
|
1134
|
+
const builtins = new Set([
|
|
1135
|
+
'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
|
|
1136
|
+
'open', 'input', 'type', 'isinstance', 'hasattr', 'getattr', 'setattr',
|
|
1137
|
+
'enumerate', 'zip', 'map', 'filter', 'sorted', 'reversed', 'sum', 'min', 'max',
|
|
1138
|
+
]);
|
|
1139
|
+
if (!receiver && builtins.has(methodName)) {
|
|
1140
|
+
return {
|
|
1141
|
+
status: 'resolved',
|
|
1142
|
+
target: `builtins.${methodName}`,
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
// If we have a receiver but can't resolve, it's likely an external method
|
|
1146
|
+
if (receiver) {
|
|
1147
|
+
return {
|
|
1148
|
+
status: 'external_method',
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return {
|
|
1152
|
+
status: 'external_method',
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
// =============================================================================
|
|
1156
|
+
// Rust Call Extraction
|
|
1157
|
+
// =============================================================================
|
|
1158
|
+
/**
|
|
1159
|
+
* Extract all function/method calls from a Rust tree.
|
|
1160
|
+
*/
|
|
1161
|
+
function extractRustCalls(tree, cache) {
|
|
1162
|
+
const calls = [];
|
|
1163
|
+
// Build Rust resolution context
|
|
1164
|
+
const context = buildRustResolutionContext(tree, cache);
|
|
1165
|
+
// Find all call expressions (function/method calls)
|
|
1166
|
+
const callExpressions = getNodesFromCache(tree.rootNode, 'call_expression', cache);
|
|
1167
|
+
for (const call of callExpressions) {
|
|
1168
|
+
const callInfo = extractRustCallInfo(call, context);
|
|
1169
|
+
if (callInfo) {
|
|
1170
|
+
calls.push(callInfo);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// Find macro invocations (println!, format!, etc.)
|
|
1174
|
+
const macroInvocations = getNodesFromCache(tree.rootNode, 'macro_invocation', cache);
|
|
1175
|
+
for (const macro of macroInvocations) {
|
|
1176
|
+
const callInfo = extractRustMacroInfo(macro);
|
|
1177
|
+
if (callInfo) {
|
|
1178
|
+
calls.push(callInfo);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return calls;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Build context for Rust method resolution.
|
|
1185
|
+
*/
|
|
1186
|
+
function buildRustResolutionContext(tree, cache) {
|
|
1187
|
+
const context = {
|
|
1188
|
+
functionNames: new Set(),
|
|
1189
|
+
structNames: new Set(),
|
|
1190
|
+
imports: new Map(),
|
|
1191
|
+
};
|
|
1192
|
+
// Collect function names
|
|
1193
|
+
const functions = getNodesFromCache(tree.rootNode, 'function_item', cache);
|
|
1194
|
+
for (const func of functions) {
|
|
1195
|
+
const nameNode = func.childForFieldName('name');
|
|
1196
|
+
if (nameNode) {
|
|
1197
|
+
context.functionNames.add(getNodeText(nameNode));
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// Collect struct names
|
|
1201
|
+
const structs = getNodesFromCache(tree.rootNode, 'struct_item', cache);
|
|
1202
|
+
for (const s of structs) {
|
|
1203
|
+
const nameNode = s.childForFieldName('name');
|
|
1204
|
+
if (nameNode) {
|
|
1205
|
+
context.structNames.add(getNodeText(nameNode));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
// Collect use declarations
|
|
1209
|
+
const useDecls = getNodesFromCache(tree.rootNode, 'use_declaration', cache);
|
|
1210
|
+
for (const useDecl of useDecls) {
|
|
1211
|
+
const text = getNodeText(useDecl);
|
|
1212
|
+
// Parse: use std::io; or use actix_web::{web, App}; or use foo::bar::Baz;
|
|
1213
|
+
const simpleMatch = text.match(/use\s+([\w:]+);/);
|
|
1214
|
+
if (simpleMatch) {
|
|
1215
|
+
const path = simpleMatch[1];
|
|
1216
|
+
const parts = path.split('::');
|
|
1217
|
+
const name = parts[parts.length - 1];
|
|
1218
|
+
context.imports.set(name, path);
|
|
1219
|
+
}
|
|
1220
|
+
// Parse grouped imports: use foo::{Bar, Baz};
|
|
1221
|
+
const groupMatch = text.match(/use\s+([\w:]+)::\{([^}]+)\}/);
|
|
1222
|
+
if (groupMatch) {
|
|
1223
|
+
const basePath = groupMatch[1];
|
|
1224
|
+
const items = groupMatch[2].split(',').map(s => s.trim());
|
|
1225
|
+
for (const item of items) {
|
|
1226
|
+
// Handle aliasing: Foo as F
|
|
1227
|
+
const [name, alias] = item.split(/\s+as\s+/);
|
|
1228
|
+
const importName = alias || name;
|
|
1229
|
+
context.imports.set(importName.trim(), `${basePath}::${name.trim()}`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return context;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Extract call information from a Rust call_expression node.
|
|
1237
|
+
*/
|
|
1238
|
+
function extractRustCallInfo(node, context) {
|
|
1239
|
+
const funcNode = node.childForFieldName('function');
|
|
1240
|
+
if (!funcNode)
|
|
1241
|
+
return null;
|
|
1242
|
+
let methodName = '';
|
|
1243
|
+
let receiver = null;
|
|
1244
|
+
let receiverType = null;
|
|
1245
|
+
if (funcNode.type === 'identifier') {
|
|
1246
|
+
// Simple function call: foo()
|
|
1247
|
+
methodName = getNodeText(funcNode);
|
|
1248
|
+
}
|
|
1249
|
+
else if (funcNode.type === 'field_expression') {
|
|
1250
|
+
// Method call: obj.method()
|
|
1251
|
+
const objectNode = funcNode.childForFieldName('value');
|
|
1252
|
+
const fieldNode = funcNode.childForFieldName('field');
|
|
1253
|
+
if (objectNode && fieldNode) {
|
|
1254
|
+
receiver = getNodeText(objectNode);
|
|
1255
|
+
methodName = getNodeText(fieldNode);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
else if (funcNode.type === 'scoped_identifier') {
|
|
1259
|
+
// Scoped call: Foo::bar() or std::io::read()
|
|
1260
|
+
const text = getNodeText(funcNode);
|
|
1261
|
+
const parts = text.split('::');
|
|
1262
|
+
methodName = parts.pop() || '';
|
|
1263
|
+
receiver = parts.join('::');
|
|
1264
|
+
receiverType = receiver;
|
|
1265
|
+
}
|
|
1266
|
+
else {
|
|
1267
|
+
// Other expression
|
|
1268
|
+
methodName = getNodeText(funcNode);
|
|
1269
|
+
}
|
|
1270
|
+
// Extract arguments
|
|
1271
|
+
const argsNode = node.childForFieldName('arguments');
|
|
1272
|
+
const args = argsNode ? extractRustArguments(argsNode) : [];
|
|
1273
|
+
// Resolve the call
|
|
1274
|
+
const resolution = resolveRustCall(methodName, receiver, context);
|
|
1275
|
+
return {
|
|
1276
|
+
method_name: methodName,
|
|
1277
|
+
receiver: receiver,
|
|
1278
|
+
receiver_type: receiverType,
|
|
1279
|
+
arguments: args,
|
|
1280
|
+
location: {
|
|
1281
|
+
line: node.startPosition.row + 1,
|
|
1282
|
+
column: node.startPosition.column,
|
|
1283
|
+
},
|
|
1284
|
+
resolution,
|
|
1285
|
+
is_constructor: false,
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Extract macro invocation information.
|
|
1290
|
+
*/
|
|
1291
|
+
function extractRustMacroInfo(node) {
|
|
1292
|
+
// Macro: println!("hello") or format!("foo {}", bar)
|
|
1293
|
+
const macroNode = node.childForFieldName('macro');
|
|
1294
|
+
if (!macroNode) {
|
|
1295
|
+
// Try first child
|
|
1296
|
+
const firstChild = node.child(0);
|
|
1297
|
+
if (!firstChild)
|
|
1298
|
+
return null;
|
|
1299
|
+
const text = getNodeText(firstChild);
|
|
1300
|
+
const methodName = text.replace(/!$/, '') + '!';
|
|
1301
|
+
// Extract arguments from token_tree
|
|
1302
|
+
const args = [];
|
|
1303
|
+
for (let i = 1; i < node.childCount; i++) {
|
|
1304
|
+
const child = node.child(i);
|
|
1305
|
+
if (child?.type === 'token_tree') {
|
|
1306
|
+
const tokenText = getNodeText(child);
|
|
1307
|
+
args.push({
|
|
1308
|
+
position: 0,
|
|
1309
|
+
value: null,
|
|
1310
|
+
expression: tokenText,
|
|
1311
|
+
});
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
method_name: methodName,
|
|
1317
|
+
receiver: null,
|
|
1318
|
+
receiver_type: null,
|
|
1319
|
+
arguments: args,
|
|
1320
|
+
location: {
|
|
1321
|
+
line: node.startPosition.row + 1,
|
|
1322
|
+
column: node.startPosition.column,
|
|
1323
|
+
},
|
|
1324
|
+
resolution: {
|
|
1325
|
+
status: 'resolved',
|
|
1326
|
+
target: `std::macros::${methodName}`,
|
|
1327
|
+
},
|
|
1328
|
+
is_constructor: false,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
const methodName = getNodeText(macroNode) + '!';
|
|
1332
|
+
return {
|
|
1333
|
+
method_name: methodName,
|
|
1334
|
+
receiver: null,
|
|
1335
|
+
receiver_type: null,
|
|
1336
|
+
arguments: [],
|
|
1337
|
+
location: {
|
|
1338
|
+
line: node.startPosition.row + 1,
|
|
1339
|
+
column: node.startPosition.column,
|
|
1340
|
+
},
|
|
1341
|
+
resolution: {
|
|
1342
|
+
status: 'resolved',
|
|
1343
|
+
target: `std::macros::${methodName}`,
|
|
1344
|
+
},
|
|
1345
|
+
is_constructor: false,
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Extract arguments from Rust function call.
|
|
1350
|
+
*/
|
|
1351
|
+
function extractRustArguments(argsNode) {
|
|
1352
|
+
const args = [];
|
|
1353
|
+
for (let i = 0; i < argsNode.childCount; i++) {
|
|
1354
|
+
const child = argsNode.child(i);
|
|
1355
|
+
if (!child)
|
|
1356
|
+
continue;
|
|
1357
|
+
// Skip punctuation
|
|
1358
|
+
if (child.type === '(' || child.type === ')' || child.type === ',')
|
|
1359
|
+
continue;
|
|
1360
|
+
const text = getNodeText(child);
|
|
1361
|
+
// Check for literal value
|
|
1362
|
+
let value = null;
|
|
1363
|
+
if (child.type === 'string_literal') {
|
|
1364
|
+
// Remove quotes
|
|
1365
|
+
value = text.replace(/^["']|["']$/g, '');
|
|
1366
|
+
}
|
|
1367
|
+
else if (child.type === 'integer_literal' || child.type === 'float_literal' || child.type === 'boolean_literal') {
|
|
1368
|
+
value = text;
|
|
1369
|
+
}
|
|
1370
|
+
args.push({
|
|
1371
|
+
position: args.length,
|
|
1372
|
+
value,
|
|
1373
|
+
expression: text,
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
return args;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Resolve a Rust function/method call.
|
|
1380
|
+
*/
|
|
1381
|
+
function resolveRustCall(methodName, receiver, context) {
|
|
1382
|
+
// Check if it's a local function
|
|
1383
|
+
if (!receiver && context.functionNames.has(methodName)) {
|
|
1384
|
+
return {
|
|
1385
|
+
status: 'resolved',
|
|
1386
|
+
target: methodName,
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
// Check if receiver is a known struct
|
|
1390
|
+
if (receiver && context.structNames.has(receiver)) {
|
|
1391
|
+
return {
|
|
1392
|
+
status: 'resolved',
|
|
1393
|
+
target: `${receiver}::${methodName}`,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
// Check imports
|
|
1397
|
+
if (receiver && context.imports.has(receiver)) {
|
|
1398
|
+
return {
|
|
1399
|
+
status: 'resolved',
|
|
1400
|
+
target: `${context.imports.get(receiver)}::${methodName}`,
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
// Check if method name is imported
|
|
1404
|
+
if (!receiver && context.imports.has(methodName)) {
|
|
1405
|
+
return {
|
|
1406
|
+
status: 'resolved',
|
|
1407
|
+
target: context.imports.get(methodName),
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
// Common Rust standard library methods
|
|
1411
|
+
const stdMethods = new Set([
|
|
1412
|
+
'unwrap', 'unwrap_or', 'expect', 'ok', 'err', 'map', 'and_then', 'or_else',
|
|
1413
|
+
'clone', 'to_string', 'to_owned', 'into', 'from', 'as_ref', 'as_mut',
|
|
1414
|
+
'push', 'pop', 'insert', 'remove', 'get', 'contains', 'len', 'is_empty',
|
|
1415
|
+
'iter', 'into_iter', 'collect', 'filter', 'map', 'fold', 'for_each',
|
|
1416
|
+
'read', 'write', 'read_to_string', 'read_to_end',
|
|
1417
|
+
]);
|
|
1418
|
+
if (stdMethods.has(methodName)) {
|
|
1419
|
+
return {
|
|
1420
|
+
status: 'resolved',
|
|
1421
|
+
target: receiver ? `${receiver}.${methodName}` : methodName,
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
// External method
|
|
1425
|
+
return {
|
|
1426
|
+
status: 'external_method',
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
//# sourceMappingURL=calls.js.map
|