cargo/diagnostics/rules/
redundant_readme.rs1use std::path::Path;
2
3use cargo_util_schemas::manifest::InheritableField;
4use cargo_util_schemas::manifest::StringOrBool;
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::STYLE;
13use crate::CargoResult;
14use crate::GlobalContext;
15use crate::core::Package;
16use crate::core::Workspace;
17use crate::diagnostics::Lint;
18use crate::diagnostics::LintLevel;
19use crate::diagnostics::LintLevelProduct;
20use crate::diagnostics::LintLevelSource;
21use crate::diagnostics::ScopedDiagnosticStats;
22use crate::diagnostics::get_key_value_span;
23use crate::diagnostics::workspace_rel_path;
24use crate::util::toml::DEFAULT_README_FILES;
25
26pub static LINT: &Lint = &Lint {
27 name: "redundant_readme",
28 desc: "explicit `package.readme` can be inferred",
29 primary_group: &STYLE,
30 msrv: Some(super::CARGO_LINTS_MSRV),
31 feature_gate: None,
32 docs: Some(
33 r#"
34### What it does
35
36Checks for `package.readme` fields that can be inferred.
37
38See also [`package.readme` reference documentation](manifest.md#the-readme-field).
39
40### Why it is bad
41
42Adds boilerplate.
43
44### Drawbacks
45
46It might not be obvious if they named their file correctly.
47
48### Example
49
50```toml
51[package]
52name = "foo"
53readme = "README.md"
54```
55
56Should be written as:
57
58```toml
59[package]
60name = "foo"
61```
62"#,
63 ),
64};
65
66#[instrument(skip_all)]
67pub(crate) fn lint_package(
68 ws: &Workspace<'_>,
69 pkg: &Package,
70 manifest_path: &Path,
71 level: LintLevelProduct,
72 pkg_stats: &mut ScopedDiagnosticStats<'_>,
73 gctx: &GlobalContext,
74) -> CargoResult<()> {
75 let LintLevelProduct {
76 level: lint_level,
77 source,
78 } = level;
79
80 let manifest_path = workspace_rel_path(ws, manifest_path);
81
82 lint_package_inner(pkg, &manifest_path, lint_level, source, pkg_stats, gctx)
83}
84
85fn lint_package_inner(
86 pkg: &Package,
87 manifest_path: &str,
88 lint_level: LintLevel,
89 source: LintLevelSource,
90 pkg_stats: &mut ScopedDiagnosticStats<'_>,
91 gctx: &GlobalContext,
92) -> CargoResult<()> {
93 let manifest = pkg.manifest();
94
95 let Some(original_toml) = manifest.original_toml() else {
97 return Ok(());
98 };
99 let Some(original_pkg) = &original_toml.package else {
100 return Ok(());
101 };
102 let Some(readme) = &original_pkg.readme else {
103 return Ok(());
104 };
105
106 let InheritableField::Value(StringOrBool::String(readme)) = readme else {
107 return Ok(());
110 };
111
112 if !DEFAULT_README_FILES.contains(&readme.as_str()) {
113 return Ok(());
114 }
115
116 let document = manifest.document();
117 let contents = manifest.contents();
118 let level = lint_level.to_diagnostic_level();
119 let emitted_source = LINT.emitted_source(lint_level, source);
120
121 let mut primary = Group::with_title(level.primary_title(LINT.desc));
122 if let Some(document) = document
123 && let Some(contents) = contents
124 && let Some(span) = get_key_value_span(document, &["package", "readme"])
125 {
126 let span = span.key.start..span.value.end;
127 primary = primary.element(
128 Snippet::source(contents)
129 .path(manifest_path)
130 .annotation(AnnotationKind::Primary.span(span)),
131 );
132 } else {
133 primary = primary.element(Origin::path(manifest_path));
134 }
135 primary = primary.element(Level::NOTE.message(emitted_source));
136 let mut report = vec![primary];
137 let help = Group::with_title(Level::HELP.secondary_title("consider removing `package.readme`"));
138 report.push(help);
139
140 pkg_stats.record_lint(lint_level);
141 gctx.shell().print_report(&report, lint_level.force())?;
142
143 Ok(())
144}