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