Skip to main content

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