cargo/lints/rules/
unknown_lints.rs1use 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 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}