cargo/diagnostics/rules/
redundant_homepage.rs1use std::path::Path;
2
3use cargo_util_schemas::manifest::InheritableField;
4use cargo_util_terminal::report::AnnotationKind;
5use cargo_util_terminal::report::Group;
6use cargo_util_terminal::report::Level;
7use cargo_util_terminal::report::Origin;
8use cargo_util_terminal::report::Patch;
9use cargo_util_terminal::report::Snippet;
10use tracing::instrument;
11
12use super::STYLE;
13use crate::CargoResult;
14use crate::GlobalContext;
15use crate::core::Package;
16use crate::core::Workspace;
17use crate::diagnostics::DiagnosticStats;
18use crate::diagnostics::Lint;
19use crate::diagnostics::LintLevel;
20use crate::diagnostics::LintLevelProduct;
21use crate::diagnostics::LintLevelSource;
22use crate::diagnostics::get_key_value_span;
23use crate::diagnostics::rel_cwd_manifest_path;
24
25pub static LINT: &Lint = &Lint {
26 name: "redundant_homepage",
27 desc: "`package.homepage` is redundant with another manifest field",
28 primary_group: &STYLE,
29 msrv: Some(super::CARGO_LINTS_MSRV),
30 feature_gate: None,
31 docs: Some(
32 r#"
33### What it does
34
35Checks if the value of `package.homepage` is already covered by another field.
36
37See also [`package.homepage` reference documentation](manifest.md#the-homepage-field).
38
39### Why it is bad
40
41When package browsers render each link, a redundant link adds visual noise.
42
43### Drawbacks
44
45### Example
46
47```toml
48[package]
49name = "foo"
50homepage = "https://github.com/rust-lang/cargo/"
51repository = "https://github.com/rust-lang/cargo/"
52```
53
54Should be written as:
55
56```toml
57[package]
58name = "foo"
59repository = "https://github.com/rust-lang/cargo/"
60```
61"#,
62 ),
63};
64
65#[instrument(skip_all)]
66pub(crate) fn lint_package(
67 _ws: &Workspace<'_>,
68 pkg: &Package,
69 manifest_path: &Path,
70 level: LintLevelProduct,
71 stats: &mut DiagnosticStats,
72 gctx: &GlobalContext,
73) -> CargoResult<()> {
74 let LintLevelProduct {
75 level: lint_level,
76 source,
77 } = level;
78
79 let manifest_path = rel_cwd_manifest_path(manifest_path, gctx);
80
81 lint_package_inner(pkg, &manifest_path, lint_level, source, stats, gctx)
82}
83
84fn lint_package_inner(
85 pkg: &Package,
86 manifest_path: &str,
87 lint_level: LintLevel,
88 source: LintLevelSource,
89 stats: &mut DiagnosticStats,
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, source);
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 stats.record_lint(lint_level);
157 gctx.shell().print_report(&report, lint_level.force())?;
158
159 Ok(())
160}