cargo/lints/rules/
redundant_readme.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::InheritableField;
10use cargo_util_schemas::manifest::StringOrBool;
11use cargo_util_schemas::manifest::TomlToolLints;
12
13use crate::CargoResult;
14use crate::GlobalContext;
15use crate::core::Package;
16use crate::lints::Lint;
17use crate::lints::LintLevel;
18use crate::lints::LintLevelReason;
19use crate::lints::STYLE;
20use crate::lints::get_key_value_span;
21use crate::lints::rel_cwd_manifest_path;
22use crate::util::toml::DEFAULT_README_FILES;
23
24pub static LINT: &Lint = &Lint {
25 name: "redundant_readme",
26 desc: "explicit `package.readme` can be inferred",
27 primary_group: &STYLE,
28 msrv: Some(super::CARGO_LINTS_MSRV),
29 edition_lint_opts: None,
30 feature_gate: None,
31 docs: Some(
32 r#"
33### What it does
34
35Checks for `package.readme` fields that can be inferred.
36
37See also [`package.readme` reference documentation](manifest.md#the-readme-field).
38
39### Why it is bad
40
41Adds boilerplate.
42
43### Drawbacks
44
45It might not be obvious if they named their file correctly.
46
47### Example
48
49```toml
50[package]
51name = "foo"
52readme = "README.md"
53```
54
55Should be written as:
56
57```toml
58[package]
59name = "foo"
60```
61"#,
62 ),
63};
64
65pub fn redundant_readme(
66 pkg: &Package,
67 manifest_path: &Path,
68 cargo_lints: &TomlToolLints,
69 error_count: &mut usize,
70 gctx: &GlobalContext,
71) -> CargoResult<()> {
72 let (lint_level, reason) = LINT.level(
73 cargo_lints,
74 pkg.rust_version(),
75 pkg.manifest().edition(),
76 pkg.manifest().unstable_features(),
77 );
78
79 if lint_level == LintLevel::Allow {
80 return Ok(());
81 }
82
83 let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
84
85 lint_package(pkg, &manifest_path, lint_level, reason, error_count, gctx)
86}
87
88pub fn lint_package(
89 pkg: &Package,
90 manifest_path: &str,
91 lint_level: LintLevel,
92 reason: LintLevelReason,
93 error_count: &mut usize,
94 gctx: &GlobalContext,
95) -> CargoResult<()> {
96 let manifest = pkg.manifest();
97
98 let Some(original_toml) = manifest.original_toml() else {
100 return Ok(());
101 };
102 let Some(original_pkg) = &original_toml.package else {
103 return Ok(());
104 };
105 let Some(readme) = &original_pkg.readme else {
106 return Ok(());
107 };
108
109 let InheritableField::Value(StringOrBool::String(readme)) = readme else {
110 return Ok(());
113 };
114
115 if !DEFAULT_README_FILES.contains(&readme.as_str()) {
116 return Ok(());
117 }
118
119 let document = manifest.document();
120 let contents = manifest.contents();
121 let level = lint_level.to_diagnostic_level();
122 let emitted_source = LINT.emitted_source(lint_level, reason);
123
124 let mut primary = Group::with_title(level.primary_title(LINT.desc));
125 if let Some(document) = document
126 && let Some(contents) = contents
127 && let Some(span) = get_key_value_span(document, &["package", "readme"])
128 {
129 let span = span.key.start..span.value.end;
130 primary = primary.element(
131 Snippet::source(contents)
132 .path(manifest_path)
133 .annotation(AnnotationKind::Primary.span(span)),
134 );
135 } else {
136 primary = primary.element(Origin::path(manifest_path));
137 }
138 primary = primary.element(Level::NOTE.message(emitted_source));
139 let mut report = vec![primary];
140 if let Some(document) = document
141 && let Some(contents) = contents
142 && let Some(span) = get_key_value_span(document, &["package", "readme"])
143 {
144 let span = if let Some(workspace_span) =
145 get_key_value_span(document, &["package", "readme", "workspace"])
146 {
147 span.key.start..workspace_span.value.end
148 } else {
149 span.key.start..span.value.end
150 };
151 let mut help =
152 Group::with_title(Level::HELP.secondary_title("consider removing `package.readme`"));
153 help = help.element(
154 Snippet::source(contents)
155 .path(manifest_path)
156 .patch(Patch::new(span, "")),
157 );
158 report.push(help);
159 }
160
161 if lint_level.is_error() {
162 *error_count += 1;
163 }
164 gctx.shell().print_report(&report, lint_level.force())?;
165
166 Ok(())
167}