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::diagnostics::DiagnosticStats;
16use crate::diagnostics::ManifestFor;
17use crate::diagnostics::get_key_value_span;
18use crate::diagnostics::rel_cwd_manifest_path;
19
20#[instrument(skip_all)]
21pub(crate) fn diagnose_manifest(
22 manifest: ManifestFor<'_>,
23 manifest_path: &Path,
24 stats: &mut DiagnosticStats,
25 gctx: &GlobalContext,
26) -> CargoResult<()> {
27 let normalized_toml = match &manifest {
28 ManifestFor::Package(pkg) => pkg.manifest().normalized_toml(),
29 ManifestFor::Workspace {
30 maybe_pkg: MaybePackage::Virtual(vm),
31 ..
32 } => vm.normalized_toml(),
33 ManifestFor::Workspace {
34 maybe_pkg: MaybePackage::Package(_),
35 ..
36 } => {
37 return Ok(());
39 }
40 };
41
42 let ws_lints = normalized_toml
43 .workspace
44 .as_ref()
45 .and_then(|ws| ws.lints.as_ref())
46 .and_then(|lints| lints.get("cargo"));
47 let pkg_lints = normalized_toml
48 .lints
49 .as_ref()
50 .map(|lints| &lints.lints)
51 .and_then(|lints| lints.get("cargo"));
52
53 if let Some(cargo_lints) = ws_lints {
54 diagnose_manifest_inner(&manifest, manifest_path, cargo_lints, stats, gctx)?;
55 }
56 if let Some(cargo_lints) = pkg_lints {
57 diagnose_manifest_inner(&manifest, manifest_path, cargo_lints, stats, gctx)?;
58 }
59
60 Ok(())
61}
62
63fn diagnose_manifest_inner(
64 manifest: &ManifestFor<'_>,
65 manifest_path: &Path,
66 cargo_lints: &manifest::TomlToolLints,
67 stats: &mut DiagnosticStats,
68 gctx: &GlobalContext,
69) -> CargoResult<()> {
70 let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
71 for lint_name in cargo_lints.keys().map(|name| name) {
72 let Some((name, default_level, feature_gate)) = find_lint_or_group(lint_name) else {
73 continue;
74 };
75
76 let (_, source, _) =
77 crate::diagnostics::lint::level_priority(name, *default_level, cargo_lints);
78
79 if !source.is_user_specified() {
81 continue;
82 }
83
84 if let Some(feature_gate) = feature_gate
86 && !manifest.unstable_features().is_enabled(feature_gate)
87 {
88 report_feature_not_enabled(name, feature_gate, &manifest, &manifest_path, stats, gctx)?;
89 }
90 }
91
92 Ok(())
93}
94
95fn report_feature_not_enabled(
96 lint_name: &str,
97 feature_gate: &Feature,
98 manifest: &ManifestFor<'_>,
99 manifest_path: &str,
100 stats: &mut DiagnosticStats,
101 gctx: &GlobalContext,
102) -> CargoResult<()> {
103 let dash_feature_name = feature_gate.name().replace("_", "-");
104
105 let mut error = Group::with_title(
106 Level::ERROR.primary_title(format!("use of unstable lint `{lint_name}`")),
107 );
108
109 if let Some(document) = manifest.document()
110 && let Some(contents) = manifest.contents()
111 {
112 let key_path = match manifest {
113 ManifestFor::Package(_) => &["lints", "cargo", lint_name][..],
114 ManifestFor::Workspace { .. } => &["workspace", "lints", "cargo", lint_name][..],
115 };
116 let Some(span) = get_key_value_span(document, key_path) else {
117 return Ok(());
119 };
120
121 error = error.element(Snippet::source(contents).path(manifest_path).annotation(
122 AnnotationKind::Primary.span(span.key).label(format!(
123 "this is behind `{dash_feature_name}`, which is not enabled"
124 )),
125 ))
126 }
127
128 let report = [error.element(Level::HELP.message(format!(
129 "consider adding `cargo-features = [\"{dash_feature_name}\"]` to the top of the manifest"
130 )))];
131
132 stats.record_error();
133 gctx.shell().print_report(&report, true)?;
134
135 Ok(())
136}