Reuse rustdoc's doc comment handling in Clippy
This commit is contained in:
parent
309af3442a
commit
caaf1eb887
24 changed files with 324 additions and 302 deletions
|
@ -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,
|
||||
)))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue