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