use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MatchConfidence {
None = 0,
Low = 1, Medium = 2, High = 3, }
pub fn generate_function_name_variants(name: &str) -> Vec<String> {
let mut variants = Vec::with_capacity(4);
variants.push(name.to_string());
if let Some(without_generics) = name.split('<').next() {
if without_generics != name && !without_generics.is_empty() {
variants.push(without_generics.to_string());
}
}
if let Some(method_name) = name.rsplit("::").next() {
if method_name != name && !method_name.is_empty() {
if !variants.contains(&method_name.to_string()) {
variants.push(method_name.to_string());
}
if let Some(method_no_generics) = method_name.split('<').next() {
if method_no_generics != method_name && !method_no_generics.is_empty() {
variants.push(method_no_generics.to_string());
}
}
}
}
let mut seen = HashSet::new();
variants.retain(|v| seen.insert(v.clone()));
variants
}
pub fn extract_closure_parent(name: &str) -> Option<String> {
if !name.contains("{{closure}}") {
return None;
}
name.split("::{{closure}}")
.next()
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
}
pub fn function_names_match(query: &str, lcov: &str) -> (bool, MatchConfidence) {
if query == lcov {
return (true, MatchConfidence::High);
}
if let Some(parent) = extract_closure_parent(lcov) {
if query == parent {
return (true, MatchConfidence::High);
}
}
if let Some(parent) = extract_closure_parent(query) {
if parent == lcov {
return (true, MatchConfidence::High);
}
}
let query_variants = generate_function_name_variants(query);
let lcov_variants = generate_function_name_variants(lcov);
for qv in &query_variants {
for lv in &lcov_variants {
if qv == lv {
return (true, MatchConfidence::Medium);
}
}
}
if query.contains(lcov) || lcov.contains(query) {
return (true, MatchConfidence::Low);
}
(false, MatchConfidence::None)
}
#[derive(Debug, Clone)]
pub struct MatchableFunction<T> {
pub name: String,
pub data: T,
}
pub fn find_matching_function<'a, T>(
query_name: &str,
available_functions: &'a [MatchableFunction<T>],
) -> Option<(&'a MatchableFunction<T>, MatchConfidence)> {
let mut best_match: Option<(&MatchableFunction<T>, MatchConfidence)> = None;
for func in available_functions {
let (matches, confidence) = function_names_match(query_name, &func.name);
if !matches {
continue;
}
if let Some((_, best_confidence)) = best_match {
if confidence > best_confidence {
best_match = Some((func, confidence));
}
} else {
best_match = Some((func, confidence));
}
if confidence == MatchConfidence::High {
break;
}
}
best_match
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_variants_simple() {
let variants = generate_function_name_variants("simple_func");
assert_eq!(variants, vec!["simple_func"]);
}
#[test]
fn test_generate_variants_type_method() {
let variants = generate_function_name_variants("Type::method");
assert!(variants.contains(&"Type::method".to_string()));
assert!(variants.contains(&"method".to_string()));
assert_eq!(variants.len(), 2);
}
#[test]
fn test_generate_variants_with_generics() {
let variants = generate_function_name_variants("process<T, U>");
assert!(variants.contains(&"process<T, U>".to_string()));
assert!(variants.contains(&"process".to_string()));
assert_eq!(variants.len(), 2);
}
#[test]
fn test_generate_variants_type_method_with_generics() {
let variants = generate_function_name_variants("Type::method<T>");
assert!(variants.contains(&"Type::method<T>".to_string()));
assert!(variants.contains(&"Type::method".to_string()));
assert!(variants.contains(&"method<T>".to_string()));
assert!(variants.contains(&"method".to_string()));
assert_eq!(variants.len(), 4);
}
#[test]
fn test_generate_variants_nested_path() {
let variants = generate_function_name_variants("crate::module::Type::method<T>");
assert!(variants.contains(&"method".to_string()));
assert!(variants.contains(&"method<T>".to_string()));
assert_eq!(variants.len(), 4);
}
#[test]
fn test_generate_variants_nested_path_no_generics() {
let variants = generate_function_name_variants("crate::module::Type::method");
assert!(variants.contains(&"crate::module::Type::method".to_string()));
assert!(variants.contains(&"method".to_string()));
assert_eq!(variants.len(), 2);
}
#[test]
fn test_generate_variants_empty_string() {
let variants = generate_function_name_variants("");
assert_eq!(variants, vec![""]);
}
#[test]
fn test_generate_variants_unicode() {
let variants = generate_function_name_variants("测试函数");
assert!(variants.contains(&"测试函数".to_string()));
assert_eq!(variants.len(), 1);
}
#[test]
fn test_generate_variants_very_long_name() {
let long_name = "a".repeat(1000);
let variants = generate_function_name_variants(&long_name);
assert_eq!(variants.len(), 1);
assert_eq!(variants[0], long_name);
}
#[test]
fn test_generate_variants_special_characters() {
let variants = generate_function_name_variants("func_with_$special");
assert_eq!(variants.len(), 1);
assert_eq!(variants[0], "func_with_$special");
}
#[test]
fn test_extract_closure_parent_basic() {
assert_eq!(
extract_closure_parent("async_fn::{{closure}}"),
Some("async_fn".to_string())
);
}
#[test]
fn test_extract_closure_parent_numbered() {
assert_eq!(
extract_closure_parent("process::{{closure}}#0"),
Some("process".to_string())
);
}
#[test]
fn test_extract_closure_parent_nested() {
assert_eq!(
extract_closure_parent("module::Type::method::{{closure}}"),
Some("module::Type::method".to_string())
);
}
#[test]
fn test_extract_closure_parent_regular_function() {
assert_eq!(extract_closure_parent("regular_function"), None);
}
#[test]
fn test_extract_closure_parent_empty_parent() {
assert_eq!(extract_closure_parent("::{{closure}}"), None);
}
#[test]
fn test_function_names_match_exact() {
let (matches, confidence) = function_names_match("foo", "foo");
assert!(matches);
assert_eq!(confidence, MatchConfidence::High);
}
#[test]
fn test_function_names_match_variant() {
let (matches, confidence) = function_names_match("Type::method", "method");
assert!(matches);
assert_eq!(confidence, MatchConfidence::Medium);
}
#[test]
fn test_function_names_match_variant_reverse() {
let (matches, confidence) = function_names_match("method", "Type::method");
assert!(matches);
assert_eq!(confidence, MatchConfidence::Medium);
}
#[test]
fn test_function_names_match_closure() {
let (matches, confidence) = function_names_match("async_fn", "async_fn::{{closure}}");
assert!(matches);
assert_eq!(confidence, MatchConfidence::High);
}
#[test]
fn test_function_names_match_closure_reverse() {
let (matches, confidence) = function_names_match("async_fn::{{closure}}", "async_fn");
assert!(matches);
assert_eq!(confidence, MatchConfidence::High);
}
#[test]
fn test_function_names_match_with_generics() {
let (matches, confidence) = function_names_match("process<T>", "process");
assert!(matches);
assert_eq!(confidence, MatchConfidence::Medium);
}
#[test]
fn test_function_names_match_fuzzy_contains() {
let (matches, confidence) =
function_names_match("RecursiveDetector::visit_expr", "visit_expr");
assert!(matches);
assert_eq!(confidence, MatchConfidence::Medium);
}
#[test]
fn test_function_names_match_no_match() {
let (matches, confidence) = function_names_match("foo", "bar");
assert!(!matches);
assert_eq!(confidence, MatchConfidence::None);
}
#[test]
fn test_function_names_match_trait_impl() {
let (matches, confidence) = function_names_match("Visitor::visit_expr", "visit_expr");
assert!(matches);
assert_eq!(confidence, MatchConfidence::Medium);
}
#[test]
fn variant_generation_never_panics_sample() {
let long_name = "a".repeat(1000);
let test_cases = vec![
"",
"a",
":::",
"<<<",
">>>",
"a::b::c::d::e",
"func<>",
"{{closure}}",
"测试",
&long_name,
];
for name in test_cases {
let _ = generate_function_name_variants(name);
}
}
#[test]
fn original_always_in_variants() {
let test_cases = vec!["simple", "Type::method", "func<T>", "a::b::c"];
for name in test_cases {
let variants = generate_function_name_variants(name);
assert!(
variants.contains(&name.to_string()),
"Original name '{}' not in variants: {:?}",
name,
variants
);
}
}
#[test]
fn matching_is_reflexive() {
let test_cases = vec![
"simple",
"Type::method",
"func<T>",
"async_fn::{{closure}}",
"测试",
];
for name in test_cases {
let (matches, _) = function_names_match(name, name);
assert!(matches, "Name '{}' should match itself", name);
}
}
}