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