diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 8a8bec35d8194..03431da8bbb36 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -360,6 +360,7 @@ symbols! { SyncUnsafeCell, T, Target, + TestDescAndFn, ToOwned, ToString, TokenStream, diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index d4ff69a99338b..223c41e8225ac 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -830,3 +830,34 @@ will be split as follows: "you today?", ] ``` + +### `--document-tests`: show test items + +Using this flag looks like this: + +```bash +$ rustdoc src/lib.rs -Z unstable-options --cfg test --document-private-items --document-tests +``` + +By default, `rustdoc` does not document test items. + +```rust +/// by default this test function would not be documented +#[test] +fn test_in_module() { + assert_eq!(2, 1 + 1); +} +/// by default this test module would not be documented +#[cfg(test)] +mod tests { + /// by default this test function would not be documented + #[test] + fn test_in_a_test_module() { + assert_eq!(2, 1 + 1); + } +} +``` + +Note: +* `--cfg test` must be set because tests are guarded by #[cfg(test)]. +* `--document-private-items` is typically required because it is standard practice to keep test items private. By enabling this option, you ensure that private items, including tests, are documented as needed while maintaining their non-public status. diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4ecf702d7b6ef..df732db308454 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1048,7 +1048,11 @@ fn clean_fn_or_proc_macro<'tcx>( None => { let mut func = clean_function(cx, sig, generics, FunctionArgs::Body(body_id)); clean_fn_decl_legacy_const_generics(&mut func, attrs); - FunctionItem(func) + if cx.cache.document_tests && cx.cache.tests.contains(&item.owner_id.to_def_id()) { + TestItem(func) + } else { + FunctionItem(func) + } } } } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 3b2dcb3db81db..6d21dd4535c14 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -697,7 +697,8 @@ impl Item { } ItemKind::FunctionItem(_) | ItemKind::MethodItem(_, _) - | ItemKind::RequiredMethodItem(_) => { + | ItemKind::RequiredMethodItem(_) + | ItemKind::TestItem(_) => { let def_id = self.def_id().unwrap(); build_fn_header(def_id, tcx, tcx.asyncness(def_id)) } @@ -874,6 +875,7 @@ pub(crate) enum ItemKind { UnionItem(Union), EnumItem(Enum), FunctionItem(Box), + TestItem(Box), ModuleItem(Module), TypeAliasItem(Box), StaticItem(Static), @@ -934,6 +936,7 @@ impl ItemKind { ExternCrateItem { .. } | ImportItem(_) | FunctionItem(_) + | TestItem(_) | TypeAliasItem(_) | StaticItem(_) | ConstantItem(_) diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index 7ff5026150b16..aa43b2c228d2d 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -71,7 +71,7 @@ fn should_not_trim() { fn is_same_generic() { use crate::clean::types::{PrimitiveType, Type}; use crate::formats::cache::Cache; - let cache = Cache::new(false, false); + let cache = Cache::new(false, false, false); let generic = Type::Generic(rustc_span::symbol::sym::Any); let unit = Type::Primitive(PrimitiveType::Unit); assert!(!generic.is_doc_subtype_of(&unit, &cache)); diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 23a2bcd9011ee..54a07139c6c44 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -283,6 +283,8 @@ pub(crate) struct RenderOptions { pub(crate) document_private: bool, /// Document items that have `doc(hidden)`. pub(crate) document_hidden: bool, + /// Document tests. + pub(crate) document_tests: bool, /// If `true`, generate a JSON file in the crate folder instead of HTML redirection files. pub(crate) generate_redirect_map: bool, /// Show the memory layout of types in the docs. @@ -806,6 +808,7 @@ impl Options { } let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx); + let document_tests = matches.opt_present("document-tests"); let with_examples = matches.opt_strs("with-examples"); let call_locations = crate::scrape_examples::load_call_locations(with_examples, dcx); let doctest_compilation_args = matches.opt_strs("doctest-compilation-args"); @@ -880,6 +883,7 @@ impl Options { markdown_playground_url, document_private, document_hidden, + document_tests, generate_redirect_map, show_type_layout, unstable_features, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index c47e42670c909..2ce3b1742e81a 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -250,7 +250,8 @@ pub(crate) fn create_config( } else { ResolveDocLinks::Exported }; - let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false); + let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false) + || (cfgs.iter().any(|cfg| cfg == "test") && render_options.document_tests); // plays with error output here! let sessopts = config::Options { sysroot, @@ -369,7 +370,11 @@ pub(crate) fn run_global_ctxt( impl_trait_bounds: Default::default(), generated_synthetics: Default::default(), auto_traits, - cache: Cache::new(render_options.document_private, render_options.document_hidden), + cache: Cache::new( + render_options.document_private, + render_options.document_hidden, + render_options.document_tests, + ), inlined: FxHashSet::default(), output_format, render_options, diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index c03d16ad081bf..3545f45a8530a 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -79,6 +79,7 @@ pub(crate) trait DocFolder: Sized { ExternCrateItem { src: _ } | ImportItem(_) | FunctionItem(_) + | TestItem(_) | StaticItem(_) | ConstantItem(..) | TraitAliasItem(_) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 2648641e53e75..0a0f08cd4def1 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -92,6 +92,11 @@ pub(crate) struct Cache { /// Whether to document hidden items. /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. pub(crate) document_hidden: bool, + /// Whether to document tests. + /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. + pub(crate) document_tests: bool, + /// DefIds of all functions which are tests. + pub(crate) tests: FxHashSet, /// Crates marked with [`#[doc(masked)]`][doc_masked]. /// @@ -144,8 +149,8 @@ struct CacheBuilder<'a, 'tcx> { } impl Cache { - pub(crate) fn new(document_private: bool, document_hidden: bool) -> Self { - Cache { document_private, document_hidden, ..Cache::default() } + pub(crate) fn new(document_private: bool, document_hidden: bool, document_tests: bool) -> Self { + Cache { document_private, document_hidden, document_tests, ..Cache::default() } } /// Populates the `Cache` with more data. The returned `Crate` will be missing some data that was @@ -302,6 +307,7 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::TraitItem(..) | clean::TraitAliasItem(..) | clean::FunctionItem(..) + | clean::TestItem(..) | clean::ModuleItem(..) | clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index de6537e992f19..8f88ff3a02e7c 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -57,6 +57,7 @@ pub(crate) enum ItemType { TraitAlias = 25, // This number is reserved for use in JavaScript // Generic = 26, + Test = 27, } impl Serialize for ItemType { @@ -83,6 +84,7 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::UnionItem(..) => ItemType::Union, clean::EnumItem(..) => ItemType::Enum, clean::FunctionItem(..) => ItemType::Function, + clean::TestItem(..) => ItemType::Test, clean::TypeAliasItem(..) => ItemType::TypeAlias, clean::StaticItem(..) => ItemType::Static, clean::ConstantItem(..) => ItemType::Constant, @@ -178,6 +180,7 @@ impl ItemType { ItemType::Union => "union", ItemType::Enum => "enum", ItemType::Function => "fn", + ItemType::Test => "test", ItemType::TypeAlias => "type", ItemType::Static => "static", ItemType::Trait => "trait", diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8dfde1679fe11..08711dc584458 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -341,6 +341,7 @@ struct AllTypes { attribute_macros: FxIndexSet, derive_macros: FxIndexSet, trait_aliases: FxIndexSet, + tests: FxIndexSet, } impl AllTypes { @@ -360,6 +361,7 @@ impl AllTypes { attribute_macros: new_set(100), derive_macros: new_set(100), trait_aliases: new_set(100), + tests: new_set(100), } } @@ -385,6 +387,7 @@ impl AllTypes { } ItemType::ProcDerive => self.derive_macros.insert(ItemEntry::new(new_url, name)), ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), + ItemType::Test => self.tests.insert(ItemEntry::new(new_url, name)), _ => true, }; } @@ -414,6 +417,9 @@ impl AllTypes { if !self.functions.is_empty() { sections.insert(ItemSection::Functions); } + if !self.tests.is_empty() { + sections.insert(ItemSection::Tests); + } if !self.type_aliases.is_empty() { sections.insert(ItemSection::TypeAliases); } @@ -432,6 +438,9 @@ impl AllTypes { if !self.trait_aliases.is_empty() { sections.insert(ItemSection::TraitAliases); } + if !self.tests.is_empty() { + sections.insert(ItemSection::Tests); + } sections } @@ -470,6 +479,7 @@ impl AllTypes { print_entries(f, &self.attribute_macros, ItemSection::AttributeMacros); print_entries(f, &self.derive_macros, ItemSection::DeriveMacros); print_entries(f, &self.functions, ItemSection::Functions); + print_entries(f, &self.tests, ItemSection::Tests); print_entries(f, &self.type_aliases, ItemSection::TypeAliases); print_entries(f, &self.trait_aliases, ItemSection::TraitAliases); print_entries(f, &self.statics, ItemSection::Statics); @@ -2370,6 +2380,7 @@ pub(crate) enum ItemSection { Statics, Traits, Functions, + Tests, TypeAliases, Unions, Implementations, @@ -2402,6 +2413,7 @@ impl ItemSection { Statics, Traits, Functions, + Tests, TypeAliases, Unions, Implementations, @@ -2427,6 +2439,7 @@ impl ItemSection { Self::Unions => "unions", Self::Enums => "enums", Self::Functions => "functions", + Self::Tests => "tests", Self::TypeAliases => "types", Self::Statics => "statics", Self::Constants => "constants", @@ -2456,6 +2469,7 @@ impl ItemSection { Self::Unions => "Unions", Self::Enums => "Enums", Self::Functions => "Functions", + Self::Tests => "Test Cases", Self::TypeAliases => "Type Aliases", Self::Statics => "Statics", Self::Constants => "Constants", @@ -2486,6 +2500,7 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::Union => ItemSection::Unions, ItemType::Enum => ItemSection::Enums, ItemType::Function => ItemSection::Functions, + ItemType::Test => ItemSection::Tests, ItemType::TypeAlias => ItemSection::TypeAliases, ItemType::Static => ItemSection::Statics, ItemType::Constant => ItemSection::Constants, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 3c5c2ce19767d..c6742c9aa4187 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -162,6 +162,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp } } clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => "Function ", + clean::TestItem(..) => "Test ", clean::TraitItem(..) => "Trait ", clean::StructItem(..) => "Struct ", clean::UnionItem(..) => "Union ", @@ -228,7 +229,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp clean::ModuleItem(m) => { write!(buf, "{}", item_module(cx, item, &m.items)) } - clean::FunctionItem(f) | clean::ForeignFunctionItem(f, _) => { + clean::FunctionItem(f) | clean::ForeignFunctionItem(f, _) | clean::TestItem(f) => { write!(buf, "{}", item_function(cx, item, f)) } clean::TraitItem(t) => write!(buf, "{}", item_trait(cx, item, t)), @@ -326,6 +327,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i ItemType::Static => 8, ItemType::Trait => 9, ItemType::Function => 10, + ItemType::Test => 11, ItemType::TypeAlias => 12, ItemType::Union => 13, _ => 14 + ty as u8, diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 4150c5609a97e..310f28f3b6243 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -654,6 +654,7 @@ function preLoadCss(cssUrl) { block("static", "static", "Statics"); block("trait", "traits", "Traits"); block("fn", "functions", "Functions"); + block("test", "tests", "Tests"); block("type", "types", "Type Aliases"); block("union", "unions", "Unions"); // No point, because these items don't appear in modules diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index a5351b350dd5f..4d3708907b729 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -251,7 +251,9 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum { StructFieldItem(f) => ItemEnum::StructField(f.into_json(renderer)), EnumItem(e) => ItemEnum::Enum(e.into_json(renderer)), VariantItem(v) => ItemEnum::Variant(v.into_json(renderer)), - FunctionItem(f) => ItemEnum::Function(from_function(*f, true, header.unwrap(), renderer)), + FunctionItem(f) | TestItem(f) => { + ItemEnum::Function(from_function(*f, true, header.unwrap(), renderer)) + } ForeignFunctionItem(f, _) => { ItemEnum::Function(from_function(*f, false, header.unwrap(), renderer)) } @@ -820,7 +822,7 @@ impl FromClean for ItemKind { Struct => ItemKind::Struct, Union => ItemKind::Union, Enum => ItemKind::Enum, - Function | TyMethod | Method => ItemKind::Function, + Function | Test | TyMethod | Method => ItemKind::Function, TypeAlias => ItemKind::TypeAlias, Static => ItemKind::Static, Constant => ItemKind::Constant, diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 4fe5e13c3afe0..23c923f19271b 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -699,6 +699,7 @@ fn opts() -> Vec { "removed, see issue #44136 for more information", "[rust]", ), + opt(Unstable, FlagMulti, "", "document-tests", "Generate documentation for tests", ""), ] } diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index fdab2b087799a..5800dc20d1b90 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -77,6 +77,7 @@ impl DocFolder for StabilityPropagator<'_, '_> { | ItemKind::UnionItem(..) | ItemKind::EnumItem(..) | ItemKind::FunctionItem(..) + | ItemKind::TestItem(..) | ItemKind::ModuleItem(..) | ItemKind::TypeAliasItem(..) | ItemKind::StaticItem(..) diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index eedbbca0f8dfc..e3e59aeb2b480 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -57,6 +57,7 @@ impl DocFolder for Stripper<'_, '_> { | clean::EnumItem(..) | clean::TraitItem(..) | clean::FunctionItem(..) + | clean::TestItem(..) | clean::VariantItem(..) | clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index b8b619514aad9..032e4e0c40527 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -31,6 +31,7 @@ pub(crate) trait DocVisitor<'a>: Sized { ExternCrateItem { src: _ } | ImportItem(_) | FunctionItem(_) + | TestItem(_) | TypeAliasItem(_) | StaticItem(_) | ConstantItem(..) diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 254549e72c649..7ad9f1d3f4d5d 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -8,7 +8,7 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet}; use rustc_hir::intravisit::{Visitor, walk_body, walk_item}; -use rustc_hir::{CRATE_HIR_ID, Node}; +use rustc_hir::{CRATE_HIR_ID, Node, TyKind}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::Span; @@ -86,6 +86,7 @@ pub(crate) struct RustdocVisitor<'a, 'tcx> { modules: Vec>, is_importable_from_parent: bool, inside_body: bool, + next_is_test: bool, } impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { @@ -110,6 +111,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { modules: vec![om], is_importable_from_parent: true, inside_body: false, + next_is_test: false, } } @@ -383,6 +385,13 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { _ => false, } { + if self.cx.cache.document_tests + && let hir::ItemKind::Fn { .. } = item.kind + && self.next_is_test + { + self.cx.cache.tests.insert(item.owner_id.to_def_id()); + self.next_is_test = false; + }; self.modules .last_mut() .unwrap() @@ -495,8 +504,16 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { hir::ItemKind::Mod(_, m) => { self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id); } - hir::ItemKind::Fn { .. } - | hir::ItemKind::ExternCrate(..) + hir::ItemKind::Fn { sig: fn_sig, .. } => { + // Don't show auto created function "main" that is not in the source code (empty span) when documenting tests. + if !(self.cx.cache.document_tests + && fn_sig.span.is_empty() + && get_name().as_str() == "main") + { + self.add_to_current_mod(item, renamed, import_id); + } + } + hir::ItemKind::ExternCrate(..) | hir::ItemKind::Enum(..) | hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) @@ -506,11 +523,24 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | hir::ItemKind::TraitAlias(..) => { self.add_to_current_mod(item, renamed, import_id); } - hir::ItemKind::Const(..) => { + hir::ItemKind::Const(_, ty, _, _) => { // Underscore constants do not correspond to a nameable item and // so are never useful in documentation. if get_name() != kw::Underscore { - self.add_to_current_mod(item, renamed, import_id); + if self.cx.cache.document_tests + && let TyKind::Path(rustc_hir::QPath::Resolved(_, path)) = ty.kind + { + let path_names = path.segments.iter().map(|s| s.ident.name); + if path_names.eq([sym::test, sym::TestDescAndFn]) { + // Intentionally do not add this, since we want to document the test + // and not the generated constants. + self.next_is_test = true; + } else { + self.add_to_current_mod(item, renamed, import_id); + } + } else { + self.add_to_current_mod(item, renamed, import_id); + } } } hir::ItemKind::Impl(impl_) => { diff --git a/tests/run-make/rustdoc-default-output/output-default.stdout b/tests/run-make/rustdoc-default-output/output-default.stdout index 01f470f6e162b..6c5b03241bd81 100644 --- a/tests/run-make/rustdoc-default-output/output-default.stdout +++ b/tests/run-make/rustdoc-default-output/output-default.stdout @@ -214,6 +214,8 @@ Options: removed, see issue #44136 for more information + --document-tests + Generate documentation for tests @path Read newline separated options from `path`