Skip to content

Point to the rustdoc attribute where intralink resolution failed. #51111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use std::rc::Rc;
use std::cell::RefCell;
use std::sync::Arc;
use std::u32;
use std::ops::Range;

use core::{self, DocContext};
use doctree;
Expand Down Expand Up @@ -954,12 +955,20 @@ fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String
(kind, article, format!("{}@{}", kind, path_str))
}

fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
if attrs.doc_strings.is_empty() {
return DUMMY_SP;
}
let start = attrs.doc_strings[0].span();
let end = attrs.doc_strings.last().unwrap().span();
start.to(end)
}

fn ambiguity_error(cx: &DocContext, attrs: &Attributes,
path_str: &str,
article1: &str, kind1: &str, disambig1: &str,
article2: &str, kind2: &str, disambig2: &str) {
let sp = attrs.doc_strings.first()
.map_or(DUMMY_SP, |a| a.span());
let sp = span_of_attrs(attrs);
cx.sess()
.struct_span_warn(sp,
&format!("`{}` is both {} {} and {} {}",
Expand Down Expand Up @@ -1174,8 +1183,39 @@ enum PathKind {
Type,
}

fn resolution_failure(cx: &DocContext, path_str: &str) {
cx.sess().warn(&format!("[{}] cannot be resolved, ignoring it...", path_str));
fn resolution_failure(
cx: &DocContext,
attrs: &Attributes,
path_str: &str,
dox: &str,
link_range: Option<Range<usize>>,
) {
let sp = span_of_attrs(attrs);
let mut diag = cx.sess()
.struct_span_warn(sp, &format!("[{}] cannot be resolved, ignoring it...", path_str));

if let Some(link_range) = link_range {
// 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 line = dox[last_new_line_offset..].lines().next().unwrap_or("");

// Print the line containing the `link_range` and manually mark it with '^'s
diag.note(&format!(
"the link appears in this line:\n\n{line}\n{indicator: <before$}{indicator:^<found$}",
line=line,
indicator="",
before=link_range.start - last_new_line_offset,
found=link_range.len(),
));
} else {

}

diag.emit();
}

impl Clean<Attributes> for [ast::Attribute] {
Expand All @@ -1184,7 +1224,7 @@ impl Clean<Attributes> for [ast::Attribute] {

if UnstableFeatures::from_environment().is_nightly_build() {
let dox = attrs.collapsed_doc_value().unwrap_or_else(String::new);
for ori_link in markdown_links(&dox) {
for (ori_link, link_range) in markdown_links(&dox) {
// bail early for real links
if ori_link.contains('/') {
continue;
Expand Down Expand Up @@ -1228,7 +1268,7 @@ impl Clean<Attributes> for [ast::Attribute] {
if let Ok(def) = resolve(cx, path_str, true) {
def
} else {
resolution_failure(cx, path_str);
resolution_failure(cx, &attrs, path_str, &dox, link_range);
// this could just be a normal link or a broken link
// we could potentially check if something is
// "intra-doc-link-like" and warn in that case
Expand All @@ -1239,7 +1279,7 @@ impl Clean<Attributes> for [ast::Attribute] {
if let Ok(def) = resolve(cx, path_str, false) {
def
} else {
resolution_failure(cx, path_str);
resolution_failure(cx, &attrs, path_str, &dox, link_range);
// this could just be a normal link
continue;
}
Expand Down Expand Up @@ -1284,7 +1324,7 @@ impl Clean<Attributes> for [ast::Attribute] {
} else if let Ok(value_def) = resolve(cx, path_str, true) {
value_def
} else {
resolution_failure(cx, path_str);
resolution_failure(cx, &attrs, path_str, &dox, link_range);
// this could just be a normal link
continue;
}
Expand All @@ -1293,7 +1333,7 @@ impl Clean<Attributes> for [ast::Attribute] {
if let Some(def) = macro_resolve(cx, path_str) {
(def, None)
} else {
resolution_failure(cx, path_str);
resolution_failure(cx, &attrs, path_str, &dox, link_range);
continue
}
}
Expand Down
25 changes: 22 additions & 3 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::default::Default;
use std::fmt::{self, Write};
use std::borrow::Cow;
use std::ops::Range;
use std::str;
use syntax::feature_gate::UnstableFeatures;
use syntax::codemap::Span;
Expand Down Expand Up @@ -747,7 +749,7 @@ pub fn plain_summary_line(md: &str) -> String {
s
}

pub fn markdown_links(md: &str) -> Vec<String> {
pub fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
if md.is_empty() {
return vec![];
}
Expand All @@ -760,8 +762,22 @@ pub fn markdown_links(md: &str) -> Vec<String> {
let shortcut_links = RefCell::new(vec![]);

{
let locate = |s: &str| unsafe {
let s_start = s.as_ptr();
let s_end = s_start.add(s.len());
let md_start = md.as_ptr();
let md_end = md_start.add(md.len());
if md_start <= s_start && s_end <= md_end {
let start = s_start.offset_from(md_start) as usize;
let end = s_end.offset_from(md_start) as usize;
Some(start..end)
} else {
None
}
};

let push = |_: &str, s: &str| {
shortcut_links.borrow_mut().push(s.to_owned());
shortcut_links.borrow_mut().push((s.to_owned(), locate(s)));
None
};
let p = Parser::new_with_broken_link_callback(md, opts,
Expand All @@ -772,7 +788,10 @@ pub fn markdown_links(md: &str) -> Vec<String> {
for ev in iter {
if let Event::Start(Tag::Link(dest, _)) = ev {
debug!("found link: {}", dest);
links.push(dest.into_owned());
links.push(match dest {
Cow::Borrowed(s) => (s.to_owned(), locate(s)),
Cow::Owned(s) => (s, None),
});
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#![feature(test)]
#![feature(vec_remove_item)]
#![feature(entry_and_modify)]
#![feature(ptr_offset_from)]

#![recursion_limit="256"]

Expand Down
4 changes: 3 additions & 1 deletion src/test/rustdoc-ui/intra-links-warning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

// compile-pass

//! Test with [Foo::baz], [Bar::foo], [Uniooon::X]
//! Test with [Foo::baz], [Bar::foo], ...
//!
//! and [Uniooon::X].

pub struct Foo {
pub bar: usize,
Expand Down
33 changes: 33 additions & 0 deletions src/test/rustdoc-ui/intra-links-warning.stderr
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
warning: [Foo::baz] cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:13:1
|
13 | / //! Test with [Foo::baz], [Bar::foo], ...
14 | | //!
15 | | //! and [Uniooon::X].
| |_____________________^
|
= note: the link appears in this line:

Test with [Foo::baz], [Bar::foo], ...
^^^^^^^^

warning: [Bar::foo] cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:13:1
|
13 | / //! Test with [Foo::baz], [Bar::foo], ...
14 | | //!
15 | | //! and [Uniooon::X].
| |_____________________^
|
= note: the link appears in this line:

Test with [Foo::baz], [Bar::foo], ...
^^^^^^^^

warning: [Uniooon::X] cannot be resolved, ignoring it...
--> $DIR/intra-links-warning.rs:13:1
|
13 | / //! Test with [Foo::baz], [Bar::foo], ...
14 | | //!
15 | | //! and [Uniooon::X].
| |_____________________^
|
= note: the link appears in this line:

and [Uniooon::X].
^^^^^^^^^^