Skip to main content

frs/cmds/rsl/rules/
aliased_import.rs

1//! Aliased-import rule for `frs rsl`.
2
3use std::path::Path;
4
5use proc_macro2::Span;
6use syn::Item;
7use syn::UseTree;
8
9use super::common::Location;
10use super::common::module_idx;
11use crate::cmds::rsl::engine::FileContext;
12use crate::cmds::rsl::rules::TypedRule;
13use crate::cmds::rsl::rules::TypedRuleViolation;
14
15#[derive(Debug)]
16#[cfg_attr(test, derive(Eq, PartialEq))]
17pub struct AliasedImportViolation {
18    pub location: Location,
19    pub unaliased_import: String,
20}
21
22impl AliasedImportViolation {
23    pub(super) fn new(path: &Path, span: Span, unaliased_import: String) -> Self {
24        Self {
25            location: Location::from_span(path, span),
26            unaliased_import,
27        }
28    }
29}
30
31pub struct AliasedImportRule;
32
33impl TypedRule for AliasedImportRule {
34    type Violation = AliasedImportViolation;
35
36    fn code() -> &'static str {
37        "aliased_import"
38    }
39
40    fn check(&self, ctx: &FileContext<'_>) -> Vec<Self::Violation> {
41        let idx = module_idx(ctx.file);
42        let mut violations = Vec::new();
43
44        for scope in &idx.scopes {
45            for item in scope.items {
46                if let Item::Use(item_use) = item {
47                    check_aliases(ctx.path, &item_use.tree, &mut violations);
48                }
49            }
50        }
51
52        violations
53    }
54}
55
56fn check_aliases(path: &std::path::Path, tree: &UseTree, violations: &mut Vec<AliasedImportViolation>) {
57    let mut pending = vec![(tree, Vec::new())];
58
59    while let Some((tree, prefix)) = pending.pop() {
60        match tree {
61            UseTree::Path(use_path) => {
62                let mut next_prefix = prefix;
63                next_prefix.push(use_path.ident.to_string());
64                pending.push((use_path.tree.as_ref(), next_prefix));
65            }
66            UseTree::Group(group) => {
67                for tree in group.items.iter().rev() {
68                    pending.push((tree, prefix.clone()));
69                }
70            }
71            UseTree::Rename(rename) if rename.rename != "_" => {
72                let unaliased_import = if rename.ident == "self" {
73                    prefix.join("::")
74                } else {
75                    let mut import_path = prefix;
76                    import_path.push(rename.ident.to_string());
77                    import_path.join("::")
78                };
79                violations.push(AliasedImportViolation::new(
80                    path,
81                    rename.rename.span(),
82                    unaliased_import,
83                ));
84            }
85            UseTree::Name(_) | UseTree::Glob(_) | UseTree::Rename(_) => {}
86        }
87    }
88}
89
90impl TypedRuleViolation for AliasedImportViolation {
91    type Rule = AliasedImportRule;
92}
93
94#[cfg(test)]
95mod tests {
96    use std::path::PathBuf;
97
98    use test_that::prelude::*;
99
100    use super::*;
101    use crate::cmds::rsl::rules::TypedRule;
102    use crate::cmds::rsl::rules::common::Location;
103
104    #[test]
105    fn test_aliased_import_check_when_alias_is_private_reports_alias() {
106        let syntax = syn::parse_file(
107            r"
108            use std::fmt::Display as Formatter;
109            ",
110        )
111        .unwrap();
112
113        let result = AliasedImportRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
114
115        assert_that!(
116            result,
117            eq(vec![AliasedImportViolation {
118                location: Location::new(PathBuf::from("test.rs"), 2, 38),
119                unaliased_import: "std::fmt::Display".to_owned(),
120            }])
121        );
122    }
123
124    #[test]
125    fn test_aliased_import_check_when_alias_is_wildcard_returns_no_violations() {
126        let syntax = syn::parse_file(
127            r"
128            use std::fmt::Display as _;
129            ",
130        )
131        .unwrap();
132
133        let result = AliasedImportRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
134
135        assert_that!(result, is_empty());
136    }
137
138    #[test]
139    fn test_aliased_import_check_when_public_reexport_is_renamed_reports_alias() {
140        let syntax = syn::parse_file(
141            r"
142            pub use std::fmt::Debug as Formatter;
143            ",
144        )
145        .unwrap();
146
147        let result = AliasedImportRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
148
149        assert_that!(
150            result,
151            eq(vec![AliasedImportViolation {
152                location: Location::new(PathBuf::from("test.rs"), 2, 40),
153                unaliased_import: "std::fmt::Debug".to_owned(),
154            }])
155        );
156    }
157
158    #[test]
159    fn test_aliased_import_check_when_reexport_alias_is_restricted_reports_alias() {
160        let syntax = syn::parse_file(
161            r"
162            pub(crate) use std::fmt::Debug as Formatter;
163            ",
164        )
165        .unwrap();
166
167        let result = AliasedImportRule.check(&crate::cmds::rsl::rules::test_ctx(&syntax));
168
169        assert_that!(
170            result,
171            eq(vec![AliasedImportViolation {
172                location: Location::new(PathBuf::from("test.rs"), 2, 47),
173                unaliased_import: "std::fmt::Debug".to_owned(),
174            }])
175        );
176    }
177}