Skip to main content

cargo/diagnostics/rules/
mod.rs

1mod blanket_hint_mostly_unused;
2mod deferred_parse_diagnostics;
3mod im_a_teapot;
4mod implicit_minimum_version_req;
5mod missing_lints_features;
6mod missing_lints_inheritance;
7mod non_kebab_case_bins;
8mod non_kebab_case_features;
9mod non_kebab_case_packages;
10mod non_snake_case_features;
11mod non_snake_case_packages;
12mod redundant_homepage;
13mod redundant_readme;
14mod text_direction_codepoint_in_comment;
15mod text_direction_codepoint_in_literal;
16mod unknown_lints;
17pub mod unused_dependencies;
18mod unused_workspace_dependencies;
19mod unused_workspace_package_fields;
20
21use super::LintGroup;
22use super::LintLevel;
23use super::passes::ParsePassRule;
24use crate::core::Feature;
25
26pub const PARSE_PASS_RULES: &[ParsePassRule<'static>] = &[
27    ParsePassRule::DiagnosticManifest {
28        rule: deferred_parse_diagnostics::diagnose_manifest,
29    },
30    ParsePassRule::DiagnosticManifest {
31        rule: missing_lints_features::diagnose_manifest,
32    },
33    ParsePassRule::LintManifest {
34        rule: text_direction_codepoint_in_comment::lint_manifest,
35        lint: text_direction_codepoint_in_comment::LINT,
36    },
37    ParsePassRule::LintManifest {
38        rule: text_direction_codepoint_in_literal::lint_manifest,
39        lint: text_direction_codepoint_in_literal::LINT,
40    },
41    ParsePassRule::LintManifest {
42        rule: unknown_lints::lint_manifest,
43        lint: unknown_lints::LINT,
44    },
45    ParsePassRule::LintWorkspace {
46        rule: blanket_hint_mostly_unused::lint_workspace,
47        lint: blanket_hint_mostly_unused::LINT,
48    },
49    ParsePassRule::LintWorkspace {
50        rule: unused_workspace_dependencies::lint_workspace,
51        lint: unused_workspace_dependencies::LINT,
52    },
53    ParsePassRule::LintWorkspace {
54        rule: unused_workspace_package_fields::lint_workspace,
55        lint: unused_workspace_package_fields::LINT,
56    },
57    ParsePassRule::LintWorkspace {
58        rule: implicit_minimum_version_req::lint_workspace,
59        lint: implicit_minimum_version_req::LINT,
60    },
61    // `warn`
62    ParsePassRule::LintPackage {
63        rule: missing_lints_inheritance::lint_package,
64        lint: missing_lints_inheritance::LINT,
65    },
66    ParsePassRule::LintPackage {
67        rule: non_kebab_case_bins::lint_package,
68        lint: non_kebab_case_bins::LINT,
69    },
70    ParsePassRule::LintPackage {
71        rule: redundant_homepage::lint_package,
72        lint: redundant_homepage::LINT,
73    },
74    ParsePassRule::LintPackage {
75        rule: redundant_readme::lint_package,
76        lint: redundant_readme::LINT,
77    },
78    ParsePassRule::LintPackage {
79        rule: unused_dependencies::lint_package,
80        lint: unused_dependencies::LINT,
81    },
82    ParsePassRule::LintPackage {
83        rule: im_a_teapot::lint_package,
84        lint: im_a_teapot::LINT,
85    },
86    // `allow`
87    ParsePassRule::LintPackage {
88        rule: implicit_minimum_version_req::lint_package,
89        lint: implicit_minimum_version_req::LINT,
90    },
91    ParsePassRule::LintPackage {
92        rule: non_kebab_case_features::lint_package,
93        lint: non_kebab_case_features::LINT,
94    },
95    ParsePassRule::LintPackage {
96        rule: non_kebab_case_packages::lint_package,
97        lint: non_kebab_case_packages::LINT,
98    },
99    ParsePassRule::LintPackage {
100        rule: non_snake_case_features::lint_package,
101        lint: non_snake_case_features::LINT,
102    },
103    ParsePassRule::LintPackage {
104        rule: non_snake_case_packages::lint_package,
105        lint: non_snake_case_packages::LINT,
106    },
107];
108
109pub static LINTS: &[&crate::diagnostics::Lint] = &[
110    blanket_hint_mostly_unused::LINT,
111    implicit_minimum_version_req::LINT,
112    im_a_teapot::LINT,
113    missing_lints_inheritance::LINT,
114    non_kebab_case_bins::LINT,
115    non_kebab_case_features::LINT,
116    non_kebab_case_packages::LINT,
117    non_snake_case_features::LINT,
118    non_snake_case_packages::LINT,
119    redundant_homepage::LINT,
120    redundant_readme::LINT,
121    text_direction_codepoint_in_comment::LINT,
122    text_direction_codepoint_in_literal::LINT,
123    unknown_lints::LINT,
124    unused_dependencies::LINT,
125    unused_workspace_dependencies::LINT,
126    unused_workspace_package_fields::LINT,
127];
128
129/// Version required for specifying `[lints.cargo]`
130///
131/// Before this, it was an error.  No on-by-default lint should fire before this time without
132/// another way of disabling it.
133static CARGO_LINTS_MSRV: cargo_util_schemas::manifest::RustVersion =
134    cargo_util_schemas::manifest::RustVersion::new(1, 79, 0);
135
136pub static LINT_GROUPS: &[LintGroup] = &[
137    COMPLEXITY,
138    CORRECTNESS,
139    NURSERY,
140    PEDANTIC,
141    PERF,
142    RESTRICTION,
143    STYLE,
144    SUSPICIOUS,
145    TEST_DUMMY_UNSTABLE,
146];
147
148const COMPLEXITY: LintGroup = LintGroup {
149    name: "complexity",
150    desc: "code that does something simple but in a complex way",
151    default_level: LintLevel::Warn,
152    feature_gate: None,
153    hidden: false,
154};
155
156const CORRECTNESS: LintGroup = LintGroup {
157    name: "correctness",
158    desc: "code that is outright wrong or useless",
159    default_level: LintLevel::Deny,
160    feature_gate: None,
161    hidden: false,
162};
163
164const NURSERY: LintGroup = LintGroup {
165    name: "nursery",
166    desc: "new lints that are still under development",
167    default_level: LintLevel::Allow,
168    feature_gate: None,
169    hidden: false,
170};
171
172const PEDANTIC: LintGroup = LintGroup {
173    name: "pedantic",
174    desc: "lints which are rather strict or have occasional false positives",
175    default_level: LintLevel::Allow,
176    feature_gate: None,
177    hidden: false,
178};
179
180const PERF: LintGroup = LintGroup {
181    name: "perf",
182    desc: "code that can be written to run faster",
183    default_level: LintLevel::Warn,
184    feature_gate: None,
185    hidden: false,
186};
187
188const RESTRICTION: LintGroup = LintGroup {
189    name: "restriction",
190    desc: "lints which prevent the use of Cargo features",
191    default_level: LintLevel::Allow,
192    feature_gate: None,
193    hidden: false,
194};
195
196const STYLE: LintGroup = LintGroup {
197    name: "style",
198    desc: "code that should be written in a more idiomatic way",
199    default_level: LintLevel::Warn,
200    feature_gate: None,
201    hidden: false,
202};
203
204const SUSPICIOUS: LintGroup = LintGroup {
205    name: "suspicious",
206    desc: "code that is most likely wrong or useless",
207    default_level: LintLevel::Warn,
208    feature_gate: None,
209    hidden: false,
210};
211
212/// This lint group is only to be used for testing purposes
213const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
214    name: "test_dummy_unstable",
215    desc: "test_dummy_unstable is meant to only be used in tests",
216    default_level: LintLevel::Allow,
217    feature_gate: Some(crate::core::Feature::test_dummy_unstable()),
218    hidden: true,
219};
220
221fn find_lint_or_group<'a>(
222    name: &str,
223) -> Option<(&'static str, &LintLevel, &Option<&'static Feature>)> {
224    if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
225        Some((
226            lint.name,
227            &lint.primary_group.default_level,
228            &lint.feature_gate,
229        ))
230    } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
231        Some((group.name, &group.default_level, &group.feature_gate))
232    } else {
233        None
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use itertools::Itertools;
240    use snapbox::ToDebug;
241    use std::collections::HashSet;
242
243    use super::*;
244
245    #[test]
246    fn ensure_lint_groups_do_not_default_to_forbid() {
247        let forbid_groups = LINT_GROUPS
248            .iter()
249            .filter(|g| matches!(g.default_level, LintLevel::Forbid))
250            .collect::<Vec<_>>();
251
252        assert!(
253            forbid_groups.is_empty(),
254            "\n`LintGroup`s should never default to `forbid`, but the following do:\n\
255            {}\n",
256            forbid_groups.iter().map(|g| g.name).join("\n")
257        );
258    }
259
260    #[test]
261    fn ensure_sorted_lints() {
262        // This will be printed out if the fields are not sorted.
263        let location = std::panic::Location::caller();
264        println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
265
266        let actual = LINTS
267            .iter()
268            .map(|l| l.name.to_uppercase())
269            .collect::<Vec<_>>();
270
271        let mut expected = actual.clone();
272        expected.sort();
273        snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
274    }
275
276    #[test]
277    fn ensure_sorted_lint_groups() {
278        // This will be printed out if the fields are not sorted.
279        let location = std::panic::Location::caller();
280        println!(
281            "\nTo fix this test, sort `LINT_GROUPS` in {}\n",
282            location.file(),
283        );
284        let actual = LINT_GROUPS
285            .iter()
286            .map(|l| l.name.to_uppercase())
287            .collect::<Vec<_>>();
288
289        let mut expected = actual.clone();
290        expected.sort();
291        snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
292    }
293
294    #[test]
295    fn ensure_sorted_parse_pass_rules() {
296        let actual = parse_pass_rule_names(PARSE_PASS_RULES);
297        let mut ordered_parse_pass = PARSE_PASS_RULES.to_vec();
298        ordered_parse_pass.sort_by_key(|rule| {
299            let (lint, scope) = match rule {
300                ParsePassRule::DiagnosticManifest { .. } => {
301                    let scope = 0;
302                    (None, scope)
303                }
304                ParsePassRule::LintManifest { lint, .. } => {
305                    let scope = 0;
306                    (Some(lint), scope)
307                }
308                ParsePassRule::DiagnosticWorkspace { .. } => {
309                    let scope = 1;
310                    (None, scope)
311                }
312                ParsePassRule::LintWorkspace { lint, .. } => {
313                    let scope = 1;
314                    (Some(lint), scope)
315                }
316                ParsePassRule::DiagnosticPackage { .. } => {
317                    let scope = 2;
318                    (None, scope)
319                }
320                ParsePassRule::LintPackage { lint, .. } => {
321                    let scope = 2;
322                    (Some(lint), scope)
323                }
324            };
325            let is_lint = lint.is_some();
326            let level = lint.map(|l| std::cmp::Reverse(l.primary_group.default_level));
327            let name = lint.map(|l| l.name);
328            (is_lint, scope, level, name)
329        });
330        let expected = parse_pass_rule_names(&ordered_parse_pass);
331
332        println!("`PARSE_PASS_RULES` sort order:");
333        snapbox::assert_data_eq!(actual.join("\n"), expected.join("\n"));
334    }
335
336    #[test]
337    fn ensure_parse_passed_in_lints() {
338        let parse_pass_lint_names =
339            HashSet::from_iter(parse_pass_rule_names(PARSE_PASS_RULES).into_iter());
340        let lint_names = LINTS
341            .iter()
342            .map(|l| l.name)
343            .collect::<std::collections::HashSet<_>>();
344        let diff = parse_pass_lint_names
345            .difference(&lint_names)
346            .sorted()
347            .collect::<Vec<_>>();
348        let mut need_added = String::new();
349        for name in &diff {
350            need_added.push_str(&format!("{name}\n"));
351        }
352        assert!(
353            diff.is_empty(),
354            "\n`LINTS` did not contain all `Lint`s found in `PARSE_PASS_RULES`\n\
355            Please add the following to `LINTS`:\n\
356            {need_added}",
357        );
358    }
359
360    fn parse_pass_rule_names(rules: &[ParsePassRule<'_>]) -> Vec<&'static str> {
361        rules
362            .iter()
363            .filter_map(|rule| match rule {
364                ParsePassRule::DiagnosticManifest { .. }
365                | ParsePassRule::DiagnosticWorkspace { .. }
366                | ParsePassRule::DiagnosticPackage { .. } => None,
367                ParsePassRule::LintManifest { lint, .. }
368                | ParsePassRule::LintWorkspace { lint, .. }
369                | ParsePassRule::LintPackage { lint, .. } => Some(lint.name),
370            })
371            .collect()
372    }
373
374    #[test]
375    fn ensure_updated_lints() {
376        let dir = snapbox::utils::current_dir!();
377        let mut expected = HashSet::new();
378        for entry in std::fs::read_dir(&dir).unwrap() {
379            let entry = entry.unwrap();
380            let path = entry.path();
381            if path.ends_with("mod.rs") {
382                continue;
383            }
384            let content = std::fs::read_to_string(&path).unwrap();
385            if !content.contains("LINT") {
386                // diagnostic
387                continue;
388            }
389            let lint_name = path.file_stem().unwrap().to_string_lossy();
390            assert!(expected.insert(lint_name.into()), "duplicate lint found");
391        }
392
393        let actual = LINTS
394            .iter()
395            .map(|l| l.name.to_string())
396            .collect::<HashSet<_>>();
397        let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
398
399        let mut need_added = String::new();
400        for name in &diff {
401            need_added.push_str(&format!("{name}\n"));
402        }
403        assert!(
404            diff.is_empty(),
405            "\n`LINTS` did not contain all `Lint`s found in {}\n\
406            Please add the following to `LINTS`:\n\
407            {need_added}",
408            dir.display(),
409        );
410    }
411
412    #[test]
413    fn ensure_updated_lint_groups() {
414        let path = snapbox::utils::current_rs!();
415        let expected = std::fs::read_to_string(&path).unwrap();
416        let expected = expected
417            .lines()
418            .filter_map(|l| {
419                if l.ends_with(": LintGroup = LintGroup {") {
420                    Some(
421                        l.chars()
422                            .skip(6)
423                            .take_while(|c| *c != ':')
424                            .collect::<String>(),
425                    )
426                } else {
427                    None
428                }
429            })
430            .collect::<HashSet<_>>();
431        let actual = LINT_GROUPS
432            .iter()
433            .map(|l| l.name.to_uppercase())
434            .collect::<HashSet<_>>();
435        let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
436
437        let mut need_added = String::new();
438        for name in &diff {
439            need_added.push_str(&format!("{}\n", name));
440        }
441        assert!(
442            diff.is_empty(),
443            "\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
444            Please add the following to `LINT_GROUPS`:\n\
445            {}",
446            path.display(),
447            need_added
448        );
449    }
450}