Skip to main content

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