1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use syn::spanned::Spanned;
use super::{
is_default_value_expr, self_type_of, single_return_expr, trait_name_of, BoilerplateFind,
};
use crate::config::sections::BoilerplateConfig;
/// Detect `impl Default` where all fields use default values.
/// Operation: AST pattern matching logic; helper calls in closures.
pub(super) fn check_manual_default(
parsed: &[(String, String, syn::File)],
config: &BoilerplateConfig,
) -> Vec<BoilerplateFind> {
pattern_guard!("BP-005", config);
parsed
.iter()
.flat_map(|(file, _, syntax)| {
syntax.items.iter().filter_map({
let file = file.clone();
move |item| {
let imp = if let syn::Item::Impl(imp) = item {
imp
} else {
return None;
};
if trait_name_of(imp).as_deref() != Some("Default") {
return None;
}
let methods: Vec<_> = imp
.items
.iter()
.filter_map(|i| {
if let syn::ImplItem::Fn(m) = i {
Some(m)
} else {
None
}
})
.collect();
if methods.len() != 1 || methods[0].sig.ident != "default" {
return None;
}
let expr = single_return_expr(&methods[0].block)?;
// Must be a struct literal with all default-value fields
let all_defaults = if let syn::Expr::Struct(s) = expr {
s.rest.is_none()
&& !s.fields.is_empty()
&& s.fields.iter().all(|f| is_default_value_expr(&f.expr))
} else {
false
};
if !all_defaults {
return None;
}
Some(BoilerplateFind {
pattern_id: "BP-005".to_string(),
file: file.clone(),
line: imp.self_ty.span().start().line,
struct_name: self_type_of(imp),
description:
"Manual Default implementation where all fields use default values"
.to_string(),
suggestion: "Consider using #[derive(Default)]".to_string(),
})
}
})
})
.collect()
}