Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 91 additions & 41 deletions src/librustdoc/passes/lint/redundant_explicit_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ use rustc_resolve::rustdoc::{prepare_to_doc_link_resolution, source_span_for_mar
use rustc_span::def_id::DefId;
use rustc_span::{Span, Symbol};

use crate::clean::Item;
use crate::clean::utils::{find_nearest_parent_module, inherits_doc_hidden};
use crate::clean::{Item, inline};
use crate::core::DocContext;
use crate::formats::item_type::ItemType;
use crate::html::format::href_relative_parts;
use crate::html::markdown::main_body_opts;

#[derive(Debug)]
Expand Down Expand Up @@ -71,12 +73,13 @@ fn check_redundant_explicit_link_for_did(
return;
};

check_redundant_explicit_link(cx, item, hir_id, doc, resolutions);
check_redundant_explicit_link(cx, item, module_id, hir_id, doc, resolutions);
}

fn check_redundant_explicit_link<'md>(
cx: &DocContext<'_>,
item: &Item,
module_id: DefId,
hir_id: HirId,
doc: &'md str,
resolutions: &DocLinkResMap,
Expand Down Expand Up @@ -114,41 +117,41 @@ fn check_redundant_explicit_link<'md>(
continue;
}

if dest_url.ends_with(resolvable_link) || resolvable_link.ends_with(&*dest_url) {
let check_result = match link_type {
LinkType::Inline | LinkType::ReferenceUnknown => {
check_inline_or_reference_unknown_redundancy(
cx,
item,
hir_id,
doc,
resolutions,
link_range,
dest_url.to_string(),
link_data,
if link_type == LinkType::Inline { (b'(', b')') } else { (b'[', b']') },
)
}
LinkType::Reference => check_reference_redundancy(
let check_result = match link_type {
LinkType::Inline | LinkType::ReferenceUnknown => {
check_inline_or_reference_unknown_redundancy(
cx,
item,
module_id,
hir_id,
doc,
resolutions,
link_range,
&dest_url,
dest_url.to_string(),
link_data,
),
_ => Ok(()),
};
if let Err(lint) = check_result {
cx.tcx.emit_node_span_lint(
crate::lint::REDUNDANT_EXPLICIT_LINKS,
hir_id,
item.attr_span(cx.tcx),
lint,
);
if link_type == LinkType::Inline { (b'(', b')') } else { (b'[', b']') },
)
}
LinkType::Reference => check_reference_redundancy(
cx,
item,
module_id,
hir_id,
doc,
resolutions,
link_range,
&dest_url,
link_data,
),
_ => Ok(()),
};
if let Err(lint) = check_result {
cx.tcx.emit_node_span_lint(
crate::lint::REDUNDANT_EXPLICIT_LINKS,
hir_id,
item.attr_span(cx.tcx),
lint,
);
}
}
}
Expand Down Expand Up @@ -179,6 +182,7 @@ impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksWithoutSuggestion {
fn check_inline_or_reference_unknown_redundancy(
cx: &DocContext<'_>,
item: &Item,
module_id: DefId,
hir_id: HirId,
doc: &str,
resolutions: &DocLinkResMap,
Expand Down Expand Up @@ -226,13 +230,8 @@ fn check_inline_or_reference_unknown_redundancy(
else {
return Ok(());
};
let (Some(dest_res), Some(display_res)) =
(find_resolution(resolutions, &dest), find_resolution(resolutions, resolvable_link))
else {
return Ok(());
};

if dest_res == display_res {
if explicit_link_is_redundant(cx, module_id, resolutions, &dest, resolvable_link) {
let attr_span = item.attr_span(cx.tcx);
let link_span =
match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings)
Expand Down Expand Up @@ -302,6 +301,7 @@ fn check_inline_or_reference_unknown_redundancy(
fn check_reference_redundancy(
cx: &DocContext<'_>,
item: &Item,
module_id: DefId,
hir_id: HirId,
doc: &str,
resolutions: &DocLinkResMap,
Expand Down Expand Up @@ -347,13 +347,8 @@ fn check_reference_redundancy(
else {
return Ok(());
};
let (Some(dest_res), Some(display_res)) =
(find_resolution(resolutions, dest), find_resolution(resolutions, resolvable_link))
else {
return Ok(());
};

if dest_res == display_res {
if explicit_link_is_redundant(cx, module_id, resolutions, dest, resolvable_link) {
let attr_span = item.attr_span(cx.tcx);
let link_span =
match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings)
Expand Down Expand Up @@ -437,6 +432,61 @@ fn check_reference_redundancy(
Ok(())
}

fn explicit_link_is_redundant(
cx: &DocContext<'_>,
module_id: DefId,
resolutions: &DocLinkResMap,
dest: &str,
resolvable_link: &str,
) -> bool {
let Some(display_res) = find_resolution(resolutions, resolvable_link) else {
return false;
};

if (dest.ends_with(resolvable_link) || resolvable_link.ends_with(dest))
&& find_resolution(resolutions, dest).is_some_and(|dest_res| dest_res == display_res)
{
return true;
}

if dest.contains('#') || !dest.ends_with(".html") {
return false;
}

local_href_for_res(cx, module_id, display_res).is_some_and(|href| href == dest)
}

fn local_href_for_res(cx: &DocContext<'_>, module_id: DefId, res: Res<NodeId>) -> Option<String> {
let mut did = res.opt_def_id()?;
if matches!(cx.tcx.def_kind(did), DefKind::Ctor(..)) {
did = cx.tcx.parent(did);
}

if matches!(
cx.tcx.def_kind(did),
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst { .. } | DefKind::Variant
) || !did.is_local()
{
return None;
}

let item_type = ItemType::from_def_id(did, cx.tcx);
let fqp = inline::get_item_path(cx.tcx, did, item_type);
let module_fqp = if item_type == ItemType::Module { &fqp[..] } else { &fqp[..fqp.len() - 1] };
let current_fqp = inline::get_item_path(cx.tcx, module_id, ItemType::Module);

let mut url_parts = href_relative_parts(module_fqp, &current_fqp);
match item_type {
ItemType::Module => url_parts.push("index.html"),
_ => url_parts.push_fmt(format_args!(
"{}.{last}.html",
item_type.as_str(),
last = fqp.last()?
)),
}
Some(url_parts.finish())
}

fn find_resolution(resolutions: &DocLinkResMap, path: &str) -> Option<Res<NodeId>> {
[Namespace::TypeNS, Namespace::ValueNS, Namespace::MacroNS]
.into_iter()
Expand Down
6 changes: 6 additions & 0 deletions tests/rustdoc-ui/lints/no-redundancy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
/// [Vec][std::vec::Vec#examples] should not warn, because it's not actually redundant!
/// [This is just an `Option`][std::option::Option] has different display content to actual link!
pub fn func() {}

// Regression guard for https://github.com/rust-lang/rust/issues/155458.
/// [NoRedundancyTarget](struct.NoRedundancyTarget.html#fragment) should not warn.
pub struct NoRedundancySource;

pub struct NoRedundancyTarget;
7 changes: 7 additions & 0 deletions tests/rustdoc-ui/lints/redundant_explicit_links.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,10 @@ pub fn should_warn_reference() {}
/// [`Vec<T>`]: Vec
/// [`Vec<T>`]: std::vec::Vec
pub fn should_not_warn_reference() {}

// Regression test for https://github.com/rust-lang/rust/issues/155458.
/// [Issue155458B]
//~^ ERROR redundant explicit link target
pub struct Issue155458A;

pub struct Issue155458B;
7 changes: 7 additions & 0 deletions tests/rustdoc-ui/lints/redundant_explicit_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,10 @@ pub fn should_warn_reference() {}
/// [`Vec<T>`]: Vec
/// [`Vec<T>`]: std::vec::Vec
pub fn should_not_warn_reference() {}

// Regression test for https://github.com/rust-lang/rust/issues/155458.
/// [Issue155458B](struct.Issue155458B.html)
//~^ ERROR redundant explicit link target
pub struct Issue155458A;

pub struct Issue155458B;
18 changes: 17 additions & 1 deletion tests/rustdoc-ui/lints/redundant_explicit_links.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -1063,5 +1063,21 @@ LL - /// [`dummy_target`][dummy_target] TEXT
LL + /// [`dummy_target`] TEXT
|

error: aborting due to 60 previous errors
error: redundant explicit link target
--> $DIR/redundant_explicit_links.rs:161:20
|
LL | /// [Issue155458B](struct.Issue155458B.html)
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^ explicit target is redundant
| |
| because label contains path that resolves to same destination
|
= note: when a link's destination is not specified,
the label is used to resolve intra-doc links
help: remove explicit link target
|
LL - /// [Issue155458B](struct.Issue155458B.html)
LL + /// [Issue155458B]
|

error: aborting due to 61 previous errors

Loading