Skip to content

Commit ea5843c

Browse files
committed
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.
1 parent 9e8a982 commit ea5843c

File tree

4 files changed

+140
-50
lines changed

4 files changed

+140
-50
lines changed

src/librustdoc/clean/mod.rs

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

src/librustdoc/passes/collect_intra_doc_links.rs

+57-24
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,19 @@ fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
497497
start.to(end)
498498
}
499499

500+
/// Reports a resolution failure diagnostic.
501+
///
502+
/// Ideally we can report the diagnostic with the actual span in the source where the link failure
503+
/// occurred. However, there's a mismatch between the span in the source code and the span in the
504+
/// markdown, so we have to do a bit of work to figure out the correspondence.
505+
///
506+
/// It's not too hard to find the span for sugared doc comments (`///` and `/**`), because the
507+
/// source will match the markdown exactly, excluding the comment markers. However, it's much more
508+
/// difficult to calculate the spans for unsugared docs, because we have to deal with escaping and
509+
/// other source features. So, we attempt to find the exact source span of the resolution failure
510+
/// in sugared docs, but use the span of the documentation attributes themselves for unsugared
511+
/// docs. Because this span might be overly large, we display the markdown line containing the
512+
/// failure as a note.
500513
fn resolution_failure(
501514
cx: &DocContext,
502515
attrs: &Attributes,
@@ -507,34 +520,50 @@ fn resolution_failure(
507520
let sp = span_of_attrs(attrs);
508521
let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str);
509522

510-
let code_dox = sp.to_src(cx);
511-
512-
let doc_comment_padding = 3;
513523
let mut diag = if let Some(link_range) = link_range {
514-
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
515-
// ^ ~~~~~~
516-
// | link_range
517-
// last_new_line_offset
518-
519524
let mut diag;
520-
if dox.lines().count() == code_dox.lines().count() {
521-
let line_offset = dox[..link_range.start].lines().count();
522-
// The span starts in the `///`, so we don't have to account for the leading whitespace
523-
let code_dox_len = if line_offset <= 1 {
524-
doc_comment_padding
525-
} else {
526-
// The first `///`
527-
doc_comment_padding +
528-
// Each subsequent leading whitespace and `///`
529-
code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| {
530-
sum + doc_comment_padding + line.len() - line.trim_start().len()
531-
})
532-
};
533525

534-
// Extract the specific span
526+
if attrs.doc_strings.iter().all(|frag| match frag {
527+
DocFragment::SugaredDoc(..) => true,
528+
_ => false,
529+
}) {
530+
let source_dox = sp.to_src(cx);
531+
let mut source_lines = source_dox.lines().peekable();
532+
let mut md_lines = dox.lines().peekable();
533+
534+
// The number of bytes from the start of the source span to the resolution failure that
535+
// are *not* part of the markdown, like comment markers.
536+
let mut source_offset = 0;
537+
538+
// Eat any source lines before the markdown starts (e.g., `/**` on its own line).
539+
while let Some(source_line) = source_lines.peek() {
540+
if source_line.contains(md_lines.peek().unwrap()) {
541+
break;
542+
}
543+
544+
// Include the newline.
545+
source_offset += source_line.len() + 1;
546+
source_lines.next().unwrap();
547+
}
548+
549+
// The number of lines up to and including the resolution failure.
550+
let num_lines = dox[..link_range.start].lines().count();
551+
552+
// Consume inner comment markers (e.g., `///` or ` *`).
553+
for (source_line, md_line) in source_lines.zip(md_lines).take(num_lines) {
554+
source_offset += if md_line.is_empty() {
555+
// If there is no markdown on this line, then the whole line is a comment
556+
// marker. We don't have to count the newline here because it's in the markdown
557+
// too.
558+
source_line.len()
559+
} else {
560+
source_line.find(md_line).unwrap()
561+
};
562+
}
563+
535564
let sp = sp.from_inner_byte_pos(
536-
link_range.start + code_dox_len,
537-
link_range.end + code_dox_len,
565+
link_range.start + source_offset,
566+
link_range.end + source_offset,
538567
);
539568

540569
diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
@@ -548,6 +577,10 @@ fn resolution_failure(
548577
sp,
549578
&msg);
550579

580+
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
581+
// ^ ~~~~
582+
// | link_range
583+
// last_new_line_offset
551584
let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
552585
let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
553586

src/test/rustdoc-ui/intra-links-warning.rs

+23
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,26 @@ macro_rules! f {
5555
}
5656
}
5757
f!("Foo\nbar [BarF] bar\nbaz");
58+
59+
/** # for example,
60+
*
61+
* time to introduce a link [error]*/
62+
pub struct A;
63+
64+
/**
65+
* # for example,
66+
*
67+
* time to introduce a link [error]
68+
*/
69+
pub struct B;
70+
71+
#[doc = "single line [error]"]
72+
pub struct C;
73+
74+
#[doc = "single line with \"escaping\" [error]"]
75+
pub struct D;
76+
77+
/// Item docs.
78+
#[doc="Hello there!"]
79+
/// [error]
80+
pub struct E;

src/test/rustdoc-ui/intra-links-warning.stderr

+60-24
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,60 @@ LL | /// [Qux:Y]
5555
|
5656
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
5757

58+
warning: `[error]` cannot be resolved, ignoring it...
59+
--> $DIR/intra-links-warning.rs:61:30
60+
|
61+
LL | * time to introduce a link [error]*/
62+
| ^^^^^ cannot be resolved, ignoring
63+
|
64+
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
65+
66+
warning: `[error]` cannot be resolved, ignoring it...
67+
--> $DIR/intra-links-warning.rs:67:30
68+
|
69+
LL | * time to introduce a link [error]
70+
| ^^^^^ cannot be resolved, ignoring
71+
|
72+
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
73+
74+
warning: `[error]` cannot be resolved, ignoring it...
75+
--> $DIR/intra-links-warning.rs:71:1
76+
|
77+
LL | #[doc = "single line [error]"]
78+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
79+
|
80+
= note: the link appears in this line:
81+
82+
single line [error]
83+
^^^^^
84+
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
85+
86+
warning: `[error]` cannot be resolved, ignoring it...
87+
--> $DIR/intra-links-warning.rs:74:1
88+
|
89+
LL | #[doc = "single line with /"escaping/" [error]"]
90+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91+
|
92+
= note: the link appears in this line:
93+
94+
single line with "escaping" [error]
95+
^^^^^
96+
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
97+
98+
warning: `[error]` cannot be resolved, ignoring it...
99+
--> $DIR/intra-links-warning.rs:77:1
100+
|
101+
LL | / /// Item docs.
102+
LL | | #[doc="Hello there!"]
103+
LL | | /// [error]
104+
| |___________^
105+
|
106+
= note: the link appears in this line:
107+
108+
[error]
109+
^^^^^
110+
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
111+
58112
warning: `[BarA]` cannot be resolved, ignoring it...
59113
--> $DIR/intra-links-warning.rs:24:10
60114
|
@@ -64,37 +118,19 @@ LL | /// bar [BarA] bar
64118
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
65119

66120
warning: `[BarB]` cannot be resolved, ignoring it...
67-
--> $DIR/intra-links-warning.rs:28:1
121+
--> $DIR/intra-links-warning.rs:30:9
68122
|
69-
LL | / /**
70-
LL | | * Foo
71-
LL | | * bar [BarB] bar
72-
LL | | * baz
73-
LL | | */
74-
| |___^
123+
LL | * bar [BarB] bar
124+
| ^^^^ cannot be resolved, ignoring
75125
|
76-
= note: the link appears in this line:
77-
78-
bar [BarB] bar
79-
^^^^
80126
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
81127

82128
warning: `[BarC]` cannot be resolved, ignoring it...
83-
--> $DIR/intra-links-warning.rs:35:1
129+
--> $DIR/intra-links-warning.rs:37:6
84130
|
85-
LL | / /** Foo
86-
LL | |
87-
LL | | bar [BarC] bar
88-
LL | | baz
89-
... |
90-
LL | |
91-
LL | | */
92-
| |__^
131+
LL | bar [BarC] bar
132+
| ^^^^ cannot be resolved, ignoring
93133
|
94-
= note: the link appears in this line:
95-
96-
bar [BarC] bar
97-
^^^^
98134
= help: to escape `[` and `]` characters, just add '/' before them like `/[` or `/]`
99135

100136
warning: `[BarD]` cannot be resolved, ignoring it...

0 commit comments

Comments
 (0)