cargo/lints/rules/
redundant_homepage.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::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}