@@ -460,6 +460,19 @@ pub fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
460460 start. to ( end)
461461}
462462
463+ /// Reports a resolution failure diagnostic.
464+ ///
465+ /// Ideally we can report the diagnostic with the actual span in the source where the link failure
466+ /// occurred. However, there's a mismatch between the span in the source code and the span in the
467+ /// markdown, so we have to do a bit of work to figure out the correspondence.
468+ ///
469+ /// It's not too hard to find the span for sugared doc comments (`///` and `/**`), because the
470+ /// source will match the markdown exactly, excluding the comment markers. However, it's much more
471+ /// difficult to calculate the spans for unsugared docs, because we have to deal with escaping and
472+ /// other source features. So, we attempt to find the exact source span of the resolution failure
473+ /// in sugared docs, but use the span of the documentation attributes themselves for unsugared
474+ /// docs. Because this span might be overly large, we display the markdown line containing the
475+ /// failure as a note.
463476fn resolution_failure (
464477 cx : & DocContext ,
465478 attrs : & Attributes ,
@@ -470,34 +483,50 @@ fn resolution_failure(
470483 let sp = span_of_attrs ( attrs) ;
471484 let msg = format ! ( "`[{}]` cannot be resolved, ignoring it..." , path_str) ;
472485
473- let code_dox = sp. to_src ( cx) ;
474-
475- let doc_comment_padding = 3 ;
476486 let mut diag = if let Some ( link_range) = link_range {
477- // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
478- // ^ ~~~~~~
479- // | link_range
480- // last_new_line_offset
481-
482487 let mut diag;
483- if dox. lines ( ) . count ( ) == code_dox. lines ( ) . count ( ) {
484- let line_offset = dox[ ..link_range. start ] . lines ( ) . count ( ) ;
485- // The span starts in the `///`, so we don't have to account for the leading whitespace
486- let code_dox_len = if line_offset <= 1 {
487- doc_comment_padding
488- } else {
489- // The first `///`
490- doc_comment_padding +
491- // Each subsequent leading whitespace and `///`
492- code_dox. lines ( ) . skip ( 1 ) . take ( line_offset - 1 ) . fold ( 0 , |sum, line| {
493- sum + doc_comment_padding + line. len ( ) - line. trim_start ( ) . len ( )
494- } )
495- } ;
496488
497- // Extract the specific span
489+ if attrs. doc_strings . iter ( ) . all ( |frag| match frag {
490+ DocFragment :: SugaredDoc ( ..) => true ,
491+ _ => false ,
492+ } ) {
493+ let source_dox = sp. to_src ( cx) ;
494+ let mut source_lines = source_dox. lines ( ) . peekable ( ) ;
495+ let mut md_lines = dox. lines ( ) . peekable ( ) ;
496+
497+ // The number of bytes from the start of the source span to the resolution failure that
498+ // are *not* part of the markdown, like comment markers.
499+ let mut source_offset = 0 ;
500+
501+ // Eat any source lines before the markdown starts (e.g., `/**` on its own line).
502+ while let Some ( source_line) = source_lines. peek ( ) {
503+ if source_line. contains ( md_lines. peek ( ) . unwrap ( ) ) {
504+ break ;
505+ }
506+
507+ // Include the newline.
508+ source_offset += source_line. len ( ) + 1 ;
509+ source_lines. next ( ) . unwrap ( ) ;
510+ }
511+
512+ // The number of lines up to and including the resolution failure.
513+ let num_lines = dox[ ..link_range. start ] . lines ( ) . count ( ) ;
514+
515+ // Consume inner comment markers (e.g., `///` or ` *`).
516+ for ( source_line, md_line) in source_lines. zip ( md_lines) . take ( num_lines) {
517+ source_offset += if md_line. is_empty ( ) {
518+ // If there is no markdown on this line, then the whole line is a comment
519+ // marker. We don't have to count the newline here because it's in the markdown
520+ // too.
521+ source_line. len ( )
522+ } else {
523+ source_line. find ( md_line) . unwrap ( )
524+ } ;
525+ }
526+
498527 let sp = sp. from_inner_byte_pos (
499- link_range. start + code_dox_len ,
500- link_range. end + code_dox_len ,
528+ link_range. start + source_offset ,
529+ link_range. end + source_offset ,
501530 ) ;
502531
503532 diag = cx. tcx . struct_span_lint_node ( lint:: builtin:: INTRA_DOC_LINK_RESOLUTION_FAILURE ,
@@ -511,6 +540,10 @@ fn resolution_failure(
511540 sp,
512541 & msg) ;
513542
543+ // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
544+ // ^ ~~~~
545+ // | link_range
546+ // last_new_line_offset
514547 let last_new_line_offset = dox[ ..link_range. start ] . rfind ( '\n' ) . map_or ( 0 , |n| n + 1 ) ;
515548 let line = dox[ last_new_line_offset..] . lines ( ) . next ( ) . unwrap_or ( "" ) ;
516549
0 commit comments