rustdoc: point at span in include_str!-ed md file

This commit is contained in:
Michael Howell 2024-03-29 12:31:34 -07:00
parent d74804636f
commit 98642da6a9
10 changed files with 121 additions and 42 deletions

View file

@ -194,12 +194,12 @@ pub fn attrs_to_doc_fragments<'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 kind = if attr.is_doc_comment() {
DocFragmentKind::SugaredDoc
let (span, kind) = if attr.is_doc_comment() {
(attr.span, DocFragmentKind::SugaredDoc)
} else {
DocFragmentKind::RawDoc
(span_for_value(attr), DocFragmentKind::RawDoc)
};
let fragment = DocFragment { span: attr.span, doc, kind, item_id, indent: 0 };
let fragment = DocFragment { span, doc, kind, item_id, indent: 0 };
doc_fragments.push(fragment);
} else if !doc_only {
other_attrs.push(attr.clone());
@ -211,6 +211,16 @@ pub fn attrs_to_doc_fragments<'a>(
(doc_fragments, other_attrs)
}
fn span_for_value(attr: &ast::Attribute) -> Span {
if let ast::AttrKind::Normal(normal) = &attr.kind
&& let ast::AttrArgs::Eq(_, ast::AttrArgsEq::Hir(meta)) = &normal.item.args
{
meta.span.with_ctxt(attr.span.ctxt())
} else {
attr.span
}
}
/// Return the doc-comments on this item, grouped by the module they came from.
/// The module can be different if this is a re-export with added documentation.
///
@ -482,15 +492,36 @@ pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
/// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
///
/// This method will return `None` if we cannot construct a span from the source map or if the
/// fragments are not all sugared doc comments. It's difficult to calculate the correct span in
/// that case due to escaping and other source features.
/// This method does not always work, because markdown bytes don't necessarily match source bytes,
/// like if escapes are used in the string. In this case, it returns `None`.
///
/// This method will return `Some` only if:
///
/// - The doc is made entirely from sugared doc comments, which cannot contain escapes
/// - The doc is entirely from a single doc fragment, with a string literal, exactly equal
/// - The doc comes from `include_str!`
pub fn source_span_for_markdown_range(
tcx: TyCtxt<'_>,
markdown: &str,
md_range: &Range<usize>,
fragments: &[DocFragment],
) -> Option<Span> {
if let &[fragment] = &fragments
&& fragment.kind == DocFragmentKind::RawDoc
&& let Ok(snippet) = tcx.sess.source_map().span_to_snippet(fragment.span)
&& snippet.trim_end() == markdown.trim_end()
&& let Ok(md_range_lo) = u32::try_from(md_range.start)
&& 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(),
));
}
let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc);
if !is_all_sugared_doc {