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.
Files changed (194) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +200 -0
  3. package/configs/sinks/code_injection.yaml +672 -0
  4. package/configs/sinks/command.yaml +917 -0
  5. package/configs/sinks/deserialization.yaml +105 -0
  6. package/configs/sinks/ldap.yaml +136 -0
  7. package/configs/sinks/nodejs.json +629 -0
  8. package/configs/sinks/path.yaml +715 -0
  9. package/configs/sinks/python.json +501 -0
  10. package/configs/sinks/rust.json +339 -0
  11. package/configs/sinks/sql.yaml +233 -0
  12. package/configs/sinks/ssrf.yaml +160 -0
  13. package/configs/sinks/xpath.yaml +121 -0
  14. package/configs/sinks/xss.yaml +727 -0
  15. package/configs/sources/db_sources.yaml +90 -0
  16. package/configs/sources/env_sources.yaml +94 -0
  17. package/configs/sources/express.json +197 -0
  18. package/configs/sources/file_sources.yaml +164 -0
  19. package/configs/sources/http_sources.yaml +379 -0
  20. package/configs/sources/io_sources.yaml +519 -0
  21. package/configs/sources/network_sources.yaml +99 -0
  22. package/configs/sources/python.json +230 -0
  23. package/configs/sources/rust.json +286 -0
  24. package/configs/sources/spring.yaml +70 -0
  25. package/dist/analysis/advisory-db.d.ts +86 -0
  26. package/dist/analysis/advisory-db.js +104 -0
  27. package/dist/analysis/advisory-db.js.map +1 -0
  28. package/dist/analysis/cargo-parser.d.ts +42 -0
  29. package/dist/analysis/cargo-parser.js +102 -0
  30. package/dist/analysis/cargo-parser.js.map +1 -0
  31. package/dist/analysis/config-loader.d.ts +37 -0
  32. package/dist/analysis/config-loader.js +1561 -0
  33. package/dist/analysis/config-loader.js.map +1 -0
  34. package/dist/analysis/constant-propagation/ast-utils.d.ts +25 -0
  35. package/dist/analysis/constant-propagation/ast-utils.js +34 -0
  36. package/dist/analysis/constant-propagation/ast-utils.js.map +1 -0
  37. package/dist/analysis/constant-propagation/evaluator.d.ts +32 -0
  38. package/dist/analysis/constant-propagation/evaluator.js +296 -0
  39. package/dist/analysis/constant-propagation/evaluator.js.map +1 -0
  40. package/dist/analysis/constant-propagation/index.d.ts +62 -0
  41. package/dist/analysis/constant-propagation/index.js +152 -0
  42. package/dist/analysis/constant-propagation/index.js.map +1 -0
  43. package/dist/analysis/constant-propagation/patterns.d.ts +8 -0
  44. package/dist/analysis/constant-propagation/patterns.js +126 -0
  45. package/dist/analysis/constant-propagation/patterns.js.map +1 -0
  46. package/dist/analysis/constant-propagation/propagator.d.ts +180 -0
  47. package/dist/analysis/constant-propagation/propagator.js +1985 -0
  48. package/dist/analysis/constant-propagation/propagator.js.map +1 -0
  49. package/dist/analysis/constant-propagation/types.d.ts +63 -0
  50. package/dist/analysis/constant-propagation/types.js +5 -0
  51. package/dist/analysis/constant-propagation/types.js.map +1 -0
  52. package/dist/analysis/constant-propagation.d.ts +9 -0
  53. package/dist/analysis/constant-propagation.js +18 -0
  54. package/dist/analysis/constant-propagation.js.map +1 -0
  55. package/dist/analysis/dependency-scanner.d.ts +79 -0
  56. package/dist/analysis/dependency-scanner.js +122 -0
  57. package/dist/analysis/dependency-scanner.js.map +1 -0
  58. package/dist/analysis/dfg-verifier.d.ts +116 -0
  59. package/dist/analysis/dfg-verifier.js +399 -0
  60. package/dist/analysis/dfg-verifier.js.map +1 -0
  61. package/dist/analysis/findings.d.ts +11 -0
  62. package/dist/analysis/findings.js +228 -0
  63. package/dist/analysis/findings.js.map +1 -0
  64. package/dist/analysis/index.d.ts +16 -0
  65. package/dist/analysis/index.js +18 -0
  66. package/dist/analysis/index.js.map +1 -0
  67. package/dist/analysis/interprocedural.d.ts +99 -0
  68. package/dist/analysis/interprocedural.js +526 -0
  69. package/dist/analysis/interprocedural.js.map +1 -0
  70. package/dist/analysis/path-finder.d.ts +133 -0
  71. package/dist/analysis/path-finder.js +354 -0
  72. package/dist/analysis/path-finder.js.map +1 -0
  73. package/dist/analysis/rules.d.ts +75 -0
  74. package/dist/analysis/rules.js +332 -0
  75. package/dist/analysis/rules.js.map +1 -0
  76. package/dist/analysis/semver.d.ts +27 -0
  77. package/dist/analysis/semver.js +127 -0
  78. package/dist/analysis/semver.js.map +1 -0
  79. package/dist/analysis/taint-matcher.d.ts +15 -0
  80. package/dist/analysis/taint-matcher.js +634 -0
  81. package/dist/analysis/taint-matcher.js.map +1 -0
  82. package/dist/analysis/taint-propagation.d.ts +67 -0
  83. package/dist/analysis/taint-propagation.js +298 -0
  84. package/dist/analysis/taint-propagation.js.map +1 -0
  85. package/dist/analysis/unresolved.d.ts +14 -0
  86. package/dist/analysis/unresolved.js +202 -0
  87. package/dist/analysis/unresolved.js.map +1 -0
  88. package/dist/analyzer.d.ts +43 -0
  89. package/dist/analyzer.js +1010 -0
  90. package/dist/analyzer.js.map +1 -0
  91. package/dist/browser/circle-ir.js +16576 -0
  92. package/dist/browser.d.ts +38 -0
  93. package/dist/browser.js +38 -0
  94. package/dist/browser.js.map +1 -0
  95. package/dist/core/circle-ir-core.cjs +13626 -0
  96. package/dist/core/circle-ir-core.d.ts +59 -0
  97. package/dist/core/circle-ir-core.js +13591 -0
  98. package/dist/core/extractors/calls.d.ts +13 -0
  99. package/dist/core/extractors/calls.js +1429 -0
  100. package/dist/core/extractors/calls.js.map +1 -0
  101. package/dist/core/extractors/cfg.d.ts +9 -0
  102. package/dist/core/extractors/cfg.js +519 -0
  103. package/dist/core/extractors/cfg.js.map +1 -0
  104. package/dist/core/extractors/dfg.d.ts +12 -0
  105. package/dist/core/extractors/dfg.js +1081 -0
  106. package/dist/core/extractors/dfg.js.map +1 -0
  107. package/dist/core/extractors/exports.d.ts +14 -0
  108. package/dist/core/extractors/exports.js +80 -0
  109. package/dist/core/extractors/exports.js.map +1 -0
  110. package/dist/core/extractors/imports.d.ts +9 -0
  111. package/dist/core/extractors/imports.js +739 -0
  112. package/dist/core/extractors/imports.js.map +1 -0
  113. package/dist/core/extractors/index.d.ts +10 -0
  114. package/dist/core/extractors/index.js +11 -0
  115. package/dist/core/extractors/index.js.map +1 -0
  116. package/dist/core/extractors/meta.d.ts +10 -0
  117. package/dist/core/extractors/meta.js +109 -0
  118. package/dist/core/extractors/meta.js.map +1 -0
  119. package/dist/core/extractors/types.d.ts +10 -0
  120. package/dist/core/extractors/types.js +1479 -0
  121. package/dist/core/extractors/types.js.map +1 -0
  122. package/dist/core/index.d.ts +5 -0
  123. package/dist/core/index.js +8 -0
  124. package/dist/core/index.js.map +1 -0
  125. package/dist/core/parser.d.ts +84 -0
  126. package/dist/core/parser.js +250 -0
  127. package/dist/core/parser.js.map +1 -0
  128. package/dist/core-lib.d.ts +59 -0
  129. package/dist/core-lib.js +62 -0
  130. package/dist/core-lib.js.map +1 -0
  131. package/dist/index.d.ts +15 -0
  132. package/dist/index.js +20 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/languages/index.d.ts +11 -0
  135. package/dist/languages/index.js +14 -0
  136. package/dist/languages/index.js.map +1 -0
  137. package/dist/languages/plugins/base.d.ts +44 -0
  138. package/dist/languages/plugins/base.js +82 -0
  139. package/dist/languages/plugins/base.js.map +1 -0
  140. package/dist/languages/plugins/index.d.ts +14 -0
  141. package/dist/languages/plugins/index.js +25 -0
  142. package/dist/languages/plugins/index.js.map +1 -0
  143. package/dist/languages/plugins/java.d.ts +49 -0
  144. package/dist/languages/plugins/java.js +402 -0
  145. package/dist/languages/plugins/java.js.map +1 -0
  146. package/dist/languages/plugins/javascript.d.ts +48 -0
  147. package/dist/languages/plugins/javascript.js +445 -0
  148. package/dist/languages/plugins/javascript.js.map +1 -0
  149. package/dist/languages/plugins/python.d.ts +47 -0
  150. package/dist/languages/plugins/python.js +480 -0
  151. package/dist/languages/plugins/python.js.map +1 -0
  152. package/dist/languages/plugins/rust.d.ts +47 -0
  153. package/dist/languages/plugins/rust.js +405 -0
  154. package/dist/languages/plugins/rust.js.map +1 -0
  155. package/dist/languages/registry.d.ts +30 -0
  156. package/dist/languages/registry.js +80 -0
  157. package/dist/languages/registry.js.map +1 -0
  158. package/dist/languages/types.d.ts +184 -0
  159. package/dist/languages/types.js +8 -0
  160. package/dist/languages/types.js.map +1 -0
  161. package/dist/resolution/cross-file.d.ts +146 -0
  162. package/dist/resolution/cross-file.js +439 -0
  163. package/dist/resolution/cross-file.js.map +1 -0
  164. package/dist/resolution/index.d.ts +12 -0
  165. package/dist/resolution/index.js +10 -0
  166. package/dist/resolution/index.js.map +1 -0
  167. package/dist/resolution/symbol-table.d.ts +136 -0
  168. package/dist/resolution/symbol-table.js +336 -0
  169. package/dist/resolution/symbol-table.js.map +1 -0
  170. package/dist/resolution/type-hierarchy.d.ts +124 -0
  171. package/dist/resolution/type-hierarchy.js +515 -0
  172. package/dist/resolution/type-hierarchy.js.map +1 -0
  173. package/dist/types/config.d.ts +45 -0
  174. package/dist/types/config.js +5 -0
  175. package/dist/types/config.js.map +1 -0
  176. package/dist/types/index.d.ts +392 -0
  177. package/dist/types/index.js +7 -0
  178. package/dist/types/index.js.map +1 -0
  179. package/dist/utils/logger.d.ts +85 -0
  180. package/dist/utils/logger.js +198 -0
  181. package/dist/utils/logger.js.map +1 -0
  182. package/dist/wasm/tree-sitter-java.wasm +0 -0
  183. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  184. package/dist/wasm/tree-sitter-python.wasm +0 -0
  185. package/dist/wasm/tree-sitter-rust.wasm +0 -0
  186. package/dist/wasm/web-tree-sitter.wasm +0 -0
  187. package/docs/SPEC.md +1021 -0
  188. package/examples/browser-example.html +610 -0
  189. package/examples/node-example.ts +215 -0
  190. package/package.json +107 -0
  191. package/wasm/tree-sitter-java.wasm +0 -0
  192. package/wasm/tree-sitter-javascript.wasm +0 -0
  193. package/wasm/tree-sitter-python.wasm +0 -0
  194. 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