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::LintLevelProduct;
19use crate::diagnostics::ManifestFor;
20use crate::diagnostics::ScopedDiagnosticStats;
21use crate::diagnostics::get_key_value_span;
22use crate::diagnostics::workspace_rel_path;
23use crate::workspace::MaybePackage;
24use crate::workspace::Workspace;
25
26pub static LINT: &Lint = &Lint {
27 name: "unknown_lints",
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 is this 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 ws: &Workspace<'_>,
54 manifest: ManifestFor<'_>,
55 manifest_path: &Path,
56 level: LintLevelProduct,
57 pkg_stats: &mut ScopedDiagnosticStats<'_>,
58 gctx: &GlobalContext,
59) -> CargoResult<()> {
60 let normalized_toml = match &manifest {
61 ManifestFor::Package(pkg) => pkg.manifest().normalized_toml(),
62 ManifestFor::Workspace {
63 maybe_pkg: MaybePackage::Virtual(vm),
64 ..
65 } => vm.normalized_toml(),
66 ManifestFor::Workspace {
67 maybe_pkg: MaybePackage::Package(_),
68 ..
69 } => {
70 return Ok(());
72 }
73 };
74
75 let ws_lints = normalized_toml
76 .workspace
77 .as_ref()
78 .and_then(|ws| ws.lints.as_ref())
79 .and_then(|lints| lints.get("cargo"));
80 let pkg_lints = normalized_toml
81 .lints
82 .as_ref()
83 .map(|lints| &lints.lints)
84 .and_then(|lints| lints.get("cargo"));
85
86 if let Some(cargo_lints) = ws_lints {
87 lint_manifest_inner(
88 ws,
89 &manifest,
90 manifest_path,
91 &level,
92 cargo_lints,
93 pkg_stats,
94 gctx,
95 )?;
96 }
97 if let Some(cargo_lints) = pkg_lints {
98 lint_manifest_inner(
99 ws,
100 &manifest,
101 manifest_path,
102 &level,
103 cargo_lints,
104 pkg_stats,
105 gctx,
106 )?;
107 }
108
109 Ok(())
110}
111
112fn lint_manifest_inner(
113 ws: &Workspace<'_>,
114 manifest: &ManifestFor<'_>,
115 manifest_path: &Path,
116 level: &LintLevelProduct,
117 cargo_lints: &TomlToolLints,
118 pkg_stats: &mut ScopedDiagnosticStats<'_>,
119 gctx: &GlobalContext,
120) -> CargoResult<()> {
121 let LintLevelProduct {
122 level: lint_level,
123 source,
124 } = level;
125
126 let manifest_path = workspace_rel_path(ws, manifest_path);
127 let mut unknown_lints = Vec::new();
128 for lint_name in cargo_lints.keys().map(|name| name) {
129 let Some(_) = find_lint_or_group(lint_name) else {
130 unknown_lints.push(lint_name);
131 continue;
132 };
133 }
134
135 let level = lint_level.to_diagnostic_level();
136 let mut emitted_source = None;
137 for lint_name in unknown_lints {
138 let title = format!("unknown lint: `{lint_name}`");
139 let underscore_lint_name = lint_name.replace("-", "_");
140 let matching = if let Some(lint) = LINTS.iter().find(|l| l.name == underscore_lint_name) {
141 Some((lint.name, "lint"))
142 } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == underscore_lint_name) {
143 Some((group.name, "group"))
144 } else {
145 None
146 };
147 let help =
148 matching.map(|(name, kind)| format!("there is a {kind} with a similar name: `{name}`"));
149
150 let key_path = match manifest {
151 ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
152 ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..],
153 };
154
155 let mut report = Vec::new();
156 let mut group = Group::with_title(level.clone().primary_title(title));
157
158 if let Some(document) = manifest.document()
159 && let Some(contents) = manifest.contents()
160 {
161 let Some(span) = get_key_value_span(document, key_path) else {
162 return Ok(());
164 };
165 group = group.element(
166 Snippet::source(contents)
167 .path(&manifest_path)
168 .annotation(AnnotationKind::Primary.span(span.key)),
169 );
170 } else {
171 group = group.element(Origin::path(&manifest_path));
172 }
173
174 if emitted_source.is_none() {
175 emitted_source = Some(LINT.emitted_source(*lint_level, *source));
176 group = group.element(Level::NOTE.message(emitted_source.as_ref().unwrap()));
177 }
178 if let Some(help) = help.as_ref() {
179 group = group.element(Level::HELP.message(help));
180 }
181 report.push(group);
182
183 pkg_stats.record_lint(*lint_level);
184 gctx.shell().print_report(&report, lint_level.force())?;
185 }
186
187 Ok(())
188}