Skip to main content

frs/cmds/rsl/rules/
overqualified_call.rs

1//! Overqualified fn-call rule for `frs rsl`.
2
3use 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}