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