Fix relative path handling for --extern-html-root-url

This commit is contained in:
arferreira
2026-02-23 08:09:08 -05:00
parent d8b2222b11
commit b9614b2dc2
5 changed files with 60 additions and 26 deletions
+9 -2
View File
@@ -203,7 +203,14 @@ fn to_remote(url: impl ToString) -> ExternalLocation {
if !url.ends_with('/') {
url.push('/');
}
Remote(url)
let is_absolute = url.starts_with('/')
|| url.split_once("://").is_some_and(|(scheme, _)| {
scheme.bytes().next().is_some_and(|b| b.is_ascii_alphabetic())
&& scheme
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'+' | b'-' | b'.'))
});
Remote { url, is_absolute }
}
// See if there's documentation generated into the local directory
@@ -316,7 +323,7 @@ fn as_primitive(def_id: DefId, tcx: TyCtxt<'_>) -> Option<(DefId, PrimitiveType)
#[derive(Debug)]
pub(crate) enum ExternalLocation {
/// Remote URL root of the external crate
Remote(String),
Remote { url: String, is_absolute: bool },
/// This external crate can be found in the local doc/ folder
Local,
/// The external crate could not be found.
+31 -21
View File
@@ -402,9 +402,10 @@ fn generate_macro_def_id_path(
}
let url = match cache.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
// `ExternalLocation::Remote` always end with a `/`.
format!("{s}{path}", path = fmt::from_fn(|f| path.iter().joined("/", f)))
ExternalLocation::Remote { ref url, is_absolute } => {
let mut prefix = remote_url_prefix(url, is_absolute, cx.current.len());
prefix.extend(path.iter().copied());
prefix.finish()
}
ExternalLocation::Local => {
// `root_path` always end with a `/`.
@@ -458,10 +459,10 @@ fn generate_item_def_id_path(
let shortty = ItemType::from_def_id(def_id, tcx);
let module_fqp = to_module_fqp(shortty, &fqp);
let mut is_remote = false;
let mut is_absolute = false;
let url_parts = url_parts(cx.cache(), def_id, module_fqp, &cx.current, &mut is_remote)?;
let mut url_parts = make_href(root_path, shortty, url_parts, &fqp, is_remote);
let url_parts = url_parts(cx.cache(), def_id, module_fqp, &cx.current, &mut is_absolute)?;
let mut url_parts = make_href(root_path, shortty, url_parts, &fqp, is_absolute);
if def_id != original_def_id {
let kind = ItemType::from_def_id(original_def_id, tcx);
url_parts = format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id))
@@ -493,18 +494,29 @@ fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
}
fn remote_url_prefix(url: &str, is_absolute: bool, depth: usize) -> UrlPartsBuilder {
let url = url.trim_end_matches('/');
if is_absolute {
UrlPartsBuilder::singleton(url)
} else {
let extra = depth.saturating_sub(1);
let mut b: UrlPartsBuilder = iter::repeat_n("..", extra).collect();
b.push(url);
b
}
}
fn url_parts(
cache: &Cache,
def_id: DefId,
module_fqp: &[Symbol],
relative_to: &[Symbol],
is_remote: &mut bool,
is_absolute: &mut bool,
) -> Result<UrlPartsBuilder, HrefError> {
match cache.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
*is_remote = true;
let s = s.trim_end_matches('/');
let mut builder = UrlPartsBuilder::singleton(s);
ExternalLocation::Remote { ref url, is_absolute: abs } => {
*is_absolute = abs;
let mut builder = remote_url_prefix(url, abs, relative_to.len());
builder.extend(module_fqp.iter().copied());
Ok(builder)
}
@@ -518,9 +530,9 @@ fn make_href(
shortty: ItemType,
mut url_parts: UrlPartsBuilder,
fqp: &[Symbol],
is_remote: bool,
is_absolute: bool,
) -> String {
if !is_remote && let Some(root_path) = root_path {
if !is_absolute && let Some(root_path) = root_path {
let root = root_path.trim_end_matches('/');
url_parts.push_front(root);
}
@@ -583,7 +595,7 @@ pub(crate) fn href_with_root_path(
}
}
let mut is_remote = false;
let mut is_absolute = false;
let (fqp, shortty, url_parts) = match cache.paths.get(&did) {
Some(&(ref fqp, shortty)) => (fqp, shortty, {
let module_fqp = to_module_fqp(shortty, fqp.as_slice());
@@ -597,7 +609,7 @@ pub(crate) fn href_with_root_path(
let def_id_to_get = if root_path.is_some() { original_did } else { did };
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
let module_fqp = to_module_fqp(shortty, fqp);
(fqp, shortty, url_parts(cache, did, module_fqp, relative_to, &mut is_remote)?)
(fqp, shortty, url_parts(cache, did, module_fqp, relative_to, &mut is_absolute)?)
} else if matches!(def_kind, DefKind::Macro(_)) {
return generate_macro_def_id_path(did, cx, root_path);
} else if did.is_local() {
@@ -608,7 +620,7 @@ pub(crate) fn href_with_root_path(
}
};
Ok(HrefInfo {
url: make_href(root_path, shortty, url_parts, fqp, is_remote),
url: make_href(root_path, shortty, url_parts, fqp, is_absolute),
kind: shortty,
rust_path: fqp.clone(),
})
@@ -762,12 +774,10 @@ fn primitive_link_fragment(
}
Some(&def_id) => {
let loc = match m.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
ExternalLocation::Remote { ref url, is_absolute } => {
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
let builder: UrlPartsBuilder =
[s.as_str().trim_end_matches('/'), cname_sym.as_str()]
.into_iter()
.collect();
let mut builder = remote_url_prefix(url, is_absolute, cx.current.len());
builder.push(cname_sym.as_str());
Some(builder)
}
ExternalLocation::Local => {
+3 -2
View File
@@ -386,8 +386,9 @@ pub(crate) fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Opti
let e = ExternalCrate { crate_num: cnum };
(e.name(self.tcx()), e.src_root(self.tcx()))
}
ExternalLocation::Remote(ref s) => {
root = s.to_string();
ExternalLocation::Remote { ref url, .. } => {
// FIXME: relative extern URLs are not depth-adjusted for source pages
root = url.to_string();
let e = ExternalCrate { crate_num: cnum };
(e.name(self.tcx()), e.src_root(self.tcx()))
}
+2 -1
View File
@@ -280,7 +280,8 @@ fn after_krate(self) -> Result<(), Error> {
types::ExternalCrate {
name: e.name(self.tcx).to_string(),
html_root_url: match external_location {
ExternalLocation::Remote(s) => Some(s.clone()),
// FIXME: relative extern URLs are not resolved here
ExternalLocation::Remote { url, .. } => Some(url.clone()),
_ => None,
},
path: self
@@ -0,0 +1,15 @@
//@ compile-flags:-Z unstable-options --extern-html-root-url core=../ --extern-html-root-takes-precedence
// At depth 1 (top-level), the href should be ../core/...
//@ has extern_html_root_url_relative/index.html
//@ has - '//a/@href' '../core/iter/index.html'
#[doc(no_inline)]
pub use std::iter;
// At depth 2 (inside a module), the href should be ../../core/...
pub mod nested {
//@ has extern_html_root_url_relative/nested/index.html
//@ has - '//a/@href' '../../core/iter/index.html'
#[doc(no_inline)]
pub use std::iter;
}