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 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 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}