frs/cmds/rsl/rules/
overqualified_call.rs1use std::path::Path;
4
5use super::common::CallDetails;
6use super::common::FnCallFinding;
7use super::common::FnCallKind;
8use super::common::Location;
9use super::common::find_fn_calls;
10use crate::cmds::rsl::engine::FileContext;
11use crate::cmds::rsl::rules::TypedRule;
12use crate::cmds::rsl::rules::TypedRuleViolation;
13
14pub struct OverqualifiedCallRule;
15
16impl TypedRule for OverqualifiedCallRule {
17 type Violation = OverqualifiedCallViolation;
18
19 fn code() -> &'static str {
20 "overqualified_call"
21 }
22
23 fn check(&self, ctx: &FileContext<'_>) -> Vec<Self::Violation> {
24 find_fn_calls(ctx.file, FnCallKind::Overqualified)
25 .into_iter()
26 .map(|finding| OverqualifiedCallViolation::new(ctx.path, finding))
27 .collect()
28 }
29}
30
31#[derive(Debug)]
32#[cfg_attr(test, derive(Eq, PartialEq))]
33pub struct OverqualifiedCallViolation {
34 pub location: Location,
35 pub details: CallDetails,
36}
37
38impl OverqualifiedCallViolation {
39 fn new(path: &Path, finding: FnCallFinding) -> Self {
40 Self {
41 location: Location::from_span(path, finding.span),
42 details: CallDetails {
43 actual_path: finding.actual_path,
44 replacement_path: finding.suggestion.expected_path,
45 add_import: finding.suggestion.required_import,
46 },
47 }
48 }
49}
50
51impl TypedRuleViolation for OverqualifiedCallViolation {
52 type Rule = OverqualifiedCallRule;
53}
54
55#[cfg(test)]
56mod tests {
57 use std::path::PathBuf;
58
59 use test_that::prelude::*;
60
61 use super::*;
62 use crate::cmds::rsl::rules::TypedRule;
63 use crate::cmds::rsl::rules::common::Location;
64
65 #[test]
66 fn test_overqualified_call_check_when_foreign_module_call_has_one_module_prefix_returns_no_violations() {
67 let syntax = syn::parse_file(
68 r"
69 mod helper {
70 pub fn run() {}
71 }
72 fn main() {
73 helper::run();
74 }
75 ",
76 )
77 .unwrap();
78
79 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
80
81 assert_that!(result, is_empty());
82 }
83
84 #[test]
85 fn test_overqualified_call_check_when_foreign_module_call_uses_crate_returns_no_violations() {
86 let syntax = syn::parse_file(
87 r"
88 mod helper {
89 pub fn run() {}
90 }
91 fn main() {
92 crate::helper::run();
93 }
94 ",
95 )
96 .unwrap();
97
98 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
99
100 assert_that!(result, is_empty());
101 }
102
103 #[test]
104 fn test_overqualified_call_check_when_free_fn_call_has_one_module_prefix_returns_no_violations() {
105 let syntax = syn::parse_file(
106 r#"
107 fn read() {
108 fs::read_to_string("foo.md");
109 }
110 "#,
111 )
112 .unwrap();
113
114 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
115
116 assert_that!(result, is_empty());
117 }
118
119 #[test]
120 fn test_overqualified_call_check_when_free_fn_call_has_multiple_module_prefixes_reports_call() {
121 let syntax = syn::parse_file(
122 r#"
123 fn read() {
124 std::fs::read_to_string("foo.md");
125 }
126 "#,
127 )
128 .unwrap();
129
130 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
131
132 assert_that!(
133 result,
134 eq(vec![OverqualifiedCallViolation {
135 location: Location::new(PathBuf::from("test.rs"), 3, 17),
136 details: CallDetails {
137 actual_path: "std::fs::read_to_string".to_owned(),
138 replacement_path: "fs::read_to_string".to_owned(),
139 add_import: Some("use std::fs;".to_owned()),
140 },
141 }])
142 );
143 }
144
145 #[test]
146 fn test_overqualified_call_check_when_shortened_module_name_conflicts_with_import_returns_no_violations() {
147 let syntax = syn::parse_file(
148 r#"
149 mod other {
150 pub mod fs {}
151 }
152 use crate::other::fs;
153 fn read() {
154 std::fs::read_to_string("foo.md");
155 }
156 "#,
157 )
158 .unwrap();
159
160 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
161
162 assert_that!(result, is_empty());
163 }
164
165 #[test]
166 fn test_overqualified_call_check_when_shortened_module_name_conflicts_with_local_module_returns_no_violations() {
167 let syntax = syn::parse_file(
168 r#"
169 mod fs {}
170 fn read() {
171 std::fs::read_to_string("foo.md");
172 }
173 "#,
174 )
175 .unwrap();
176
177 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
178
179 assert_that!(result, is_empty());
180 }
181
182 #[test]
183 fn test_overqualified_call_check_when_imported_fn_module_name_conflicts_returns_no_violations() {
184 let syntax = syn::parse_file(
185 r"
186 mod source {
187 pub fn run() {}
188 }
189 mod other {
190 pub mod source {}
191 }
192 use crate::other::source;
193 use crate::source::run;
194 fn main() {
195 run();
196 }
197 ",
198 )
199 .unwrap();
200
201 let result = OverqualifiedCallRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
202
203 assert_that!(result, is_empty());
204 }
205}