cargo/lints/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;
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
82pub 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}