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