cargo/lints/rules/
unknown_lints.rs

1use annotate_snippets::AnnotationKind;
2use annotate_snippets::Group;
3use annotate_snippets::Level;
4use annotate_snippets::Origin;
5use annotate_snippets::Snippet;
6use cargo_util_schemas::manifest::TomlToolLints;
7
8use crate::CargoResult;
9use crate::GlobalContext;
10use crate::lints::LINT_GROUPS;
11use crate::lints::LINTS;
12use crate::lints::Lint;
13use crate::lints::LintLevel;
14use crate::lints::ManifestFor;
15use crate::lints::SUSPICIOUS;
16use crate::lints::get_key_value_span;
17
18pub const LINT: Lint = Lint {
19    name: "unknown_lints",
20    desc: "unknown lint",
21    primary_group: &SUSPICIOUS,
22    edition_lint_opts: None,
23    feature_gate: None,
24    docs: Some(
25        r#"
26### What it does
27Checks for unknown lints in the `[lints.cargo]` table
28
29### Why it is bad
30- The lint name could be misspelled, leading to confusion as to why it is
31  not working as expected
32- The unknown lint could end up causing an error if `cargo` decides to make
33  a lint with the same name in the future
34
35### Example
36```toml
37[lints.cargo]
38this-lint-does-not-exist = "warn"
39```
40"#,
41    ),
42};
43
44pub fn output_unknown_lints(
45    unknown_lints: Vec<&String>,
46    manifest: &ManifestFor<'_>,
47    manifest_path: &str,
48    cargo_lints: &TomlToolLints,
49    error_count: &mut usize,
50    gctx: &GlobalContext,
51) -> CargoResult<()> {
52    let (lint_level, reason) = manifest.lint_level(cargo_lints, LINT);
53    if lint_level == LintLevel::Allow {
54        return Ok(());
55    }
56
57    let level = lint_level.to_diagnostic_level();
58    let mut emitted_source = None;
59    for lint_name in unknown_lints {
60        if lint_level.is_error() {
61            *error_count += 1;
62        }
63        let title = format!("{}: `{lint_name}`", LINT.desc);
64        let underscore_lint_name = lint_name.replace("-", "_");
65        let matching = if let Some(lint) = LINTS.iter().find(|l| l.name == underscore_lint_name) {
66            Some((lint.name, "lint"))
67        } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == underscore_lint_name) {
68            Some((group.name, "group"))
69        } else {
70            None
71        };
72        let help =
73            matching.map(|(name, kind)| format!("there is a {kind} with a similar name: `{name}`"));
74
75        let key_path = match manifest {
76            ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
77            ManifestFor::Workspace(_) => &["workspace", "lints", "cargo", lint_name][..],
78        };
79
80        let mut report = Vec::new();
81        let mut group = Group::with_title(level.clone().primary_title(title));
82
83        if let Some(document) = manifest.document()
84            && let Some(contents) = manifest.contents()
85        {
86            let Some(span) = get_key_value_span(document, key_path) else {
87                // This lint is handled by either package or workspace lint.
88                return Ok(());
89            };
90            group = group.element(
91                Snippet::source(contents)
92                    .path(manifest_path)
93                    .annotation(AnnotationKind::Primary.span(span.key)),
94            );
95        } else {
96            group = group.element(Origin::path(manifest_path));
97        }
98
99        if emitted_source.is_none() {
100            emitted_source = Some(LINT.emitted_source(lint_level, reason));
101            group = group.element(Level::NOTE.message(emitted_source.as_ref().unwrap()));
102        }
103        if let Some(help) = help.as_ref() {
104            group = group.element(Level::HELP.message(help));
105        }
106        report.push(group);
107
108        gctx.shell().print_report(&report, lint_level.force())?;
109    }
110
111    Ok(())
112}