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 pkg_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(
87 &manifest,
88 manifest_path,
89 &level,
90 cargo_lints,
91 pkg_stats,
92 gctx,
93 )?;
94 }
95 if let Some(cargo_lints) = pkg_lints {
96 lint_manifest_inner(
97 &manifest,
98 manifest_path,
99 &level,
100 cargo_lints,
101 pkg_stats,
102 gctx,
103 )?;
104 }
105
106 Ok(())
107}
108
109fn lint_manifest_inner(
110 manifest: &ManifestFor<'_>,
111 manifest_path: &Path,
112 level: &LintLevelProduct,
113 cargo_lints: &TomlToolLints,
114 pkg_stats: &mut DiagnosticStats,
115 gctx: &GlobalContext,
116) -> CargoResult<()> {
117 let LintLevelProduct {
118 level: lint_level,
119 source,
120 } = level;
121
122 let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
123 let mut unknown_lints = Vec::new();
124 for lint_name in cargo_lints.keys().map(|name| name) {
125 let Some(_) = find_lint_or_group(lint_name) else {
126 unknown_lints.push(lint_name);
127 continue;
128 };
129 }
130
131 let level = lint_level.to_diagnostic_level();
132 let mut emitted_source = None;
133 for lint_name in unknown_lints {
134 let title = format!("{}: `{lint_name}`", LINT.desc);
135 let underscore_lint_name = lint_name.replace("-", "_");
136 let matching = if let Some(lint) = LINTS.iter().find(|l| l.name == underscore_lint_name) {
137 Some((lint.name, "lint"))
138 } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == underscore_lint_name) {
139 Some((group.name, "group"))
140 } else {
141 None
142 };
143 let help =
144 matching.map(|(name, kind)| format!("there is a {kind} with a similar name: `{name}`"));
145
146 let key_path = match manifest {
147 ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
148 ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..],
149 };
150
151 let mut report = Vec::new();
152 let mut group = Group::with_title(level.clone().primary_title(title));
153
154 if let Some(document) = manifest.document()
155 && let Some(contents) = manifest.contents()
156 {
157 let Some(span) = get_key_value_span(document, key_path) else {
158 return Ok(());
160 };
161 group = group.element(
162 Snippet::source(contents)
163 .path(&manifest_path)
164 .annotation(AnnotationKind::Primary.span(span.key)),
165 );
166 } else {
167 group = group.element(Origin::path(&manifest_path));
168 }
169
170 if emitted_source.is_none() {
171 emitted_source = Some(LINT.emitted_source(*lint_level, *source));
172 group = group.element(Level::NOTE.message(emitted_source.as_ref().unwrap()));
173 }
174 if let Some(help) = help.as_ref() {
175 group = group.element(Level::HELP.message(help));
176 }
177 report.push(group);
178
179 pkg_stats.record_lint(*lint_level);
180 gctx.shell().print_report(&report, lint_level.force())?;
181 }
182
183 Ok(())
184}