Skip to main content

cargo/lints/rules/
redundant_readme.rs

1use 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    // Must check `original_toml`, before any inferring happened
97    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        // Not checking inheritance because at most one package can be identified from the lint and
109        // consistency of inheritance is likely best.
110        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}