diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index df9e8631bbdd6..ff441e235b276 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -91,6 +91,8 @@ pub(crate) struct Link<'a> { href: Cow<'a, str>, /// Nested list of links (used only in top-toc) children: Vec>, + /// Whether this link represents a deprecated item + deprecated: bool, } impl Ord for Link<'_> { @@ -115,7 +117,7 @@ impl PartialOrd for Link<'_> { impl<'a> Link<'a> { pub fn new(href: impl Into>, name: impl Into>) -> Self { - Self { href: href.into(), name: name.into(), children: vec![], name_html: None } + Self { href: href.into(), name: name.into(), children: vec![], name_html: None, deprecated: false } } pub fn empty() -> Link<'static> { Link::new("", "") @@ -153,7 +155,7 @@ pub(super) fn print_sidebar( clean::EnumItem(ref e) => sidebar_enum(cx, it, e, &mut blocks, &deref_id_map), clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t, &mut blocks, &deref_id_map), clean::ModuleItem(ref m) => { - blocks.push(sidebar_module(&m.items, &mut ids, ModuleLike::from(it))) + blocks.extend(sidebar_module(cx, &m.items, &mut ids, ModuleLike::from(it))); } clean::ForeignTypeItem => sidebar_foreign_type(cx, it, &mut blocks, &deref_id_map), _ => {} @@ -243,20 +245,24 @@ fn docblock_toc<'a>( .children .entries .into_iter() - .map(|entry| Link { - name_html: if entry.html == entry.name { - None - } else { - Some(entry.html.into()) - }, - name: entry.name.into(), - href: entry.id.into(), - // Only a single level of nesting is shown here. - // Going the full six could break the layout, - // so we have to cut it off somewhere. - children: vec![], + .map(|entry| { + Link { + name_html: if entry.html == entry.name { + None + } else { + Some(entry.html.into()) + }, + name: entry.name.into(), + href: entry.id.into(), + // Only a single level of nesting is shown here. + // Going the full six could break the layout, + // so we have to cut it off somewhere. + children: vec![], + deprecated: false, + } }) .collect(), + deprecated: false, } }) .collect(); @@ -439,8 +445,8 @@ fn sidebar_assoc_items<'a>( { let used_links_bor = &mut used_links; for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) { - assoc_consts.extend(get_associated_constants(impl_, used_links_bor)); - assoc_types.extend(get_associated_types(impl_, used_links_bor)); + assoc_consts.extend(get_associated_constants(impl_, used_links_bor, cx.tcx())); + assoc_types.extend(get_associated_types(impl_, used_links_bor, cx.tcx())); methods.extend(get_methods(impl_, false, used_links_bor, false, cx.tcx())); } // We want links' order to be reproducible so we don't use unstable sort. @@ -453,14 +459,36 @@ fn sidebar_assoc_items<'a>( LinkBlock::new( Link::new("implementations", "Associated Constants"), "associatedconstant", - assoc_consts, + assoc_consts + .into_iter() + .map(|l| { + // Inject non-visual marker for deprecated filtering in sidebar entries + // by appending a hidden span to the displayed name. + // Note: we rely on `Link::new` to accept enriched HTML for the name. + Link::new(l.href.clone(), format!("{}", l.name)) + }) + .collect(), ), LinkBlock::new( Link::new("implementations", "Associated Types"), "associatedtype", - assoc_types, + assoc_types + .into_iter() + .map(|l| { + Link::new(l.href.clone(), format!("{}", l.name)) + }) + .collect(), + ), + LinkBlock::new( + Link::new("implementations", "Methods"), + "method", + methods + .into_iter() + .map(|l| { + Link::new(l.href.clone(), format!("{}", l.name)) + }) + .collect(), ), - LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), ]; if v.iter().any(|i| i.inner_impl().trait_.is_some()) { @@ -633,10 +661,12 @@ pub(crate) fn sidebar_module_like( } fn sidebar_module( + cx: &Context<'_>, items: &[clean::Item], ids: &mut IdMap, module_like: ModuleLike, -) -> LinkBlock<'static> { +) -> Vec> { + // Header block linking to the module/crate items section let item_sections_in_use: FxHashSet<_> = items .iter() .filter(|it| { @@ -657,7 +687,122 @@ fn sidebar_module( .map(|it| item_ty_to_section(it.type_())) .collect(); - sidebar_module_like(item_sections_in_use, ids, module_like) + let mut blocks = vec![sidebar_module_like(item_sections_in_use, ids, module_like)]; + + // Collect per-item lists for module-scope entries + let mut structs: Vec> = Vec::new(); + let mut functions: Vec> = Vec::new(); + let mut enums: Vec> = Vec::new(); + let mut type_aliases: Vec> = Vec::new(); + let mut constants: Vec> = Vec::new(); + let mut modules: Vec> = Vec::new(); + + for it in items.iter() { + if it.is_stripped() { + continue; + } + let Some(name) = it.name.as_ref() else { continue }; + let name_str = name.as_str(); + match it.kind { + clean::StructItem(..) => { + let mut l = Link::new(format!("struct.{name_str}"), name_str.to_string()); + if it.deprecation(cx.tcx()).is_some_and(|d| d.is_in_effect()) { + l.deprecated = true; + l.name_html = Some(format!( + "{} Deprecated", + name_str + ).into()); + } + structs.push(l); + } + clean::FunctionItem(..) => { + let mut l = Link::new(format!("fn.{name_str}"), name_str.to_string()); + if it.deprecation(cx.tcx()).is_some_and(|d| d.is_in_effect()) { + l.deprecated = true; + l.name_html = Some(format!( + "{} Deprecated", + name_str + ).into()); + } + functions.push(l); + } + clean::EnumItem(..) => { + let mut l = Link::new(format!("enum.{name_str}"), name_str.to_string()); + if it.deprecation(cx.tcx()).is_some_and(|d| d.is_in_effect()) { + l.deprecated = true; + l.name_html = Some(format!( + "{} Deprecated", + name_str + ).into()); + } + enums.push(l); + } + clean::TypeAliasItem(..) => { + let mut l = Link::new(format!("type.{name_str}"), name_str.to_string()); + if it.deprecation(cx.tcx()).is_some_and(|d| d.is_in_effect()) { + l.deprecated = true; + l.name_html = Some(format!( + "{} Deprecated", + name_str + ).into()); + } + type_aliases.push(l); + } + clean::ConstantItem(..) => { + let mut l = Link::new(format!("constant.{name_str}"), name_str.to_string()); + if it.deprecation(cx.tcx()).is_some_and(|d| d.is_in_effect()) { + l.deprecated = true; + l.name_html = Some(format!( + "{} Deprecated", + name_str + ).into()); + } + constants.push(l); + } + clean::ModuleItem(..) => { + let mut l = Link::new(format!("mod.{name_str}"), name_str.to_string()); + if it.deprecation(cx.tcx()).is_some_and(|d| d.is_in_effect()) { + l.deprecated = true; + l.name_html = Some(format!( + "{} Deprecated", + name_str + ).into()); + } + modules.push(l); + } + _ => {} + } + } + + // Sort lists + structs.sort(); + functions.sort(); + enums.sort(); + type_aliases.sort(); + constants.sort(); + modules.sort(); + + // Append blocks for each non-empty category with specific block classes + if !modules.is_empty() { + blocks.push(LinkBlock::new(Link::new("modules", "Modules"), "mod", modules)); + } + if !structs.is_empty() { + blocks.push(LinkBlock::new(Link::new("structs", "Structs"), "struct", structs)); + } + if !enums.is_empty() { + blocks.push(LinkBlock::new(Link::new("enums", "Enums"), "enum", enums)); + } + if !type_aliases.is_empty() { + blocks.push(LinkBlock::new(Link::new("types", "Type Aliases"), "type", type_aliases)); + } + if !constants.is_empty() { + blocks.push(LinkBlock::new(Link::new("constants", "Constants"), "constant", constants)); + } + if !functions.is_empty() { + blocks.push(LinkBlock::new(Link::new("functions", "Functions"), "fn", functions)); + } + + blocks } fn sidebar_foreign_type<'a>( @@ -746,10 +891,25 @@ fn get_methods<'a>( && item.is_method() && (!for_deref || super::should_render_item(item, deref_mut, tcx)) { - Some(Link::new( - get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::Method)), - name.as_str(), - )) + { + let mut link = Link::new( + get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::Method)), + name.as_str(), + ); + if item + .deprecation(tcx) + .is_some_and(|d| d.is_in_effect()) + { + // Mark as deprecated for template-based class insertion + link.deprecated = true; + // Also render the native deprecation tag in the link label + link.name_html = Some(format!( + "{} Deprecated", + name + ).into()); + } + Some(link) + } } else { None } @@ -760,6 +920,7 @@ fn get_methods<'a>( fn get_associated_constants<'a>( i: &'a clean::Impl, used_links: &mut FxHashSet, + tcx: TyCtxt<'_>, ) -> Vec> { i.items .iter() @@ -767,10 +928,25 @@ fn get_associated_constants<'a>( if let Some(ref name) = item.name && item.is_associated_const() { - Some(Link::new( - get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocConst)), - name.as_str(), - )) + { + let mut link = Link::new( + get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocConst)), + name.as_str(), + ); + if item + .deprecation(tcx) + .is_some_and(|d| d.is_in_effect()) + { + // Mark for template-based class insertion + link.deprecated = true; + // Also render the native deprecation tag in the link label + link.name_html = Some(format!( + "{} Deprecated", + name + ).into()); + } + Some(link) + } } else { None } @@ -781,6 +957,7 @@ fn get_associated_constants<'a>( fn get_associated_types<'a>( i: &'a clean::Impl, used_links: &mut FxHashSet, + tcx: TyCtxt<'_>, ) -> Vec> { i.items .iter() @@ -788,10 +965,21 @@ fn get_associated_types<'a>( if let Some(ref name) = item.name && item.is_associated_type() { - Some(Link::new( + let mut link = Link::new( get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocType)), name.as_str(), - )) + ); + if item + .deprecation(tcx) + .is_some_and(|d| d.is_in_effect()) + { + link.deprecated = true; + link.name_html = Some(format!( + "{} Deprecated", + name + ).into()); + } + Some(link) } else { None } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 7f47856948493..cfa4c17e50cce 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -191,6 +191,16 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ } /* General structure and fonts */ +.hide-depr dt:has(.stab.deprecated), +.hide-depr dt:has(.stab.deprecated) + dd { + display: none; +} + +.hide-depr ul.block.struct li.depr-item { display: none; } +.hide-depr ul.block.fn li.depr-item { display: none; } +.hide-depr ul.block.enum li.depr-item { display: none; } +.hide-depr ul.block.type li.depr-item { display: none; } +.hide-depr ul.block.constant li.depr-item { display: none; } body { /* Line spacing at least 1.5 per Web Content Accessibility Guidelines diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index dff40485e9a90..7dc60a336b327 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -54,6 +54,339 @@ function showMain() { window.rootPath = getVar("root-path"); window.currentCrate = getVar("current-crate"); +// Tag deprecated entries in the current page and sidebar, and expose an updater +function rustdocTagDeprecated() { + // Mark any entry in the current page that contains the native deprecation tag + onEachLazy(document.querySelectorAll(".stab.deprecated"), el => { + let p = el.parentElement; + while (p && p !== document.body) { + if (p.tagName === "LI" || p.tagName === "TR" || p.tagName === "DD") { + addClass(p, "depr-item"); + break; + } + if (p.classList && + (p.classList.contains("item-row") || + p.classList.contains("module-item") || + p.classList.contains("item"))) { + addClass(p, "depr-item"); + break; + } + p = p.parentElement; + } + }); + + // Tag sidebar entries that link to deprecated items within this page + onEachLazy(document.querySelectorAll(".sidebar li a[href*='#']"), a => { + const href = a.getAttribute("href"); + if (!href) { + return; + } + const idx = href.indexOf("#"); + if (idx === -1) { + return; + } + const frag = href.slice(idx + 1); + + // Locate target element by id or name (anchors sometimes use name) + let target = document.getElementById(frag); + if (!target) { + target = document.querySelector(`[id="${frag}"], [name="${frag}"]`); + } + if (!target) { + return; + } + + // Search broadly around the target for a native deprecation tag + // - check the target subtree + // - walk up ancestors + // - check immediate siblings (common in summary/detail structures) + let found = false; + let scope = target; + + // 1) target subtree + if (scope.querySelector && scope.querySelector(".stab.deprecated")) { + found = true; + } + + // 2) ancestor walk (up to a reasonable depth) + for (let i = 0; !found && i < 8 && scope; i += 1) { + if (scope.classList && scope.classList.contains("stab") && + scope.classList.contains("deprecated")) { + found = true; + break; + } + if (scope.querySelector && scope.querySelector(".stab.deprecated")) { + found = true; + break; + } + scope = scope.parentElement; + } + + // 3) siblings near the anchor (covers dt/dd, li/ul, table row patterns) + if (!found) { + const parent = target.parentElement; + if (parent) { + const sibs = parent.children; + for (let i = 0; i < sibs.length; i += 1) { + const s = sibs[i]; + if (s === target) { + continue; + } + if (s.classList && s.classList.contains("stab") && + s.classList.contains("deprecated")) { + found = true; + break; + } + if (s.querySelector && s.querySelector(".stab.deprecated")) { + found = true; + break; + } + } + } + } + + if (!found) { + return; + } + + // Tag only the actual item entry in the sidebar (closest
  • that owns this link) + const li = a.closest("li"); + if (li && li.querySelector("a") === a) { + addClass(li, "depr-item"); + } + }); +} + +function rustdocApplyHideDeprecatedVisibility() { + const hide = document.documentElement.classList.contains("hide-depr"); + onEachLazy(document.querySelectorAll(".depr-item"), el => { + if (hide) { + addClass(el, "hidden"); + } else { + removeClass(el, "hidden"); + } + }); +} + +// Expose an updater so other code can re-run visibility application if needed. +window.rustdocUpdateDeprecatedVisibility = rustdocApplyHideDeprecatedVisibility; + +// Run once on load +rustdocTagDeprecated(); +rustdocApplyHideDeprecatedVisibility(); + +// Keep in sync with the setting toggle by watching class changes +new MutationObserver(mutations => { + for (const m of mutations) { + if (m.attributeName === "class") { + rustdocApplyHideDeprecatedVisibility(); + break; + } + } +}).observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); +window.rootPath = getVar("root-path"); +window.currentCrate = getVar("current-crate"); + +// Tag deprecated entries in the current page and sidebar, and expose an updater +function rustdocTagDeprecated() { + // Collect anchor fragments (id or name) for deprecated items by inspecting item headings + /** @type {Set} */ + const deprecatedFragments = new Set(); + + // Heuristic: item headings typically are anchors or have an id/name on the first child. + // We walk through elements that are potential anchors and look for a nearby .stab.deprecated. + onEachLazy(document.querySelectorAll("[id], [name]"), el => { + // Skip elements that are unlikely to be item anchors (like container sections without text) + const tag = el.tagName.toLowerCase(); + if (tag === "section" || tag === "div" || tag === "nav") { + return; + } + + // Determine fragment name + const frag = el.getAttribute("id") || el.getAttribute("name"); + if (!frag) { + return; + } + + // Search around this element to determine deprecation: + // 1) inside subtree, 2) up ancestors, 3) immediate siblings + let found = false; + + // 1) subtree + if (el.querySelector && el.querySelector(".stab.deprecated")) { + found = true; + } + + // 2) ancestors + let scope = el.parentElement; + for (let i = 0; !found && i < 8 && scope; i += 1) { + if (scope.classList && scope.classList.contains("stab") && + scope.classList.contains("deprecated")) { + found = true; + break; + } + if (scope.querySelector && scope.querySelector(".stab.deprecated")) { + found = true; + break; + } + scope = scope.parentElement; + } + + // 3) siblings + if (!found && el.parentElement) { + const sibs = el.parentElement.children; + for (let i = 0; i < sibs.length; i += 1) { + const s = sibs[i]; + if (s === el) { + continue; + } + if (s.classList && s.classList.contains("stab") && + s.classList.contains("deprecated")) { + found = true; + break; + } + if (s.querySelector && s.querySelector(".stab.deprecated")) { + found = true; + break; + } + } + } + + if (found) { + deprecatedFragments.add(frag); + } + }); + + // Also tag container elements in the main content that already show the native deprecation tag + onEachLazy(document.querySelectorAll(".stab.deprecated"), el => { + let p = el.parentElement; + while (p && p !== document.body) { + if (p.tagName === "LI" || p.tagName === "TR" || p.tagName === "DD") { + addClass(p, "depr-item"); + break; + } + if (p.classList && + (p.classList.contains("item-row") || + p.classList.contains("module-item") || + p.classList.contains("item"))) { + addClass(p, "depr-item"); + break; + } + p = p.parentElement; + } + }); + + // Tag only actual sidebar item entries (closest
  • owning the link) if they point to deprecated fragments + onEachLazy(document.querySelectorAll(".sidebar li a[href*='#']"), a => { + const href = a.getAttribute("href"); + if (!href) { + return; + } + const idx = href.indexOf("#"); + if (idx === -1) { + return; + } + const frag = href.slice(idx + 1); + + // If anchor matches a known deprecated fragment, mark its li + if (deprecatedFragments.has(frag)) { + const li = a.closest("li"); + if (li && li.querySelector("a") === a) { + addClass(li, "depr-item"); + } + return; + } + + // Fallback: resolve target by id or name and search locally for deprecation marker + let target = document.getElementById(frag); + if (!target) { + target = document.querySelector(`[id="${frag}"], [name="${frag}"]`); + } + if (!target) { + return; + } + + let found = false; + + // target subtree + if (target.querySelector && target.querySelector(".stab.deprecated")) { + found = true; + } + + // ancestor walk + let scope = target; + for (let i = 0; !found && i < 8 && scope; i += 1) { + if (scope.classList && scope.classList.contains("stab") && + scope.classList.contains("deprecated")) { + found = true; + break; + } + if (scope.querySelector && scope.querySelector(".stab.deprecated")) { + found = true; + break; + } + scope = scope.parentElement; + } + + // siblings near target + if (!found && target.parentElement) { + const sibs = target.parentElement.children; + for (let i = 0; i < sibs.length; i += 1) { + const s = sibs[i]; + if (s === target) { + continue; + } + if (s.classList && s.classList.contains("stab") && + s.classList.contains("deprecated")) { + found = true; + break; + } + if (s.querySelector && s.querySelector(".stab.deprecated")) { + found = true; + break; + } + } + } + + if (!found) { + return; + } + + const li = a.closest("li"); + if (li && li.querySelector("a") === a) { + addClass(li, "depr-item"); + } + }); +} + +function rustdocApplyHideDeprecatedVisibility() { + const hide = document.documentElement.classList.contains("hide-depr"); + onEachLazy(document.querySelectorAll(".depr-item"), el => { + if (hide) { + addClass(el, "hidden"); + } else { + removeClass(el, "hidden"); + } + }); +} + +// Expose an updater so other code can re-run visibility application if needed. +window.rustdocUpdateDeprecatedVisibility = rustdocApplyHideDeprecatedVisibility; + +// Run once on load +rustdocTagDeprecated(); +rustdocApplyHideDeprecatedVisibility(); + +// Keep in sync with the setting toggle by watching class changes +new MutationObserver(mutations => { + for (const m of mutations) { + if (m.attributeName === "class") { + rustdocApplyHideDeprecatedVisibility(); + break; + } + } +}).observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); + /** * Gets the human-readable string for the virtual-key code of the * given KeyboardEvent, ev. diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 347d3d0750ece..c7c596c9fbe5d 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -78,6 +78,13 @@ removeClass(document.documentElement, "word-wrap-source-code"); } break; + case "hide-deprecated-items": + if (value === true) { + addClass(document.documentElement, "hide-depr"); + } else { + removeClass(document.documentElement, "hide-depr"); + } + break; } } @@ -274,6 +281,11 @@ "js_name": "word-wrap-source-code", "default": false, }, + { + "name": "Hide deprecated items", + "js_name": "hide-deprecated-items", + "default": false, + }, ]; // Then we build the DOM. diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 40ab8be03c93d..5be074f221480 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -334,6 +334,12 @@ if (getSettingValue("sans-serif-fonts") === "true") { if (getSettingValue("word-wrap-source-code") === "true") { addClass(document.documentElement, "word-wrap-source-code"); } +const hideDepr = getSettingValue("hide-deprecated-items"); +if (hideDepr === "true") { + addClass(document.documentElement, "hide-depr"); +} else { + removeClass(document.documentElement, "hide-depr"); +} function updateSidebarWidth() { const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); if (desktopSidebarWidth && desktopSidebarWidth !== "null") { diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index 7f852b489fa24..d12299fd8f823 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -22,7 +22,7 @@

    {# #} {% if !block.links.is_empty() %}