use crate::Range;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct MethodCall {
pub caller: String,
pub method_name: String,
pub receiver: Option<String>,
pub is_static: bool,
pub range: Range,
pub caller_range: Option<Range>,
}
impl MethodCall {
pub fn new(caller: &str, method_name: &str, range: Range) -> Self {
Self {
caller: caller.to_string(),
method_name: method_name.to_string(),
receiver: None,
is_static: false,
range,
caller_range: None,
}
}
pub fn with_receiver(mut self, receiver: &str) -> Self {
self.receiver = Some(receiver.to_string());
self
}
pub fn static_method(mut self) -> Self {
self.is_static = true;
self
}
pub fn with_caller_range(mut self, range: Range) -> Self {
self.caller_range = Some(range);
self
}
#[inline]
pub fn is_self_call(&self) -> bool {
self.receiver.as_deref() == Some("self")
}
#[inline]
pub fn is_function_call(&self) -> bool {
self.receiver.is_none()
}
#[must_use = "The formatted name should be used"]
pub fn qualified_name(&self) -> String {
match (&self.receiver, self.is_static) {
(Some(receiver), true) => format!("{receiver}::{}", self.method_name),
(Some(receiver), false) => format!("{receiver}.{}", self.method_name),
(None, _) => self.method_name.clone(),
}
}
}
#[derive(Debug, Default)]
pub struct MethodCallResolver {
variable_types: HashMap<String, String>,
method_calls: Vec<MethodCall>,
}
impl MethodCallResolver {
pub fn new() -> Self {
Self::default()
}
pub fn register_variable_type(&mut self, var: &str, type_name: &str) {
self.variable_types
.insert(var.to_string(), type_name.to_string());
}
pub fn add_method_call(&mut self, call: MethodCall) -> bool {
let exists = self.method_calls.iter().any(|mc| {
mc.caller == call.caller
&& mc.method_name == call.method_name
&& mc.range.start_line == call.range.start_line
&& mc.range.start_column == call.range.start_column
});
if exists {
return false;
}
self.method_calls.push(call);
true
}
pub fn variable_types(&self) -> &HashMap<String, String> {
&self.variable_types
}
pub fn find_method_call(&self, caller: &str, method_name: &str) -> Option<&MethodCall> {
self.method_calls
.iter()
.find(|mc| mc.caller == caller && mc.method_name == method_name)
}
pub fn method_calls(&self) -> &[MethodCall] {
&self.method_calls
}
pub fn has_method_call_at(
&self,
caller: &str,
method_name: &str,
line: u32,
column: u16,
) -> bool {
self.method_calls.iter().any(|mc| {
mc.caller == caller
&& mc.method_name == method_name
&& mc.range.start_line == line
&& mc.range.start_column == column
})
}
pub fn is_empty(&self) -> bool {
self.method_calls.is_empty() && self.variable_types.is_empty()
}
pub fn clear(&mut self) {
self.variable_types.clear();
self.method_calls.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_range() -> Range {
Range::new(10, 5, 10, 20)
}
#[test]
fn test_basic_construction() {
let call = MethodCall::new("main", "process", test_range());
assert_eq!(call.caller, "main");
assert_eq!(call.method_name, "process");
assert_eq!(call.receiver, None);
assert!(!call.is_static);
}
#[test]
fn test_builder_pattern() {
let call = MethodCall::new("handler", "clone", test_range()).with_receiver("data");
assert_eq!(call.receiver, Some("data".to_string()));
assert!(!call.is_static);
}
#[test]
fn test_static_method() {
let call = MethodCall::new("main", "new", test_range())
.with_receiver("HashMap")
.static_method();
assert_eq!(call.receiver, Some("HashMap".to_string()));
assert!(call.is_static);
}
#[test]
fn test_helper_methods() {
let self_call = MethodCall::new("foo", "bar", test_range()).with_receiver("self");
assert!(self_call.is_self_call());
assert!(!self_call.is_function_call());
let func_call = MethodCall::new("main", "println", test_range());
assert!(!func_call.is_self_call());
assert!(func_call.is_function_call());
}
#[test]
fn test_qualified_name() {
let static_call = MethodCall::new("main", "new", test_range())
.with_receiver("Vec")
.static_method();
assert_eq!(static_call.qualified_name(), "Vec::new");
let instance_call = MethodCall::new("process", "push", test_range()).with_receiver("items");
assert_eq!(instance_call.qualified_name(), "items.push");
let func_call = MethodCall::new("main", "println", test_range());
assert_eq!(func_call.qualified_name(), "println");
}
#[test]
fn test_equality() {
let call1 = MethodCall::new("main", "test", test_range()).with_receiver("obj");
let call2 = MethodCall::new("main", "test", test_range()).with_receiver("obj");
let call3 = MethodCall::new("main", "test", test_range()).with_receiver("other");
assert_eq!(call1, call2);
assert_ne!(call1, call3);
}
#[test]
fn test_resolver_new() {
let resolver = MethodCallResolver::new();
assert!(resolver.is_empty());
assert!(resolver.variable_types().is_empty());
assert!(resolver.method_calls().is_empty());
}
#[test]
fn test_resolver_variable_types() {
let mut resolver = MethodCallResolver::new();
resolver.register_variable_type("calc", "Calculator");
resolver.register_variable_type("items", "Vec<Item>");
assert_eq!(
resolver.variable_types().get("calc"),
Some(&"Calculator".to_string())
);
assert_eq!(
resolver.variable_types().get("items"),
Some(&"Vec<Item>".to_string())
);
assert_eq!(resolver.variable_types().get("unknown"), None);
}
#[test]
fn test_resolver_add_method_call() {
let mut resolver = MethodCallResolver::new();
let call1 =
MethodCall::new("process", "add", Range::new(5, 4, 5, 14)).with_receiver("calc");
let call2 =
MethodCall::new("process", "multiply", Range::new(6, 4, 6, 18)).with_receiver("calc");
assert!(resolver.add_method_call(call1));
assert!(resolver.add_method_call(call2));
assert_eq!(resolver.method_calls().len(), 2);
}
#[test]
fn test_resolver_deduplication() {
let mut resolver = MethodCallResolver::new();
let call1 =
MethodCall::new("process", "add", Range::new(5, 4, 5, 14)).with_receiver("calc");
let call2 =
MethodCall::new("process", "add", Range::new(5, 4, 5, 14)).with_receiver("calc");
let call3 =
MethodCall::new("process", "add", Range::new(5, 4, 5, 14)).with_receiver("other");
assert!(resolver.add_method_call(call1)); assert!(!resolver.add_method_call(call2)); assert!(!resolver.add_method_call(call3));
assert_eq!(resolver.method_calls().len(), 1);
}
#[test]
fn test_resolver_find_method_call() {
let mut resolver = MethodCallResolver::new();
let call = MethodCall::new("process", "add", test_range()).with_receiver("calc");
resolver.add_method_call(call);
assert!(resolver.find_method_call("process", "add").is_some());
assert!(resolver.find_method_call("process", "subtract").is_none());
assert!(resolver.find_method_call("other", "add").is_none());
}
#[test]
fn test_resolver_clear() {
let mut resolver = MethodCallResolver::new();
resolver.register_variable_type("calc", "Calculator");
resolver.add_method_call(MethodCall::new("process", "add", test_range()));
assert!(!resolver.is_empty());
resolver.clear();
assert!(resolver.is_empty());
assert!(resolver.variable_types().is_empty());
assert!(resolver.method_calls().is_empty());
}
#[test]
fn test_resolver_integration() {
let mut resolver = MethodCallResolver::new();
resolver.register_variable_type("calc", "Calculator");
resolver.register_variable_type("items", "Vec<i32>");
let call1 = MethodCall::new("process_numbers", "add", Range::new(7, 4, 7, 14))
.with_receiver("calc");
let call2 = MethodCall::new("process_numbers", "multiply", Range::new(8, 4, 8, 18))
.with_receiver("calc");
let call3 = MethodCall::new("compute_total", "add", Range::new(16, 8, 16, 18))
.with_receiver("calc");
resolver.add_method_call(call1);
resolver.add_method_call(call2);
resolver.add_method_call(call3);
let mc = resolver.find_method_call("process_numbers", "add").unwrap();
assert_eq!(mc.receiver.as_deref(), Some("calc"));
let receiver_type = resolver.variable_types().get(mc.receiver.as_ref().unwrap());
assert_eq!(receiver_type, Some(&"Calculator".to_string()));
}
}