Skip to content

Commit dffcb0e

Browse files
committed
Provide a better error message for broken links in mdbook-spec
1 parent b06e1f3 commit dffcb0e

File tree

2 files changed

+30
-4
lines changed

2 files changed

+30
-4
lines changed

mdbook-spec/src/lib.rs

+15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use once_cell::sync::Lazy;
1010
use regex::{Captures, Regex};
1111
use semver::{Version, VersionReq};
1212
use std::io;
13+
use std::ops::Range;
1314
use std::path::PathBuf;
1415

1516
mod rules;
@@ -205,3 +206,17 @@ impl Preprocessor for Spec {
205206
Ok(book)
206207
}
207208
}
209+
210+
fn line_from_range<'a>(contents: &'a str, range: &Range<usize>) -> &'a str {
211+
assert!(range.start < contents.len());
212+
213+
let mut start_index = 0;
214+
for line in contents.lines() {
215+
let end_index = start_index + line.len();
216+
if range.start >= start_index && range.start <= end_index {
217+
return line;
218+
}
219+
start_index = end_index + 1;
220+
}
221+
panic!("did not find line {range:?} in contents");
222+
}

mdbook-spec/src/std_links.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub fn std_links(book: &mut Book) {
8484
}
8585
let key = ch.source_path.as_ref().unwrap();
8686
// Create a list of replacements to make in the raw markdown to point to the new url.
87-
let replacements = compute_replacements(&ch.content, &chapter_links[key], &ch_urls[key]);
87+
let replacements = compute_replacements(&ch, &chapter_links[key], &ch_urls[key]);
8888

8989
let mut new_contents = ch.content.clone();
9090
for (md_link, url, range) in replacements {
@@ -120,6 +120,9 @@ struct Link<'a> {
120120
dest_url: CowStr<'a>,
121121
/// The span in the original markdown where the link is located.
122122
///
123+
/// Note that this is the post-processed markdown (such as having rules
124+
/// expanded), not the markdown on the disk.
125+
///
123126
/// Note that during translation, all links will be converted to inline
124127
/// links. That means that for reference-style links, the link reference
125128
/// definition will end up being ignored in the final markdown. For
@@ -288,19 +291,27 @@ fn relative_url(url: &str, chapter: &Chapter) -> String {
288291
/// - `url` is the URL to the standard library.
289292
/// - `range` is the range in the original markdown to replace with the new link.
290293
fn compute_replacements<'a>(
291-
contents: &'a str,
294+
chapter: &'a Chapter,
292295
links: &[Link<'_>],
293296
urls: &[&'a str],
294297
) -> Vec<(&'a str, &'a str, Range<usize>)> {
295298
let mut replacements = Vec::new();
296299

297300
for (url, link) in urls.iter().zip(links) {
298301
let Some(cap) = ANCHOR_URL.captures(url) else {
299-
eprintln!("error: could not find anchor in:\n{url}\nlink={link:#?}");
302+
let line = super::line_from_range(&chapter.content, &link.range);
303+
eprintln!(
304+
"error: broken markdown link found in {}\n\
305+
Line is: {line}\n\
306+
Link to `{}` could not be resolved by rustdoc to a known URL (result was `{}`).\n",
307+
chapter.source_path.as_ref().unwrap().display(),
308+
link.dest_url,
309+
url
310+
);
300311
process::exit(1);
301312
};
302313
let url = cap.get(1).unwrap().as_str();
303-
let md_link = &contents[link.range.clone()];
314+
let md_link = &chapter.content[link.range.clone()];
304315

305316
let range = link.range.clone();
306317
let add_link = |re: &Regex| {

0 commit comments

Comments
 (0)