Do not emit redundant_explicit_links rustdoc lint if the doc comment comes from expansion

This commit is contained in:
Guillaume Gomez 2025-05-27 15:16:51 +02:00
parent 22be76b7e2
commit 7e683cc4d1
7 changed files with 101 additions and 55 deletions

View file

@ -49,6 +49,9 @@ pub struct DocFragment {
pub doc: Symbol,
pub kind: DocFragmentKind,
pub indent: usize,
/// Because we temper with the spans context, this information cannot be correctly retrieved
/// later on. So instead, we compute it and store it here.
pub from_expansion: bool,
}
#[derive(Clone, Copy, Debug)]
@ -208,17 +211,18 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>(
for (attr, item_id) in attrs {
if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
let doc = beautify_doc_string(doc_str, comment_kind);
let (span, kind) = if attr.is_doc_comment() {
(attr.span(), DocFragmentKind::SugaredDoc)
let (span, kind, from_expansion) = if attr.is_doc_comment() {
let span = attr.span();
(span, DocFragmentKind::SugaredDoc, span.from_expansion())
} else {
(
attr.value_span()
.map(|i| i.with_ctxt(attr.span().ctxt()))
.unwrap_or(attr.span()),
DocFragmentKind::RawDoc,
)
let attr_span = attr.span();
let (span, from_expansion) = match attr.value_span() {
Some(sp) => (sp.with_ctxt(attr_span.ctxt()), sp.from_expansion()),
None => (attr_span, attr_span.from_expansion()),
};
(span, DocFragmentKind::RawDoc, from_expansion)
};
let fragment = DocFragment { span, doc, kind, item_id, indent: 0 };
let fragment = DocFragment { span, doc, kind, item_id, indent: 0, from_expansion };
doc_fragments.push(fragment);
} else if !doc_only {
other_attrs.push(attr.clone());
@ -505,17 +509,22 @@ fn collect_link_data<'input, F: BrokenLinkCallback<'input>>(
display_text.map(String::into_boxed_str)
}
/// Returns a span encompassing all the document fragments.
pub fn span_of_fragments_with_expansion(fragments: &[DocFragment]) -> Option<(Span, bool)> {
let Some(first_fragment) = fragments.first() else { return None };
if first_fragment.span == DUMMY_SP {
return None;
}
let last_fragment = fragments.last().expect("no doc strings provided");
Some((
first_fragment.span.to(last_fragment.span),
first_fragment.from_expansion || last_fragment.from_expansion,
))
}
/// Returns a span encompassing all the document fragments.
pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
if fragments.is_empty() {
return None;
}
let start = fragments[0].span;
if start == DUMMY_SP {
return None;
}
let end = fragments.last().expect("no doc strings provided").span;
Some(start.to(end))
span_of_fragments_with_expansion(fragments).map(|(sp, _)| sp)
}
/// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
@ -540,7 +549,7 @@ pub fn source_span_for_markdown_range(
markdown: &str,
md_range: &Range<usize>,
fragments: &[DocFragment],
) -> Option<Span> {
) -> Option<(Span, bool)> {
let map = tcx.sess.source_map();
source_span_for_markdown_range_inner(map, markdown, md_range, fragments)
}
@ -551,7 +560,7 @@ pub fn source_span_for_markdown_range_inner(
markdown: &str,
md_range: &Range<usize>,
fragments: &[DocFragment],
) -> Option<Span> {
) -> Option<(Span, bool)> {
use rustc_span::BytePos;
if let &[fragment] = &fragments
@ -562,11 +571,14 @@ pub fn source_span_for_markdown_range_inner(
&& let Ok(md_range_hi) = u32::try_from(md_range.end)
{
// Single fragment with string that contains same bytes as doc.
return Some(Span::new(
fragment.span.lo() + rustc_span::BytePos(md_range_lo),
fragment.span.lo() + rustc_span::BytePos(md_range_hi),
fragment.span.ctxt(),
fragment.span.parent(),
return Some((
Span::new(
fragment.span.lo() + rustc_span::BytePos(md_range_lo),
fragment.span.lo() + rustc_span::BytePos(md_range_hi),
fragment.span.ctxt(),
fragment.span.parent(),
),
fragment.from_expansion,
));
}
@ -598,19 +610,21 @@ pub fn source_span_for_markdown_range_inner(
{
match_data = Some((i, match_start));
} else {
// Heirustic produced ambiguity, return nothing.
// Heuristic produced ambiguity, return nothing.
return None;
}
}
}
if let Some((i, match_start)) = match_data {
let sp = fragments[i].span;
let fragment = &fragments[i];
let sp = fragment.span;
// we need to calculate the span start,
// then use that in our calulations for the span end
let lo = sp.lo() + BytePos(match_start as u32);
return Some(
return Some((
sp.with_lo(lo).with_hi(lo + BytePos((md_range.end - md_range.start) as u32)),
);
fragment.from_expansion,
));
}
return None;
}
@ -664,8 +678,12 @@ pub fn source_span_for_markdown_range_inner(
}
}
Some(span_of_fragments(fragments)?.from_inner(InnerSpan::new(
md_range.start + start_bytes,
md_range.end + start_bytes + end_bytes,
)))
let (span, from_expansion) = span_of_fragments_with_expansion(fragments)?;
Some((
span.from_inner(InnerSpan::new(
md_range.start + start_bytes,
md_range.end + start_bytes + end_bytes,
)),
from_expansion,
))
}

View file

@ -1387,13 +1387,15 @@ impl LinkCollector<'_, '_> {
ori_link: &MarkdownLinkRange,
item: &Item,
) {
let span = source_span_for_markdown_range(
let span = match source_span_for_markdown_range(
self.cx.tcx,
dox,
ori_link.inner_range(),
&item.attrs.doc_strings,
)
.unwrap_or_else(|| item.attr_span(self.cx.tcx));
) {
Some((sp, _)) => sp,
None => item.attr_span(self.cx.tcx),
};
rustc_session::parse::feature_err(
self.cx.tcx.sess,
sym::intra_doc_pointers,
@ -1836,7 +1838,7 @@ fn report_diagnostic(
let mut md_range = md_range.clone();
let sp =
source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs.doc_strings)
.map(|mut sp| {
.map(|(mut sp, _)| {
while dox.as_bytes().get(md_range.start) == Some(&b' ')
|| dox.as_bytes().get(md_range.start) == Some(&b'`')
{
@ -1854,7 +1856,8 @@ fn report_diagnostic(
(sp, MarkdownLinkRange::Destination(md_range))
}
MarkdownLinkRange::WholeLink(md_range) => (
source_span_for_markdown_range(tcx, dox, md_range, &item.attrs.doc_strings),
source_span_for_markdown_range(tcx, dox, md_range, &item.attrs.doc_strings)
.map(|(sp, _)| sp),
link_range.clone(),
),
};

View file

@ -18,7 +18,8 @@ use crate::html::markdown::main_body_opts;
pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range<usize>| {
let maybe_sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings);
let maybe_sp = source_span_for_markdown_range(cx.tcx, dox, &range, &item.attrs.doc_strings)
.map(|(sp, _)| sp);
let sp = maybe_sp.unwrap_or_else(|| item.attr_span(cx.tcx));
cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
lint.primary_message(msg)

View file

@ -87,7 +87,7 @@ fn check_rust_syntax(
&code_block.range,
&item.attrs.doc_strings,
) {
Some(sp) => (sp, true),
Some((sp, _)) => (sp, true),
None => (item.attr_span(cx.tcx), false),
};

View file

@ -16,7 +16,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
let tcx = cx.tcx;
let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
let sp = match source_span_for_markdown_range(tcx, dox, range, &item.attrs.doc_strings) {
Some(sp) => sp,
Some((sp, _)) => sp,
None => item.attr_span(tcx),
};
tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
@ -55,7 +55,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
&(generics_start..generics_end),
&item.attrs.doc_strings,
) {
Some(sp) => sp,
Some((sp, _)) => sp,
None => item.attr_span(tcx),
};
// Sometimes, we only extract part of a path. For example, consider this:

View file

@ -161,15 +161,26 @@ fn check_inline_or_reference_unknown_redundancy(
if dest_res == display_res {
let link_span =
source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings)
.unwrap_or(item.attr_span(cx.tcx));
let explicit_span = source_span_for_markdown_range(
match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings)
{
Some((sp, from_expansion)) => {
if from_expansion {
return None;
}
sp
}
None => item.attr_span(cx.tcx),
};
let (explicit_span, from_expansion) = source_span_for_markdown_range(
cx.tcx,
doc,
&offset_explicit_range(doc, link_range, open, close),
&item.attrs.doc_strings,
)?;
let display_span = source_span_for_markdown_range(
if from_expansion {
return None;
}
let (display_span, _) = source_span_for_markdown_range(
cx.tcx,
doc,
resolvable_link_range,
@ -206,21 +217,32 @@ fn check_reference_redundancy(
if dest_res == display_res {
let link_span =
source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings)
.unwrap_or(item.attr_span(cx.tcx));
let explicit_span = source_span_for_markdown_range(
match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings)
{
Some((sp, from_expansion)) => {
if from_expansion {
return None;
}
sp
}
None => item.attr_span(cx.tcx),
};
let (explicit_span, from_expansion) = source_span_for_markdown_range(
cx.tcx,
doc,
&offset_explicit_range(doc, link_range.clone(), b'[', b']'),
&item.attrs.doc_strings,
)?;
let display_span = source_span_for_markdown_range(
if from_expansion {
return None;
}
let (display_span, _) = source_span_for_markdown_range(
cx.tcx,
doc,
resolvable_link_range,
&item.attrs.doc_strings,
)?;
let def_span = source_span_for_markdown_range(
let (def_span, _) = source_span_for_markdown_range(
cx.tcx,
doc,
&offset_reference_def_range(doc, dest, link_range),

View file

@ -42,13 +42,15 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &
// If we can't get a span of the backtick, because it is in a `#[doc = ""]` attribute,
// use the span of the entire attribute as a fallback.
let span = source_span_for_markdown_range(
let span = match source_span_for_markdown_range(
tcx,
dox,
&(backtick_index..backtick_index + 1),
&item.attrs.doc_strings,
)
.unwrap_or_else(|| item.attr_span(tcx));
) {
Some((sp, _)) => sp,
None => item.attr_span(tcx),
};
tcx.node_span_lint(crate::lint::UNESCAPED_BACKTICKS, hir_id, span, |lint| {
lint.primary_message("unescaped backtick");
@ -419,7 +421,7 @@ fn suggest_insertion(
/// Maximum bytes of context to show around the insertion.
const CONTEXT_MAX_LEN: usize = 80;
if let Some(span) = source_span_for_markdown_range(
if let Some((span, _)) = source_span_for_markdown_range(
cx.tcx,
dox,
&(insert_index..insert_index),