1use super::edge::{CodeEdge, DetectionMethod, EdgeId};
8use super::node::{NodeId, Span};
9
10#[derive(Debug, Clone, PartialEq)]
12pub struct CallSite {
13 pub caller: NodeId,
15 pub callee: NodeId,
17 pub edge_id: EdgeId,
19 pub span: Option<Span>,
21 pub metadata: CallSiteMetadata,
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct CallSiteMetadata {
28 pub confidence: f32,
30 pub detection_method: DetectionMethod,
32 pub detector_hint: Option<String>,
34 pub extras: CallSiteExtras,
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub enum CallSiteExtras {
41 None,
43 Cpp {
45 argument_count: usize,
47 is_template_instantiation: bool,
49 },
50 JavaScript {
52 is_async: bool,
54 uses_await: bool,
56 http_method: Option<String>,
58 http_endpoint: Option<String>,
60 },
61 Python {
63 is_async: bool,
65 is_method: bool,
67 uses_decorator: bool,
69 },
70 Rust {
72 is_async: bool,
74 uses_await: bool,
76 is_unsafe: bool,
78 is_method: bool,
80 is_unsafe_block: bool,
82 },
83 Java {
85 is_static: bool,
87 },
88 Go {
90 is_goroutine: bool,
92 is_deferred: bool,
94 is_builtin: bool,
96 is_method: bool,
98 },
99 C {
101 is_function_pointer: bool,
103 },
104 CSharp {
106 is_async: bool,
108 uses_await: bool,
110 is_property_access: bool,
112 },
113 Lua {
115 is_method: bool,
117 },
118 Perl {
120 is_method: bool,
122 },
123 Shell {
125 is_builtin: bool,
127 },
128 Groovy {
130 is_closure: bool,
132 },
133}
134
135impl CallSite {
136 #[must_use]
138 pub fn from_edge(edge: &CodeEdge, extras: CallSiteExtras) -> Self {
139 Self {
140 caller: edge.from.clone(),
141 callee: edge.to.clone(),
142 edge_id: edge.id,
143 span: edge.metadata.span,
144 metadata: CallSiteMetadata {
145 confidence: edge.metadata.confidence,
146 detection_method: edge.metadata.detection_method,
147 detector_hint: edge.metadata.reason.clone(),
148 extras,
149 },
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::graph::edge::{EdgeKind, EdgeMetadata};
158 use crate::graph::node::{Language, Position};
159
160 fn make_test_edge() -> CodeEdge {
161 CodeEdge {
162 id: EdgeId::new(),
163 from: NodeId::new(Language::Rust, "src/main.rs", "main"),
164 to: NodeId::new(Language::Rust, "src/lib.rs", "helper"),
165 kind: EdgeKind::Call {
166 argument_count: 2,
167 is_async: false,
168 },
169 metadata: EdgeMetadata {
170 span: Some(Span::new(Position::new(10, 5), Position::new(10, 20))),
171 confidence: 0.95,
172 detection_method: DetectionMethod::ASTAnalysis,
173 reason: Some("direct call".to_string()),
174 caller_identity: None,
175 callee_identity: None,
176 },
177 }
178 }
179
180 #[test]
182 fn test_call_site_from_edge() {
183 let edge = make_test_edge();
184 let call_site = CallSite::from_edge(&edge, CallSiteExtras::None);
185
186 assert_eq!(call_site.caller, edge.from);
187 assert_eq!(call_site.callee, edge.to);
188 assert_eq!(call_site.edge_id, edge.id);
189 assert_eq!(call_site.span, edge.metadata.span);
190 assert!((call_site.metadata.confidence - 0.95).abs() < f32::EPSILON);
191 assert_eq!(
192 call_site.metadata.detection_method,
193 DetectionMethod::ASTAnalysis
194 );
195 assert_eq!(
196 call_site.metadata.detector_hint,
197 Some("direct call".to_string())
198 );
199 }
200
201 #[test]
202 fn test_call_site_from_edge_no_span() {
203 let mut edge = make_test_edge();
204 edge.metadata.span = None;
205 edge.metadata.reason = None;
206
207 let call_site = CallSite::from_edge(&edge, CallSiteExtras::None);
208 assert!(call_site.span.is_none());
209 assert!(call_site.metadata.detector_hint.is_none());
210 }
211
212 #[test]
213 fn test_call_site_clone() {
214 let edge = make_test_edge();
215 let call_site = CallSite::from_edge(&edge, CallSiteExtras::None);
216 let cloned = call_site.clone();
217
218 assert_eq!(call_site, cloned);
219 }
220
221 #[test]
222 fn test_call_site_debug() {
223 let edge = make_test_edge();
224 let call_site = CallSite::from_edge(&edge, CallSiteExtras::None);
225 let debug_str = format!("{:?}", call_site);
226
227 assert!(debug_str.contains("CallSite"));
228 assert!(debug_str.contains("caller"));
229 assert!(debug_str.contains("callee"));
230 }
231
232 #[test]
234 fn test_call_site_metadata_eq() {
235 let meta1 = CallSiteMetadata {
236 confidence: 0.9,
237 detection_method: DetectionMethod::ASTAnalysis,
238 detector_hint: None,
239 extras: CallSiteExtras::None,
240 };
241 let meta2 = CallSiteMetadata {
242 confidence: 0.9,
243 detection_method: DetectionMethod::ASTAnalysis,
244 detector_hint: None,
245 extras: CallSiteExtras::None,
246 };
247 assert_eq!(meta1, meta2);
248 }
249
250 #[test]
251 fn test_call_site_metadata_ne() {
252 let meta1 = CallSiteMetadata {
253 confidence: 0.9,
254 detection_method: DetectionMethod::ASTAnalysis,
255 detector_hint: None,
256 extras: CallSiteExtras::None,
257 };
258 let meta2 = CallSiteMetadata {
259 confidence: 0.8,
260 detection_method: DetectionMethod::ASTAnalysis,
261 detector_hint: None,
262 extras: CallSiteExtras::None,
263 };
264 assert_ne!(meta1, meta2);
265 }
266
267 #[test]
269 fn test_call_site_extras_none() {
270 let extras = CallSiteExtras::None;
271 assert_eq!(extras, CallSiteExtras::None);
272 }
273
274 #[test]
275 fn test_call_site_extras_cpp() {
276 let extras = CallSiteExtras::Cpp {
277 argument_count: 3,
278 is_template_instantiation: true,
279 };
280 if let CallSiteExtras::Cpp {
281 argument_count,
282 is_template_instantiation,
283 } = extras
284 {
285 assert_eq!(argument_count, 3);
286 assert!(is_template_instantiation);
287 } else {
288 panic!("Expected Cpp variant");
289 }
290 }
291
292 #[test]
293 fn test_call_site_extras_javascript() {
294 let extras = CallSiteExtras::JavaScript {
295 is_async: true,
296 uses_await: true,
297 http_method: Some("POST".to_string()),
298 http_endpoint: Some("/api/users".to_string()),
299 };
300 if let CallSiteExtras::JavaScript {
301 is_async,
302 uses_await,
303 http_method,
304 http_endpoint,
305 } = extras
306 {
307 assert!(is_async);
308 assert!(uses_await);
309 assert_eq!(http_method, Some("POST".to_string()));
310 assert_eq!(http_endpoint, Some("/api/users".to_string()));
311 } else {
312 panic!("Expected JavaScript variant");
313 }
314 }
315
316 #[test]
317 fn test_call_site_extras_python() {
318 let extras = CallSiteExtras::Python {
319 is_async: true,
320 is_method: true,
321 uses_decorator: true,
322 };
323 if let CallSiteExtras::Python {
324 is_async,
325 is_method,
326 uses_decorator,
327 } = extras
328 {
329 assert!(is_async);
330 assert!(is_method);
331 assert!(uses_decorator);
332 } else {
333 panic!("Expected Python variant");
334 }
335 }
336
337 #[test]
338 fn test_call_site_extras_rust() {
339 let extras = CallSiteExtras::Rust {
340 is_async: true,
341 uses_await: true,
342 is_unsafe: false,
343 is_method: true,
344 is_unsafe_block: false,
345 };
346 if let CallSiteExtras::Rust {
347 is_async,
348 uses_await,
349 is_unsafe,
350 is_method,
351 is_unsafe_block,
352 } = extras
353 {
354 assert!(is_async);
355 assert!(uses_await);
356 assert!(!is_unsafe);
357 assert!(is_method);
358 assert!(!is_unsafe_block);
359 } else {
360 panic!("Expected Rust variant");
361 }
362 }
363
364 #[test]
365 fn test_call_site_extras_java() {
366 let extras = CallSiteExtras::Java { is_static: true };
367 if let CallSiteExtras::Java { is_static } = extras {
368 assert!(is_static);
369 } else {
370 panic!("Expected Java variant");
371 }
372 }
373
374 #[test]
375 fn test_call_site_extras_go() {
376 let extras = CallSiteExtras::Go {
377 is_goroutine: true,
378 is_deferred: false,
379 is_builtin: false,
380 is_method: true,
381 };
382 if let CallSiteExtras::Go {
383 is_goroutine,
384 is_deferred,
385 is_builtin,
386 is_method,
387 } = extras
388 {
389 assert!(is_goroutine);
390 assert!(!is_deferred);
391 assert!(!is_builtin);
392 assert!(is_method);
393 } else {
394 panic!("Expected Go variant");
395 }
396 }
397
398 #[test]
399 fn test_call_site_extras_c() {
400 let extras = CallSiteExtras::C {
401 is_function_pointer: true,
402 };
403 if let CallSiteExtras::C {
404 is_function_pointer,
405 } = extras
406 {
407 assert!(is_function_pointer);
408 } else {
409 panic!("Expected C variant");
410 }
411 }
412
413 #[test]
414 fn test_call_site_extras_csharp() {
415 let extras = CallSiteExtras::CSharp {
416 is_async: true,
417 uses_await: true,
418 is_property_access: false,
419 };
420 if let CallSiteExtras::CSharp {
421 is_async,
422 uses_await,
423 is_property_access,
424 } = extras
425 {
426 assert!(is_async);
427 assert!(uses_await);
428 assert!(!is_property_access);
429 } else {
430 panic!("Expected CSharp variant");
431 }
432 }
433
434 #[test]
435 fn test_call_site_extras_lua() {
436 let extras = CallSiteExtras::Lua { is_method: true };
437 if let CallSiteExtras::Lua { is_method } = extras {
438 assert!(is_method);
439 } else {
440 panic!("Expected Lua variant");
441 }
442 }
443
444 #[test]
445 fn test_call_site_extras_perl() {
446 let extras = CallSiteExtras::Perl { is_method: true };
447 if let CallSiteExtras::Perl { is_method } = extras {
448 assert!(is_method);
449 } else {
450 panic!("Expected Perl variant");
451 }
452 }
453
454 #[test]
455 fn test_call_site_extras_shell() {
456 let extras = CallSiteExtras::Shell { is_builtin: true };
457 if let CallSiteExtras::Shell { is_builtin } = extras {
458 assert!(is_builtin);
459 } else {
460 panic!("Expected Shell variant");
461 }
462 }
463
464 #[test]
465 fn test_call_site_extras_groovy() {
466 let extras = CallSiteExtras::Groovy { is_closure: true };
467 if let CallSiteExtras::Groovy { is_closure } = extras {
468 assert!(is_closure);
469 } else {
470 panic!("Expected Groovy variant");
471 }
472 }
473
474 #[test]
475 fn test_call_site_extras_clone() {
476 let extras = CallSiteExtras::Rust {
477 is_async: true,
478 uses_await: true,
479 is_unsafe: false,
480 is_method: true,
481 is_unsafe_block: false,
482 };
483 let cloned = extras.clone();
484 assert_eq!(extras, cloned);
485 }
486
487 #[test]
489 fn test_call_site_with_heuristic_detection() {
490 let mut edge = make_test_edge();
491 edge.metadata.detection_method = DetectionMethod::Heuristic;
492 edge.metadata.confidence = 0.7;
493
494 let call_site = CallSite::from_edge(&edge, CallSiteExtras::None);
495 assert_eq!(
496 call_site.metadata.detection_method,
497 DetectionMethod::Heuristic
498 );
499 assert!((call_site.metadata.confidence - 0.7).abs() < f32::EPSILON);
500 }
501
502 #[test]
503 fn test_call_site_with_type_inference_detection() {
504 let mut edge = make_test_edge();
505 edge.metadata.detection_method = DetectionMethod::TypeInference;
506
507 let call_site = CallSite::from_edge(&edge, CallSiteExtras::None);
508 assert_eq!(
509 call_site.metadata.detection_method,
510 DetectionMethod::TypeInference
511 );
512 }
513
514 #[test]
515 fn test_call_site_from_edge_with_cpp_extras() {
516 let edge = make_test_edge();
517 let extras = CallSiteExtras::Cpp {
518 argument_count: 5,
519 is_template_instantiation: false,
520 };
521 let call_site = CallSite::from_edge(&edge, extras.clone());
522
523 assert_eq!(call_site.metadata.extras, extras);
524 }
525}