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    DEFAULT,
138    CORRECTNESS,
139    COMPLEXITY,
140    PERF,
141    STYLE,
142    SUSPICIOUS,
143    NURSERY,
144    PEDANTIC,
145    RESTRICTION,
146    TEST_DUMMY_UNSTABLE,
147];
148
149const DEFAULT: LintGroup = LintGroup {
150    name: "default",
151    desc: "all lints that are on by default (correctness, suspicious, style, complexity, perf)",
152    default_level: LintLevel::Warn,
153    feature_gate: None,
154    hidden: false,
155};
156
157const COMPLEXITY: LintGroup = LintGroup {
158    name: "complexity",
159    desc: "code that does something simple but in a complex way",
160    default_level: LintLevel::Warn,
161    feature_gate: None,
162    hidden: false,
163};
164
165const CORRECTNESS: LintGroup = LintGroup {
166    name: "correctness",
167    desc: "code that is outright wrong or useless",
168    default_level: LintLevel::Deny,
169    feature_gate: None,
170    hidden: false,
171};
172
173const NURSERY: LintGroup = LintGroup {
174    name: "nursery",
175    desc: "new lints that are still under development",
176    default_level: LintLevel::Allow,
177    feature_gate: None,
178    hidden: false,
179};
180
181const PEDANTIC: LintGroup = LintGroup {
182    name: "pedantic",
183    desc: "lints which are rather strict or have occasional false positives",
184    default_level: LintLevel::Allow,
185    feature_gate: None,
186    hidden: false,
187};
188
189const PERF: LintGroup = LintGroup {
190    name: "perf",
191    desc: "code that can be written to run faster",
192    default_level: LintLevel::Warn,
193    feature_gate: None,
194    hidden: false,
195};
196
197const RESTRICTION: LintGroup = LintGroup {
198    name: "restriction",
199    desc: "lints which prevent the use of Cargo features",
200    default_level: LintLevel::Allow,
201    feature_gate: None,
202    hidden: false,
203};
204
205const STYLE: LintGroup = LintGroup {
206    name: "style",
207    desc: "code that should be written in a more idiomatic way",
208    default_level: LintLevel::Warn,
209    feature_gate: None,
210    hidden: false,
211};
212
213const SUSPICIOUS: LintGroup = LintGroup {
214    name: "suspicious",
215    desc: "code that is most likely wrong or useless",
216    default_level: LintLevel::Warn,
217    feature_gate: None,
218    hidden: false,
219};
220
221/// This lint group is only to be used for testing purposes
222const TEST_DUMMY_UNSTABLE: LintGroup = LintGroup {
223    name: "test_dummy_unstable",
224    desc: "test_dummy_unstable is meant to only be used in tests",
225    default_level: LintLevel::Allow,
226    feature_gate: Some(crate::core::Feature::test_dummy_unstable()),
227    hidden: true,
228};
229
230fn find_lint_or_group<'a>(
231    name: &str,
232) -> Option<(&'static str, &LintLevel, &Option<&'static Feature>)> {
233    if let Some(lint) = LINTS.iter().find(|l| l.name == name) {
234        Some((
235            lint.name,
236            &lint.primary_group.default_level,
237            &lint.feature_gate,
238        ))
239    } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == name) {
240        Some((group.name, &group.default_level, &group.feature_gate))
241    } else {
242        None
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use itertools::Itertools;
249    use snapbox::ToDebug;
250    use std::cmp::Reverse;
251    use std::collections::HashSet;
252
253    use super::*;
254
255    #[test]
256    fn ensure_lint_groups_do_not_default_to_forbid() {
257        let forbid_groups = LINT_GROUPS
258            .iter()
259            .filter(|g| matches!(g.default_level, LintLevel::Forbid))
260            .collect::<Vec<_>>();
261
262        assert!(
263            forbid_groups.is_empty(),
264            "\n`LintGroup`s should never default to `forbid`, but the following do:\n\
265            {}\n",
266            forbid_groups.iter().map(|g| g.name).join("\n")
267        );
268    }
269
270    #[test]
271    fn ensure_sorted_lints() {
272        // This will be printed out if the fields are not sorted.
273        let location = std::panic::Location::caller();
274        println!("\nTo fix this test, sort `LINTS` in {}\n", location.file(),);
275
276        let actual = LINTS
277            .iter()
278            .map(|l| l.name.to_uppercase())
279            .collect::<Vec<_>>();
280
281        let mut expected = actual.clone();
282        expected.sort();
283        snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
284    }
285
286    #[test]
287    fn ensure_sorted_lint_groups() {
288        // This will be printed out if the fields are not sorted.
289        let location = std::panic::Location::caller();
290        println!(
291            "\nTo fix this test, sort `LINT_GROUPS` in {}\n",
292            location.file(),
293        );
294        let actual = LINT_GROUPS
295            .iter()
296            .map(|l| {
297                (
298                    l.name != "default",
299                    Reverse(l.default_level),
300                    l.name.to_uppercase(),
301                )
302            })
303            .collect::<Vec<_>>();
304
305        let mut expected = actual.clone();
306        expected.sort();
307        snapbox::assert_data_eq!(actual.to_debug(), expected.to_debug());
308    }
309
310    #[test]
311    fn ensure_sorted_parse_pass_rules() {
312        let actual = parse_pass_rule_names(PARSE_PASS_RULES);
313        let mut ordered_parse_pass = PARSE_PASS_RULES.to_vec();
314        ordered_parse_pass.sort_by_key(|rule| {
315            let (lint, scope) = match rule {
316                ParsePassRule::DiagnosticManifest { .. } => {
317                    let scope = 0;
318                    (None, scope)
319                }
320                ParsePassRule::LintManifest { lint, .. } => {
321                    let scope = 0;
322                    (Some(lint), scope)
323                }
324                ParsePassRule::DiagnosticWorkspace { .. } => {
325                    let scope = 1;
326                    (None, scope)
327                }
328                ParsePassRule::LintWorkspace { lint, .. } => {
329                    let scope = 1;
330                    (Some(lint), scope)
331                }
332                ParsePassRule::DiagnosticPackage { .. } => {
333                    let scope = 2;
334                    (None, scope)
335                }
336                ParsePassRule::LintPackage { lint, .. } => {
337                    let scope = 2;
338                    (Some(lint), scope)
339                }
340            };
341            let is_lint = lint.is_some();
342            let level = lint.map(|l| std::cmp::Reverse(l.primary_group.default_level));
343            let name = lint.map(|l| l.name);
344            (is_lint, scope, level, name)
345        });
346        let expected = parse_pass_rule_names(&ordered_parse_pass);
347
348        println!("`PARSE_PASS_RULES` sort order:");
349        snapbox::assert_data_eq!(actual.join("\n"), expected.join("\n"));
350    }
351
352    #[test]
353    fn ensure_parse_passed_in_lints() {
354        let parse_pass_lint_names =
355            HashSet::from_iter(parse_pass_rule_names(PARSE_PASS_RULES).into_iter());
356        let lint_names = LINTS
357            .iter()
358            .map(|l| l.name)
359            .collect::<std::collections::HashSet<_>>();
360        let diff = parse_pass_lint_names
361            .difference(&lint_names)
362            .sorted()
363            .collect::<Vec<_>>();
364        let mut need_added = String::new();
365        for name in &diff {
366            need_added.push_str(&format!("{name}\n"));
367        }
368        assert!(
369            diff.is_empty(),
370            "\n`LINTS` did not contain all `Lint`s found in `PARSE_PASS_RULES`\n\
371            Please add the following to `LINTS`:\n\
372            {need_added}",
373        );
374    }
375
376    fn parse_pass_rule_names(rules: &[ParsePassRule<'_>]) -> Vec<&'static str> {
377        rules
378            .iter()
379            .filter_map(|rule| match rule {
380                ParsePassRule::DiagnosticManifest { .. }
381                | ParsePassRule::DiagnosticWorkspace { .. }
382                | ParsePassRule::DiagnosticPackage { .. } => None,
383                ParsePassRule::LintManifest { lint, .. }
384                | ParsePassRule::LintWorkspace { lint, .. }
385                | ParsePassRule::LintPackage { lint, .. } => Some(lint.name),
386            })
387            .collect()
388    }
389
390    #[test]
391    fn ensure_updated_lints() {
392        let dir = snapbox::utils::current_dir!();
393        let mut expected = HashSet::new();
394        for entry in std::fs::read_dir(&dir).unwrap() {
395            let entry = entry.unwrap();
396            let path = entry.path();
397            if path.ends_with("mod.rs") {
398                continue;
399            }
400            let content = std::fs::read_to_string(&path).unwrap();
401            if !content.contains("LINT") {
402                // diagnostic
403                continue;
404            }
405            let lint_name = path.file_stem().unwrap().to_string_lossy();
406            assert!(expected.insert(lint_name.into()), "duplicate lint found");
407        }
408
409        let actual = LINTS
410            .iter()
411            .map(|l| l.name.to_string())
412            .collect::<HashSet<_>>();
413        let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
414
415        let mut need_added = String::new();
416        for name in &diff {
417            need_added.push_str(&format!("{name}\n"));
418        }
419        assert!(
420            diff.is_empty(),
421            "\n`LINTS` did not contain all `Lint`s found in {}\n\
422            Please add the following to `LINTS`:\n\
423            {need_added}",
424            dir.display(),
425        );
426    }
427
428    #[test]
429    fn ensure_updated_lint_groups() {
430        let path = snapbox::utils::current_rs!();
431        let expected = std::fs::read_to_string(&path).unwrap();
432        let expected = expected
433            .lines()
434            .filter_map(|l| {
435                if l.ends_with(": LintGroup = LintGroup {") {
436                    Some(
437                        l.chars()
438                            .skip(6)
439                            .take_while(|c| *c != ':')
440                            .collect::<String>(),
441                    )
442                } else {
443                    None
444                }
445            })
446            .collect::<HashSet<_>>();
447        let actual = LINT_GROUPS
448            .iter()
449            .map(|l| l.name.to_uppercase())
450            .collect::<HashSet<_>>();
451        let diff = expected.difference(&actual).sorted().collect::<Vec<_>>();
452
453        let mut need_added = String::new();
454        for name in &diff {
455            need_added.push_str(&format!("{}\n", name));
456        }
457        assert!(
458            diff.is_empty(),
459            "\n`LINT_GROUPS` did not contain all `LintGroup`s found in {}\n\
460            Please add the following to `LINT_GROUPS`:\n\
461            {}",
462            path.display(),
463            need_added
464        );
465    }
466}