cargo/lints/rules/
unused_workspace_dependencies.rs1use std::path::Path;
2
3use annotate_snippets::AnnotationKind;
4use annotate_snippets::Group;
5use annotate_snippets::Level;
6use annotate_snippets::Origin;
7use annotate_snippets::Patch;
8use annotate_snippets::Snippet;
9use cargo_util_schemas::manifest::InheritableDependency;
10use cargo_util_schemas::manifest::TomlToolLints;
11use indexmap::IndexSet;
12
13use crate::CargoResult;
14use crate::GlobalContext;
15use crate::core::MaybePackage;
16use crate::core::Workspace;
17use crate::lints::Lint;
18use crate::lints::LintLevel;
19use crate::lints::SUSPICIOUS;
20use crate::lints::get_key_value_span;
21use crate::lints::rel_cwd_manifest_path;
22
23pub const LINT: Lint = Lint {
24 name: "unused_workspace_dependencies",
25 desc: "unused workspace dependency",
26 primary_group: &SUSPICIOUS,
27 edition_lint_opts: None,
28 feature_gate: None,
29 docs: Some(
30 r#"
31### What it does
32Checks for any entry in `[workspace.dependencies]` that has not been inherited
33
34### Why it is bad
35They can give the false impression that these dependencies are used
36
37### Example
38```toml
39[workspace.dependencies]
40regex = "1"
41
42[dependencies]
43```
44"#,
45 ),
46};
47
48pub fn unused_workspace_dependencies(
49 ws: &Workspace<'_>,
50 maybe_pkg: &MaybePackage,
51 manifest_path: &Path,
52 cargo_lints: &TomlToolLints,
53 error_count: &mut usize,
54 gctx: &GlobalContext,
55) -> CargoResult<()> {
56 let (lint_level, reason) = LINT.level(
57 cargo_lints,
58 maybe_pkg.edition(),
59 maybe_pkg.unstable_features(),
60 );
61 if lint_level == LintLevel::Allow {
62 return Ok(());
63 }
64
65 let workspace_deps: IndexSet<_> = maybe_pkg
66 .original_toml()
67 .and_then(|t| t.workspace.as_ref())
68 .and_then(|w| w.dependencies.as_ref())
69 .iter()
70 .flat_map(|d| d.keys())
71 .collect();
72
73 let mut inherited_deps = IndexSet::new();
74 for member in ws.members() {
75 let Some(original_toml) = member.manifest().original_toml() else {
76 return Ok(());
77 };
78 inherited_deps.extend(
79 original_toml
80 .build_dependencies()
81 .into_iter()
82 .flatten()
83 .filter(|(_, d)| is_inherited(d))
84 .map(|(name, _)| name),
85 );
86 inherited_deps.extend(
87 original_toml
88 .dependencies
89 .iter()
90 .flatten()
91 .filter(|(_, d)| is_inherited(d))
92 .map(|(name, _)| name),
93 );
94 inherited_deps.extend(
95 original_toml
96 .dev_dependencies()
97 .into_iter()
98 .flatten()
99 .filter(|(_, d)| is_inherited(d))
100 .map(|(name, _)| name),
101 );
102 for target in original_toml.target.iter().flat_map(|t| t.values()) {
103 inherited_deps.extend(
104 target
105 .build_dependencies()
106 .into_iter()
107 .flatten()
108 .filter(|(_, d)| is_inherited(d))
109 .map(|(name, _)| name),
110 );
111 inherited_deps.extend(
112 target
113 .dependencies
114 .iter()
115 .flatten()
116 .filter(|(_, d)| is_inherited(d))
117 .map(|(name, _)| name),
118 );
119 inherited_deps.extend(
120 target
121 .dev_dependencies()
122 .into_iter()
123 .flatten()
124 .filter(|(_, d)| is_inherited(d))
125 .map(|(name, _)| name),
126 );
127 }
128 }
129
130 for (i, unused) in workspace_deps.difference(&inherited_deps).enumerate() {
131 let document = maybe_pkg.document();
132 let contents = maybe_pkg.contents();
133 let level = lint_level.to_diagnostic_level();
134 let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
135 let emitted_source = LINT.emitted_source(lint_level, reason);
136
137 let mut primary = Group::with_title(level.primary_title(LINT.desc));
138 if let Some(document) = document
139 && let Some(contents) = contents
140 {
141 let mut snippet = Snippet::source(contents).path(&manifest_path);
142 if let Some(span) =
143 get_key_value_span(document, &["workspace", "dependencies", unused.as_str()])
144 {
145 snippet = snippet.annotation(AnnotationKind::Primary.span(span.key));
146 }
147 primary = primary.element(snippet);
148 } else {
149 primary = primary.element(Origin::path(&manifest_path));
150 }
151 if i == 0 {
152 primary = primary.element(Level::NOTE.message(emitted_source));
153 }
154 let mut report = vec![primary];
155 if let Some(document) = document
156 && let Some(contents) = contents
157 {
158 let mut help = Group::with_title(
159 Level::HELP.secondary_title("consider removing the unused dependency"),
160 );
161 let mut snippet = Snippet::source(contents).path(&manifest_path);
162 if let Some(span) =
163 get_key_value_span(document, &["workspace", "dependencies", unused.as_str()])
164 {
165 snippet = snippet.patch(Patch::new(span.key.start..span.value.end, ""));
166 }
167 help = help.element(snippet);
168 report.push(help);
169 }
170
171 if lint_level.is_error() {
172 *error_count += 1;
173 }
174 gctx.shell().print_report(&report, lint_level.force())?;
175 }
176
177 Ok(())
178}
179
180fn is_inherited(dep: &InheritableDependency) -> bool {
181 matches!(dep, InheritableDependency::Inherit(_))
182}