#[inline]
pub fn build_call_target(lang: &str, kind: &str, callee: &str) -> String {
format!("{lang}:{kind}::{callee}")
}
#[derive(Debug, PartialEq)]
pub struct UnresolvedTarget<'a> {
pub lang: &'a str,
pub kind: &'a str,
pub callee: &'a str,
}
pub fn parse_call_target(s: &str) -> Option<UnresolvedTarget<'_>> {
if !s.contains("::") {
return None;
}
let parts: Vec<&str> = s.splitn(4, ':').collect();
if parts.len() == 4 && parts[2].is_empty() {
Some(UnresolvedTarget {
lang: parts[0],
kind: parts[1],
callee: parts[3],
})
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unresolved_target_roundtrips() {
let t = build_call_target("rust", "Function", "my_fn");
assert_eq!(t, "rust:Function::my_fn");
let parsed = parse_call_target(&t).expect("should parse");
assert_eq!(parsed.lang, "rust");
assert_eq!(parsed.kind, "Function");
assert_eq!(parsed.callee, "my_fn");
}
#[test]
fn real_node_id_does_not_parse() {
assert!(parse_call_target("rust:Function:src/lib.rs:my_fn").is_none());
assert!(parse_call_target("csharp:Method:Foo.cs:MyClass:MyMethod").is_none());
}
#[test]
fn non_target_strings_return_none() {
assert!(parse_call_target("").is_none());
assert!(parse_call_target("hello").is_none());
assert!(parse_call_target("a:b:c").is_none());
}
#[test]
fn callee_with_colons_roundtrips() {
let t = build_call_target("rust", "Function", "std::mem::drop");
let parsed = parse_call_target(&t).expect("should parse");
assert_eq!(parsed.callee, "std::mem::drop");
}
}