cargo/diagnostics/rules/
unused_workspace_package_fields.rs1use 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}