diff --git a/mdbook-spec/src/lib.rs b/mdbook-spec/src/lib.rs index ae614ed69..37f65508b 100644 --- a/mdbook-spec/src/lib.rs +++ b/mdbook-spec/src/lib.rs @@ -10,6 +10,7 @@ use once_cell::sync::Lazy; use regex::{Captures, Regex}; use semver::{Version, VersionReq}; use std::io; +use std::ops::Range; use std::path::PathBuf; mod rules; @@ -205,3 +206,17 @@ impl Preprocessor for Spec { Ok(book) } } + +fn line_from_range<'a>(contents: &'a str, range: &Range) -> &'a str { + assert!(range.start < contents.len()); + + let mut start_index = 0; + for line in contents.lines() { + let end_index = start_index + line.len(); + if range.start >= start_index && range.start <= end_index { + return line; + } + start_index = end_index + 1; + } + panic!("did not find line {range:?} in contents"); +} diff --git a/mdbook-spec/src/std_links.rs b/mdbook-spec/src/std_links.rs index e5b9448d1..fc3d8f93e 100644 --- a/mdbook-spec/src/std_links.rs +++ b/mdbook-spec/src/std_links.rs @@ -84,7 +84,7 @@ pub fn std_links(book: &mut Book) { } let key = ch.source_path.as_ref().unwrap(); // Create a list of replacements to make in the raw markdown to point to the new url. - let replacements = compute_replacements(&ch.content, &chapter_links[key], &ch_urls[key]); + let replacements = compute_replacements(&ch, &chapter_links[key], &ch_urls[key]); let mut new_contents = ch.content.clone(); for (md_link, url, range) in replacements { @@ -120,6 +120,9 @@ struct Link<'a> { dest_url: CowStr<'a>, /// The span in the original markdown where the link is located. /// + /// Note that this is the post-processed markdown (such as having rules + /// expanded), not the markdown on the disk. + /// /// Note that during translation, all links will be converted to inline /// links. That means that for reference-style links, the link reference /// definition will end up being ignored in the final markdown. For @@ -288,7 +291,7 @@ fn relative_url(url: &str, chapter: &Chapter) -> String { /// - `url` is the URL to the standard library. /// - `range` is the range in the original markdown to replace with the new link. fn compute_replacements<'a>( - contents: &'a str, + chapter: &'a Chapter, links: &[Link<'_>], urls: &[&'a str], ) -> Vec<(&'a str, &'a str, Range)> { @@ -296,11 +299,19 @@ fn compute_replacements<'a>( for (url, link) in urls.iter().zip(links) { let Some(cap) = ANCHOR_URL.captures(url) else { - eprintln!("error: could not find anchor in:\n{url}\nlink={link:#?}"); + let line = super::line_from_range(&chapter.content, &link.range); + eprintln!( + "error: broken markdown link found in {}\n\ + Line is: {line}\n\ + Link to `{}` could not be resolved by rustdoc to a known URL (result was `{}`).\n", + chapter.source_path.as_ref().unwrap().display(), + link.dest_url, + url + ); process::exit(1); }; let url = cap.get(1).unwrap().as_str(); - let md_link = &contents[link.range.clone()]; + let md_link = &chapter.content[link.range.clone()]; let range = link.range.clone(); let add_link = |re: &Regex| {