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 ),
76};
77
78pub 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}