wowlab_tidy/languages/rust/rules/style/
inherent_before_trait_impl.rs1use ra_ap_syntax::{AstNode, ast};
2use wowlab_types::sim::FastMap;
3
4use crate::{AstCtx, Example, Violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8 Example {
9 label: "inherent impl first",
10 code: "struct Item;\nimpl Item { fn new() -> Self { Self } }\nimpl Default for Item { fn default() -> Self { Self } }",
11 pass: true,
12 },
13 Example {
14 label: "trait impl first",
15 code: "struct Item;\nimpl Default for Item { fn default() -> Self { Self } }\nimpl Item { fn new() -> Self { Self } }",
16 pass: false,
17 },
18 Example {
19 label: "different types",
20 code: "struct One; struct Two;\nimpl Default for One { fn default() -> Self { Self } }\nimpl Two {}",
21 pass: true,
22 },
23 Example {
24 label: "trait impl only",
25 code: "struct Item;\nimpl Default for Item { fn default() -> Self { Self } }",
26 pass: true,
27 },
28];
29
30crate::ast_rule!(
31 inherent_before_trait_impl,
32 "Require an inherent impl to precede trait impls for the same local type.",
33 "Putting the type's own API first makes its primary behavior easier to discover.",
34 Low,
35);
36
37fn check_inherent_before_trait_impl(ctx: &AstCtx<'_>) -> Vec<Violation> {
39 let mut first_inherent = FastMap::<String, usize>::default();
40 let mut trait_impls = Vec::new();
41
42 for item_impl in ctx.nodes::<ast::Impl>() {
43 let Some(self_type) = item_impl.self_ty() else {
44 continue;
45 };
46 let key: String = self_type
47 .syntax()
48 .text()
49 .to_string()
50 .chars()
51 .filter(|ch| !ch.is_whitespace())
52 .collect();
53 let offset: usize = item_impl.syntax().text_range().start().into();
54
55 if item_impl.trait_().is_none() {
56 first_inherent.entry(key).or_insert(offset);
57 } else {
58 trait_impls.push((key, offset, item_impl));
59 }
60 }
61
62 trait_impls
63 .into_iter()
64 .filter_map(|(key, offset, item_impl)| {
65 first_inherent
66 .get(&key)
67 .is_some_and(|inherent_offset| offset < *inherent_offset)
68 .then(|| {
69 ctx.violation(
70 &item_impl,
71 "trait impl appears before the inherent impl for this type",
72 )
73 })
74 })
75 .collect()
76}
77
78crate::tidy_ast_test!(check_inherent_before_trait_impl, {
79 crate::example_tests!(EXAMPLES, check_inherent_before_trait_impl);
80});