cargo/diagnostics/rules/
mostly_unused_hint.rs1use std::path::Path;
2
3use cargo_util_schemas::manifest::TomlProfile;
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 crate::CargoResult;
12use crate::GlobalContext;
13use crate::diagnostics::ScopedDiagnosticStats;
14use crate::diagnostics::TomlSpan;
15use crate::diagnostics::get_key_value_span;
16use crate::diagnostics::workspace_rel_path;
17use crate::workspace::MaybePackage;
18use crate::workspace::Package;
19use crate::workspace::Workspace;
20
21const HELP: &str = "pass `-Zprofile-hint-mostly-unused` to enable it";
22const PROFILE_TITLE: &str = "ignoring `hint-mostly-unused` profile option";
23const HINT_KEY: &str = "hint-mostly-unused";
24
25#[instrument(skip_all)]
28pub(crate) fn diagnose_package(
29 ws: &Workspace<'_>,
30 pkg: &Package,
31 path: &Path,
32 pkg_stats: &mut ScopedDiagnosticStats<'_>,
33 gctx: &GlobalContext,
34) -> CargoResult<()> {
35 let Some(value) = pkg.hints().and_then(|hints| hints.mostly_unused.as_ref()) else {
36 return Ok(());
37 };
38 let manifest_path = workspace_rel_path(ws, path);
39 let group = match value {
40 toml::Value::Boolean(true) if !gctx.cli_unstable().profile_hint_mostly_unused => {
43 let group =
44 Group::with_title(Level::WARNING.primary_title("ignoring `hints.mostly-unused`"));
45 let group = match hint_span(pkg) {
46 Some((contents, span)) => group.element(
47 Snippet::source(contents)
48 .path(&manifest_path)
49 .annotation(AnnotationKind::Primary.span(span.key.start..span.value.end)),
50 ),
51 None => group.element(Origin::path(&manifest_path)),
52 };
53 group.element(Level::HELP.message(HELP))
54 }
55 toml::Value::Boolean(_) => return Ok(()),
56 value => {
57 let title = format!(
58 "ignoring unsupported value type ({}) for `hints.mostly-unused`",
59 value.type_str()
60 );
61 let group = Group::with_title(Level::WARNING.primary_title(title));
62 match hint_span(pkg) {
63 Some((contents, span)) => group.element(
64 Snippet::source(contents).path(&manifest_path).annotation(
65 AnnotationKind::Primary
66 .span(span.value)
67 .label("expected a boolean"),
68 ),
69 ),
70 None => group.element(Origin::path(&manifest_path)),
71 }
72 }
73 };
74 pkg_stats.record_warning();
75 gctx.shell().print_report(&[group], false)?;
76 Ok(())
77}
78
79fn hint_span(pkg: &Package) -> Option<(&str, TomlSpan)> {
81 let manifest = pkg.manifest();
82 let contents = manifest.contents()?;
83 let document = manifest.document()?;
84 let span = get_key_value_span(document, &["hints", "mostly-unused"])?;
85 Some((contents, span))
86}
87
88#[instrument(skip_all)]
92pub(crate) fn diagnose_workspace(
93 ws: &Workspace<'_>,
94 maybe_pkg: &MaybePackage,
95 path: &Path,
96 pkg_stats: &mut ScopedDiagnosticStats<'_>,
97 gctx: &GlobalContext,
98) -> CargoResult<()> {
99 if gctx.cli_unstable().profile_hint_mostly_unused {
100 return Ok(());
101 }
102 let Some(profiles) = maybe_pkg.profiles() else {
103 return Ok(());
104 };
105 let manifest_path = workspace_rel_path(ws, path);
106
107 for (name, profile) in profiles.get_all() {
109 for key in enabled_keys(name.as_str(), profile) {
110 let group = Group::with_title(Level::WARNING.primary_title(PROFILE_TITLE));
111 let group = if let Some(contents) = maybe_pkg.contents()
112 && let Some(document) = maybe_pkg.document()
113 && let Some(table_span) = get_key_value_span(document, &key[..key.len() - 1])
114 && let Some(span) = get_key_value_span(document, &key)
115 {
116 group.element(
117 Snippet::source(contents)
118 .path(&manifest_path)
119 .annotation(AnnotationKind::Primary.span(span.key.start..span.value.end))
120 .annotation(AnnotationKind::Context.span(table_span.key)),
121 )
122 } else {
123 group.element(Origin::path(&manifest_path))
124 };
125 let group = group.element(Level::HELP.message(HELP));
126 pkg_stats.record_warning();
127 gctx.shell().print_report(&[group], false)?;
128 }
129 }
130 Ok(())
131}
132
133fn enabled_keys(name: &str, profile: &TomlProfile) -> Vec<Vec<String>> {
136 let mut candidates = vec![(profile, vec![])];
137 if let Some(build_override) = &profile.build_override {
138 candidates.push((build_override, vec!["build-override".to_owned()]));
139 }
140 for (spec, package) in profile.package.iter().flatten() {
141 candidates.push((package, vec!["package".to_owned(), spec.to_string()]));
144 }
145 let mut keys = Vec::new();
146 for (sub_profile, suffix) in candidates {
147 if sub_profile.hint_mostly_unused == Some(true) {
148 let mut key = vec!["profile".to_owned(), name.to_owned()];
149 key.extend(suffix);
150 key.push(HINT_KEY.to_owned());
151 keys.push(key);
152 }
153 }
154 keys
155}