Skip to main content

wowlab_tidy/languages/rust/rules/api/
no_prelude.rs

1#[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}