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"
};
parsed
.iter()
.flat_map(|(file, _, syntax)| {
syntax
.items
.iter()
.flat_map(super::item_fns)
.filter(|(sig, _)| !is_conversion_fn(sig))
.filter_map(|(sig, block)| {
repetitive_match_find(block, file, sig.ident.span().start().line, suggest)
})
.collect::<Vec<_>>()
})
.collect()
}
fn is_conversion_fn(sig: &syn::Signature) -> bool {
CONVERSION_FN_NAMES.contains(&sig.ident.to_string().as_str())
}
fn repetitive_match_find(
block: &syn::Block,
file: &str,
line: usize,
suggest: &str,
) -> Option<BoilerplateFind> {
block.stmts.iter().find_map(|stmt| {
let m = stmt_match(stmt)?;
let is_mapping = !matches!(&*m.expr, syn::Expr::Tuple(_))
&& m.arms.len() >= MIN_REPETITIVE_MATCH_ARMS
&& is_repetitive_enum_mapping(&m.arms);
is_mapping.then(|| bp006(file, line, m.arms.len(), suggest))
})
}
fn stmt_match(stmt: &syn::Stmt) -> Option<&syn::ExprMatch> {
match stmt {
syn::Stmt::Expr(syn::Expr::Match(m), _) => Some(m),
syn::Stmt::Local(local) => match local.init.as_ref().map(|i| &*i.expr) {
Some(syn::Expr::Match(m)) => Some(m),
_ => None,
},
_ => None,
}
}
fn bp006(file: &str, line: usize, arms: usize, suggest: &str) -> BoilerplateFind {
BoilerplateFind {
pattern_id: "BP-006".to_string(),
file: file.to_string(),
line,
struct_name: None,
description: format!(
"Match with {arms} arms all mapping variants — may be replaceable with a derive"
),
suggestion: suggest.to_string(),
suppressed: false,
}
}