Skip to main content

cargo/lints/rules/
blanket_hint_mostly_unused.rs

1use std::path::Path;
2
3use annotate_snippets::AnnotationKind;
4use annotate_snippets::Group;
5use annotate_snippets::Level;
6use annotate_snippets::Origin;
7use annotate_snippets::Patch;
8use annotate_snippets::Snippet;
9use cargo_util_schemas::manifest::ProfilePackageSpec;
10use cargo_util_schemas::manifest::TomlToolLints;
11
12use crate::CargoResult;
13use crate::GlobalContext;
14use crate::core::MaybePackage;
15use crate::core::Workspace;
16use crate::lints::Lint;
17use crate::lints::LintLevel;
18use crate::lints::SUSPICIOUS;
19use crate::lints::get_key_value_span;
20use crate::lints::rel_cwd_manifest_path;
21
22pub static LINT: &Lint = &Lint {
23    name: "blanket_hint_mostly_unused",
24    desc: "blanket_hint_mostly_unused lint",
25    primary_group: &SUSPICIOUS,
26    msrv: Some(super::CARGO_LINTS_MSRV),
27    edition_lint_opts: None,
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
58pub fn blanket_hint_mostly_unused(
59    ws: &Workspace<'_>,
60    maybe_pkg: &MaybePackage,
61    path: &Path,
62    pkg_lints: &TomlToolLints,
63    error_count: &mut usize,
64    gctx: &GlobalContext,
65) -> CargoResult<()> {
66    let (lint_level, reason) = LINT.level(
67        pkg_lints,
68        ws.lowest_rust_version(),
69        maybe_pkg.edition(),
70        maybe_pkg.unstable_features(),
71    );
72
73    if lint_level == LintLevel::Allow {
74        return Ok(());
75    }
76
77    let level = lint_level.to_diagnostic_level();
78    let manifest_path = rel_cwd_manifest_path(path, gctx);
79    let mut paths = Vec::new();
80
81    if let Some(profiles) = maybe_pkg.profiles() {
82        for (profile_name, top_level_profile) in &profiles.0 {
83            if let Some(true) = top_level_profile.hint_mostly_unused {
84                paths.push((
85                    vec!["profile", profile_name.as_str(), "hint-mostly-unused"],
86                    true,
87                ));
88            }
89
90            if let Some(build_override) = &top_level_profile.build_override
91                && let Some(true) = build_override.hint_mostly_unused
92            {
93                paths.push((
94                    vec![
95                        "profile",
96                        profile_name.as_str(),
97                        "build-override",
98                        "hint-mostly-unused",
99                    ],
100                    false,
101                ));
102            }
103
104            if let Some(packages) = &top_level_profile.package
105                && let Some(profile) = packages.get(&ProfilePackageSpec::All)
106                && let Some(true) = profile.hint_mostly_unused
107            {
108                paths.push((
109                    vec![
110                        "profile",
111                        profile_name.as_str(),
112                        "package",
113                        "*",
114                        "hint-mostly-unused",
115                    ],
116                    false,
117                ));
118            }
119        }
120    }
121
122    for (i, (path, show_per_pkg_suggestion)) in paths.iter().enumerate() {
123        if lint_level.is_error() {
124            *error_count += 1;
125        }
126        let title = "`hint-mostly-unused` is being blanket applied to all dependencies";
127        let help_txt =
128            "scope `hint-mostly-unused` to specific packages with a lot of unused object code";
129
130        let mut report = Vec::new();
131        let mut primary_group = Group::with_title(level.clone().primary_title(title));
132
133        if let Some(contents) = maybe_pkg.contents()
134            && let Some(document) = maybe_pkg.document()
135            && let Some(span) = get_key_value_span(document, &path)
136            && let Some(table_span) = get_key_value_span(document, &path[..path.len() - 1])
137        {
138            primary_group = primary_group.element(
139                Snippet::source(contents)
140                    .path(&manifest_path)
141                    .annotation(
142                        AnnotationKind::Primary.span(table_span.key.start..table_span.key.end),
143                    )
144                    .annotation(AnnotationKind::Context.span(span.key.start..span.value.end)),
145            );
146        } else {
147            primary_group = primary_group.element(Origin::path(&manifest_path))
148        }
149
150        if *show_per_pkg_suggestion {
151            let help_group = Group::with_title(Level::HELP.secondary_title(help_txt));
152
153            report.push(
154                if let Some(contents) = maybe_pkg.contents()
155                    && let Some(document) = maybe_pkg.document()
156                    && let Some(table_span) = get_key_value_span(document, &path[..path.len() - 1])
157                {
158                    help_group.element(Snippet::source(contents).path(&manifest_path).patch(
159                        Patch::new(
160                            table_span.key.end..table_span.key.end,
161                            ".package.<pkg_name>",
162                        ),
163                    ))
164                } else {
165                    help_group.element(Origin::path(&manifest_path))
166                },
167            );
168        } else {
169            primary_group = primary_group.element(Level::HELP.message(help_txt));
170        }
171
172        if i == 0 {
173            primary_group =
174                primary_group.element(Level::NOTE.message(LINT.emitted_source(lint_level, reason)));
175        }
176
177        // The primary group should always be first
178        report.insert(0, primary_group);
179
180        gctx.shell().print_report(&report, lint_level.force())?;
181    }
182
183    Ok(())
184}