Skip to main content

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

1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "builder parameter",
9        code: "fn make(b: WidgetBuilder) {}",
10        pass: false,
11    },
12    Example {
13        label: "factory reference parameter",
14        code: "fn make(f: &WidgetFactory) {}",
15        pass: false,
16    },
17    Example {
18        label: "builder parameter on method",
19        code: "struct App;\nimpl App {\n    fn install(&self, b: WidgetBuilder) {}\n}",
20        pass: false,
21    },
22    Example {
23        label: "closure factory parameter",
24        code: "fn make(f: impl Fn() -> Widget) {}",
25        pass: true,
26    },
27    Example {
28        label: "plain parameter",
29        code: "fn make(w: Widget) {}",
30        pass: true,
31    },
32    Example {
33        label: "builder parameter in test module",
34        code: "#[cfg(test)]\nmod tests {\n    fn make(b: WidgetBuilder) {}\n}",
35        pass: true,
36    },
37];
38
39crate::ast_rule!(
40    builder_param,
41    "Flag parameters typed `*Builder`/`*Factory` — ask for `impl Fn() -> T` instead.",
42    "Accepting factories or builders as parameters imports OO indirection; an impl Fn() -> T expresses repeatable instantiation idiomatically.",
43    Low,
44);
45
46fn check_builder_param(ctx: &AstCtx<'_>) -> Vec<Violation> {
47    ctx.nodes::<ast::Fn>()
48        .filter(|function| !ctx.is_in_test(function))
49        .flat_map(|function| {
50            function
51                .param_list()
52                .into_iter()
53                .flat_map(|params| params.params())
54                .filter_map(|param| {
55                    let ty = param.ty()?;
56                    let ident = weasel_param_ident(&ty)?;
57
58                    Some(ctx.violation(
59                        &ty,
60                        format!(
61                            "parameter of type `{ident}` — accept `impl Fn() -> T` instead of a builder/factory"
62                        ),
63                    ))
64                })
65                .collect::<Vec<_>>()
66        })
67        .collect()
68}
69
70fn weasel_param_ident(ty: &ast::Type) -> Option<String> {
71    let inner = match ty {
72        ast::Type::RefType(reference) => reference.ty()?,
73        _ => ty.clone(),
74    };
75    let ast::Type::PathType(path) = inner else {
76        return None;
77    };
78    let name = path.path()?.segment()?.name_ref()?.text().to_string();
79
80    (name.ends_with("Builder") || name.ends_with("Factory")).then_some(name)
81}
82
83crate::tidy_ast_test!(check_builder_param, {
84    crate::example_tests!(EXAMPLES, check_builder_param);
85});