wowlab_tidy/languages/rust/rules/api/
glob_reexport.rs1use ra_ap_syntax::{
2 AstNode,
3 ast::{self, HasAttrs, HasVisibility},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10 Example {
11 label: "pub glob reexport",
12 code: "pub use internals::*;",
13 pass: false,
14 },
15 Example {
16 label: "pub crate glob reexport",
17 code: "pub(crate) use internals::*;",
18 pass: false,
19 },
20 Example {
21 label: "glob inside group",
22 code: "pub use internals::{helpers::*, Config};",
23 pass: false,
24 },
25 Example {
26 label: "private glob import",
27 code: "fn f() { use internals::*; }",
28 pass: true,
29 },
30 Example {
31 label: "named reexports",
32 code: "pub use internals::{Config, Helper};",
33 pass: true,
34 },
35 Example {
36 label: "windows hal forwarding",
37 code: "#[cfg(windows)]\npub use windows_impl::*;",
38 pass: true,
39 },
40 Example {
41 label: "target os hal forwarding",
42 code: "#[cfg(target_os = \"linux\")]\npub use linux_impl::*;",
43 pass: true,
44 },
45 Example {
46 label: "target arch hal forwarding",
47 code: "#[cfg(target_arch = \"wasm32\")]\npub use wasm_impl::*;",
48 pass: true,
49 },
50 Example {
51 label: "unix hal forwarding",
52 code: "#[cfg(unix)]\npub use unix_impl::*;",
53 pass: true,
54 },
55 Example {
56 label: "glob reexport in test module",
57 code: "#[cfg(test)]\nmod tests {\n pub use internals::*;\n}",
58 pass: true,
59 },
60];
61
62crate::ast_rule!(
63 glob_reexport,
64 "Flag `pub use foo::*` glob re-exports outside platform-cfg'd HAL forwarding.",
65 "Glob re-exports silently widen the public surface and are unreviewable in diffs; re-export items individually (M-NO-GLOB-REEXPORTS).",
66 Medium,
67);
68
69fn check_glob_reexport(ctx: &AstCtx<'_>) -> Vec<Violation> {
70 let public_uses = ctx
71 .nodes::<ast::Use>()
72 .filter(|item| !ctx.is_in_test(item) && item.visibility().is_some())
73 .filter(|item| !has_platform_cfg(item));
74
75 public_uses
76 .flat_map(|item| {
77 item.syntax()
78 .descendants()
79 .filter_map(ast::UseTree::cast)
80 .filter(|tree| tree.star_token().is_some())
81 .map(|tree| {
82 ctx.violation(
83 &tree,
84 "glob re-export — re-export items individually (M-NO-GLOB-REEXPORTS)",
85 )
86 })
87 .collect::<Vec<_>>()
88 })
89 .collect()
90}
91
92fn has_platform_cfg(item: &ast::Use) -> bool {
94 item.attrs().any(|attr| {
95 if attr.simple_name().as_deref() != Some("cfg") {
96 return false;
97 }
98
99 let text: String = attr
100 .syntax()
101 .text()
102 .to_string()
103 .chars()
104 .filter(|ch| !ch.is_whitespace())
105 .collect();
106
107 text == "#[cfg(windows)]"
108 || text == "#[cfg(unix)]"
109 || text.starts_with("#[cfg(target_os=")
110 || text.starts_with("#[cfg(target_arch=")
111 })
112}
113
114crate::tidy_ast_test!(check_glob_reexport, {
115 crate::example_tests!(EXAMPLES, check_glob_reexport);
116});