Skip to main content

wowlab_tidy/languages/rust/rules/complexity/
multiple_inherent_impl.rs

1// #t(file: rust_default_hasher) small per-file dedup set; fast-hasher dependency not warranted
2
3use std::collections::HashSet;
4
5#[cfg(test)]
6use googletest::prelude::*;
7use ra_ap_syntax::ast;
8
9use super::support;
10use crate::{AstCtx, Example, Violation};
11
12#[rustfmt::skip]
13const EXAMPLES: &[Example] = &[
14    Example {
15        label: "duplicate impl blocks",
16        code: "struct Foo;\nimpl Foo { fn a() {} }\nimpl Foo { fn b() {} }",
17        pass: false,
18    },
19    Example {
20        label: "different types",
21        code: "struct Foo;\nstruct Bar;\nimpl Foo {}\nimpl Bar {}",
22        pass: true,
23    },
24    Example {
25        label: "trait impl not counted",
26        code: "struct Foo;\nimpl Foo {}\nimpl std::fmt::Display for Foo { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } }",
27        pass: true,
28    },
29    Example {
30        label: "duplicate impl in test module",
31        code: "#[cfg(test)]\nmod tests {\n  struct Foo;\n  impl Foo { fn a() {} }\n  impl Foo { fn b() {} }\n}",
32        pass: true,
33    },
34];
35
36crate::ast_rule!(
37    multiple_inherent_impl,
38    "Flag multiple `impl Foo` blocks for the same type in one file.",
39    "Split impl blocks for the same type scatter related methods. Keep them in one block for discoverability.",
40);
41
42fn check_multiple_inherent_impl(ctx: &AstCtx<'_>) -> Vec<Violation> {
43    let mut seen = HashSet::new();
44    let mut violations = Vec::new();
45
46    for item_impl in ctx
47        .nodes::<ast::Impl>()
48        .filter(|item_impl| !ctx.is_in_test(item_impl) && item_impl.trait_().is_none())
49    {
50        let Some(self_type) = item_impl.self_ty() else {
51            continue;
52        };
53        let Some(name) = support::self_type_name(&self_type) else {
54            continue;
55        };
56
57        if seen.contains(&name) {
58            violations.push(ctx.violation(
59                &self_type,
60                format!("multiple inherent impl blocks for `{name}` in the same file — merge them"),
61            ));
62        } else {
63            seen.insert(name);
64        }
65    }
66
67    violations
68}
69
70crate::tidy_ast_test!(check_multiple_inherent_impl, {
71    crate::example_tests!(EXAMPLES, check_multiple_inherent_impl);
72
73    #[gtest]
74    fn three_impls_flags_second_and_third() -> Result<()> {
75        let v = run("struct Foo;\nimpl Foo {}\nimpl Foo {}\nimpl Foo {}");
76        verify_eq!(v.len(), 2)?;
77
78        Ok(())
79    }
80});