cargo/diagnostics/rules/
unused_dependencies.rs1use 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#[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}