Skip to main content

cargo/diagnostics/rules/
unknown_lints.rs

1use 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            // For real manifests, lint as a package, rather than a workspace
72            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                // This lint is handled by either package or workspace lint.
164                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}