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