Change:
let sp = span_of_attrs(attrs); let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str); <del> let code_dox = sp.to_src(cx); let doc_comment_padding = 3; </del> let mut diag = if let Some(link_range) = link_range { <del> // blah blah blahnblahnblah [blah] blah blahnblah blah //                       ^    ~~~~~~ //                       |    link_range //                       last_new_line_offset let mut diag; if dox.lines().count() == code_dox.lines().count() { let line_offset = dox[..link_range.start].lines().count(); // The span starts in the `///`, so we don't have to account for the leading whitespace. let code_dox_len = if line_offset <= 1 { doc_comment_padding } else { // The first `///`. doc_comment_padding + // Each subsequent leading whitespace and `///`. code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| { sum + doc_comment_padding + line.len() - line.trim_start().len() }) }; </del> <ins> let src = cx.sess().source_map().span_to_snippet(sp); let is_all_sugared_doc = attrs.doc_strings.iter().all(|frag| match frag { DocFragment::SugaredDoc(..) => true, _ => false, }); if let (Ok(src), true) = (src, is_all_sugared_doc) { // The number of markdown lines up to and including the resolution failure. let num_lines = dox[..link_range.start].lines().count(); // We use `split_terminator('n')` instead of `lines()` when counting bytes to ensure // that DOS-style line endings do not cause the spans to be calculated incorrectly. let mut src_lines = src.split_terminator('n'); let mut md_lines = dox.split_terminator('n').take(num_lines).peekable(); // The number of bytes from the start of the source span to the resolution failure that // are *not* part of the markdown, like comment markers. let mut extra_src_bytes = 0; while let Some(md_line) = md_lines.next() { loop { let source_line = src_lines .next() .expect("could not find markdown line in source"); match source_line.find(md_line) { Some(offset) => { extra_src_bytes += if md_lines.peek().is_some() { source_line.len() - md_line.len() } else { offset }; 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. extra_src_bytes += source_line.len() + 1; } } } } </ins> <del> // Extract the specific span. </del> let sp = sp.from_inner_byte_pos( <del> link_range.start + code_dox_len, link_range.end + code_dox_len, </del> <ins> link_range.start + extra_src_bytes, link_range.end + extra_src_bytes, </ins> ); <del> diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, NodeId::from_u32(0), sp, &msg); </del> <ins> let mut diag = cx.tcx.struct_span_lint_node( lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, NodeId::from_u32(0), sp, &msg, ); </ins> diag.span_label(sp, "cannot be resolved, ignoring"); <ins> diag </ins> } else { <del> diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, NodeId::from_u32(0), sp, &msg); </del> <ins> let mut diag = cx.tcx.struct_span_lint_node( lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, NodeId::from_u32(0), sp, &msg, ); // blah blah blahnblahnblah [blah] blah blahnblah blah //                       ^     ~~~~ //                       |     link_range //                       last_new_line_offset </ins> let last_new_line_offset = dox[..link_range.start].rfind('n').map_or(0, |n| n + 1); let line = dox[last_new_line_offset..].lines().next().unwrap_or("");

Discussion:
Given the following source: The span conversion calculation in the error reporting will give the wrong offset when reporting the resolution failure, leading to a confusing report: Strangely enough, putting either the leading or the closing on its own line will cause the report to change to something that looks correct, if a little too broad: I believe the code at fault is this section here: Because it assumes that every doc comment will have a padding of 3 characters (the or ), it includes that in its calculation of each line's offset. However, for block-style comments, this assumption is false.
I'm working on this.
You can also reproduce this with unsugared docs: