frs/cmds/rsl/rules/
misordered_visibility.rs1use std::path::Path;
4
5use proc_macro2::Span;
6use syn::Item;
7
8use super::common::Location;
9use crate::cmds::rsl::ast::VisibilityClass;
10use crate::cmds::rsl::engine::FileContext;
11use crate::cmds::rsl::rules::TypedRule;
12use crate::cmds::rsl::rules::TypedRuleViolation;
13
14pub struct MisorderedVisibilityRule {
15 visibility_order: [VisibilityClass; 4],
16}
17
18impl MisorderedVisibilityRule {
19 pub(super) fn new(visibility_order: Option<[VisibilityClass; 4]>) -> Self {
20 Self {
21 visibility_order: visibility_order.unwrap_or([
22 VisibilityClass::Public,
23 VisibilityClass::Crate,
24 VisibilityClass::Restricted,
25 VisibilityClass::Private,
26 ]),
27 }
28 }
29
30 fn check_visibility_order(
31 &self,
32 nodes: &[crate::cmds::rsl::ast::OrderNode],
33 path: &Path,
34 violations: &mut Vec<MisorderedVisibilityViolation>,
35 ) {
36 let mut current_group = None;
37 let mut highest_visibility: Option<&crate::cmds::rsl::ast::OrderNode> = None;
38
39 for node in nodes {
40 if node.group != current_group {
41 current_group = node.group;
42 highest_visibility = None;
43 }
44
45 let Some(visibility) = node.visibility else {
46 continue;
47 };
48
49 if let Some(previous) = highest_visibility
50 && self.visibility_rank(visibility)
51 < self.visibility_rank(previous.visibility.unwrap_or(VisibilityClass::Private))
52 {
53 violations.push(MisorderedVisibilityViolation::new(
54 path,
55 node.span,
56 previous.label.clone(),
57 node.label.clone(),
58 ));
59 }
60
61 if highest_visibility.is_none_or(|previous| {
62 self.visibility_rank(visibility)
63 > self.visibility_rank(previous.visibility.unwrap_or(VisibilityClass::Private))
64 }) {
65 highest_visibility = Some(node);
66 }
67 }
68 }
69
70 fn visibility_rank(&self, visibility: VisibilityClass) -> usize {
71 self.visibility_order
72 .iter()
73 .position(|expected| *expected == visibility)
74 .unwrap_or(self.visibility_order.len())
75 }
76}
77
78impl TypedRule for MisorderedVisibilityRule {
79 type Violation = MisorderedVisibilityViolation;
80
81 fn code() -> &'static str {
82 "misordered_visibility"
83 }
84
85 fn check(&self, ctx: &FileContext<'_>) -> Vec<Self::Violation> {
86 let mut violations = Vec::new();
87
88 for items in &ctx.module_item_lists {
89 let nodes = crate::cmds::rsl::ast::module_nodes(items);
90 let order_nodes: Vec<_> = nodes.iter().map(|node| node.order.clone()).collect();
91 self.check_visibility_order(&order_nodes, ctx.path, &mut violations);
92
93 for module_item in items.iter().rev() {
94 let item = module_item.item();
95 if let Item::Impl(item_impl) = item
96 && item_impl.trait_.is_none()
97 {
98 self.check_visibility_order(
99 &crate::cmds::rsl::ast::impl_nodes(item_impl),
100 ctx.path,
101 &mut violations,
102 );
103 }
104 }
105 }
106
107 violations
108 }
109}
110
111#[derive(Debug)]
112#[cfg_attr(test, derive(Eq, PartialEq))]
113pub struct MisorderedVisibilityViolation {
114 pub location: Location,
115 pub details: MisorderedVisibilityDetails,
116}
117
118impl MisorderedVisibilityViolation {
119 fn new(path: &Path, span: Span, expected_before: String, item_label: String) -> Self {
120 Self {
121 location: Location::from_span(path, span),
122 details: MisorderedVisibilityDetails {
123 expected_before,
124 item_label,
125 },
126 }
127 }
128}
129
130impl TypedRuleViolation for MisorderedVisibilityViolation {
131 type Rule = MisorderedVisibilityRule;
132}
133
134#[derive(Debug)]
135#[cfg_attr(test, derive(Eq, PartialEq))]
136pub struct MisorderedVisibilityDetails {
137 pub expected_before: String,
138 pub item_label: String,
139}
140
141#[cfg(test)]
142mod tests {
143 use std::path::PathBuf;
144
145 use test_that::prelude::*;
146
147 use super::MisorderedVisibilityDetails;
148 use super::MisorderedVisibilityRule;
149 use super::MisorderedVisibilityViolation;
150 use crate::cmds::rsl::rules::TypedRule;
151 use crate::cmds::rsl::rules::common::Location;
152
153 #[test]
154 fn test_misordered_visibility_rule_check_when_visibility_decreases_reports_public_item() {
155 let syntax = syn::parse_file(
156 r"
157 fn private() {}
158 pub fn public() {}
159 ",
160 )
161 .unwrap();
162
163 let result = MisorderedVisibilityRule::new(None).check(&crate::cmds::rsl::rules::test_ctx(&syntax));
164
165 assert_that!(
166 result,
167 eq(vec![MisorderedVisibilityViolation {
168 location: Location::new(PathBuf::from("test.rs"), 3, 17),
169 details: MisorderedVisibilityDetails {
170 expected_before: "fn private".to_owned(),
171 item_label: "fn public".to_owned(),
172 },
173 }])
174 );
175 }
176
177 #[test]
178 fn test_misordered_visibility_rule_check_when_type_is_more_visible_than_previous_item_reports_type() {
179 let syntax = syn::parse_file(
180 r"
181 fn private() {}
182 pub struct Data;
183 impl Data {}
184 ",
185 )
186 .unwrap();
187
188 let result = MisorderedVisibilityRule::new(None).check(&crate::cmds::rsl::rules::test_ctx(&syntax));
189
190 assert_that!(
191 result,
192 eq(vec![MisorderedVisibilityViolation {
193 location: Location::new(PathBuf::from("test.rs"), 3, 17),
194 details: MisorderedVisibilityDetails {
195 expected_before: "fn private".to_owned(),
196 item_label: "struct Data".to_owned(),
197 },
198 }])
199 );
200 }
201
202 #[test]
203 fn test_misordered_visibility_rule_check_when_associated_visibility_decreases_reports_public_item() {
204 let syntax = syn::parse_file(
205 r"
206 struct Data;
207 impl Data {
208 fn helper() {}
209 pub fn api() {}
210 }
211 ",
212 )
213 .unwrap();
214
215 let result = MisorderedVisibilityRule::new(None).check(&crate::cmds::rsl::rules::test_ctx(&syntax));
216
217 assert_that!(
218 result,
219 eq(vec![MisorderedVisibilityViolation {
220 location: Location::new(PathBuf::from("test.rs"), 5, 21),
221 details: MisorderedVisibilityDetails {
222 expected_before: "fn helper".to_owned(),
223 item_label: "fn api".to_owned(),
224 },
225 }])
226 );
227 }
228}