@@ -514,20 +514,25 @@ pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
514
514
/// This method does not always work, because markdown bytes don't necessarily match source bytes,
515
515
/// like if escapes are used in the string. In this case, it returns `None`.
516
516
///
517
- /// This method will return `Some` only if:
517
+ /// `markdown` is typically the entire documentation for an item,
518
+ /// after combining fragments.
519
+ ///
520
+ /// This method will return `Some` only if one of the following is true:
518
521
///
519
522
/// - The doc is made entirely from sugared doc comments, which cannot contain escapes
520
523
/// - The doc is entirely from a single doc fragment, with a string literal, exactly equal
521
524
/// - The doc comes from `include_str!`
525
+ /// - The doc includes exactly one substring matching `markdown[md_range]` which is contained in a single doc fragment.
522
526
pub fn source_span_for_markdown_range (
523
527
tcx : TyCtxt < ' _ > ,
524
528
markdown : & str ,
525
529
md_range : & Range < usize > ,
526
530
fragments : & [ DocFragment ] ,
527
531
) -> Option < Span > {
532
+ let span_to_snippet = |span| tcx. sess . source_map ( ) . span_to_snippet ( span) ;
528
533
if let & [ fragment] = & fragments
529
534
&& fragment. kind == DocFragmentKind :: RawDoc
530
- && let Ok ( snippet) = tcx . sess . source_map ( ) . span_to_snippet ( fragment. span )
535
+ && let Ok ( snippet) = span_to_snippet ( fragment. span )
531
536
&& snippet. trim_end ( ) == markdown. trim_end ( )
532
537
&& let Ok ( md_range_lo) = u32:: try_from ( md_range. start )
533
538
&& let Ok ( md_range_hi) = u32:: try_from ( md_range. end )
@@ -544,6 +549,38 @@ pub fn source_span_for_markdown_range(
544
549
let is_all_sugared_doc = fragments. iter ( ) . all ( |frag| frag. kind == DocFragmentKind :: SugaredDoc ) ;
545
550
546
551
if !is_all_sugared_doc {
552
+ // this case ignores the markdown outside of the range so that it can
553
+ // work in cases where the markdown is made from several different
554
+ // doc fragments, but the target range does not span across multiple
555
+ // fragments.
556
+ let mut match_data = None ;
557
+ let pat = & markdown[ md_range. clone ( ) ] ;
558
+ // this heirustic doesn't make sense with a zero-sized range.
559
+ if pat. len ( ) == 0 {
560
+ return None ;
561
+ }
562
+ for ( i, fragment) in fragments. iter ( ) . enumerate ( ) {
563
+ if let Ok ( snippet) = span_to_snippet ( fragment. span )
564
+ && let Some ( match_start) = snippet. find ( pat)
565
+ {
566
+ // if there is either a match in a previous fragment,
567
+ // or multiple matches in this fragment,
568
+ // there is ambiguity.
569
+ if match_data. is_none ( ) && !snippet[ match_start + 1 ..] . contains ( pat) {
570
+ match_data = Some ( ( i, match_start) ) ;
571
+ } else {
572
+ // heirustic produced ambiguity, return nothing.
573
+ return None ;
574
+ }
575
+ }
576
+ }
577
+ if let Some ( ( i, match_start) ) = match_data {
578
+ use rustc_span:: BytePos ;
579
+ let mut sp = fragments[ i] . span ;
580
+ sp = sp. with_lo ( sp. lo ( ) + BytePos ( match_start as u32 ) ) ;
581
+ sp = sp. with_hi ( sp. lo ( ) + BytePos ( ( md_range. end - md_range. start ) as u32 ) ) ;
582
+ return Some ( sp) ;
583
+ }
547
584
return None ;
548
585
}
549
586
0 commit comments