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