Skip to main content

frs/cmds/rsl/rules/
nonadjacent_impl.rs

1//! Nonadjacent-impl rule for `frs rsl`.
2
3use std::path::Path;
4
5use proc_macro2::Span;
6
7use super::common::Location;
8use crate::cmds::rsl::ast::ItemKind;
9use crate::cmds::rsl::engine::FileContext;
10use crate::cmds::rsl::rules::TypedRule;
11use crate::cmds::rsl::rules::TypedRuleViolation;
12
13pub struct NonadjacentImplRule;
14
15impl TypedRule for NonadjacentImplRule {
16    type Violation = NonadjacentImplViolation;
17
18    fn code() -> &'static str {
19        "nonadjacent_impl"
20    }
21
22    fn check(&self, ctx: &FileContext<'_>) -> Vec<Self::Violation> {
23        let mut violations = Vec::new();
24
25        for items in &ctx.module_item_lists {
26            // Raw AST positions make opaque macros and cfg-decorated items break physical adjacency.
27            for node in &crate::cmds::rsl::ast::module_nodes(items) {
28                if node.idxs.len() < 2 {
29                    continue;
30                }
31
32                for pair in node.idxs.windows(2) {
33                    let [previous, current] = pair else {
34                        continue;
35                    };
36                    if *current == previous.saturating_add(1) {
37                        continue;
38                    }
39
40                    let Some(previous_item) = items.get(*previous).map(crate::cmds::rsl::ast::ModuleItem::item) else {
41                        continue;
42                    };
43                    let Some(classified) = items
44                        .get(*current)
45                        .map(crate::cmds::rsl::ast::ModuleItem::metadata)
46                        .and_then(crate::cmds::rsl::ast::ItemMetadata::classified)
47                    else {
48                        continue;
49                    };
50                    violations.push(NonadjacentImplViolation::new(
51                        ctx.path,
52                        classified.span,
53                        crate::cmds::rsl::ast::impl_order_label(previous_item),
54                        classified.kind,
55                    ));
56                }
57            }
58        }
59
60        violations
61    }
62}
63
64#[derive(Debug)]
65#[cfg_attr(test, derive(Eq, PartialEq))]
66pub struct NonadjacentImplViolation {
67    pub location: Location,
68    pub details: NonadjacentImplDetails,
69}
70
71impl NonadjacentImplViolation {
72    fn new(path: &Path, span: Span, expected_after: String, item: ItemKind) -> Self {
73        Self {
74            location: Location::from_span(path, span),
75            details: NonadjacentImplDetails { expected_after, item },
76        }
77    }
78}
79
80impl TypedRuleViolation for NonadjacentImplViolation {
81    type Rule = NonadjacentImplRule;
82}
83
84#[derive(Debug)]
85#[cfg_attr(test, derive(Eq, PartialEq))]
86pub struct NonadjacentImplDetails {
87    pub expected_after: String,
88    pub item: ItemKind,
89}
90
91#[cfg(test)]
92mod tests {
93    use std::path::PathBuf;
94
95    use test_that::prelude::*;
96
97    use super::NonadjacentImplDetails;
98    use super::NonadjacentImplRule;
99    use super::NonadjacentImplViolation;
100    use crate::cmds::rsl::ast::ItemKind;
101    use crate::cmds::rsl::rules::TypedRule;
102    use crate::cmds::rsl::rules::common::Location;
103
104    #[test]
105    fn test_nonadjacent_impl_rule_check_when_impl_is_not_adjacent_to_type_reports_impl() {
106        let syntax = syn::parse_file(
107            r"
108            struct Data;
109            opaque!();
110            impl Data {}
111            ",
112        )
113        .unwrap();
114
115        let result = NonadjacentImplRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
116
117        assert_that!(
118            result,
119            eq(vec![NonadjacentImplViolation {
120                location: Location::new(PathBuf::from("test.rs"), 4, 13),
121                details: NonadjacentImplDetails {
122                    expected_after: "struct Data".to_owned(),
123                    item: ItemKind::Impl,
124                },
125            }])
126        );
127    }
128
129    #[test]
130    fn test_nonadjacent_impl_rule_check_when_trait_impl_precedes_inherent_impl_reports_impls() {
131        let syntax = syn::parse_file(
132            r"
133            struct Data;
134            impl Behavior for Data {}
135            impl Data {}
136            ",
137        )
138        .unwrap();
139
140        let result = NonadjacentImplRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
141
142        assert_that!(
143            result,
144            eq(vec![
145                NonadjacentImplViolation {
146                    location: Location::new(PathBuf::from("test.rs"), 4, 13),
147                    details: NonadjacentImplDetails {
148                        expected_after: "struct Data".to_owned(),
149                        item: ItemKind::Impl,
150                    },
151                },
152                NonadjacentImplViolation {
153                    location: Location::new(PathBuf::from("test.rs"), 3, 13),
154                    details: NonadjacentImplDetails {
155                        expected_after: "inherent impl Data".to_owned(),
156                        item: ItemKind::Impl,
157                    },
158                },
159            ])
160        );
161    }
162
163    #[test]
164    fn test_nonadjacent_impl_rule_check_when_cfg_item_is_between_type_and_impl_reports_impl() {
165        let syntax = syn::parse_file(
166            r#"
167            struct Data;
168            #[cfg(feature = "extra")]
169            fn extra() {}
170            impl Data {}
171            "#,
172        )
173        .unwrap();
174
175        let result = NonadjacentImplRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
176
177        assert_that!(
178            result,
179            eq(vec![NonadjacentImplViolation {
180                location: Location::new(PathBuf::from("test.rs"), 5, 13),
181                details: NonadjacentImplDetails {
182                    expected_after: "struct Data".to_owned(),
183                    item: ItemKind::Impl,
184                },
185            }])
186        );
187    }
188}