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 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}