Skip to main content

wowlab_tidy/languages/toml/rules/cargo/
msrv.rs

1#[cfg(test)]
2use googletest::prelude::*;
3
4use super::{
5    CARGO_WORKSPACE_REL, cargo_document, is_cargo_member, key_line, nested_table, section_line,
6};
7use crate::{Example, TomlCtx, Violation, violation};
8
9#[rustfmt::skip]
10const EXAMPLES: &[Example] = &[
11    Example { label: "workspace declares MSRV", code: "[workspace]\nmembers = []\n\n[workspace.package]\nedition = \"2024\"\nrust-version = \"1.85\"\n", pass: true },
12    Example { label: "workspace without MSRV", code: "[workspace]\nmembers = []\n\n[workspace.package]\nedition = \"2024\"\n", pass: false },
13    Example { label: "member inherits MSRV", code: "[package]\nname = \"foo\"\nrust-version.workspace = true\n", pass: true },
14    Example { label: "member overrides MSRV", code: "[package]\nname = \"foo\"\nrust-version = \"1.85\"\n", pass: false },
15];
16
17crate::toml_rule!(
18    toml_cargo_msrv,
19    "Require the workspace to declare rust-version and members to inherit it instead of overriding it.",
20    "A declared MSRV makes the supported-compiler contract explicit, and per-crate overrides silently fragment it (M-MSRV).",
21    Low,
22);
23
24fn check_toml_cargo_msrv(ctx: &TomlCtx<'_>) -> Vec<Violation> {
25    let Some(document) = cargo_document(ctx) else {
26        return Vec::new();
27    };
28
29    if ctx.file.rel == CARGO_WORKSPACE_REL {
30        let declares_msrv = nested_table(&document, &["workspace", "package"])
31            .is_some_and(|package| package.contains_key("rust-version"));
32
33        if declares_msrv {
34            return Vec::new();
35        }
36
37        return vec![violation(
38            ctx.file.rel,
39            section_line(ctx.file.lines, "workspace.package"),
40            "declare `rust-version` (MSRV) in [workspace.package] (M-MSRV)",
41        )];
42    }
43
44    if is_cargo_member(ctx.file.rel) && document.contains_key("package") {
45        let overrides_msrv = nested_table(&document, &["package"])
46            .and_then(|package| package.get("rust-version"))
47            .is_some_and(|value| !value.is_table());
48
49        if overrides_msrv {
50            return vec![violation(
51                ctx.file.rel,
52                key_line(ctx.file.lines, "package", "rust-version"),
53                "member overrides the workspace MSRV; use `rust-version.workspace = true`",
54            )];
55        }
56    }
57
58    Vec::new()
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[gtest]
66    fn examples() -> Result<()> {
67        for example in EXAMPLES {
68            let rel = if example.code.contains("[workspace") {
69                "crates/Cargo.toml"
70            } else {
71                "crates/foo/Cargo.toml"
72            };
73            let violations =
74                crate::test_support::check_source_toml_at(rel, example.code, check_toml_cargo_msrv);
75
76            verify_eq!(violations.is_empty(), example.pass)?;
77        }
78
79        Ok(())
80    }
81
82    #[gtest]
83    fn member_without_msrv_inherits_implicitly() -> Result<()> {
84        let violations = crate::test_support::check_source_toml_at(
85            "crates/foo/Cargo.toml",
86            "[package]\nname = \"foo\"\nedition.workspace = true\n",
87            check_toml_cargo_msrv,
88        );
89
90        verify_true!(violations.is_empty())?;
91
92        Ok(())
93    }
94}