wowlab_tidy/languages/toml/rules/cargo/
msrv.rs1#[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}