use syn::Item;
use syn::ItemMod;
use syn::ItemTrait;
use syn::TraitItem;
use syn::TraitItemFn;
use syn::parse_file;
use crate::reporting::offence::Offence;
use crate::rule::Rule;
use crate::source_file::SourceFile;
pub struct PureTraitsRule;
impl PureTraitsRule {
pub const SOURCE_ROOT: &'static str = "src/";
pub fn new() -> Self {
Self
}
fn applies_to(file: &SourceFile) -> bool {
file.relative_path().starts_with(Self::SOURCE_ROOT)
}
fn declarations(items: &[Item]) -> Vec<&ItemTrait> {
items
.iter()
.flat_map(|item| match item {
Item::Trait(declaration) => vec![declaration],
Item::Mod(module) => Self::inside(module)
.map(Self::declarations)
.unwrap_or_default(),
_ => Vec::new(),
})
.collect()
}
fn inside(module: &ItemMod) -> Option<&[Item]> {
module.content.as_ref().map(|(_, items)| items.as_slice())
}
fn bodies(&self, file: &SourceFile, declaration: &ItemTrait) -> Vec<Offence> {
let declared_by = declaration.ident.to_string();
declaration
.items
.iter()
.filter_map(|item| match item {
TraitItem::Fn(method) if method.default.is_some() => Some(method),
_ => None,
})
.map(|method| self.offence(file, &declared_by, method))
.collect()
}
fn offence(&self, file: &SourceFile, declared_by: &str, method: &TraitItemFn) -> Offence {
let subject = format!("{declared_by}::{}", method.sig.ident);
Offence::new(
file.relative_path(),
method.sig.ident.span().start().line,
self.name(),
format!(
"`{subject}` has a default body, so an implementor that says nothing about it \
cannot be told from one that chose it"
),
"move the body into each implementor".to_string(),
)
.with_subject(&subject)
}
}
impl Default for PureTraitsRule {
fn default() -> Self {
Self::new()
}
}
impl Rule for PureTraitsRule {
fn name(&self) -> &'static str {
"pure-traits"
}
fn check(&self, file: &SourceFile) -> Vec<Offence> {
if !Self::applies_to(file) {
return Vec::new();
}
let Ok(syntax) = parse_file(&file.contents()) else {
return Vec::new();
};
Self::declarations(&syntax.items)
.into_iter()
.flat_map(|declaration| self.bodies(file, declaration))
.collect()
}
fn check_workspace(&self, _files: &[SourceFile]) -> Vec<Offence> {
Vec::new()
}
fn is_configured(&self) -> bool {
true
}
}