From b51fcf84ca23c9542a10dad9920be76934ae9cab Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 5 Sep 2026 23:47:13 +0200 Subject: [PATCH] Add new `--feature-documentation` command line option --- src/doc/rustdoc/src/unstable-features.md | 22 +++++++++ src/librustdoc/clean/types.rs | 14 +++++- src/librustdoc/clean/utils.rs | 49 ++++++++++++++++-- src/librustdoc/config.rs | 52 ++++++++++++++++++++ src/librustdoc/core.rs | 3 +- src/librustdoc/fold.rs | 1 + src/librustdoc/formats/cache.rs | 4 ++ src/librustdoc/formats/item_type.rs | 3 ++ src/librustdoc/html/render/mod.rs | 5 ++ src/librustdoc/html/render/print_item.rs | 29 ++++++----- src/librustdoc/html/static/js/search.js | 4 +- src/librustdoc/json/conversions.rs | 7 +-- src/librustdoc/lib.rs | 23 +++++++-- src/librustdoc/passes/propagate_stability.rs | 1 + src/librustdoc/passes/stripper.rs | 2 + src/librustdoc/visit.rs | 1 + src/rustdoc-json-types/lib.rs | 9 +++- tests/rustdoc-html/features/feature.rs | 23 +++++++++ tests/rustdoc-js/feature.js | 10 ++++ tests/rustdoc-js/feature.rs | 6 +++ 20 files changed, 239 insertions(+), 29 deletions(-) create mode 100644 tests/rustdoc-html/features/feature.rs create mode 100644 tests/rustdoc-js/feature.js create mode 100644 tests/rustdoc-js/feature.rs diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index ab317ba1048a4..29aac5dbad59d 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -1121,3 +1121,25 @@ pub struct S; // There will be no mention of `feature = "a"` in the documentation. pub use dep::S as Y; ``` + +## `--feature-documentation`: Generate documentation for a build (cfg) feature + +This command line flag allows to add documentation for a build (cfg) feature. For example if you +have in your code: + +```rust +#[cfg(feature = "something")] +pub struct X; +``` + +You can add the documentation of the `something` feature like this: + +```console +rustdoc --feature-documentation 'something=This feature gives access to the `X` struct' +``` + +The name of the feature and its documentation must be separated with a `=` character. If you put +nothing after the `=` character, the feature will still be present in the documentation, but with +no associated documentation. + +For now, intra-doc links don't work with the features documentation. diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 47b701e3c42d7..d61fbc5f28696 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -716,11 +716,15 @@ impl Item { /// * `ItemType::Primitive` /// * `ItemType::Keyword` /// * `ItemType::Attribute` + /// * `ItemType::Feature` /// /// They are considered fake because they only exist thanks to their /// `#[doc(primitive|keyword|attribute)]` attribute. pub(crate) fn is_fake_item(&self) -> bool { - matches!(self.type_(), ItemType::Primitive | ItemType::Keyword | ItemType::Attribute) + matches!( + self.type_(), + ItemType::Primitive | ItemType::Keyword | ItemType::Attribute | ItemType::Feature + ) } pub(crate) fn is_stripped(&self) -> bool { match self.kind { @@ -886,7 +890,10 @@ impl Item { // Primitives and Keywords are written in the source code as private modules. // The modules need to be private so that nobody actually uses them, but the // keywords and primitives that they are documenting are public. - ItemKind::KeywordItem | ItemKind::PrimitiveItem(_) | ItemKind::AttributeItem => { + ItemKind::KeywordItem + | ItemKind::PrimitiveItem(_) + | ItemKind::AttributeItem + | ItemKind::FeatureItem => { return Some(Visibility::Public); } // Variant fields inherit their enum's visibility. @@ -994,6 +1001,8 @@ pub(crate) enum ItemKind { /// This item represents an anonymous constant with a `#[doc(attribute = "...")]` attribute which is used /// to generate documentation for Rust builtin attributes. AttributeItem, + /// This item represents a `cfg` documented feature passed through the command line. + FeatureItem, } impl ItemKind { @@ -1036,6 +1045,7 @@ impl ItemKind { | StrippedItem(_) | KeywordItem | AttributeItem + | FeatureItem | PlaceholderImplItem => [].iter(), } } diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 012c4997db9c1..08a9d1ec5ef19 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -16,6 +16,7 @@ use rustc_hir::find_attr; use rustc_metadata::rendered_const; use rustc_middle::mir; use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt, TypeVisitableExt}; +use rustc_resolve::rustdoc::DocFragment; use rustc_span::def_id::ModId; use rustc_span::symbol::{Symbol, kw, sym}; use tracing::{debug, warn}; @@ -24,11 +25,12 @@ use crate::clean::auto_trait::synthesize_auto_trait_impls; use crate::clean::blanket_impl::synthesize_blanket_impls; use crate::clean::render_macro_matchers::render_macro_matcher; use crate::clean::{ - AssocItemConstraint, AssocItemConstraintKind, Crate, ExternalCrate, Generic, GenericArg, - GenericArgs, ImportSource, Item, ItemKind, Lifetime, Path, PathSegment, Primitive, - PrimitiveType, Term, Type, clean_doc_module, clean_middle_const, clean_middle_region, - clean_middle_ty, inline, + AssocItemConstraint, AssocItemConstraintKind, Attributes, Crate, ExternalCrate, Generic, + GenericArg, GenericArgs, ImportSource, Item, ItemId, ItemInner, ItemKind, Lifetime, Module, + Path, PathSegment, Primitive, PrimitiveType, Term, Type, clean_doc_module, clean_middle_const, + clean_middle_region, clean_middle_ty, inline, }; +use crate::config::Feature; use crate::core::DocContext; use crate::display::Joined as _; use crate::formats::item_type::ItemType; @@ -36,7 +38,7 @@ use crate::formats::item_type::ItemType; #[cfg(test)] mod tests; -pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { +pub(crate) fn krate(cx: &mut DocContext<'_>, features: Vec) -> Crate { let module = crate::visit_ast::RustdocVisitor::new(cx).visit(); // Clean the crate, translating the entire librustc_ast AST to one that is @@ -83,11 +85,48 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { m.items.extend(documented_attributes.into_iter().map(|(def_id, kw)| { Item::from_def_id_and_parts(def_id, Some(kw), ItemKind::AttributeItem, cx.tcx) })); + clean_features(m, features); } Crate { module, external_traits: Box::new(mem::take(&mut cx.external_traits)) } } +fn clean_features(module: &mut Module, features: Vec) { + for feature in features { + module.items.push(clean_feature(feature)); + } +} + +fn clean_feature(Feature { name, documentation }: Feature) -> Item { + use rustc_ast::token::{CommentKind, DocFragmentKind}; + + let item = ItemInner { + name: Some(Symbol::intern(&name)), + kind: ItemKind::FeatureItem, + attrs: Attributes { + // We need to convert the command line argument into something rustdoc and rustc + // understand. + doc_strings: vec![DocFragment { + span: rustc_span::DUMMY_SP, + item_id: None, + doc: documentation + .as_ref() + .map(|doc| Symbol::intern(doc)) + .unwrap_or(rustc_span::symbol::sym::empty), + kind: DocFragmentKind::Sugared(CommentKind::Block), + indent: 0, + from_expansion: false, + }], + other_attrs: ThinVec::new(), + }, + stability: None, + item_id: ItemId::from(rustc_hir::def_id::CRATE_DEF_ID.to_def_id()), + inline_stmt_id: None, + cfg: None, + }; + Item { inner: Box::new(item) } +} + pub(crate) fn clean_middle_generic_args<'tcx>( cx: &mut DocContext<'tcx>, args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>, diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index c2c58a345fa22..770a27a583520 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -59,6 +59,12 @@ pub(crate) enum MergeDoctests { Auto, } +#[derive(Clone, Debug)] +pub(crate) struct Feature { + pub(crate) name: String, + pub(crate) documentation: Option, +} + /// Configuration options for rustdoc. #[derive(Clone)] pub(crate) struct Options { @@ -172,6 +178,9 @@ pub(crate) struct Options { /// Target modifiers. pub(crate) target_modifiers: BTreeMap, + + /// Documentation for crate features. + pub(crate) documented_features: Vec, } impl fmt::Debug for Options { @@ -218,6 +227,7 @@ impl fmt::Debug for Options { .field("no_capture", &self.no_capture) .field("scrape_examples_options", &self.scrape_examples_options) .field("unstable_features", &self.unstable_features) + .field("documented_features", &self.documented_features) .finish() } } @@ -896,6 +906,8 @@ impl Options { let disable_minification = matches.opt_present("disable-minification"); + let documented_features = parse_feature_documentation(matches, dcx); + let options = Options { bin_crate, proc_macro_crate, @@ -941,6 +953,7 @@ impl Options { unstable_features, doctest_build_args, target_modifiers: collected_options.target_modifiers, + documented_features, }; let render_options = RenderOptions { output, @@ -1132,3 +1145,42 @@ fn parse_merge_doctests( } } } + +const OPT_NAME: &str = "feature-documentation"; + +fn remove_wrapping_quotes<'a>( + dcx: DiagCtxtHandle<'_>, + full: &str, + s: &'a str, + c: char, +) -> Option<&'a str> { + let Some(s) = s.strip_prefix(c) else { return None }; + let Some(s) = s.strip_suffix(c) else { + dcx.fatal(format!("unclosed documentation string for `--{OPT_NAME}` in {full:?}")); + }; + Some(s.trim()) +} + +fn parse_feature_documentation(m: &getopts::Matches, dcx: DiagCtxtHandle<'_>) -> Vec { + let entries = m.opt_strs(OPT_NAME); + let mut features: Vec = Vec::with_capacity(entries.len()); + for entry in entries { + let Some((name, doc)) = entry.split_once('=') else { + dcx.fatal(format!( + "invalid argument for `--{OPT_NAME}`: expected `name=doc`, found {entry:?}", + )); + }; + if features.iter().any(|feature| feature.name == name) { + dcx.fatal(format!("feature {name:?} is passed more than once in `--{OPT_NAME}`")); + } + let doc = doc.trim(); + let doc = remove_wrapping_quotes(dcx, &entry, doc, '"') + .or_else(|| remove_wrapping_quotes(dcx, &entry, doc, '\'')) + .unwrap_or(doc); + features.push(Feature { + name: name.to_string(), + documentation: if doc.is_empty() { None } else { Some(doc.to_string()) }, + }); + } + features +} diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index db5e281f376ad..cfef52fe04c44 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -348,6 +348,7 @@ pub(crate) fn run_global_ctxt( show_coverage: bool, render_options: RenderOptions, output_format: OutputFormat, + documented_features: Vec, ) -> (clean::Crate, RenderOptions, Cache, FxHashMap>) { // Certain queries assume that some checks were run elsewhere // (see https://github.com/rust-lang/rust/pull/73566#issuecomment-656954425), @@ -407,7 +408,7 @@ pub(crate) fn run_global_ctxt( ctxt.external_traits.insert(sized_trait_did, sized_trait); } - let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt)); + let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt, documented_features)); if krate.module.doc_value().is_empty() { let help = format!( diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index e8755ccd76a3a..c03dff19487e0 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -98,6 +98,7 @@ pub(crate) trait DocFolder: Sized { | AssocTypeItem(..) | KeywordItem | AttributeItem + | FeatureItem | PlaceholderImplItem => kind, } } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index ccee062584e01..2b7b00bf5306f 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -405,6 +405,7 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::AssocTypeItem(..) | clean::StrippedItem(..) | clean::KeywordItem + | clean::FeatureItem | clean::AttributeItem => { // FIXME: Do these need handling? // The person writing this comment doesn't know. @@ -577,6 +578,9 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It } } } + // Features have the `DefId` of the crate, so if we go in the condition below, they'll be + // ignored. + clean::FeatureItem => (None, &*cache.stack), _ => { // Don't index if item is crate root, which is inserted later on when serializing the index. // Don't index if containing module is stripped (i.e., private), diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index ed8804281334f..fecf1d0028d3e 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -107,6 +107,7 @@ item_type! { // still having the filtering working as expected. DeclMacroAttribute = 28, DeclMacroDerive = 29, + Feature = 30, } impl<'a> From<&'a clean::Item> for ItemType { @@ -150,6 +151,7 @@ impl<'a> From<&'a clean::Item> for ItemType { MacroKind::Attr => ItemType::ProcAttribute, MacroKind::Derive => ItemType::ProcDerive, }, + clean::FeatureItem => ItemType::Feature, clean::StrippedItem(..) => unreachable!(), } } @@ -230,6 +232,7 @@ impl ItemType { ItemType::ProcDerive | ItemType::DeclMacroDerive => "derive", ItemType::TraitAlias => "traitalias", ItemType::Attribute => "attribute", + ItemType::Feature => "feature", } } pub(crate) fn is_method(&self) -> bool { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index a9ecf6d66d001..b07d9f4506073 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2612,6 +2612,7 @@ pub(crate) enum ItemSection { AttributeMacros, DeriveMacros, TraitAliases, + Features, } impl ItemSection { @@ -2620,6 +2621,7 @@ impl ItemSection { // NOTE: The order here affects the order in the UI. // Keep this synchronized with addSidebarItems in main.js &[ + Features, Reexports, PrimitiveTypes, Modules, @@ -2675,6 +2677,7 @@ impl ItemSection { Self::AttributeMacros => "attributes", Self::DeriveMacros => "derives", Self::TraitAliases => "trait-aliases", + Self::Features => "features", } } @@ -2705,6 +2708,7 @@ impl ItemSection { Self::AttributeMacros => "Attribute Macros", Self::DeriveMacros => "Derive Macros", Self::TraitAliases => "Trait Aliases", + Self::Features => "Features", } } } @@ -2736,6 +2740,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::ProcAttribute | ItemType::DeclMacroAttribute => ItemSection::AttributeMacros, ItemType::ProcDerive | ItemType::DeclMacroDerive => ItemSection::DeriveMacros, ItemType::TraitAlias => ItemSection::TraitAliases, + ItemType::Feature => ItemSection::Features, } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6f66dcf9eae83..c5de3f1fad9d9 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -105,6 +105,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::KeywordItem => "Keyword ", clean::AttributeItem => "Attribute ", clean::TraitAliasItem(..) => "Trait Alias ", + clean::FeatureItem => "Feature ", _ => { // We don't generate pages for any other type. unreachable!(); @@ -211,6 +212,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::TraitAliasItem(ta) => { write!(buf, "{}", item_trait_alias(cx, item, ta)) } + clean::FeatureItem => write!(buf, "{}", item_feature(cx, item)), _ => { // We don't generate pages for any other type. unreachable!(); @@ -278,17 +280,18 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i // the order of item types in the listing fn reorder(ty: ItemType) -> u8 { match ty { - ItemType::ExternCrate => 0, - ItemType::Import => 1, - ItemType::Primitive => 2, - ItemType::Module => 3, - ItemType::Macro => 4, - ItemType::Struct => 5, - ItemType::Enum => 6, - ItemType::Constant => 7, - ItemType::Static => 8, - ItemType::Trait => 9, - ItemType::Function => 10, + ItemType::Feature => 0, + ItemType::ExternCrate => 1, + ItemType::Import => 2, + ItemType::Primitive => 3, + ItemType::Module => 4, + ItemType::Macro => 5, + ItemType::Struct => 6, + ItemType::Enum => 7, + ItemType::Constant => 8, + ItemType::Static => 9, + ItemType::Trait => 10, + ItemType::Function => 11, ItemType::TypeAlias => 12, ItemType::Union => 13, _ => 14 + ty as u8, @@ -2215,6 +2218,10 @@ fn item_keyword_or_attribute(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Di document(cx, it, None, HeadingOffset::H2) } +fn item_feature(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display { + document(cx, it, None, HeadingOffset::H2) +} + /// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). /// /// This code is copied from [`rustfmt`], and should probably be released as a crate at some point. diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 8584e0aff0538..10dddd92d0ef0 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -122,6 +122,7 @@ const itemTypes = Object.freeze({ attribute: 27, decl_macro_attribute: 28, decl_macro_derive: 29, + feature: 30, }); const itemTypesName = Array.from(Object.keys(itemTypes)); @@ -2179,7 +2180,7 @@ class DocSearch { displayPath = item.modulePath + "::"; href = this.rootPath + item.modulePath.replace(/::/g, "/") + "/index.html#reexport." + name; - } else if (type === "primitive" || type === "keyword" || type === "attribute") { + } else if (["primitive", "keyword", "attribute", "feature"].includes(type)) { displayPath = ""; exactPath = ""; href = this.rootPath + path.replace(/::/g, "/") + @@ -4827,6 +4828,7 @@ const longItemTypes = [ "attribute", "", // decl macro attribute, never used as is "", // decl macro derive, never used as is + "feature", ]; // @ts-expect-error let currentResults; diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 5a0e9d9e810e7..0be36ec8c155c 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -440,9 +440,9 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum ) .map(|stab| stab.into_json(renderer)), }, - // `convert_item` early returns `None` for stripped items, keywords, attributes and - // "special" macro rules. - KeywordItem | AttributeItem => unreachable!(), + // `convert_item` early returns `None` for stripped items, keywords, attributes, features + // and "special" macro rules. + KeywordItem | AttributeItem | FeatureItem => unreachable!(), StrippedItem(inner) => { match inner.as_ref() { ModuleItem(m) => ItemEnum::Module(Module { @@ -1004,6 +1004,7 @@ impl FromClean for ItemKind { ForeignType => ItemKind::ExternType, Keyword => ItemKind::Keyword, Attribute => ItemKind::Attribute, + Feature => ItemKind::Feature, TraitAlias => ItemKind::TraitAlias, ProcAttribute | DeclMacroAttribute => ItemKind::ProcAttribute, ProcDerive | DeclMacroDerive => ItemKind::ProcDerive, diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index fd50a7b306783..1a01e903fd63c 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -674,6 +674,14 @@ fn opts() -> Vec { "Add possibility to expand macros in the HTML source code pages", "", ), + opt( + Unstable, + Multi, + "", + "feature-documentation", + "Add a feature with its documentation in the generated output", + "name=value", + ), // deprecated / removed options opt( Stable, @@ -836,7 +844,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { // Note that we discard any distinction between different non-zero exit // codes from `from_matches` here. - let (input, options, render_options, loaded_paths) = + let (input, mut options, render_options, loaded_paths) = match config::Options::from_matches(early_dcx, &matches, args) { Some(opts) => opts, None => return, @@ -971,6 +979,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { let bin_crate = options.bin_crate; let output_format = options.output_format; + let documented_features = std::mem::take(&mut options.documented_features); let config = core::create_config(input, options, &render_options); let registered_lints = config.register_lints.is_some(); @@ -1002,9 +1011,15 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { sess.dcx().fatal("Compilation failed, aborting rustdoc"); } - let (krate, render_opts, mut cache, expanded_macros) = sess - .time("run_global_ctxt", || { - core::run_global_ctxt(tcx, show_coverage, render_options, output_format) + let (krate, render_opts, mut cache, expanded_macros) = + sess.time("run_global_ctxt", || { + core::run_global_ctxt( + tcx, + show_coverage, + render_options, + output_format, + documented_features, + ) }); info!("finished with rustc"); diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index 9afde1e6195e7..8ba7ead09a24b 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -104,6 +104,7 @@ impl DocFolder for StabilityPropagator<'_, '_> { | ItemKind::PrimitiveItem(..) | ItemKind::KeywordItem | ItemKind::AttributeItem + | ItemKind::FeatureItem | ItemKind::PlaceholderImplItem => own_stability, ItemKind::StrippedItem(..) => unreachable!(), diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 4c6d62917ab24..b58b0fab30373 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -139,6 +139,8 @@ impl DocFolder for Stripper<'_, '_> { clean::KeywordItem => {} // Attributes are never stripped clean::AttributeItem => {} + // Features are never stripped + clean::FeatureItem => {} } let fastreturn = match i.kind { diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index 86115f3853011..b5a179a59b810 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -51,6 +51,7 @@ pub(crate) trait DocVisitor<'a>: Sized { | AssocTypeItem(..) | KeywordItem | AttributeItem + | FeatureItem | PlaceholderImplItem => {} } } diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 3c20d392aab91..804c85adb4de4 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -114,8 +114,8 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line // are deliberately not in a doc comment, because they need not be in public docs.) // -// Latest feature: Make `Stability` work with non-self-describing formats -pub const FORMAT_VERSION: u32 = 61; +// Latest feature: Add support for (cfg) feature documentation +pub const FORMAT_VERSION: u32 = 62; /// The root of the emitted JSON blob. /// @@ -799,6 +799,11 @@ pub enum ItemKind { /// [`Item`]s of this kind only come from the core library and exist solely /// to carry documentation for the respective builtin attributes. Attribute, + /// A feature declaration. + /// + /// [`Item`]s of this kind come from the rustdoc `--feature-documentation` command line + /// argument. + Feature, } /// Specific fields of an item. diff --git a/tests/rustdoc-html/features/feature.rs b/tests/rustdoc-html/features/feature.rs new file mode 100644 index 0000000000000..4d82bd2db7457 --- /dev/null +++ b/tests/rustdoc-html/features/feature.rs @@ -0,0 +1,23 @@ +//@ compile-flags: -Zunstable-options --feature-documentation x=tadam +//@ compile-flags: --feature-documentation 'y= yup ' +//@ compile-flags: --feature-documentation 'z= another ' +//@ compile-flags: --feature-documentation 'z-z=why not' + +#![crate_name = "foo"] + +// First we check they're correctly listed in the items list. +//@ has 'foo/index.html' +//@ count - '//dt/a[@class="feature"]' 4 +//@ has - '//dt/a[@href="feature.x.html"]' 'x' +//@ has - '//dt/a[@href="feature.y.html"]' 'y' +//@ has - '//dt/a[@href="feature.z.html"]' 'z' +//@ has - '//dt/a[@href="feature.z-z.html"]' 'z-z' + +// Then we check the "features" section is listed in the sidebar. +//@ has - '//*[@id="rustdoc-toc"]/ul/li/a[@href="#features"]' 'Features' + +// And we check the files exist. +//@ has 'foo/feature.x.html' '//*[@class="docblock"]' 'tadam' +//@ has 'foo/feature.y.html' '//*[@class="docblock"]' 'yup' +//@ has 'foo/feature.z.html' '//*[@class="docblock"]' 'another' +//@ has 'foo/feature.z-z.html' '//*[@class="docblock"]' 'why not' diff --git a/tests/rustdoc-js/feature.js b/tests/rustdoc-js/feature.js new file mode 100644 index 0000000000000..240d9bb76ed11 --- /dev/null +++ b/tests/rustdoc-js/feature.js @@ -0,0 +1,10 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'x', + 'others': [ + { 'path': 'foo', 'name': 'x', 'desc': 'tadam' }, + ], + }, +]; diff --git a/tests/rustdoc-js/feature.rs b/tests/rustdoc-js/feature.rs new file mode 100644 index 0000000000000..2847d9adfe3cb --- /dev/null +++ b/tests/rustdoc-js/feature.rs @@ -0,0 +1,6 @@ +// This test ensures that the "features" passed through command line `--feature-documentation` are +// also included into the search index (and results). + +//@ compile-flags: -Zunstable-options --feature-documentation x=tadam + +#![crate_name = "foo"]