wowlab_tidy/languages/rust/rules/api/
suspicious_enum_default.rs1use ra_ap_syntax::ast::{self, HasAttrs, HasName};
2
3use super::support::has_derive;
4use crate::{AstCtx, Example, Violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8 Example {
9 label: "derive Default without marker",
10 code: "#[derive(Default)]\nenum E { A, B }",
11 pass: false,
12 },
13 Example {
14 label: "derive Default with marker",
15 code: "#[derive(Default)]\nenum E { #[default] A, B }",
16 pass: true,
17 },
18 Example {
19 label: "no derive Default",
20 code: "enum E { A, B }",
21 pass: true,
22 },
23 Example {
24 label: "derive other traits only",
25 code: "#[derive(Debug, Clone)]\nenum E { A, B }",
26 pass: true,
27 },
28 Example {
29 label: "derive Default in test module",
30 code: "#[cfg(test)]\nmod tests {\n #[derive(Default)]\n enum E { A, B }\n}",
31 pass: true,
32 },
33];
34
35crate::ast_rule!(
36 suspicious_enum_default,
37 "Flag `#[derive(Default)]` on enums without explicit `#[default]` variant.",
38 "derive(Default) on an enum without #[default] silently picks the first variant, which may not be the intended default.",
39 Medium,
40);
41
42fn check_suspicious_enum_default(ctx: &AstCtx<'_>) -> Vec<Violation> {
43 let default_enums = ctx
44 .nodes::<ast::Enum>()
45 .filter(|item| !ctx.is_in_test(item))
46 .filter(|item| has_derive(item, "Default"));
47
48 default_enums
49 .filter(|item| {
50 item.variant_list().is_none_or(|variants| {
51 !variants.variants().any(|variant| {
52 variant
53 .attrs()
54 .any(|attr| attr.simple_name().as_deref() == Some("default"))
55 })
56 })
57 })
58 .filter_map(|item| {
59 let name = item.name()?;
60
61 Some(ctx.violation(
62 &name,
63 format!(
64 "enum `{name}` derives Default without a #[default] variant — mark the intended default explicitly"
65 ),
66 ))
67 })
68 .collect()
69}
70
71crate::tidy_ast_test!(check_suspicious_enum_default, {
72 crate::example_tests!(EXAMPLES, check_suspicious_enum_default);
73});