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::core::MaybePackage;
18use crate::diagnostics::DiagnosticStats;
19use crate::diagnostics::Lint;
20use crate::diagnostics::LintLevelProduct;
21use crate::diagnostics::ManifestFor;
22use crate::diagnostics::get_key_value_span;
23use crate::diagnostics::rel_cwd_manifest_path;
24
25pub static LINT: &Lint = &Lint {
26 name: "unknown_lints",
27 desc: "unknown lint",
28 primary_group: &SUSPICIOUS,
29 msrv: Some(super::CARGO_LINTS_MSRV),
30 feature_gate: None,
31 docs: Some(
32 r#"
33### What it does
34Checks for unknown lints in the `[lints.cargo]` table
35
36### Why it is bad
37- The lint name could be misspelled, leading to confusion as to why it is
38 not working as expected
39- The unknown lint could end up causing an error if `cargo` decides to make
40 a lint with the same name in the future
41
42### Example
43```toml
44[lints.cargo]
45this-lint-does-not-exist = "warn"
46```
47"#,
48 ),
49};
50
51#[instrument(skip_all)]
52pub(crate) fn lint_manifest(
53 manifest: ManifestFor<'_>,
54 manifest_path: &Path,
55 level: LintLevelProduct,
56 stats: &mut DiagnosticStats,
57 gctx: &GlobalContext,
58) -> CargoResult<()> {
59 let normalized_toml = match &manifest {
60 ManifestFor::Package(pkg) => pkg.manifest().normalized_toml(),
61 ManifestFor::Workspace {
62 maybe_pkg: MaybePackage::Virtual(vm),
63 ..
64 } => vm.normalized_toml(),
65 ManifestFor::Workspace {
66 maybe_pkg: MaybePackage::Package(_),
67 ..
68 } => {
69 return Ok(());
71 }
72 };
73
74 let ws_lints = normalized_toml
75 .workspace
76 .as_ref()
77 .and_then(|ws| ws.lints.as_ref())
78 .and_then(|lints| lints.get("cargo"));
79 let pkg_lints = normalized_toml
80 .lints
81 .as_ref()
82 .map(|lints| &lints.lints)
83 .and_then(|lints| lints.get("cargo"));
84
85 if let Some(cargo_lints) = ws_lints {
86 lint_manifest_inner(&manifest, manifest_path, &level, cargo_lints, stats, gctx)?;
87 }
88 if let Some(cargo_lints) = pkg_lints {
89 lint_manifest_inner(&manifest, manifest_path, &level, cargo_lints, stats, gctx)?;
90 }
91
92 Ok(())
93}
94
95fn lint_manifest_inner(
96 manifest: &ManifestFor<'_>,
97 manifest_path: &Path,
98 level: &LintLevelProduct,
99 cargo_lints: &TomlToolLints,
100 stats: &mut DiagnosticStats,
101 gctx: &GlobalContext,
102) -> CargoResult<()> {
103 let LintLevelProduct {
104 level: lint_level,
105 source,
106 } = level;
107
108 let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
109 let mut unknown_lints = Vec::new();
110 for lint_name in cargo_lints.keys().map(|name| name) {
111 let Some(_) = find_lint_or_group(lint_name) else {
112 unknown_lints.push(lint_name);
113 continue;
114 };
115 }
116
117 let level = lint_level.to_diagnostic_level();
118 let mut emitted_source = None;
119 for lint_name in unknown_lints {
120 let title = format!("{}: `{lint_name}`", LINT.desc);
121 let underscore_lint_name = lint_name.replace("-", "_");
122 let matching = if let Some(lint) = LINTS.iter().find(|l| l.name == underscore_lint_name) {
123 Some((lint.name, "lint"))
124 } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == underscore_lint_name) {
125 Some((group.name, "group"))
126 } else {
127 None
128 };
129 let help =
130 matching.map(|(name, kind)| format!("there is a {kind} with a similar name: `{name}`"));
131
132 let key_path = match manifest {
133 ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
134 ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..],
135 };
136
137 let mut report = Vec::new();
138 let mut group = Group::with_title(level.clone().primary_title(title));
139
140 if let Some(document) = manifest.document()
141 && let Some(contents) = manifest.contents()
142 {
143 let Some(span) = get_key_value_span(document, key_path) else {
144 return Ok(());
146 };
147 group = group.element(
148 Snippet::source(contents)
149 .path(&manifest_path)
150 .annotation(AnnotationKind::Primary.span(span.key)),
151 );
152 } else {
153 group = group.element(Origin::path(&manifest_path));
154 }
155
156 if emitted_source.is_none() {
157 emitted_source = Some(LINT.emitted_source(*lint_level, *source));
158 group = group.element(Level::NOTE.message(emitted_source.as_ref().unwrap()));
159 }
160 if let Some(help) = help.as_ref() {
161 group = group.element(Level::HELP.message(help));
162 }
163 report.push(group);
164
165 stats.record_lint(*lint_level);
166 gctx.shell().print_report(&report, lint_level.force())?;
167 }
168
169 Ok(())
170}