Skip to main content

cargo/diagnostics/rules/
unused_workspace_dependencies.rs

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