wowlab_tidy/languages/toml/rules/cargo/
feature_names.rs1#[cfg(test)]
2use googletest::prelude::*;
3
4use super::{assignment_line, is_cargo_manifest};
5use crate::{Example, TomlCtx, Violation, violation};
6
7#[rustfmt::skip]
8const EXAMPLES: &[Example] = &[
9 Example { label: "capability-named features", code: "[features]\ndefault = []\nstd = []\nserde = [\"dep:serde\"]\n", pass: true },
10 Example { label: "no features table", code: "[package]\nname = \"foo\"\n", pass: true },
11 Example { label: "use- prefix", code: "[features]\nuse-serde = [\"dep:serde\"]\n", pass: false },
12 Example { label: "with_ prefix", code: "[features]\nwith_tokio = [\"dep:tokio\"]\n", pass: false },
13 Example { label: "-support suffix", code: "[features]\nserde-support = [\"dep:serde\"]\n", pass: false },
14 Example { label: "_support suffix", code: "[features]\nserde_support = [\"dep:serde\"]\n", pass: false },
15];
16
17crate::toml_rule!(
18 toml_cargo_feature_names,
19 "Flag Cargo feature names with use-/with- prefixes or -support suffixes.",
20 "Feature names should describe the capability itself; placeholder affixes add noise without meaning (C-FEATURE).",
21 Low,
22 params {
23 allowed: [String] = [],
24 },
25);
26
27fn check_toml_cargo_feature_names(ctx: &TomlCtx<'_>) -> Vec<Violation> {
28 if !is_cargo_manifest(ctx.file.rel) || !ctx.parse.errors.is_empty() {
29 return Vec::new();
30 }
31
32 let Ok(document) = toml::from_str::<toml::Table>(ctx.file.contents) else {
33 return Vec::new();
34 };
35 let Some(features) = document.get("features").and_then(toml::Value::as_table) else {
36 return Vec::new();
37 };
38 let allowed = ctx
39 .file
40 .config
41 .get_str_array("toml_cargo_feature_names", &PARAMS[0]);
42
43 features
44 .keys()
45 .filter(|name| has_placeholder_affix(name) && !allowed.contains(name))
46 .map(|name| {
47 violation(
48 ctx.file.rel,
49 assignment_line(ctx.file.lines, name),
50 format!("feature `{name}` uses a placeholder affix; name the capability itself (e.g. `serde` instead of `use-serde` or `serde-support`)"),
51 )
52 })
53 .collect()
54}
55
56fn has_placeholder_affix(name: &str) -> bool {
57 const PREFIXES: &[&str] = &["use-", "use_", "with-", "with_"];
58 const SUFFIXES: &[&str] = &["-support", "_support"];
59
60 PREFIXES.iter().any(|prefix| name.starts_with(prefix))
61 || SUFFIXES.iter().any(|suffix| name.ends_with(suffix))
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 fn run(source: &str) -> Vec<Violation> {
69 crate::test_support::check_source_toml_at(
70 "crates/foo/Cargo.toml",
71 source,
72 check_toml_cargo_feature_names,
73 )
74 }
75
76 crate::example_tests!(EXAMPLES, check_toml_cargo_feature_names);
77
78 #[gtest]
79 fn ignores_non_cargo_toml() -> Result<()> {
80 let violations = crate::test_support::check_source_toml_at(
81 "crates/foo/other.toml",
82 "[features]\nuse-serde = []\n",
83 check_toml_cargo_feature_names,
84 );
85
86 verify_true!(violations.is_empty())?;
87
88 Ok(())
89 }
90}