Skip to main content

cargo/diagnostics/rules/
unknown_lints.rs

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