wowlab_tidy/languages/toml/rules/cargo/
workspace_lints.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, RuleParam, TomlCtx, Violation, violation};
8
9#[rustfmt::skip]
10const EXAMPLES: &[Example] = &[
11 Example {
12 label: "workspace with full lint set",
13 code: r#"[workspace.lints.rust]
14ambiguous_negative_literals = "warn"
15missing_debug_implementations = "warn"
16redundant_imports = "warn"
17redundant_lifetimes = "warn"
18trivial_numeric_casts = "warn"
19unsafe_op_in_unsafe_fn = "warn"
20unused_lifetimes = "warn"
21
22[workspace.lints.clippy]
23cargo = { level = "warn", priority = -1 }
24complexity = { level = "warn", priority = -1 }
25correctness = { level = "deny", priority = -1 }
26pedantic = { level = "warn", priority = -1 }
27perf = { level = "warn", priority = -1 }
28style = { level = "warn", priority = -1 }
29suspicious = { level = "warn", priority = -1 }
30allow_attributes_without_reason = "warn"
31as_pointer_underscore = "warn"
32assertions_on_result_states = "warn"
33clone_on_ref_ptr = "warn"
34deref_by_slicing = "warn"
35disallowed_script_idents = "warn"
36empty_drop = "warn"
37empty_enum_variants_with_brackets = "warn"
38empty_structs_with_brackets = "warn"
39fn_to_numeric_cast_any = "warn"
40if_then_some_else_none = "warn"
41implicit_clone = "warn"
42map_err_ignore = "warn"
43redundant_type_annotations = "warn"
44renamed_function_params = "warn"
45semicolon_outside_block = "warn"
46undocumented_unsafe_blocks = "warn"
47unnecessary_safety_comment = "warn"
48unnecessary_safety_doc = "warn"
49unneeded_field_pattern = "warn"
50unused_result_ok = "warn"
51"#,
52 pass: true,
53 },
54 Example { label: "workspace missing required lints", code: "[workspace]\nmembers = []\n", pass: false },
55 Example { label: "workspace lint downgraded to allow", code: "[workspace.lints.rust]\nunsafe_op_in_unsafe_fn = \"allow\"\n", pass: false },
56 Example { label: "member inherits workspace lints", code: "[package]\nname = \"foo\"\n\n[lints]\nworkspace = true\n", pass: true },
57 Example { label: "member without lint inheritance", code: "[package]\nname = \"foo\"\n", pass: false },
58];
59
60crate::toml_rule!(
61 toml_cargo_workspace_lints,
62 "Require the workspace to enable the standard rust/clippy lint set and members to inherit it via `[lints] workspace = true`.",
63 "Static verification only catches issues when every crate compiles under the same vetted workspace lint tables (M-STATIC-VERIFICATION).",
64 Medium,
65 params {
66 required_rust: [String] = [
67 "ambiguous_negative_literals",
68 "missing_debug_implementations",
69 "redundant_imports",
70 "redundant_lifetimes",
71 "trivial_numeric_casts",
72 "unsafe_op_in_unsafe_fn",
73 "unused_lifetimes",
74 ],
75 required_clippy: [String] = [
76 "cargo",
77 "complexity",
78 "correctness",
79 "pedantic",
80 "perf",
81 "style",
82 "suspicious",
83 "allow_attributes_without_reason",
84 "as_pointer_underscore",
85 "assertions_on_result_states",
86 "clone_on_ref_ptr",
87 "deref_by_slicing",
88 "disallowed_script_idents",
89 "empty_drop",
90 "empty_enum_variants_with_brackets",
91 "empty_structs_with_brackets",
92 "fn_to_numeric_cast_any",
93 "if_then_some_else_none",
94 "implicit_clone",
95 "map_err_ignore",
96 "redundant_type_annotations",
97 "renamed_function_params",
98 "semicolon_outside_block",
99 "undocumented_unsafe_blocks",
100 "unnecessary_safety_comment",
101 "unnecessary_safety_doc",
102 "unneeded_field_pattern",
103 "unused_result_ok",
104 ],
105 },
106);
107
108fn check_toml_cargo_workspace_lints(ctx: &TomlCtx<'_>) -> Vec<Violation> {
109 let Some(document) = cargo_document(ctx) else {
110 return Vec::new();
111 };
112
113 if ctx.file.rel == CARGO_WORKSPACE_REL {
114 let mut violations = Vec::new();
115
116 required_group_violations(ctx, &document, "rust", &PARAMS[0], &mut violations);
117 required_group_violations(ctx, &document, "clippy", &PARAMS[1], &mut violations);
118
119 return violations;
120 }
121
122 if is_cargo_member(ctx.file.rel) && document.contains_key("package") {
123 return member_violations(ctx, &document);
124 }
125
126 Vec::new()
127}
128
129fn required_group_violations(
130 ctx: &TomlCtx<'_>,
131 document: &toml::Table,
132 group: &str,
133 param: &RuleParam,
134 violations: &mut Vec<Violation>,
135) {
136 let section = format!("workspace.lints.{group}");
137 let table = nested_table(document, &["workspace", "lints", group]);
138
139 for lint in ctx
140 .file
141 .config
142 .get_str_array("toml_cargo_workspace_lints", param)
143 {
144 match table.and_then(|table| table.get(&lint)) {
145 Some(value) if lint_level(value) == Some("allow") => violations.push(violation(
146 ctx.file.rel,
147 key_line(ctx.file.lines, §ion, &lint),
148 format!("[{section}] sets `{lint}` to allow; it is required at warn or deny"),
149 )),
150 Some(_) => {}
151 None => violations.push(violation(
152 ctx.file.rel,
153 section_line(ctx.file.lines, §ion),
154 format!("[{section}] must enable `{lint}` at warn or deny"),
155 )),
156 }
157 }
158}
159
160fn lint_level(value: &toml::Value) -> Option<&str> {
161 match value {
162 toml::Value::String(level) => Some(level),
163 toml::Value::Table(table) => table.get("level").and_then(toml::Value::as_str),
164 _ => None,
165 }
166}
167
168fn member_violations(ctx: &TomlCtx<'_>, document: &toml::Table) -> Vec<Violation> {
169 let inherits = nested_table(document, &["lints"])
170 .and_then(|lints| lints.get("workspace"))
171 .and_then(toml::Value::as_bool)
172 == Some(true);
173
174 if inherits {
175 return Vec::new();
176 }
177
178 vec![violation(
179 ctx.file.rel,
180 section_line(ctx.file.lines, "lints"),
181 "member manifest must inherit workspace lints via `[lints] workspace = true`",
182 )]
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[gtest]
190 fn examples() -> Result<()> {
191 for example in EXAMPLES {
192 let rel = if example.code.contains("[workspace") {
193 "crates/Cargo.toml"
194 } else {
195 "crates/foo/Cargo.toml"
196 };
197 let violations = crate::test_support::check_source_toml_at(
198 rel,
199 example.code,
200 check_toml_cargo_workspace_lints,
201 );
202
203 verify_eq!(violations.is_empty(), example.pass)?;
204 }
205
206 Ok(())
207 }
208
209 #[gtest]
210 fn missing_workspace_lints_flags_every_required_entry() -> Result<()> {
211 let violations = crate::test_support::check_source_toml_at(
212 "crates/Cargo.toml",
213 "[workspace]\nmembers = []\n",
214 check_toml_cargo_workspace_lints,
215 );
216
217 verify_eq!(violations.len(), 35)?;
218
219 Ok(())
220 }
221
222 #[gtest]
223 fn non_cargo_toml_is_ignored() -> Result<()> {
224 let violations = crate::test_support::check_source_toml_at(
225 "crates/tidy.toml",
226 "[rules.x]\nenabled = true\n",
227 check_toml_cargo_workspace_lints,
228 );
229
230 verify_true!(violations.is_empty())?;
231
232 Ok(())
233 }
234}