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### Configuration
76
77- `ignore`: a list of dependency names to allow the lint on
78"#,
79    ),
80};
81
82/// Lint for `[build-dependencies]` without a `build.rs`
83///
84/// These are always unused.
85///
86/// This must be determined independent of the compiler since there are no build targets to pass to
87/// rustc to report on these.
88pub fn unused_build_dependencies_no_build_rs(
89    pkg: &Package,
90    manifest_path: &Path,
91    cargo_lints: &TomlToolLints,
92    error_count: &mut usize,
93    gctx: &GlobalContext,
94) -> CargoResult<()> {
95    let (lint_level, reason) = LINT.level(
96        cargo_lints,
97        pkg.rust_version(),
98        pkg.manifest().unstable_features(),
99    );
100
101    if lint_level == LintLevel::Allow {
102        return Ok(());
103    }
104
105    let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
106
107    let manifest = pkg.manifest();
108    let Some(package) = &manifest.normalized_toml().package else {
109        return Ok(());
110    };
111    if package.build != Some(TomlPackageBuild::Auto(false)) {
112        return Ok(());
113    }
114
115    let document = manifest.document();
116    let contents = manifest.contents();
117
118    let mut ignore = Vec::new();
119    if let Some(unused_dependencies) = cargo_lints.get("unused_dependencies") {
120        if let Some(config) = unused_dependencies.config() {
121            if let Some(config_ignore) = config.get("ignore") {
122                if let Ok(config_ignore) =
123                    toml::Value::try_into::<Vec<String>>(config_ignore.clone())
124                {
125                    ignore = config_ignore
126                } else {
127                    anyhow::bail!(
128                        "`lints.cargo.unused_dependencies.ignore` must be a list of string"
129                    );
130                }
131            }
132        }
133    }
134
135    for (i, dep_name) in manifest
136        .normalized_toml()
137        .build_dependencies()
138        .iter()
139        .flat_map(|m| m.keys())
140        .enumerate()
141    {
142        if ignore.contains(&dep_name) {
143            continue;
144        }
145
146        let level = lint_level.to_diagnostic_level();
147        let emitted_source = LINT.emitted_source(lint_level, reason);
148
149        let mut primary = Group::with_title(level.primary_title(LINT.desc));
150        if let Some(document) = document
151            && let Some(contents) = contents
152            && let Some(span) = get_key_value_span(document, &["build-dependencies", dep_name])
153        {
154            let span = span.key.start..span.value.end;
155            primary = primary.element(
156                Snippet::source(contents)
157                    .path(&manifest_path)
158                    .annotation(AnnotationKind::Primary.span(span)),
159            );
160        } else {
161            primary = primary.element(Origin::path(&manifest_path));
162        }
163        if i == 0 {
164            primary = primary.element(Level::NOTE.message(emitted_source));
165        }
166        let mut report = vec![primary];
167        if let Some(document) = document
168            && let Some(contents) = contents
169            && let Some(span) = get_key_value_span(document, &["build-dependencies", dep_name])
170        {
171            let span = span.key.start..span.value.end;
172            let mut help = Group::with_title(Level::HELP.secondary_title("remove the dependency"));
173            help = help.element(
174                Snippet::source(contents)
175                    .path(&manifest_path)
176                    .patch(Patch::new(span, "")),
177            );
178            report.push(help);
179        }
180
181        if lint_level.is_error() {
182            *error_count += 1;
183        }
184        gctx.shell().print_report(&report, lint_level.force())?;
185    }
186
187    Ok(())
188}