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