cargo/diagnostics/rules/
missing_lints_features.rs1use 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 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 if !source.is_user_specified() {
84 continue;
85 }
86
87 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 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}