wowlab_tidy/languages/rust/rules/hygiene/
wildcard_imports.rs1#[cfg(test)]
2use googletest::prelude::*;
3use ra_ap_syntax::{
4 AstNode,
5 ast::{self, HasVisibility},
6};
7
8use crate::{AstCtx, Example, Violation};
9
10#[rustfmt::skip]
11const EXAMPLES: &[Example] = &[
12 Example {
13 label: "wildcard import",
14 code: "use std::collections::*;",
15 pass: false,
16 },
17 Example {
18 label: "explicit import",
19 code: "use std::collections::HashMap;",
20 pass: true,
21 },
22 Example {
23 label: "prelude allowed",
24 code: "use my_crate::prelude::*;",
25 pass: true,
26 },
27 Example {
28 label: "super star allowed",
29 code: "use super::*;",
30 pass: true,
31 },
32 Example {
33 label: "super nested allowed",
34 code: "use super::types::*;",
35 pass: true,
36 },
37 Example {
38 label: "pub use reexport allowed",
39 code: "pub use my_module::*;",
40 pass: true,
41 },
42 Example {
43 label: "pub crate reexport allowed",
44 code: "pub(crate) use my_module::*;",
45 pass: true,
46 },
47 Example {
48 label: "private third party glob",
49 code: "use some_crate::types::*;",
50 pass: false,
51 },
52 Example {
53 label: "enum glob in fn allowed",
54 code: "fn f() { use MyEnum::*; }",
55 pass: true,
56 },
57 Example {
58 label: "enum glob at module level allowed",
59 code: "use ColType::*;",
60 pass: true,
61 },
62 Example {
63 label: "crate internal types glob allowed",
64 code: "use crate::types::*;",
65 pass: true,
66 },
67 Example {
68 label: "deep external glob",
69 code: "use external_crate::deep::module::*;",
70 pass: false,
71 },
72 Example {
73 label: "wildcard in test module",
74 code: "#[cfg(test)]\nmod tests {\n use std::collections::*;\n}",
75 pass: true,
76 },
77 Example {
78 label: "nested prelude allowed",
79 code: "use some_crate::module::prelude::*;",
80 pass: true,
81 },
82];
83
84crate::ast_rule!(
85 wildcard_imports,
86 "Ban `use foo::*` outside tests and preludes.",
87 "Wildcard imports make it unclear where names come from and cause surprising breakage when upstream adds new items.",
88 Medium,
89);
90
91fn check_wildcard_imports(ctx: &AstCtx<'_>) -> Vec<Violation> {
92 ctx.nodes::<ast::Use>()
93 .filter(|item| !ctx.is_in_test(item) && item.visibility().is_none())
94 .flat_map(|item| {
95 item.use_tree()
96 .into_iter()
97 .flat_map(|tree| tree.syntax().descendants().filter_map(ast::UseTree::cast))
98 .filter_map(|tree| {
99 let star = tree.star_token()?;
100 let names = use_path_names(&tree);
101
102 should_flag_glob(&names).then(|| {
103 let line =
104 ctx.line_index.line_col(star.text_range().start()).line as usize + 1;
105
106 crate::violation(
107 ctx.file.rel,
108 line,
109 "wildcard import (use explicit imports)",
110 )
111 })
112 })
113 .collect::<Vec<_>>()
114 })
115 .collect()
116}
117
118fn is_pascal_case(s: &str) -> bool {
119 let mut chars = s.chars();
120 let Some(first) = chars.next() else {
121 return false;
122 };
123
124 first.is_ascii_uppercase() && chars.any(|c| c.is_ascii_lowercase())
125}
126
127fn use_path_names(tree: &ast::UseTree) -> Vec<String> {
128 let mut trees: Vec<ast::UseTree> = tree
129 .syntax()
130 .ancestors()
131 .filter_map(ast::UseTree::cast)
132 .collect();
133
134 trees.reverse();
135
136 trees
137 .into_iter()
138 .filter_map(|tree| tree.path())
139 .flat_map(|path| {
140 path.syntax()
141 .descendants()
142 .filter_map(ast::NameRef::cast)
143 .map(|name| name.text().to_string())
144 .collect::<Vec<_>>()
145 })
146 .collect()
147}
148
149fn should_flag_glob(names: &[String]) -> bool {
150 let parent = names.last().map(String::as_str);
151 let grandparent = names.iter().rev().nth(1).map(String::as_str);
152
153 !parent.is_some_and(|name| name == "prelude" || name == "super")
154 && !grandparent.is_some_and(|name| name == "super" || name == "crate")
155 && !parent.is_some_and(is_pascal_case)
156}
157
158crate::tidy_ast_test!(check_wildcard_imports, {
159 crate::example_tests!(EXAMPLES, check_wildcard_imports);
160
161 #[gtest]
162 fn production_caught_test_allowed() -> Result<()> {
163 let v = run("use std::io::*;\n\
164 #[cfg(test)]\n\
165 mod tests {\n\
166 use std::collections::*;\n\
167 }");
168 verify_eq!(v.len(), 1)?;
169 verify_eq!(v[0].line, 1)?;
170
171 Ok(())
172 }
173});