Skip to main content

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