Skip to main content

wowlab_tidy/languages/rust/rules/api/
non_exhaustive_on_public.rs

1use ra_ap_syntax::ast::{self, HasAttrs, HasName, HasVisibility, VisibilityKind};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "public enum without non_exhaustive",
9        code: "pub enum Color { Red, Green, Blue }",
10        pass: false,
11    },
12    Example {
13        label: "public enum with non_exhaustive",
14        code: "#[non_exhaustive]\npub enum Color { Red, Green, Blue }",
15        pass: true,
16    },
17    Example {
18        label: "private enum without non_exhaustive",
19        code: "enum Color { Red, Green, Blue }",
20        pass: true,
21    },
22    Example {
23        label: "pub(crate) enum without non_exhaustive",
24        code: "pub(crate) enum Color { Red, Green, Blue }",
25        pass: true,
26    },
27    Example {
28        label: "public enum in test",
29        code: "#[cfg(test)]\nmod tests {\n    pub enum Color { Red, Green, Blue }\n}",
30        pass: true,
31    },
32];
33
34crate::ast_rule!(
35    non_exhaustive_on_public,
36    "Flag public enums without `#[non_exhaustive]` — prevents breaking changes when adding variants.",
37    "Adding a variant to a public enum is a breaking change. #[non_exhaustive] lets you add variants without a major version bump.",
38    Medium,
39);
40
41fn check_non_exhaustive_on_public(ctx: &AstCtx<'_>) -> Vec<Violation> {
42    let public_enums = ctx
43        .nodes::<ast::Enum>()
44        .filter(|item| !ctx.is_in_test(item))
45        .filter(|item| {
46            item.visibility()
47                .is_some_and(|vis| matches!(vis.kind(), VisibilityKind::Pub))
48        });
49
50    public_enums
51        .filter(|item| {
52            !item
53                .attrs()
54                .any(|attr| attr.simple_name().as_deref() == Some("non_exhaustive"))
55        })
56        .filter_map(|item| {
57            let name = item.name()?;
58
59            Some(ctx.violation(
60                &name,
61                format!(
62                    "public enum `{name}` should have `#[non_exhaustive]` to allow adding variants without breaking downstream"
63                ),
64            ))
65        })
66        .collect()
67}
68
69crate::tidy_ast_test!(check_non_exhaustive_on_public, {
70    crate::example_tests!(EXAMPLES, check_non_exhaustive_on_public);
71});