Skip to main content

cargo/diagnostics/rules/
blanket_hint_mostly_unused.rs

1use std::path::Path;
2
3use cargo_util_schemas::manifest::ProfilePackageSpec;
4use cargo_util_schemas::manifest::TomlToolLints;
5use cargo_util_terminal::report::AnnotationKind;
6use cargo_util_terminal::report::Group;
7use cargo_util_terminal::report::Level;
8use cargo_util_terminal::report::Origin;
9use cargo_util_terminal::report::Patch;
10use cargo_util_terminal::report::Snippet;
11use tracing::instrument;
12
13use super::SUSPICIOUS;
14use crate::CargoResult;
15use crate::GlobalContext;
16use crate::core::MaybePackage;
17use crate::core::Workspace;
18use crate::diagnostics::DiagnosticStats;
19use crate::diagnostics::Lint;
20use crate::diagnostics::LintLevel;
21use crate::diagnostics::get_key_value_span;
22use crate::diagnostics::rel_cwd_manifest_path;
23
24pub static LINT: &Lint = &Lint {
25    name: "blanket_hint_mostly_unused",
26    desc: "blanket_hint_mostly_unused lint",
27    primary_group: &SUSPICIOUS,
28    msrv: Some(super::CARGO_LINTS_MSRV),
29    feature_gate: None,
30    docs: Some(
31        r#"
32### What it does
33Checks if `hint-mostly-unused` being applied to all dependencies.
34
35### Why it is bad
36`hint-mostly-unused` indicates that most of a crate's API surface will go
37unused by anything depending on it; this hint can speed up the build by
38attempting to minimize compilation time for items that aren't used at all.
39Misapplication to crates that don't fit that criteria will slow down the build
40rather than speeding it up. It should be selectively applied to dependencies
41that meet these criteria. Applying it globally is always a misapplication and
42will likely slow down the build.
43
44### Example
45```toml
46[profile.dev.package."*"]
47hint-mostly-unused = true
48```
49
50Should instead be:
51```toml
52[profile.dev.package.huge-mostly-unused-dependency]
53hint-mostly-unused = true
54```
55"#,
56    ),
57};
58
59#[instrument(skip_all)]
60pub fn blanket_hint_mostly_unused(
61    ws: &Workspace<'_>,
62    maybe_pkg: &MaybePackage,
63    path: &Path,
64    pkg_lints: &TomlToolLints,
65    stats: &mut DiagnosticStats,
66    gctx: &GlobalContext,
67) -> CargoResult<()> {
68    let (lint_level, source) = LINT.level(
69        pkg_lints,
70        ws.lowest_rust_version(),
71        maybe_pkg.unstable_features(),
72    );
73
74    if lint_level == LintLevel::Allow {
75        return Ok(());
76    }
77
78    let level = lint_level.to_diagnostic_level();
79    let manifest_path = rel_cwd_manifest_path(path, gctx);
80    let mut paths = Vec::new();
81
82    if let Some(profiles) = maybe_pkg.profiles() {
83        for (profile_name, top_level_profile) in &profiles.0 {
84            if let Some(true) = top_level_profile.hint_mostly_unused {
85                paths.push((
86                    vec!["profile", profile_name.as_str(), "hint-mostly-unused"],
87                    true,
88                ));
89            }
90
91            if let Some(build_override) = &top_level_profile.build_override
92                && let Some(true) = build_override.hint_mostly_unused
93            {
94                paths.push((
95                    vec![
96                        "profile",
97                        profile_name.as_str(),
98                        "build-override",
99                        "hint-mostly-unused",
100                    ],
101                    false,
102                ));
103            }
104
105            if let Some(packages) = &top_level_profile.package
106                && let Some(profile) = packages.get(&ProfilePackageSpec::All)
107                && let Some(true) = profile.hint_mostly_unused
108            {
109                paths.push((
110                    vec![
111                        "profile",
112                        profile_name.as_str(),
113                        "package",
114                        "*",
115                        "hint-mostly-unused",
116                    ],
117                    false,
118                ));
119            }
120        }
121    }
122
123    for (i, (path, show_per_pkg_suggestion)) in paths.iter().enumerate() {
124        let title = "`hint-mostly-unused` is being blanket applied to all dependencies";
125        let help_txt =
126            "scope `hint-mostly-unused` to specific packages with a lot of unused object code";
127
128        let mut report = Vec::new();
129        let mut primary_group = Group::with_title(level.clone().primary_title(title));
130
131        if let Some(contents) = maybe_pkg.contents()
132            && let Some(document) = maybe_pkg.document()
133            && let Some(span) = get_key_value_span(document, &path)
134            && let Some(table_span) = get_key_value_span(document, &path[..path.len() - 1])
135        {
136            primary_group = primary_group.element(
137                Snippet::source(contents)
138                    .path(&manifest_path)
139                    .annotation(
140                        AnnotationKind::Primary.span(table_span.key.start..table_span.key.end),
141                    )
142                    .annotation(AnnotationKind::Context.span(span.key.start..span.value.end)),
143            );
144        } else {
145            primary_group = primary_group.element(Origin::path(&manifest_path))
146        }
147
148        if *show_per_pkg_suggestion {
149            let help_group = Group::with_title(Level::HELP.secondary_title(help_txt));
150
151            report.push(
152                if let Some(contents) = maybe_pkg.contents()
153                    && let Some(document) = maybe_pkg.document()
154                    && let Some(table_span) = get_key_value_span(document, &path[..path.len() - 1])
155                {
156                    help_group.element(Snippet::source(contents).path(&manifest_path).patch(
157                        Patch::new(
158                            table_span.key.end..table_span.key.end,
159                            ".package.<pkg_name>",
160                        ),
161                    ))
162                } else {
163                    help_group.element(Origin::path(&manifest_path))
164                },
165            );
166        } else {
167            primary_group = primary_group.element(Level::HELP.message(help_txt));
168        }
169
170        if i == 0 {
171            primary_group =
172                primary_group.element(Level::NOTE.message(LINT.emitted_source(lint_level, source)));
173        }
174
175        // The primary group should always be first
176        report.insert(0, primary_group);
177
178        stats.record_lint(lint_level);
179        gctx.shell().print_report(&report, lint_level.force())?;
180    }
181
182    Ok(())
183}