diff options
Diffstat (limited to 'compiler/rustc_resolve/src/rustdoc.rs')
-rw-r--r-- | compiler/rustc_resolve/src/rustdoc.rs | 89 |
1 files changed, 88 insertions, 1 deletions
diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index ba7417b6d..7c41c32d0 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -2,9 +2,11 @@ use pulldown_cmark::{BrokenLink, CowStr, Event, LinkType, Options, Parser, Tag}; use rustc_ast as ast; use rustc_ast::util::comments::beautify_doc_string; use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::symbol::{kw, sym, Symbol}; -use rustc_span::Span; +use rustc_span::{InnerSpan, Span, DUMMY_SP}; +use std::ops::Range; use std::{cmp, mem}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -462,3 +464,88 @@ fn collect_link_data<'input, 'callback>( display_text.map(String::into_boxed_str) } + +/// 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)) +} + +/// 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. +pub fn source_span_for_markdown_range( + tcx: TyCtxt<'_>, + markdown: &str, + md_range: &Range<usize>, + fragments: &[DocFragment], +) -> Option<Span> { + let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc); + + if !is_all_sugared_doc { + return None; + } + + let snippet = tcx.sess.source_map().span_to_snippet(span_of_fragments(fragments)?).ok()?; + + let starting_line = markdown[..md_range.start].matches('\n').count(); + let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count(); + + // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we treat + // CRLF and LF line endings the same way. + let mut src_lines = snippet.split_terminator('\n'); + let md_lines = markdown.split_terminator('\n'); + + // The number of bytes from the source span to the markdown span that are not part + // of the markdown, like comment markers. + let mut start_bytes = 0; + let mut end_bytes = 0; + + 'outer: for (line_no, md_line) in md_lines.enumerate() { + loop { + let source_line = src_lines.next()?; + match source_line.find(md_line) { + Some(offset) => { + if line_no == starting_line { + start_bytes += offset; + + if starting_line == ending_line { + break 'outer; + } + } else if line_no == ending_line { + end_bytes += offset; + break 'outer; + } else if line_no < starting_line { + start_bytes += source_line.len() - md_line.len(); + } else { + end_bytes += source_line.len() - md_line.len(); + } + break; + } + None => { + // Since this is a source line that doesn't include a markdown line, + // we have to count the newline that we split from earlier. + if line_no <= starting_line { + start_bytes += source_line.len() + 1; + } else { + end_bytes += source_line.len() + 1; + } + } + } + } + } + + Some(span_of_fragments(fragments)?.from_inner(InnerSpan::new( + md_range.start + start_bytes, + md_range.end + start_bytes + end_bytes, + ))) +} |