frs/cmds/rsl/rules/
nonadjacent_impl.rs1use 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 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}