Skip to main content

cargo/diagnostics/rules/
mod.rs

1mod blanket_hint_mostly_unused;
2mod im_a_teapot;
3mod implicit_minimum_version_req;
4mod missing_lints_features;
5mod missing_lints_inheritance;
6mod non_kebab_case_bins;
7mod non_kebab_case_features;
8mod non_kebab_case_packages;
9mod non_snake_case_features;
10mod non_snake_case_packages;
11mod redundant_homepage;
12mod redundant_readme;
13mod text_direction_codepoint_in_comment;
14mod text_direction_codepoint_in_literal;
15mod unknown_lints;
16pub mod unused_dependencies;
17mod unused_workspace_dependencies;
18mod unused_workspace_package_fields;
19
20pub use blanket_hint_mostly_unused::blanket_hint_mostly_unused;
21pub use im_a_teapot::check_im_a_teapot;
22pub use implicit_minimum_version_req::implicit_minimum_version_req_pkg;
23pub use implicit_minimum_version_req::implicit_minimum_version_req_ws;
24pub use missing_lints_features::missing_lints_features;
25pub use missing_lints_inheritance::missing_lints_inheritance;
26pub use non_kebab_case_bins::non_kebab_case_bins;
27pub use non_kebab_case_features::non_kebab_case_features;
28pub use non_kebab_case_packages::non_kebab_case_packages;
29pub use non_snake_case_features::non_snake_case_features;
30pub use non_snake_case_packages::non_snake_case_packages;
31pub use redundant_homepage::redundant_homepage;
32pub use redundant_readme::redundant_readme;
33pub use text_direction_codepoint_in_comment::text_direction_codepoint_in_comment;
34pub use text_direction_codepoint_in_literal::text_direction_codepoint_in_literal;
35pub use unknown_lints::unknown_lints;
36pub use unused_dependencies::unused_build_dependencies_no_build_rs;
37pub use unused_workspace_dependencies::unused_workspace_dependencies;
38pub use unused_workspace_package_fields::unused_workspace_package_fields;
39
40use super::LintGroup;
41use super::LintLevel;
42use crate::core::Feature;
43
44pub static LINTS: &[&crate::diagnostics::Lint] = &[
45    blanket_hint_mostly_unused::LINT,
46    implicit_minimum_version_req::LINT,
47    im_a_teapot::LINT,
48    missing_lints_inheritance::LINT,
49    non_kebab_case_bins::LINT,
50    non_kebab_case_features::LINT,
51    non_kebab_case_packages::LINT,
52    non_snake_case_features::LINT,
53    non_snake_case_packages::LINT,
54    redundant_homepage::LINT,
55    redundant_readme::LINT,
56    text_direction_codepoint_in_comment::LINT,
57    text_direction_codepoint_in_literal::LINT,
58    unknown_lints::LINT,
59    unused_dependencies::LINT,
60    unused_workspace_dependencies::LINT,
61    unused_workspace_package_fields::LINT,
62];
63
64/// Version required for specifying `[lints.cargo]`
65///
66/// Before this, it was an error.  No on-by-default lint should fire before this time without
67/// another way of disabling it.
68static CARGO_LINTS_MSRV: cargo_util_schemas::manifest::RustVersion =
69    cargo_util_schemas::manifest::RustVersion::new(1, 79, 0);
70
71pub static LINT_GROUPS: &[LintGroup] = &[
72    COMPLEXITY,
73    CORRECTNESS,
74    NURSERY,
75    PEDANTIC,
76    PERF,
77    RESTRICTION,
78    STYLE,
79    SUSPICIOUS,
80    TEST_DUMMY_UNSTABLE,
81];
82
83const COMPLEXITY: LintGroup = LintGroup {
84    name: "complexity",
85    desc: "code that does something simple but in a complex way",
86    default_level: LintLevel::Warn,
87    feature_gate: None,
88    hidden: false,
89};
90
91const CORRECTNESS: LintGroup = LintGroup {
92    name: "correctness",
93    desc: "code that is outright wrong or useless",
94    default_level: LintLevel::Deny,
95    feature_gate: None,
96    hidden: false,
97};
98
99const NURSERY: LintGroup = LintGroup {
100    name: "nursery",
101    desc: "new lints that are still under development",
102    default_level: LintLevel::Allow,
103    feature_gate: None,
104    hidden: false,
105};
106
107const PEDANTIC: LintGroup = LintGroup {
108    name: "pedantic",
109    desc: "lints which are rather strict or have occasional false positives",
110    default_level: LintLevel::Allow,
111    feature_gate: None,
112    hidden: false,
113};
114
115const PERF: LintGroup = LintGroup {
116    name: "perf",
117    desc: "code that can be written to run faster",
118    default_level: LintLevel::Warn,
119    feature_gate: None,
120    hidden: false,
121};
122
123const RESTRICTION: LintGroup = LintGroup {
124    name: "restriction",
125    desc: "lints which prevent the use of Cargo features",
126    default_level: LintLevel::Allow,
127    feature_gate: None,
128    hidden: false,
129};
130
131const STYLE: LintGroup = LintGroup {
132    name: "style",
133    desc: "code that should be written in a more idiomatic way",
134    default_level: LintLevel::Warn,
135    feature_gate: None,
136    hidden: false,
137};
138
139const SUSPICIOUS: LintGroup = LintGroup {
140    name: "suspicious",
141    desc: "code that is most likely wrong or useless",
142    default_level: LintLevel::Warn,
143    feature_gate: None,
144    hidden: false,
145};
146
147/// This lint group is only to be used for testing purposes
148const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
149    name: "test_dummy_unstable",
150    desc: "test_dummy_unstable is meant to only be used in tests",
151    default_level: LintLevel::Allow,
152    feature_gate: Some(crate::core::Feature::test_dummy_unstable()),
153    hidden: true,
154};
155
156fn find_lint_or_group<'a>(
157    name: &str,
158) -> Option<(&'static str, &LintLevel, &Option<&'static Feature>)> {
159    if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
160        Some((
161            lint.name,
162            &lint.primary_group.default_level,
163            &lint.feature_gate,
164        ))
165    } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
166        Some((group.name, &group.default_level, &group.feature_gate))
167    } else {
168        None
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use itertools::Itertools;
175    use snapbox::ToDebug;
176    use std::collections::HashSet;
177
178    #[test]
179    fn ensure_lint_groups_do_not_default_to_forbid() {
180        let forbid_groups = super::LINT_GROUPS
181            .iter()
182            .filter(|g| matches!(g.default_level, super::LintLevel::Forbid))
183            .collect::<Vec<_>>();
184
185        assert!(
186            forbid_groups.is_empty(),
187            "\n`LintGroup`s should never default to `forbid`, but the following do:\n\
188            {}\n",
189            forbid_groups.iter().map(|g| g.name).join("\n")
190        );
191    }
192
193    #[test]
194    fn ensure_sorted_lints() {
195        // This will be printed out if the fields are not sorted.
196        let location = std::panic::Location::caller();
197        println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
198
199        let actual = super::LINTS
200            .iter()
201            .map(|l| l.name.to_uppercase())
202            .collect::<Vec<_>>();
203
204        let mut expected = actual.clone();
205        expected.sort();
206        snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
207    }
208
209    #[test]
210    fn ensure_sorted_lint_groups() {
211        // This will be printed out if the fields are not sorted.
212        let location = std::panic::Location::caller();
213        println!(
214            "\nTo fix this test, sort `LINT_GROUPS` in {}\n",
215            location.file(),
216        );
217        let actual = super::LINT_GROUPS
218            .iter()
219            .map(|l| l.name.to_uppercase())
220            .collect::<Vec<_>>();
221
222        let mut expected = actual.clone();
223        expected.sort();
224        snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
225    }
226
227    #[test]
228    fn ensure_updated_lints() {
229        let dir = snapbox::utils::current_dir!();
230        let mut expected = HashSet::new();
231        for entry in std::fs::read_dir(&dir).unwrap() {
232            let entry = entry.unwrap();
233            let path = entry.path();
234            if path.ends_with("mod.rs") {
235                continue;
236            }
237            let content = std::fs::read_to_string(&path).unwrap();
238            if !content.contains("LINT") {
239                // diagnostic
240                continue;
241            }
242            let lint_name = path.file_stem().unwrap().to_string_lossy();
243            assert!(expected.insert(lint_name.into()), "duplicate lint found");
244        }
245
246        let actual = super::LINTS
247            .iter()
248            .map(|l| l.name.to_string())
249            .collect::<HashSet<_>>();
250        let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
251
252        let mut need_added = String::new();
253        for name in &diff {
254            need_added.push_str(&format!("{name}\n"));
255        }
256        assert!(
257            diff.is_empty(),
258            "\n`LINTS` did not contain all `Lint`s found in {}\n\
259            Please add the following to `LINTS`:\n\
260            {need_added}",
261            dir.display(),
262        );
263    }
264
265    #[test]
266    fn ensure_updated_lint_groups() {
267        let path = snapbox::utils::current_rs!();
268        let expected = std::fs::read_to_string(&path).unwrap();
269        let expected = expected
270            .lines()
271            .filter_map(|l| {
272                if l.ends_with(": LintGroup = LintGroup {") {
273                    Some(
274                        l.chars()
275                            .skip(6)
276                            .take_while(|c| *c != ':')
277                            .collect::<String>(),
278                    )
279                } else {
280                    None
281                }
282            })
283            .collect::<HashSet<_>>();
284        let actual = super::LINT_GROUPS
285            .iter()
286            .map(|l| l.name.to_uppercase())
287            .collect::<HashSet<_>>();
288        let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
289
290        let mut need_added = String::new();
291        for name in &diff {
292            need_added.push_str(&format!("{}\n", name));
293        }
294        assert!(
295            diff.is_empty(),
296            "\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
297            Please add the following to `LINT_GROUPS`:\n\
298            {}",
299            path.display(),
300            need_added
301        );
302    }
303}