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