cargo/lints/rules/
unknown_lints.rs

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