Skip to main content

wowlab_tidy/languages/rust/rules/style/
redundant_field_names.rs

1use ra_ap_syntax::{AstNode, ast};
2
3use crate::{AstCtx, Example, Fix, Violation, infra::parse};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "redundant field name",
9        code: "struct S { x: i32 }\nfn f(x: i32) -> S { S { x: x } }",
10        pass: false,
11    },
12    Example {
13        label: "shorthand field init",
14        code: "struct S { x: i32 }\nfn f(x: i32) -> S { S { x } }",
15        pass: true,
16    },
17    Example {
18        label: "different name and value",
19        code: "struct S { x: i32 }\nfn f(y: i32) -> S { S { x: y } }",
20        pass: true,
21    },
22    Example {
23        label: "redundant in test",
24        code: "#[cfg(test)]\nmod tests {\n    struct S { x: i32 }\n    fn t(x: i32) -> S { S { x: x } }\n}",
25        pass: true,
26    },
27];
28
29crate::ast_rule!(
30    redundant_field_names,
31    "Flag `Foo { x: x }` — use shorthand `Foo { x }` instead.",
32    "Rust supports field init shorthand (Foo { x } instead of Foo { x: x }). The long form is needless noise.",
33    Low,
34    fix_redundant_field_names,
35);
36
37fn check_redundant_field_names(ctx: &AstCtx<'_>) -> Vec<Violation> {
38    ctx.nodes::<ast::RecordExprField>()
39        .filter(|field| !ctx.is_in_test(field) && field.colon_token().is_some())
40        .filter_map(|field| {
41            let name = field.name_ref()?;
42            let ast::Expr::PathExpr(path) = field.expr()? else {
43                return None;
44            };
45
46            (path.syntax().text().to_string() == name.text()).then(|| {
47                let name_text = name.text();
48
49                ctx.violation(
50                    &name,
51                    format!(
52                        "redundant field initializer `{name_text}: {name_text}` — use shorthand `{name_text}`"
53                    ),
54                )
55            })
56        })
57        .collect()
58}
59
60fn fix_redundant_field_names(ctx: &AstCtx<'_>, v: &Violation) -> Option<Fix> {
61    let line = ctx.file.line(v.line)?;
62    let name = parse::redundant_field_name(&v.message)?;
63    let redundant = format!("{name}: {name}");
64
65    line.contains(&redundant)
66        .then(|| Fix::replace_line(v.line, line.replacen(&redundant, name, 1)))
67}
68
69crate::tidy_ast_test!(check_redundant_field_names, {
70    crate::example_tests!(EXAMPLES, check_redundant_field_names);
71    crate::fix_tests!(ast, check_redundant_field_names, fix_redundant_field_names);
72});