Skip to main content

cargo/diagnostics/rules/
unused_workspace_package_fields.rs

1use std::path::Path;
2
3use cargo_util_terminal::report::AnnotationKind;
4use cargo_util_terminal::report::Group;
5use cargo_util_terminal::report::Level;
6use cargo_util_terminal::report::Origin;
7use cargo_util_terminal::report::Patch;
8use cargo_util_terminal::report::Snippet;
9use indexmap::IndexSet;
10use tracing::instrument;
11
12use super::SUSPICIOUS;
13use crate::CargoResult;
14use crate::GlobalContext;
15use crate::core::MaybePackage;
16use crate::core::Workspace;
17use crate::diagnostics::DiagnosticStats;
18use crate::diagnostics::Lint;
19use crate::diagnostics::LintLevelProduct;
20use crate::diagnostics::get_key_value_span;
21use crate::diagnostics::rel_cwd_manifest_path;
22
23pub static LINT: &Lint = &Lint {
24    name: "unused_workspace_package_fields",
25    desc: "unused field in `workspace.package`",
26    primary_group: &SUSPICIOUS,
27    msrv: Some(super::CARGO_LINTS_MSRV),
28    feature_gate: None,
29    docs: Some(
30        r#"
31### What it does
32Checks for any fields in `[workspace.package]` that has not been inherited
33
34### Why it is bad
35They can give the false impression that these fields are used
36
37### Example
38```toml
39[workspace.package]
40edition = "2024"
41
42[package]
43name = "foo"
44```
45"#,
46    ),
47};
48
49#[instrument(skip_all)]
50pub(crate) fn lint_workspace(
51    ws: &Workspace<'_>,
52    maybe_pkg: &MaybePackage,
53    manifest_path: &Path,
54    level: LintLevelProduct,
55    stats: &mut DiagnosticStats,
56    gctx: &GlobalContext,
57) -> CargoResult<()> {
58    let LintLevelProduct {
59        level: lint_level,
60        source,
61    } = level;
62
63    let workspace_package_fields: IndexSet<_> = maybe_pkg
64        .document()
65        .and_then(|d| d.get_ref().get("workspace"))
66        .and_then(|w| w.get_ref().get("package"))
67        .and_then(|p| p.get_ref().as_table())
68        .iter()
69        .flat_map(|d| d.keys())
70        .collect();
71
72    let mut inherited_fields = IndexSet::new();
73    for member in ws.members() {
74        inherited_fields.extend(
75            member
76                .manifest()
77                .document()
78                .and_then(|w| w.get_ref().get("package"))
79                .and_then(|p| p.get_ref().as_table())
80                .iter()
81                .flat_map(|d| {
82                    d.iter()
83                        .filter(|(_, v)| {
84                            v.get_ref()
85                                .get("workspace")
86                                .and_then(|w| w.get_ref().as_bool())
87                                == Some(true)
88                        })
89                        .map(|(k, _)| k)
90                }),
91        );
92    }
93
94    for (i, unused) in workspace_package_fields
95        .difference(&inherited_fields)
96        .enumerate()
97    {
98        let document = maybe_pkg.document();
99        let contents = maybe_pkg.contents();
100        let level = lint_level.to_diagnostic_level();
101        let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
102        let emitted_source = LINT.emitted_source(lint_level, source);
103
104        let mut primary = Group::with_title(level.primary_title(LINT.desc));
105        if let Some(document) = document
106            && let Some(contents) = contents
107        {
108            let mut snippet = Snippet::source(contents).path(&manifest_path);
109            if let Some(span) =
110                get_key_value_span(document, &["workspace", "package", unused.as_ref()])
111            {
112                snippet = snippet.annotation(AnnotationKind::Primary.span(span.key));
113            }
114            primary = primary.element(snippet);
115        } else {
116            primary = primary.element(Origin::path(&manifest_path));
117        }
118        if i == 0 {
119            primary = primary.element(Level::NOTE.message(emitted_source));
120        }
121        let mut report = vec![primary];
122        if let Some(document) = document
123            && let Some(contents) = contents
124        {
125            let mut help = Group::with_title(
126                Level::HELP.secondary_title("consider removing the unused field"),
127            );
128            let mut snippet = Snippet::source(contents).path(&manifest_path);
129            if let Some(span) =
130                get_key_value_span(document, &["workspace", "package", unused.as_ref()])
131            {
132                snippet = snippet.patch(Patch::new(span.key.start..span.value.end, ""));
133            }
134            help = help.element(snippet);
135            report.push(help);
136        }
137
138        stats.record_lint(lint_level);
139        gctx.shell().print_report(&report, lint_level.force())?;
140    }
141
142    Ok(())
143}