@@ -514,20 +514,30 @@ 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
- /// - The doc is entirely from a single doc fragment, with a string literal, exactly equal
523
+ /// - The doc is entirely from a single doc fragment with a string literal exactly equal to `markdown`.
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.
526
+ ///
527
+ /// This function is defined in the compiler so it can be used by
528
+ /// both `rustdoc` and `clippy`.
522
529
pub fn source_span_for_markdown_range (
523
530
tcx : TyCtxt < ' _ > ,
524
531
markdown : & str ,
525
532
md_range : & Range < usize > ,
526
533
fragments : & [ DocFragment ] ,
527
534
) -> Option < Span > {
535
+ use rustc_span:: BytePos ;
536
+
537
+ let map = tcx. sess . source_map ;
528
538
if let & [ fragment] = & fragments
529
539
&& fragment. kind == DocFragmentKind :: RawDoc
530
- && let Ok ( snippet) = tcx . sess . source_map ( ) . span_to_snippet ( fragment. span )
540
+ && let Ok ( snippet) = map . span_to_snippet ( fragment. span )
531
541
&& snippet. trim_end ( ) == markdown. trim_end ( )
532
542
&& let Ok ( md_range_lo) = u32:: try_from ( md_range. start )
533
543
&& let Ok ( md_range_hi) = u32:: try_from ( md_range. end )
@@ -544,10 +554,42 @@ pub fn source_span_for_markdown_range(
544
554
let is_all_sugared_doc = fragments. iter ( ) . all ( |frag| frag. kind == DocFragmentKind :: SugaredDoc ) ;
545
555
546
556
if !is_all_sugared_doc {
557
+ // This case ignores the markdown outside of the range so that it can
558
+ // work in cases where the markdown is made from several different
559
+ // doc fragments, but the target range does not span across multiple
560
+ // fragments.
561
+ let mut match_data = None ;
562
+ let pat = & markdown[ md_range. clone ( ) ] ;
563
+ // This heirustic doesn't make sense with a zero-sized range.
564
+ if pat. is_empty ( ) {
565
+ return None ;
566
+ }
567
+ for ( i, fragment) in fragments. iter ( ) . enumerate ( ) {
568
+ if let Ok ( snippet) = map. span_to_snippet ( fragment. span )
569
+ && let Some ( match_start) = snippet. find ( pat)
570
+ {
571
+ // If there is either a match in a previous fragment, or
572
+ // multiple matches in this fragment, there is ambiguity.
573
+ if match_data. is_none ( ) && !snippet[ match_start + 1 ..] . contains ( pat) {
574
+ match_data = Some ( ( i, match_start) ) ;
575
+ } else {
576
+ // Heirustic produced ambiguity, return nothing.
577
+ return None ;
578
+ }
579
+ }
580
+ }
581
+ if let Some ( ( i, match_start) ) = match_data {
582
+ return Some (
583
+ fragments[ i]
584
+ . span
585
+ . with_lo ( sp. lo ( ) + BytePos ( match_start as u32 ) )
586
+ . with_hi ( sp. lo ( ) + BytePos ( ( md_range. end - md_range. start ) as u32 ) ) ,
587
+ ) ;
588
+ }
547
589
return None ;
548
590
}
549
591
550
- let snippet = tcx. sess . source_map ( ) . span_to_snippet ( span_of_fragments ( fragments) ?) . ok ( ) ?;
592
+ let snippet = tcx. sess . source_map ( ) . map . span_to_snippet ( span_of_fragments ( fragments) ?) . ok ( ) ?;
551
593
552
594
let starting_line = markdown[ ..md_range. start ] . matches ( '\n' ) . count ( ) ;
553
595
let ending_line = starting_line + markdown[ md_range. start ..md_range. end ] . matches ( '\n' ) . count ( ) ;
0 commit comments