cargo/lints/rules/
unknown_lints.rs1use annotate_snippets::AnnotationKind;
2use annotate_snippets::Group;
3use annotate_snippets::Level;
4use annotate_snippets::Snippet;
5use cargo_util_schemas::manifest::TomlToolLints;
6
7use crate::CargoResult;
8use crate::GlobalContext;
9use crate::lints::LINT_GROUPS;
10use crate::lints::LINTS;
11use crate::lints::Lint;
12use crate::lints::LintLevel;
13use crate::lints::ManifestFor;
14use crate::lints::get_key_value_span;
15
16pub const LINT: Lint = Lint {
17 name: "unknown_lints",
18 desc: "unknown lint",
19 groups: &[],
20 default_level: LintLevel::Warn,
21 edition_lint_opts: None,
22 feature_gate: None,
23 docs: Some(
24 r#"
25### What it does
26Checks for unknown lints in the `[lints.cargo]` table
27
28### Why it is bad
29- The lint name could be misspelled, leading to confusion as to why it is
30 not working as expected
31- The unknown lint could end up causing an error if `cargo` decides to make
32 a lint with the same name in the future
33
34### Example
35```toml
36[lints.cargo]
37this-lint-does-not-exist = "warn"
38```
39"#,
40 ),
41};
42
43pub fn output_unknown_lints(
44 unknown_lints: Vec<&String>,
45 manifest: &ManifestFor<'_>,
46 manifest_path: &str,
47 cargo_lints: &TomlToolLints,
48 error_count: &mut usize,
49 gctx: &GlobalContext,
50) -> CargoResult<()> {
51 let (lint_level, reason) = manifest.lint_level(cargo_lints, LINT);
52 if lint_level == LintLevel::Allow {
53 return Ok(());
54 }
55
56 let document = manifest.document();
57 let contents = manifest.contents();
58
59 let level = lint_level.to_diagnostic_level();
60 let mut emitted_source = None;
61 for lint_name in unknown_lints {
62 if lint_level.is_error() {
63 *error_count += 1;
64 }
65 let title = format!("{}: `{lint_name}`", LINT.desc);
66 let underscore_lint_name = lint_name.replace("-", "_");
67 let matching = if let Some(lint) = LINTS.iter().find(|l| l.name == underscore_lint_name) {
68 Some((lint.name, "lint"))
69 } else if let Some(group) = LINT_GROUPS.iter().find(|g| g.name == underscore_lint_name) {
70 Some((group.name, "group"))
71 } else {
72 None
73 };
74 let help =
75 matching.map(|(name, kind)| format!("there is a {kind} with a similar name: `{name}`"));
76
77 let key_path = match manifest {
78 ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
79 ManifestFor::Workspace(_) => &["workspace", "lints", "cargo", lint_name][..],
80 };
81 let Some(span) = get_key_value_span(document, key_path) else {
82 return Ok(());
84 };
85
86 let mut report = Vec::new();
87 let mut group = Group::with_title(level.clone().primary_title(title)).element(
88 Snippet::source(contents)
89 .path(manifest_path)
90 .annotation(AnnotationKind::Primary.span(span.key)),
91 );
92 if emitted_source.is_none() {
93 emitted_source = Some(LINT.emitted_source(lint_level, reason));
94 group = group.element(Level::NOTE.message(emitted_source.as_ref().unwrap()));
95 }
96 if let Some(help) = help.as_ref() {
97 group = group.element(Level::HELP.message(help));
98 }
99 report.push(group);
100
101 gctx.shell().print_report(&report, lint_level.force())?;
102 }
103
104 Ok(())
105}