use std::collections::HashMap;
pub fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
})
.collect()
}
pub fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, ch) in s.chars().enumerate() {
if ch.is_uppercase() && i > 0 {
result.push('_');
}
result.push(ch.to_lowercase().next().unwrap());
}
result
}
pub fn is_primitive_type(type_name: &str) -> bool {
matches!(
type_name,
"String" | "str" | "usize" | "isize" | "u32" | "i32" | "u64" | "i64" |
"u8" | "i8" | "u16" | "i16" | "u128" | "i128" |
"f32" | "f64" | "bool" | "char" | "()" |
"Vec" | "Option" | "Result" | "Box" | "Rc" | "Arc" |
"HashMap" | "HashSet" | "BTreeMap" | "BTreeSet" |
"VecDeque" | "LinkedList" | "BinaryHeap" |
"Path" | "PathBuf" | "OsString" | "OsStr" |
"File" | "BufReader" | "BufWriter" |
"Cow" | "RefCell" | "Cell" | "Mutex" | "RwLock" |
"Error"
) || type_name.starts_with('&')
}
pub fn is_domain_type(type_name: &str) -> bool {
!is_primitive_type(type_name)
}
pub fn extract_base_type(type_name: &str) -> String {
let mut working = type_name;
working = working
.trim_start_matches('&')
.trim_start_matches("mut ")
.trim();
if let Some(start) = working.find('<') {
if let Some(end) = working.rfind('>') {
let inner = &working[start + 1..end];
if let Some(comma) = inner.find(',') {
return inner[..comma].trim().to_string();
}
return inner.trim().to_string();
}
}
working.to_string()
}
pub fn types_equivalent(type1: &str, type2: &str) -> bool {
if type1 == type2 {
return true;
}
let norm1 = normalize_type_for_comparison(type1);
let norm2 = normalize_type_for_comparison(type2);
norm1 == norm2
}
fn normalize_type_for_comparison(type_name: &str) -> String {
let base = extract_base_type(type_name);
if base == "str" {
"String".to_string()
} else {
base
}
}
pub fn extract_noun(type_name: &str) -> String {
const SUFFIXES: &[&str] = &[
"Location",
"Metrics",
"Data",
"Info",
"Details",
"Handler",
"Manager",
"Service",
"Provider",
"Factory",
"Builder",
"Analyzer",
"Processor",
"Controller",
"Context",
"Config",
"Settings",
"Result",
];
for suffix in SUFFIXES {
if type_name.ends_with(suffix) && type_name.len() > suffix.len() {
return type_name[..type_name.len() - suffix.len()].to_string();
}
}
type_name.to_string()
}
pub fn is_likely_verb(word: &str) -> bool {
word.ends_with("ing") || word.ends_with("tion") || word.ends_with("ment") || word.ends_with("sion") || word.ends_with("ance") || word.ends_with("ence") || matches!(
word,
"calculate" | "compute" | "process" | "handle" | "manage" |
"render" | "format" | "display" | "show" | "print" |
"validate" | "check" | "verify" | "ensure" |
"parse" | "transform" | "convert" | "serialize" | "deserialize" |
"get" | "set" | "update" | "modify" | "create" | "delete" |
"authenticate" | "authorize" | "encrypt" | "decrypt"
)
}
pub fn is_domain_term(word: &str) -> bool {
word.ends_with("metrics") ||
word.ends_with("data") ||
word.ends_with("config") ||
word.ends_with("settings") ||
word.ends_with("context") ||
word.ends_with("item") ||
word.ends_with("result") ||
(word.ends_with('s') && !word.ends_with("ss")) ||
matches!(
word,
"priority" | "god_object" | "debt" | "complexity" |
"coverage" | "analysis" | "report" | "summary"
)
}
pub fn most_common<T: std::hash::Hash + Eq + Clone>(items: &[T]) -> Option<T> {
let mut counts: HashMap<T, usize> = HashMap::new();
for item in items {
*counts.entry(item.clone()).or_insert(0) += 1;
}
counts
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|(item, _)| item)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_pascal_case() {
assert_eq!(to_pascal_case("priority_item"), "PriorityItem");
assert_eq!(to_pascal_case("god_object_metrics"), "GodObjectMetrics");
assert_eq!(to_pascal_case("single"), "Single");
}
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("PriorityItem"), "priority_item");
assert_eq!(to_snake_case("GodObjectMetrics"), "god_object_metrics");
assert_eq!(to_snake_case("single"), "single");
}
#[test]
fn test_extract_base_type() {
assert_eq!(extract_base_type("Option<Metrics>"), "Metrics");
assert_eq!(extract_base_type("Vec<Item>"), "Item");
assert_eq!(extract_base_type("Result<Data, Error>"), "Data");
assert_eq!(extract_base_type("&mut String"), "String");
assert_eq!(extract_base_type("String"), "String");
}
#[test]
fn test_types_equivalent() {
assert!(types_equivalent("Metrics", "Metrics"));
assert!(types_equivalent("Option<Metrics>", "Metrics"));
assert!(types_equivalent("&str", "String"));
assert!(!types_equivalent("Metrics", "Config"));
}
#[test]
fn test_extract_noun() {
assert_eq!(extract_noun("SourceLocation"), "Source");
assert_eq!(extract_noun("FileMetrics"), "File");
assert_eq!(extract_noun("UserData"), "User");
assert_eq!(extract_noun("Simple"), "Simple");
}
#[test]
fn test_is_likely_verb() {
assert!(is_likely_verb("rendering"));
assert!(is_likely_verb("calculation"));
assert!(is_likely_verb("format"));
assert!(!is_likely_verb("priority"));
assert!(!is_likely_verb("metrics"));
}
#[test]
fn test_is_domain_term() {
assert!(is_domain_term("priority"));
assert!(is_domain_term("metrics"));
assert!(is_domain_term("god_objects")); assert!(!is_domain_term("rendering"));
assert!(!is_domain_term("calculation"));
}
#[test]
fn test_most_common() {
let items = vec!["a", "b", "a", "c", "a", "b"];
assert_eq!(most_common(&items), Some("a"));
let empty: Vec<String> = vec![];
assert_eq!(most_common(&empty), None);
}
}