Skip to main content

cargo/diagnostics/rules/
missing_lints_features.rs

1use std::path::Path;
2
3use cargo_util_schemas::manifest;
4use cargo_util_terminal::report::AnnotationKind;
5use cargo_util_terminal::report::Group;
6use cargo_util_terminal::report::Level;
7use cargo_util_terminal::report::Snippet;
8use tracing::instrument;
9
10use super::find_lint_or_group;
11use crate::CargoResult;
12use crate::GlobalContext;
13use crate::core::Feature;
14use crate::core::MaybePackage;
15use crate::core::Workspace;
16use crate::diagnostics::ManifestFor;
17use crate::diagnostics::ScopedDiagnosticStats;
18use crate::diagnostics::get_key_value_span;
19use crate::diagnostics::workspace_rel_path;
20
21#[instrument(skip_all)]
22pub(crate) fn diagnose_manifest(
23    ws: &Workspace<'_>,
24    manifest: ManifestFor<'_>,
25    manifest_path: &Path,
26    pkg_stats: &mut ScopedDiagnosticStats<'_>,
27    gctx: &GlobalContext,
28) -> CargoResult<()> {
29    let normalized_toml = match &manifest {
30        ManifestFor::Package(pkg) => pkg.manifest().normalized_toml(),
31        ManifestFor::Workspace {
32            maybe_pkg: MaybePackage::Virtual(vm),
33            ..
34        } => vm.normalized_toml(),
35        ManifestFor::Workspace {
36            maybe_pkg: MaybePackage::Package(_),
37            ..
38        } => {
39            // For real manifests, lint as a package, rather than a workspace
40            return Ok(());
41        }
42    };
43
44    let ws_lints = normalized_toml
45        .workspace
46        .as_ref()
47        .and_then(|ws| ws.lints.as_ref())
48        .and_then(|lints| lints.get("cargo"));
49    let pkg_lints = normalized_toml
50        .lints
51        .as_ref()
52        .map(|lints| &lints.lints)
53        .and_then(|lints| lints.get("cargo"));
54
55    if let Some(cargo_lints) = ws_lints {
56        diagnose_manifest_inner(ws, &manifest, manifest_path, cargo_lints, pkg_stats, gctx)?;
57    }
58    if let Some(cargo_lints) = pkg_lints {
59        diagnose_manifest_inner(ws, &manifest, manifest_path, cargo_lints, pkg_stats, gctx)?;
60    }
61
62    Ok(())
63}
64
65fn diagnose_manifest_inner(
66    ws: &Workspace<'_>,
67    manifest: &ManifestFor<'_>,
68    manifest_path: &Path,
69    cargo_lints: &manifest::TomlToolLints,
70    pkg_stats: &mut ScopedDiagnosticStats<'_>,
71    gctx: &GlobalContext,
72) -> CargoResult<()> {
73    let manifest_path = workspace_rel_path(ws, manifest_path);
74    for lint_name in cargo_lints.keys().map(|name| name) {
75        let Some((name, default_level, feature_gate)) = find_lint_or_group(lint_name) else {
76            continue;
77        };
78
79        let (_, source, _) =
80            crate::diagnostics::lint::level_priority(name, *default_level, cargo_lints);
81
82        // Only run analysis on user-specified lints
83        if !source.is_user_specified() {
84            continue;
85        }
86
87        // Only run this on lints that are gated by a feature
88        if let Some(feature_gate) = feature_gate
89            && !manifest.unstable_features().is_enabled(feature_gate)
90        {
91            report_feature_not_enabled(
92                name,
93                feature_gate,
94                &manifest,
95                &manifest_path,
96                pkg_stats,
97                gctx,
98            )?;
99        }
100    }
101
102    Ok(())
103}
104
105fn report_feature_not_enabled(
106    lint_name: &str,
107    feature_gate: &Feature,
108    manifest: &ManifestFor<'_>,
109    manifest_path: &str,
110    pkg_stats: &mut ScopedDiagnosticStats<'_>,
111    gctx: &GlobalContext,
112) -> CargoResult<()> {
113    let dash_feature_name = feature_gate.name().replace("_", "-");
114
115    let mut error = Group::with_title(
116        Level::ERROR.primary_title(format!("use of unstable lint `{lint_name}`")),
117    );
118
119    if let Some(document) = manifest.document()
120        && let Some(contents) = manifest.contents()
121    {
122        let key_path = match manifest {
123            ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
124            ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..],
125        };
126        let Some(span) = get_key_value_span(document, key_path) else {
127            // This lint is handled by either package or workspace lint.
128            return Ok(());
129        };
130
131        error = error.element(Snippet::source(contents).path(manifest_path).annotation(
132            AnnotationKind::Primary.span(span.key).label(format!(
133                "this is behind `{dash_feature_name}`, which is not enabled"
134            )),
135        ))
136    }
137
138    let report = [error.element(Level::HELP.message(format!(
139        "consider adding `cargo-features = [\"{dash_feature_name}\"]` to the top of the manifest"
140    )))];
141
142    pkg_stats.record_error();
143    gctx.shell().print_report(&report, true)?;
144
145    Ok(())
146}