wowlab_tidy/languages/rust/rules/api/
builder_param.rs1use 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});