1
Fork 0

fix intra-link resolution spans in block comments

This commit improves the calculation of code spans for intra-doc
resolution failures. All sugared doc comments should now have the
correct spans, including those where the comment is longer than the
docs.

It also fixes an issue where the spans were calculated incorrectly for
certain unsugared doc comments. The diagnostic will now always use the
span of the attributes, as originally intended.

Fixes #55964.
This commit is contained in:
Andy Russell 2018-12-10 12:24:39 -05:00
parent b755501043
commit 56413ecffc
No known key found for this signature in database
GPG key ID: BE2221033EDBC374
7 changed files with 237 additions and 59 deletions

View file

@ -707,8 +707,6 @@ impl<I: IntoIterator<Item=ast::NestedMetaItem>> NestedAttributesExt for I {
/// kept separate because of issue #42760. /// kept separate because of issue #42760.
#[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Eq, Debug, Hash)] #[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Eq, Debug, Hash)]
pub enum DocFragment { pub enum DocFragment {
// FIXME #44229 (misdreavus): sugared and raw doc comments can be brought back together once
// hoedown is completely removed from rustdoc.
/// A doc fragment created from a `///` or `//!` doc comment. /// A doc fragment created from a `///` or `//!` doc comment.
SugaredDoc(usize, syntax_pos::Span, String), SugaredDoc(usize, syntax_pos::Span, String),
/// A doc fragment created from a "raw" `#[doc=""]` attribute. /// A doc fragment created from a "raw" `#[doc=""]` attribute.

View file

@ -459,6 +459,19 @@ pub fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
start.to(end) start.to(end)
} }
/// Reports a resolution failure diagnostic.
///
/// Ideally we can report the diagnostic with the actual span in the source where the link failure
/// occurred. However, there's a mismatch between the span in the source code and the span in the
/// markdown, so we have to do a bit of work to figure out the correspondence.
///
/// It's not too hard to find the span for sugared doc comments (`///` and `/**`), because the
/// source will match the markdown exactly, excluding the comment markers. However, it's much more
/// difficult to calculate the spans for unsugared docs, because we have to deal with escaping and
/// other source features. So, we attempt to find the exact source span of the resolution failure
/// in sugared docs, but use the span of the documentation attributes themselves for unsugared
/// docs. Because this span might be overly large, we display the markdown line containing the
/// failure as a note.
fn resolution_failure( fn resolution_failure(
cx: &DocContext, cx: &DocContext,
attrs: &Attributes, attrs: &Attributes,
@ -469,47 +482,75 @@ fn resolution_failure(
let sp = span_of_attrs(attrs); let sp = span_of_attrs(attrs);
let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str); let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
let code_dox = sp.to_src(cx);
let doc_comment_padding = 3;
let mut diag = if let Some(link_range) = link_range { let mut diag = if let Some(link_range) = link_range {
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah let src = cx.sess().source_map().span_to_snippet(sp);
// ^ ~~~~~~ let is_all_sugared_doc = attrs.doc_strings.iter().all(|frag| match frag {
// | link_range DocFragment::SugaredDoc(..) => true,
// last_new_line_offset _ => false,
});
let mut diag; if let (Ok(src), true) = (src, is_all_sugared_doc) {
if dox.lines().count() == code_dox.lines().count() { // The number of markdown lines up to and including the resolution failure.
let line_offset = dox[..link_range.start].lines().count(); let num_lines = 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 { // We use `split_terminator('\n')` instead of `lines()` when counting bytes to ensure
doc_comment_padding // 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 { } else {
// The first `///`. offset
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()
})
}; };
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;
}
}
}
}
// Extract the specific span.
let sp = sp.from_inner_byte_pos( let sp = sp.from_inner_byte_pos(
link_range.start + code_dox_len, link_range.start + extra_src_bytes,
link_range.end + code_dox_len, link_range.end + extra_src_bytes,
); );
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, let mut diag = cx.tcx.struct_span_lint_node(
lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
NodeId::from_u32(0), NodeId::from_u32(0),
sp, sp,
&msg); &msg,
);
diag.span_label(sp, "cannot be resolved, ignoring"); diag.span_label(sp, "cannot be resolved, ignoring");
diag
} else { } else {
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, let mut diag = cx.tcx.struct_span_lint_node(
lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
NodeId::from_u32(0), NodeId::from_u32(0),
sp, sp,
&msg); &msg,
);
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
// ^ ~~~~
// | link_range
// last_new_line_offset
let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); 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(""); let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
@ -522,8 +563,8 @@ fn resolution_failure(
before=link_range.start - last_new_line_offset, before=link_range.start - last_new_line_offset,
found=link_range.len(), found=link_range.len(),
)); ));
}
diag diag
}
} else { } else {
cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
NodeId::from_u32(0), NodeId::from_u32(0),

1
src/test/rustdoc-ui/.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
intra-links-warning-crlf.rs eol=crlf

View file

@ -0,0 +1,23 @@
// ignore-tidy-cr
// compile-pass
// This file checks the spans of intra-link warnings in a file with CRLF line endings. The
// .gitattributes file in this directory should enforce it.
/// [error]
pub struct A;
///
/// docs [error1]
/// docs [error2]
///
pub struct B;
/**
* This is a multi-line comment.
*
* It also has an [error].
*/
pub struct C;

View file

@ -0,0 +1,33 @@
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning-crlf.rs:8:6
|
LL | /// [error]
| ^^^^^ cannot be resolved, ignoring
|
= note: #[warn(intra_doc_link_resolution_failure)] on by default
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error1]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning-crlf.rs:12:11
|
LL | /// docs [error1]
| ^^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error2]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning-crlf.rs:14:11
|
LL | /// docs [error2]
| ^^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning-crlf.rs:21:20
|
LL | * It also has an [error].
| ^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`

View file

@ -55,3 +55,33 @@ macro_rules! f {
} }
} }
f!("Foo\nbar [BarF] bar\nbaz"); f!("Foo\nbar [BarF] bar\nbaz");
/** # for example,
*
* time to introduce a link [error]*/
pub struct A;
/**
* # for example,
*
* time to introduce a link [error]
*/
pub struct B;
#[doc = "single line [error]"]
pub struct C;
#[doc = "single line with \"escaping\" [error]"]
pub struct D;
/// Item docs.
#[doc="Hello there!"]
/// [error]
pub struct E;
///
/// docs [error1]
/// docs [error2]
///
pub struct F;

View file

@ -55,6 +55,76 @@ LL | /// [Qux:Y]
| |
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]` = help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:61:30
|
LL | * time to introduce a link [error]*/
| ^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:67:30
|
LL | * time to introduce a link [error]
| ^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:71:1
|
LL | #[doc = "single line [error]"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the link appears in this line:
single line [error]
^^^^^
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:74:1
|
LL | #[doc = "single line with /"escaping/" [error]"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the link appears in this line:
single line with "escaping" [error]
^^^^^
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:77:1
|
LL | / /// Item docs.
LL | | #[doc="Hello there!"]
LL | | /// [error]
| |___________^
|
= note: the link appears in this line:
[error]
^^^^^
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error1]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:83:11
|
LL | /// docs [error1]
| ^^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[error2]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:85:11
|
LL | /// docs [error2]
| ^^^^^^ cannot be resolved, ignoring
|
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[BarA]` cannot be resolved, ignoring it... warning: `[BarA]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:24:10 --> $DIR/intra-links-warning.rs:24:10
| |
@ -64,37 +134,19 @@ LL | /// bar [BarA] bar
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]` = help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[BarB]` cannot be resolved, ignoring it... warning: `[BarB]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:28:1 --> $DIR/intra-links-warning.rs:30:9
| |
LL | / /** LL | * bar [BarB] bar
LL | | * Foo | ^^^^ cannot be resolved, ignoring
LL | | * bar [BarB] bar
LL | | * baz
LL | | */
| |___^
| |
= note: the link appears in this line:
bar [BarB] bar
^^^^
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]` = help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[BarC]` cannot be resolved, ignoring it... warning: `[BarC]` cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:35:1 --> $DIR/intra-links-warning.rs:37:6
| |
LL | / /** Foo LL | bar [BarC] bar
LL | | | ^^^^ cannot be resolved, ignoring
LL | | bar [BarC] bar
LL | | baz
... |
LL | |
LL | | */
| |__^
| |
= note: the link appears in this line:
bar [BarC] bar
^^^^
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]` = help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
warning: `[BarD]` cannot be resolved, ignoring it... warning: `[BarD]` cannot be resolved, ignoring it...