Skip to main content

cargo/diagnostics/rules/
unused_dependencies.rs

1use std::path::Path;
2
3use cargo_util_schemas::manifest::TomlPackageBuild;
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::STYLE;
14use crate::CargoResult;
15use crate::GlobalContext;
16use crate::core::Package;
17use crate::diagnostics::Lint;
18use crate::diagnostics::LintLevel;
19use crate::diagnostics::get_key_value_span;
20use crate::diagnostics::rel_cwd_manifest_path;
21
22pub static LINT: &Lint = &Lint {
23    name: "unused_dependencies",
24    desc: "unused dependency",
25    primary_group: &STYLE,
26    msrv: Some(super::CARGO_LINTS_MSRV),
27    feature_gate: None,
28    docs: Some(
29        r#"
30### What it does
31
32Checks for dependencies that are not used by any of the cargo targets.
33
34### Why it is bad
35
36Slows down compilation time.
37
38### Drawbacks
39
40The lint is only emitted in specific circumstances as multiple cargo targets exist for the
41different dependencies tables and they must all be built to know if a dependency is unused.
42Currently, only the selected packages are checked and not all `path` dependencies like most lints.
43The cargo target selection flags,
44independent of which packages are selected, determine which dependencies tables are checked.
45As there is no way to select all cargo targets that use `[dev-dependencies]`,
46they are unchecked.
47
48Examples:
49- `cargo check` will lint `[build-dependencies]` and `[dependencies]`
50- `cargo check --all-targets` will still only lint `[build-dependencies]` and `[dependencies]` and not `[dev-dependencoes]`
51- `cargo check --bin foo` will not lint `[dependencies]` even if `foo` is the only bin though `[build-dependencies]` will be checked
52- `cargo check -p foo` will not lint any dependencies tables for the `path` dependency `bar` even if `bar` only has a `[lib]`
53
54There can be false positives when depending on a transitive dependency to activate a feature.
55
56For false positives from pinning the version of a transitive dependency in `Cargo.toml`,
57move the dependency to the `target."cfg(false)".dependencies` table.
58
59### Example
60
61```toml
62[package]
63name = "foo"
64
65[dependencies]
66unused = "1"
67```
68
69Should be written as:
70
71```toml
72[package]
73name = "foo"
74```
75"#,
76    ),
77};
78
79/// Lint for `[build-dependencies]` without a `build.rs`
80///
81/// These are always unused.
82///
83/// This must be determined independent of the compiler since there are no build targets to pass to
84/// rustc to report on these.
85#[instrument(skip_all)]
86pub fn unused_build_dependencies_no_build_rs(
87    pkg: &Package,
88    manifest_path: &Path,
89    cargo_lints: &TomlToolLints,
90    error_count: &mut usize,
91    gctx: &GlobalContext,
92) -> CargoResult<()> {
93    let (lint_level, reason) = LINT.level(
94        cargo_lints,
95        pkg.rust_version(),
96        pkg.manifest().unstable_features(),
97    );
98
99    if lint_level == LintLevel::Allow {
100        return Ok(());
101    }
102
103    let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
104
105    let manifest = pkg.manifest();
106    let Some(package) = &manifest.normalized_toml().package else {
107        return Ok(());
108    };
109    if package.build != Some(TomlPackageBuild::Auto(false)) {
110        return Ok(());
111    }
112
113    let document = manifest.document();
114    let contents = manifest.contents();
115
116    for (i, dep_name) in manifest
117        .normalized_toml()
118        .build_dependencies()
119        .iter()
120        .flat_map(|m| m.keys())
121        .enumerate()
122    {
123        let level = lint_level.to_diagnostic_level();
124        let emitted_source = LINT.emitted_source(lint_level, reason);
125
126        let mut primary = Group::with_title(level.primary_title(LINT.desc));
127        if let Some(document) = document
128            && let Some(contents) = contents
129            && let Some(span) = get_key_value_span(document, &["build-dependencies", dep_name])
130        {
131            let span = span.key.start..span.value.end;
132            primary = primary.element(
133                Snippet::source(contents)
134                    .path(&manifest_path)
135                    .annotation(AnnotationKind::Primary.span(span)),
136            );
137        } else {
138            primary = primary.element(Origin::path(&manifest_path));
139        }
140        if i == 0 {
141            primary = primary.element(Level::NOTE.message(emitted_source));
142        }
143        let mut report = vec![primary];
144        if let Some(document) = document
145            && let Some(contents) = contents
146            && let Some(span) = get_key_value_span(document, &["build-dependencies", dep_name])
147        {
148            let span = span.key.start..span.value.end;
149            let mut help = Group::with_title(Level::HELP.secondary_title("remove the dependency"));
150            help = help.element(
151                Snippet::source(contents)
152                    .path(&manifest_path)
153                    .patch(Patch::new(span, "")),
154            );
155            report.push(help);
156        }
157
158        if lint_level.is_error() {
159            *error_count += 1;
160        }
161        gctx.shell().print_report(&report, lint_level.force())?;
162    }
163
164    Ok(())
165}