Skip to content

rustdoc: add doc_link_canonical feature #143158

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {

gate_doc!(
"experimental" {
html_link_canonical => doc_link_canonical
cfg => doc_cfg
cfg_hide => doc_cfg_hide
masked => doc_masked
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ declare_features! (
(unstable, doc_cfg, "1.21.0", Some(43781)),
/// Allows `#[doc(cfg_hide(...))]`.
(unstable, doc_cfg_hide, "1.57.0", Some(43781)),
/// Allows `#![doc(html_link_canonical]`
(unstable, doc_link_canonical, "1.88.0", Some(143139)),
/// Allows `#[doc(masked)]`.
(unstable, doc_masked, "1.21.0", Some(44027)),
/// Allows `dyn* Trait` objects.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {

Some(
sym::html_favicon_url
| sym::html_link_canonical
| sym::html_logo_url
| sym::html_playground_url
| sym::issue_tracker_base_url
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ symbols! {
doc_cfg,
doc_cfg_hide,
doc_keyword,
doc_link_canonical,
doc_masked,
doc_notable_trait,
doc_primitive,
Expand Down Expand Up @@ -1134,6 +1135,7 @@ symbols! {
homogeneous_aggregate,
host,
html_favicon_url,
html_link_canonical,
html_logo_url,
html_no_source,
html_playground_url,
Expand Down
5 changes: 4 additions & 1 deletion src/librustdoc/html/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ pub(crate) struct Layout {
pub(crate) css_file_extension: Option<PathBuf>,
/// If true, then scrape-examples.js will be included in the output HTML file
pub(crate) scrape_examples_extension: bool,
/// if present, insert a rel="canonical" link with this prefix.
pub(crate) link_canonical: Option<String>,
}

pub(crate) struct Page<'a> {
/// url relative to documentation bundle root.
pub(crate) relative_url: Option<String>,
pub(crate) title: &'a str,
pub(crate) css_class: &'a str,
pub(crate) root_path: &'a str,
Expand All @@ -47,7 +51,6 @@ struct PageLayout<'a> {
static_root_path: String,
page: &'a Page<'a>,
layout: &'a Layout,

files: &'static StaticFiles,

themes: Vec<String>,
Expand Down
41 changes: 35 additions & 6 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ pub(crate) struct Context<'tcx> {
/// The current destination folder of where HTML artifacts should be placed.
/// This changes as the context descends into the module hierarchy.
pub(crate) dst: PathBuf,
/// Initial length of `dst`.
///
/// Used to split `dst` into (doc bundle path, relative path)
dst_prefix_doc_bundle: usize,
/// Tracks section IDs for `Deref` targets so they match in both the main
/// body and the sidebar.
pub(super) deref_id_map: RefCell<DefIdMap<String>>,
Expand Down Expand Up @@ -180,13 +184,24 @@ impl<'tcx> Context<'tcx> {
self.id_map.borrow_mut().derive(id)
}

pub(crate) fn dst_relative_to_doc_bundle_root(&self) -> &str {
str::from_utf8(&self.dst.as_os_str().as_encoded_bytes()[self.dst_prefix_doc_bundle..])
.expect("non-utf8 in name generated by rustdoc")
.trim_start_matches('/')
}

/// String representation of how to get back to the root path of the 'doc/'
/// folder in terms of a relative URL.
pub(super) fn root_path(&self) -> String {
"../".repeat(self.current.len())
}

fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String {
fn render_item(
&mut self,
it: &clean::Item,
is_module: bool,
relative_url: Option<String>,
) -> String {
let mut render_redirect_pages = self.info.render_redirect_pages;
// If the item is stripped but inlined, links won't point to the item so no need to generate
// a file for it.
Expand Down Expand Up @@ -238,6 +253,7 @@ impl<'tcx> Context<'tcx> {
if !render_redirect_pages {
let content = print_item(self, it);
let page = layout::Page {
relative_url,
css_class: tyname_s,
root_path: &self.root_path(),
static_root_path: self.shared.static_root_path.as_deref(),
Expand Down Expand Up @@ -511,6 +527,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
krate_version: krate_version.to_string(),
css_file_extension: extension_css,
scrape_examples_extension: !call_locations.is_empty(),
link_canonical: None,
};
let mut issue_tracker_base_url = None;
let mut include_sources = !html_no_source;
Expand All @@ -537,6 +554,14 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
(Some(sym::html_no_source), None) if attr.is_word() => {
include_sources = false;
}
(Some(sym::html_link_canonical), Some(s)) => {
let mut s = s.to_string();
// ensure trailing slash
if !s.ends_with('/') {
s.push('/');
}
layout.link_canonical = Some(s);
}
_ => {}
}
}
Expand Down Expand Up @@ -579,6 +604,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {

let mut cx = Context {
current: Vec::new(),
dst_prefix_doc_bundle: dst.as_os_str().len(),
dst,
id_map: RefCell::new(id_map),
deref_id_map: Default::default(),
Expand Down Expand Up @@ -626,6 +652,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
css_class: "mod sys",
root_path: "../",
static_root_path: shared.static_root_path.as_deref(),
relative_url: None,
description: "List of all items in this crate",
resource_suffix: &shared.resource_suffix,
rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo),
Expand Down Expand Up @@ -787,7 +814,8 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
info!("Recursing into {}", self.dst.display());

if !item.is_stripped() {
let buf = self.render_item(item, true);
let rel_path = format!("{}/index.html", self.dst_relative_to_doc_bundle_root());
let buf = self.render_item(item, true, Some(rel_path));
// buf will be empty if the module is stripped and there is no redirect for it
if !buf.is_empty() {
self.shared.ensure_dir(&self.dst)?;
Expand Down Expand Up @@ -842,12 +870,13 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
self.info.render_redirect_pages = item.is_stripped();
}

let buf = self.render_item(&item, false);
let name = item.name.as_ref().unwrap();
let item_type = item.type_();
let file_name = print_item_path(item_type, name.as_str()).to_string();
let rel_path = format!("{}/{file_name}", self.dst_relative_to_doc_bundle_root());
let buf = self.render_item(&item, false, Some(rel_path));
// buf will be empty if the item is stripped and there is no redirect for it
if !buf.is_empty() {
let name = item.name.as_ref().unwrap();
let item_type = item.type_();
let file_name = print_item_path(item_type, name.as_str()).to_string();
self.shared.ensure_dir(&self.dst)?;
let joint_dst = self.dst.join(&file_name);
self.shared.fs.write(joint_dst, buf)?;
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ impl CratesIndexPart {
title: "Index of crates",
css_class: "mod sys",
root_path: "./",
relative_url: None,
static_root_path: cx.shared.static_root_path.as_deref(),
description: "List of crates",
resource_suffix: &cx.shared.resource_suffix,
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/html/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ impl SourceCollector<'_, '_> {
title: &title,
css_class: "src",
root_path: &root_path,
relative_url: None,
static_root_path: shared.static_root_path.as_deref(),
description: &desc,
resource_suffix: &shared.resource_suffix,
Expand Down
3 changes: 3 additions & 0 deletions src/librustdoc/html/templates/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
<link rel="icon" type="image/svg+xml" {#+ #}
href="{{static_root_path|safe}}{{files.rust_favicon_svg}}">
{% endif %}
{% if layout.link_canonical.is_some() && page.relative_url.is_some() %}
<link rel="canonical" href="{{layout.link_canonical.as_ref().unwrap()|safe}}{{page.relative_url.as_ref().unwrap()|safe}}">
{% endif %}
{{ layout.external_html.in_header|safe }}
</head> {# #}
<body class="rustdoc {{+page.css_class}}"> {# #}
Expand Down
10 changes: 10 additions & 0 deletions tests/rustdoc/link-canonical.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![crate_name = "foo"]
#![feature(doc_link_canonical)]
#![doc(html_link_canonical = "https://foo.example/")]

//@ has 'foo/index.html'
//@ has - '//head/link[@rel="canonical"][@href="https://foo.example/foo/index.html"]' ''

//@ has 'foo/struct.FooBaz.html'
//@ has - '//head/link[@rel="canonical"][@href="https://foo.example/foo/struct.FooBaz.html"]' ''
pub struct FooBaz;
4 changes: 4 additions & 0 deletions tests/ui/feature-gates/feature-gate-doc-link-canonical.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#![doc(html_link_canonical = "http://example.com/")]
//~^ ERROR `#[doc(html_link_canonical)]` is experimental

fn main() {}
13 changes: 13 additions & 0 deletions tests/ui/feature-gates/feature-gate-doc-link-canonical.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error[E0658]: `#[doc(html_link_canonical)]` is experimental
--> $DIR/feature-gate-doc-link-canonical.rs:1:1
|
LL | #![doc(html_link_canonical = "http://example.com/")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #143139 <https://github.com/rust-lang/rust/issues/143139> for more information
= help: add `#![feature(doc_link_canonical)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0658`.
Loading