Skip to main content

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

1use ra_ap_syntax::{ast, ast::HasName};
2
3use super::support::type_name;
4use crate::{AstCtx, Example, Violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8    Example {
9        label: "fallible setter",
10        code: "struct HostBuilder;\nimpl HostBuilder {\n    fn port(self, port: u16) -> Result<Self, String> {\n        Ok(self)\n    }\n    fn build(self) -> u32 {\n        0\n    }\n}",
11        pass: false,
12    },
13    Example {
14        label: "fallible build",
15        code: "struct HostBuilder;\nimpl HostBuilder {\n    fn port(self, port: u16) -> Self {\n        self\n    }\n    fn build(self) -> Result<u32, String> {\n        Ok(0)\n    }\n}",
16        pass: true,
17    },
18    Example {
19        label: "fallible try_build and finish",
20        code: "struct HostBuilder;\nimpl HostBuilder {\n    fn try_build(self) -> Result<u32, String> {\n        Ok(0)\n    }\n    fn finish(self) -> Result<u32, String> {\n        Ok(0)\n    }\n}",
21        pass: true,
22    },
23    Example {
24        label: "fallible associated fn",
25        code: "struct HostBuilder;\nimpl HostBuilder {\n    fn parse(input: &str) -> Result<Self, String> {\n        Ok(HostBuilder)\n    }\n}",
26        pass: true,
27    },
28    Example {
29        label: "fallible method on non-builder",
30        code: "struct Config;\nimpl Config {\n    fn set(self, key: u32) -> Result<Self, String> {\n        Ok(self)\n    }\n}",
31        pass: true,
32    },
33    Example {
34        label: "fallible setter in test module",
35        code: "#[cfg(test)]\nmod tests {\n    struct HostBuilder;\n    impl HostBuilder {\n        fn port(self, port: u16) -> Result<Self, String> {\n            Ok(self)\n        }\n    }\n}",
36        pass: true,
37    },
38];
39
40const ALLOWED_FALLIBLE: &[&str] = &["build", "try_build", "finish"];
41
42crate::ast_rule!(
43    builder_fallible_setter,
44    "Flag builder setters returning `Result` — setters accept infallibly, validation belongs in `build()`.",
45    "Fallible setters force repeated error checks that add noise and still cannot guard interdependent conditions; a Result-carrying build() consolidates validation.",
46    Medium,
47);
48
49fn check_builder_fallible_setter(ctx: &AstCtx<'_>) -> Vec<Violation> {
50    let builders = ctx
51        .nodes::<ast::Impl>()
52        .filter(|item| !ctx.is_in_test(item) && item.trait_().is_none())
53        .filter(|item| {
54            item.self_ty()
55                .is_some_and(|ty| type_name(&ty).is_some_and(|name| name.ends_with("Builder")))
56        });
57
58    builders
59        .flat_map(|item| {
60            let associated_items = item
61                .assoc_item_list()
62                .into_iter()
63                .flat_map(|list| list.assoc_items());
64            let setters = associated_items
65                .filter_map(|assoc| match assoc {
66                    ast::AssocItem::Fn(function) => Some(function),
67                    _ => None,
68                })
69                .filter(|function| function.param_list().is_some_and(|params| params.self_param().is_some()));
70
71            setters
72                .filter(|function| {
73                    let return_type = function
74                        .ret_type()
75                        .and_then(|ret| ret.ty())
76                        .and_then(|ty| type_name(&ty));
77
78                    return_type
79                        .is_some_and(|name| name == "Result")
80                })
81                .filter_map(|function| {
82                    let name = function.name()?;
83                    let text = name.text().to_string();
84
85                    (!ALLOWED_FALLIBLE.contains(&text.as_str())).then(|| {
86                        ctx.violation(
87                            &name,
88                            format!(
89                                "fallible builder setter `{text}` — accept infallibly and validate in `build()`"
90                            ),
91                        )
92                    })
93                })
94                .collect::<Vec<_>>()
95        })
96        .collect()
97}
98
99crate::tidy_ast_test!(check_builder_fallible_setter, {
100    crate::example_tests!(EXAMPLES, check_builder_fallible_setter);
101});