wowlab_tidy/languages/rust/rules/api/
no_prelude.rs1#[cfg(test)]
2use googletest::prelude::*;
3
4use crate::{Example, FileCtx, Violation, infra::scanner, violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8 Example {
9 label: "pub prelude declaration",
10 code: "pub mod prelude;",
11 pass: false,
12 },
13 Example {
14 label: "private prelude declaration",
15 code: "mod prelude;",
16 pass: false,
17 },
18 Example {
19 label: "inline prelude module",
20 code: "pub mod prelude {\n pub struct Token;\n}",
21 pass: false,
22 },
23 Example {
24 label: "similarly named module",
25 code: "mod prelude_ext;",
26 pass: true,
27 },
28 Example {
29 label: "commented declaration",
30 code: "// mod prelude;",
31 pass: true,
32 },
33 Example {
34 label: "string mention",
35 code: "fn f() { let s = \"mod prelude;\"; }",
36 pass: true,
37 },
38 Example {
39 label: "regular module",
40 code: "pub mod parsing;",
41 pass: true,
42 },
43];
44
45crate::line_rule!(
46 no_prelude,
47 "Ban `prelude` module declarations and `prelude.rs`/`prelude/mod.rs` files.",
48 "Preludes invite glob imports that collide across crates and paper over bad module design (M-NO-PRELUDE).",
49 High,
50);
51
52fn check_no_prelude(ctx: &FileCtx<'_>) -> Vec<Violation> {
53 let mut out = Vec::new();
54
55 if ctx.rel.ends_with("/prelude.rs") || ctx.rel.ends_with("/prelude/mod.rs") {
56 out.push(violation(
57 ctx.rel,
58 1,
59 "prelude module file — crates must not define preludes (M-NO-PRELUDE)",
60 ));
61 }
62
63 for (index, line) in ctx.lines.iter().enumerate() {
64 if declares_prelude(line) {
65 out.push(violation(
66 ctx.rel,
67 index + 1,
68 "`prelude` module declaration — crates must not define preludes (M-NO-PRELUDE)",
69 ));
70 }
71 }
72
73 out
74}
75
76fn declares_prelude(line: &str) -> bool {
77 let code = scanner::code_only(line);
78 let trimmed = code.trim_start();
79 let rest = trimmed
80 .strip_prefix("pub mod prelude")
81 .or_else(|| trimmed.strip_prefix("mod prelude"));
82
83 match rest {
84 Some(rest) => matches!(rest.trim_start().chars().next(), None | Some(';' | '{')),
85 None => false,
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[gtest]
94 fn examples() -> Result<()> {
95 for ex in EXAMPLES {
96 let violations = crate::test_support::check_source_at(
97 "crates/demo/src/lib.rs",
98 ex.code,
99 check_no_prelude,
100 );
101
102 verify_eq!(violations.is_empty(), ex.pass)?;
103 }
104
105 Ok(())
106 }
107
108 #[gtest]
109 fn prelude_file_paths_flagged_on_line_one() -> Result<()> {
110 for rel in [
111 "crates/demo/src/prelude.rs",
112 "crates/demo/src/prelude/mod.rs",
113 ] {
114 let v =
115 crate::test_support::check_source_at(rel, "pub struct Token;", check_no_prelude);
116
117 verify_eq!(v.len(), 1)?;
118 verify_eq!(v[0].line, 1)?;
119 }
120
121 Ok(())
122 }
123
124 #[gtest]
125 fn regular_file_path_passes() -> Result<()> {
126 let v = crate::test_support::check_source_at(
127 "crates/demo/src/preludes.rs",
128 "pub struct Token;",
129 check_no_prelude,
130 );
131
132 verify_true!(v.is_empty())?;
133
134 Ok(())
135 }
136}