Skip to main content

cargo/diagnostics/rules/
redundant_readme.rs

1use std::path::Path;
2
3use cargo_util_schemas::manifest::InheritableField;
4use cargo_util_schemas::manifest::StringOrBool;
5use cargo_util_schemas::manifest::TomlToolLints;
6use cargo_util_terminal::report::AnnotationKind;
7use cargo_util_terminal::report::Group;
8use cargo_util_terminal::report::Level;
9use cargo_util_terminal::report::Origin;
10use cargo_util_terminal::report::Patch;
11use cargo_util_terminal::report::Snippet;
12use tracing::instrument;
13
14use super::STYLE;
15use crate::CargoResult;
16use crate::GlobalContext;
17use crate::core::Package;
18use crate::diagnostics::Lint;
19use crate::diagnostics::LintLevel;
20use crate::diagnostics::LintLevelSource;
21use crate::diagnostics::get_key_value_span;
22use crate::diagnostics::rel_cwd_manifest_path;
23use crate::util::toml::DEFAULT_README_FILES;
24
25pub static LINT: &Lint = &Lint {
26    name: "redundant_readme",
27    desc: "explicit `package.readme` can be inferred",
28    primary_group: &STYLE,
29    msrv: Some(super::CARGO_LINTS_MSRV),
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
65#[instrument(skip_all)]
66pub fn redundant_readme(
67    pkg: &Package,
68    manifest_path: &Path,
69    cargo_lints: &TomlToolLints,
70    error_count: &mut usize,
71    gctx: &GlobalContext,
72) -> CargoResult<()> {
73    let (lint_level, source) = LINT.level(
74        cargo_lints,
75        pkg.rust_version(),
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, source, error_count, gctx)
86}
87
88fn lint_package(
89    pkg: &Package,
90    manifest_path: &str,
91    lint_level: LintLevel,
92    source: LintLevelSource,
93    error_count: &mut usize,
94    gctx: &GlobalContext,
95) -> CargoResult<()> {
96    let manifest = pkg.manifest();
97
98    // Must check `original_toml`, before any inferring happened
99    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        // Not checking inheritance because at most one package can be identified from the lint and
111        // consistency of inheritance is likely best.
112        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, source);
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}