use super::{is_repetitive_enum_mapping, BoilerplateFind};
use crate::config::sections::BoilerplateConfig;
const MIN_REPETITIVE_MATCH_ARMS: usize = 4;
const CONVERSION_FN_NAMES: &[&str] = &["from_str", "from_str_opt", "from", "try_from", "fmt"];
pub(super) fn check_repetitive_match(
parsed: &[(String, String, syn::File)],
config: &BoilerplateConfig,
) -> Vec<BoilerplateFind> {
pattern_guard!("BP-006", config);
let suggest = if config.suggest_crates {
"Consider using strum or a conversion derive macro"
} else {
"Consider using a derive macro or Into/From trait for enum mapping"
};
let mut findings = Vec::new();
for (file, _, syntax) in parsed {
let check_body = |block: &syn::Block, line: usize| {
for stmt in &block.stmts {
let match_expr = match stmt {
syn::Stmt::Expr(syn::Expr::Match(m), _) => m,
syn::Stmt::Local(local) => {
if let Some(init) = &local.init {
if let syn::Expr::Match(m) = &*init.expr {
m
} else {
continue;
}
} else {
continue;
}
}
_ => continue,
};
if matches!(&*match_expr.expr, syn::Expr::Tuple(_)) {
continue;
}
if match_expr.arms.len() >= MIN_REPETITIVE_MATCH_ARMS
&& is_repetitive_enum_mapping(&match_expr.arms)
{
return Some(BoilerplateFind {
pattern_id: "BP-006".to_string(),
file: file.clone(),
line,
struct_name: None,
description: format!(
"Match with {} arms all mapping variants — may be replaceable with a derive",
match_expr.arms.len()
),
suggestion: suggest.to_string(),
});
}
}
None
};
for item in &syntax.items {
match item {
syn::Item::Fn(f) => {
let fn_name = f.sig.ident.to_string();
if CONVERSION_FN_NAMES.contains(&fn_name.as_str()) {
continue;
}
if let Some(finding) = check_body(&f.block, f.sig.ident.span().start().line) {
findings.push(finding);
}
}
syn::Item::Impl(imp) => {
for sub in &imp.items {
if let syn::ImplItem::Fn(m) = sub {
let fn_name = m.sig.ident.to_string();
if CONVERSION_FN_NAMES.contains(&fn_name.as_str()) {
continue;
}
if let Some(finding) =
check_body(&m.block, m.sig.ident.span().start().line)
{
findings.push(finding);
}
}
}
}
_ => {}
}
}
}
findings
}