pub(crate) fn try_dominant_type(entries: &[&FunctionEntry]) -> Option<(String, f32, String)> {
let type_defs: Vec<&FunctionEntry> = entries
.iter()
.filter(|e| {
matches!(
e.definition_type,
DefinitionType::Struct | DefinitionType::Enum | DefinitionType::Trait
)
})
.copied()
.collect();
if type_defs.len() == 1 {
let name = &type_defs[0].function_name;
let snake = to_snake_case(name);
let kind = match type_defs[0].definition_type {
DefinitionType::Struct => "struct",
DefinitionType::Enum => "enum",
DefinitionType::Trait => "trait",
_ => "type",
};
return Some((
snake,
0.95,
format!(
"Dominant type ({kind} {name}) | {} definitions",
entries.len()
),
));
}
if type_defs.len() >= 2 {
let mut type_method_counts: Vec<(&str, usize)> = type_defs
.iter()
.map(|td| {
let snake = to_snake_case(&td.function_name);
let count = entries
.iter()
.filter(|e| {
e.definition_type == DefinitionType::Function
&& e.function_name.contains(&snake)
})
.count();
(td.function_name.as_str(), count)
})
.collect();
type_method_counts.sort_by(|a, b| b.1.cmp(&a.1));
if let Some((dominant_name, dominant_count)) = type_method_counts.first() {
let total_fns = entries
.iter()
.filter(|e| e.definition_type == DefinitionType::Function)
.count();
if total_fns > 0 && *dominant_count as f32 / total_fns as f32 > 0.5 {
let snake = to_snake_case(dominant_name);
return Some((
snake,
0.90,
format!(
"Dominant type ({dominant_name}, {dominant_count}/{total_fns} methods) | {} definitions",
entries.len()
),
));
}
}
}
None
}
fn try_existing_suffix(
file_path: &str,
entries: &[&FunctionEntry],
) -> Option<(String, f32, String)> {
let stem = Path::new(file_path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
let base = strip_part_segments(stem);
for (suffix, expansion) in SUFFIX_EXPANSIONS {
if base.ends_with(suffix) {
let context = find_context_word(entries);
let name = if let Some(ctx) = context {
format!("{ctx}_{expansion}")
} else {
expansion.to_string()
};
return Some((
name.clone(),
0.88,
format!(
"Suffix expanded ({suffix} → {expansion}) | {} definitions",
entries.len()
),
));
}
}
None
}
fn try_original_base(file_path: &str) -> Option<(String, f32, String)> {
let stem = Path::new(file_path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
let base = strip_part_segments(stem);
if base.len() < 4
|| base.len() > 30
|| base == stem
|| base.starts_with("mod_")
|| base.contains("_from_")
|| matches!(base.as_str(), "mod" | "lib" | "main" | "tests" | "test")
|| GENERIC_NAMES.contains(&base.as_str())
|| !is_valid_module_name(&base)
{
return None;
}
Some((
base.clone(),
0.82,
format!("Original filename base ({base})"),
))
}
pub(crate) fn try_function_theme(entries: &[&FunctionEntry]) -> Option<(String, f32, String)> {
let fn_entries: Vec<&&FunctionEntry> = entries
.iter()
.filter(|e| e.definition_type == DefinitionType::Function)
.collect();
if fn_entries.is_empty() {
return None;
}
let mut keyword_counts: HashMap<&str, usize> = HashMap::new();
for entry in &fn_entries {
let name_lower = entry.function_name.to_lowercase();
for (keyword, _) in THEME_KEYWORDS {
if name_lower.contains(keyword) {
*keyword_counts.entry(keyword).or_insert(0) += 1;
}
}
}
let total = fn_entries.len();
let mut best: Option<(&str, usize)> = None;
for (keyword, count) in &keyword_counts {
if best.map_or(true, |(_, bc)| *count > bc) {
best = Some((keyword, *count));
}
}
if let Some((keyword, count)) = best {
let share = count as f32 / total as f32;
if share >= 0.70 {
let theme_name = THEME_KEYWORDS
.iter()
.find(|(k, _)| *k == keyword)
.map(|(_, v)| *v)
.unwrap_or(keyword);
return Some((
theme_name.to_string(),
0.85,
format!(
"Function theme ({keyword}, {count}/{total} = {:.0}%) | {} definitions",
share * 100.0,
entries.len()
),
));
}
}
None
}
pub(crate) fn try_common_prefix(entries: &[&FunctionEntry]) -> Option<(String, f32, String)> {
let fn_names: Vec<&str> = entries
.iter()
.filter(|e| e.definition_type == DefinitionType::Function)
.map(|e| e.function_name.as_str())
.collect();
if fn_names.len() < 2 {
return None;
}
let prefix = longest_common_prefix(&fn_names);
let prefix = prefix.trim_end_matches('_');
if prefix.len() >= 4 {
return Some((
prefix.to_string(),
0.80,
format!("Common prefix ({prefix}_*) | {} functions", fn_names.len()),
));
}
None
}
pub(crate) fn try_doc_comment_consensus(
entries: &[&FunctionEntry],
) -> Option<(String, f32, String)> {
let docs: Vec<&str> = entries
.iter()
.filter_map(|e| e.doc_comment.as_deref())
.collect();
if docs.is_empty() {
return None;
}
let mut word_counts: HashMap<String, usize> = HashMap::new();
for doc in &docs {
let mut seen = std::collections::HashSet::new();
for word in doc.split_whitespace() {
let word = word
.trim_matches(|c: char| !c.is_alphanumeric())
.to_lowercase();
if word.len() >= 5
&& is_valid_module_name(&word)
&& !is_stopword(&word)
&& seen.insert(word.clone())
{
*word_counts.entry(word).or_insert(0) += 1;
}
}
}
let total = docs.len();
let mut best: Option<(String, usize)> = None;
for (word, count) in &word_counts {
if best.as_ref().map_or(true, |(_, bc)| count > bc) {
best = Some((word.clone(), *count));
}
}
if let Some((word, count)) = best {
let share = count as f32 / total as f32;
if share >= 0.50 && count >= 2 {
return Some((
word.clone(),
0.70,
format!(
"Doc comment consensus ({word}, {count}/{total} docs) | {} definitions",
entries.len()
),
));
}
}
None
}