Auto merge of #82777 - GuillaumeGomez:rollup-etcsupl, r=GuillaumeGomez
Rollup of 5 pull requests Successful merges: - #76716 (Don't warn for `missing_doc_examples` when item is #[doc(hidden)]) - #82088 (Shorten html::render) - #82690 (Update rustdoc documentation) - #82752 (Add a regression test for issue-81712) - #82765 (Fix polymorphization ICE on associated types in trait decls using const generics in bounds) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
8ccc89bc31
19 changed files with 2827 additions and 2751 deletions
|
@ -438,18 +438,6 @@ impl<'tcx> TyCtxt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn optimized_mir_or_const_arg_mir(
|
||||
self,
|
||||
def: ty::WithOptConstParam<DefId>,
|
||||
) -> &'tcx Body<'tcx> {
|
||||
if let Some((did, param_did)) = def.as_const_arg() {
|
||||
self.mir_for_ctfe_of_const_arg((did, param_did))
|
||||
} else {
|
||||
self.optimized_mir(def.did)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mir_for_ctfe_opt_const_arg(self, def: ty::WithOptConstParam<DefId>) -> &'tcx Body<'tcx> {
|
||||
if let Some((did, param_did)) = def.as_const_arg() {
|
||||
|
|
|
@ -499,7 +499,7 @@ impl<'tcx> Instance<'tcx> {
|
|||
}
|
||||
|
||||
/// Returns a new `Instance` where generic parameters in `instance.substs` are replaced by
|
||||
/// identify parameters if they are determined to be unused in `instance.def`.
|
||||
/// identity parameters if they are determined to be unused in `instance.def`.
|
||||
pub fn polymorphize(self, tcx: TyCtxt<'tcx>) -> Self {
|
||||
debug!("polymorphize: running polymorphization analysis");
|
||||
if !tcx.sess.opts.debugging_opts.polymorphize {
|
||||
|
|
|
@ -2963,7 +2963,10 @@ impl<'tcx> TyCtxt<'tcx> {
|
|||
| DefKind::AnonConst => self.mir_for_ctfe_opt_const_arg(def),
|
||||
// If the caller wants `mir_for_ctfe` of a function they should not be using
|
||||
// `instance_mir`, so we'll assume const fn also wants the optimized version.
|
||||
_ => self.optimized_mir_or_const_arg_mir(def),
|
||||
_ => {
|
||||
assert_eq!(def.const_param_did, None);
|
||||
self.optimized_mir(def.did)
|
||||
}
|
||||
},
|
||||
ty::InstanceDef::VtableShim(..)
|
||||
| ty::InstanceDef::ReifyShim(..)
|
||||
|
|
|
@ -30,9 +30,8 @@ pub fn provide(providers: &mut Providers) {
|
|||
/// Determine which generic parameters are used by the function/method/closure represented by
|
||||
/// `def_id`. Returns a bitset where bits representing unused parameters are set (`is_empty`
|
||||
/// indicates all parameters are used).
|
||||
#[instrument(skip(tcx))]
|
||||
fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
||||
debug!("unused_generic_params({:?})", def_id);
|
||||
|
||||
if !tcx.sess.opts.debugging_opts.polymorphize {
|
||||
// If polymorphization disabled, then all parameters are used.
|
||||
return FiniteBitSet::new_empty();
|
||||
|
@ -46,7 +45,7 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
|||
}
|
||||
|
||||
let generics = tcx.generics_of(def_id);
|
||||
debug!("unused_generic_params: generics={:?}", generics);
|
||||
debug!(?generics);
|
||||
|
||||
// Exit early when there are no parameters to be unused.
|
||||
if generics.count() == 0 {
|
||||
|
@ -57,11 +56,11 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
|||
let context = tcx.hir().body_const_context(def_id.expect_local());
|
||||
match context {
|
||||
Some(ConstContext::ConstFn) | None if !tcx.is_mir_available(def_id) => {
|
||||
debug!("unused_generic_params: (no mir available) def_id={:?}", def_id);
|
||||
debug!("no mir available");
|
||||
return FiniteBitSet::new_empty();
|
||||
}
|
||||
Some(_) if !tcx.is_ctfe_mir_available(def_id) => {
|
||||
debug!("unused_generic_params: (no ctfe mir available) def_id={:?}", def_id);
|
||||
debug!("no ctfe mir available");
|
||||
return FiniteBitSet::new_empty();
|
||||
}
|
||||
_ => {}
|
||||
|
@ -72,9 +71,9 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
|||
generics.count().try_into().expect("more generic parameters than can fit into a `u32`");
|
||||
let mut unused_parameters = FiniteBitSet::<u32>::new_empty();
|
||||
unused_parameters.set_range(0..generics_count);
|
||||
debug!("unused_generic_params: (start) unused_parameters={:?}", unused_parameters);
|
||||
debug!(?unused_parameters, "(start)");
|
||||
mark_used_by_default_parameters(tcx, def_id, generics, &mut unused_parameters);
|
||||
debug!("unused_generic_params: (after default) unused_parameters={:?}", unused_parameters);
|
||||
debug!(?unused_parameters, "(after default)");
|
||||
|
||||
// Visit MIR and accumululate used generic parameters.
|
||||
let body = match context {
|
||||
|
@ -85,10 +84,10 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
|||
};
|
||||
let mut vis = MarkUsedGenericParams { tcx, def_id, unused_parameters: &mut unused_parameters };
|
||||
vis.visit_body(body);
|
||||
debug!("unused_generic_params: (after visitor) unused_parameters={:?}", unused_parameters);
|
||||
debug!(?unused_parameters, "(after visitor)");
|
||||
|
||||
mark_used_by_predicates(tcx, def_id, &mut unused_parameters);
|
||||
debug!("unused_generic_params: (end) unused_parameters={:?}", unused_parameters);
|
||||
debug!(?unused_parameters, "(end)");
|
||||
|
||||
// Emit errors for debugging and testing if enabled.
|
||||
if !unused_parameters.is_empty() {
|
||||
|
@ -101,25 +100,56 @@ fn unused_generic_params(tcx: TyCtxt<'_>, def_id: DefId) -> FiniteBitSet<u32> {
|
|||
/// Some parameters are considered used-by-default, such as non-generic parameters and the dummy
|
||||
/// generic parameters from closures, this function marks them as used. `leaf_is_closure` should
|
||||
/// be `true` if the item that `unused_generic_params` was invoked on is a closure.
|
||||
#[instrument(skip(tcx, def_id, generics, unused_parameters))]
|
||||
fn mark_used_by_default_parameters<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
def_id: DefId,
|
||||
generics: &'tcx ty::Generics,
|
||||
unused_parameters: &mut FiniteBitSet<u32>,
|
||||
) {
|
||||
if !tcx.is_trait(def_id) && (tcx.is_closure(def_id) || tcx.type_of(def_id).is_generator()) {
|
||||
match tcx.def_kind(def_id) {
|
||||
DefKind::Closure | DefKind::Generator => {
|
||||
for param in &generics.params {
|
||||
debug!("mark_used_by_default_parameters: (closure/gen) param={:?}", param);
|
||||
debug!(?param, "(closure/gen)");
|
||||
unused_parameters.clear(param.index);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
DefKind::Mod
|
||||
| DefKind::Struct
|
||||
| DefKind::Union
|
||||
| DefKind::Enum
|
||||
| DefKind::Variant
|
||||
| DefKind::Trait
|
||||
| DefKind::TyAlias
|
||||
| DefKind::ForeignTy
|
||||
| DefKind::TraitAlias
|
||||
| DefKind::AssocTy
|
||||
| DefKind::TyParam
|
||||
| DefKind::Fn
|
||||
| DefKind::Const
|
||||
| DefKind::ConstParam
|
||||
| DefKind::Static
|
||||
| DefKind::Ctor(_, _)
|
||||
| DefKind::AssocFn
|
||||
| DefKind::AssocConst
|
||||
| DefKind::Macro(_)
|
||||
| DefKind::ExternCrate
|
||||
| DefKind::Use
|
||||
| DefKind::ForeignMod
|
||||
| DefKind::AnonConst
|
||||
| DefKind::OpaqueTy
|
||||
| DefKind::Field
|
||||
| DefKind::LifetimeParam
|
||||
| DefKind::GlobalAsm
|
||||
| DefKind::Impl => {
|
||||
for param in &generics.params {
|
||||
debug!("mark_used_by_default_parameters: (other) param={:?}", param);
|
||||
debug!(?param, "(other)");
|
||||
if let ty::GenericParamDefKind::Lifetime = param.kind {
|
||||
unused_parameters.clear(param.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(parent) = generics.parent {
|
||||
mark_used_by_default_parameters(tcx, parent, tcx.generics_of(parent), unused_parameters);
|
||||
|
@ -128,6 +158,7 @@ fn mark_used_by_default_parameters<'tcx>(
|
|||
|
||||
/// Search the predicates on used generic parameters for any unused generic parameters, and mark
|
||||
/// those as used.
|
||||
#[instrument(skip(tcx, def_id))]
|
||||
fn mark_used_by_predicates<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
def_id: DefId,
|
||||
|
@ -135,16 +166,12 @@ fn mark_used_by_predicates<'tcx>(
|
|||
) {
|
||||
let def_id = tcx.closure_base_def_id(def_id);
|
||||
let predicates = tcx.explicit_predicates_of(def_id);
|
||||
debug!("mark_used_by_predicates: predicates_of={:?}", predicates);
|
||||
|
||||
let mut current_unused_parameters = FiniteBitSet::new_empty();
|
||||
// Run to a fixed point to support `where T: Trait<U>, U: Trait<V>`, starting with an empty
|
||||
// bit set so that this is skipped if all parameters are already used.
|
||||
while current_unused_parameters != *unused_parameters {
|
||||
debug!(
|
||||
"mark_used_by_predicates: current_unused_parameters={:?} = unused_parameters={:?}",
|
||||
current_unused_parameters, unused_parameters
|
||||
);
|
||||
debug!(?current_unused_parameters, ?unused_parameters);
|
||||
current_unused_parameters = *unused_parameters;
|
||||
|
||||
for (predicate, _) in predicates.predicates {
|
||||
|
@ -169,13 +196,13 @@ fn mark_used_by_predicates<'tcx>(
|
|||
|
||||
/// Emit errors for the function annotated by `#[rustc_polymorphize_error]`, labelling each generic
|
||||
/// parameter which was unused.
|
||||
#[instrument(skip(tcx, generics))]
|
||||
fn emit_unused_generic_params_error<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
def_id: DefId,
|
||||
generics: &'tcx ty::Generics,
|
||||
unused_parameters: &FiniteBitSet<u32>,
|
||||
) {
|
||||
debug!("emit_unused_generic_params_error: def_id={:?}", def_id);
|
||||
let base_def_id = tcx.closure_base_def_id(def_id);
|
||||
if !tcx
|
||||
.get_attrs(base_def_id)
|
||||
|
@ -185,7 +212,6 @@ fn emit_unused_generic_params_error<'tcx>(
|
|||
return;
|
||||
}
|
||||
|
||||
debug!("emit_unused_generic_params_error: unused_parameters={:?}", unused_parameters);
|
||||
let fn_span = match tcx.opt_item_name(def_id) {
|
||||
Some(ident) => ident.span,
|
||||
_ => tcx.def_span(def_id),
|
||||
|
@ -197,7 +223,7 @@ fn emit_unused_generic_params_error<'tcx>(
|
|||
while let Some(generics) = next_generics {
|
||||
for param in &generics.params {
|
||||
if unused_parameters.contains(param.index).unwrap_or(false) {
|
||||
debug!("emit_unused_generic_params_error: param={:?}", param);
|
||||
debug!(?param);
|
||||
let def_span = tcx.def_span(param.def_id);
|
||||
err.span_label(def_span, &format!("generic parameter `{}` is unused", param.name));
|
||||
}
|
||||
|
@ -219,25 +245,23 @@ struct MarkUsedGenericParams<'a, 'tcx> {
|
|||
impl<'a, 'tcx> MarkUsedGenericParams<'a, 'tcx> {
|
||||
/// Invoke `unused_generic_params` on a body contained within the current item (e.g.
|
||||
/// a closure, generator or constant).
|
||||
#[instrument(skip(self, def_id, substs))]
|
||||
fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) {
|
||||
let unused = self.tcx.unused_generic_params(def_id);
|
||||
debug!(
|
||||
"visit_child_body: unused_parameters={:?} unused={:?}",
|
||||
self.unused_parameters, unused
|
||||
);
|
||||
debug!(?self.unused_parameters, ?unused);
|
||||
for (i, arg) in substs.iter().enumerate() {
|
||||
let i = i.try_into().unwrap();
|
||||
if !unused.contains(i).unwrap_or(false) {
|
||||
arg.visit_with(self);
|
||||
}
|
||||
}
|
||||
debug!("visit_child_body: unused_parameters={:?}", self.unused_parameters);
|
||||
debug!(?self.unused_parameters);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||
#[instrument(skip(self, local))]
|
||||
fn visit_local_decl(&mut self, local: Local, local_decl: &LocalDecl<'tcx>) {
|
||||
debug!("visit_local_decl: local_decl={:?}", local_decl);
|
||||
if local == Local::from_usize(1) {
|
||||
let def_kind = self.tcx.def_kind(self.def_id);
|
||||
if matches!(def_kind, DefKind::Closure | DefKind::Generator) {
|
||||
|
@ -245,7 +269,7 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
|||
// happens because the first argument to the closure is a reference to itself and
|
||||
// that will call `visit_substs`, resulting in each generic parameter captured being
|
||||
// considered used by default.
|
||||
debug!("visit_local_decl: skipping closure substs");
|
||||
debug!("skipping closure substs");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -263,15 +287,15 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
|||
}
|
||||
|
||||
impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
||||
#[instrument(skip(self))]
|
||||
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
debug!("visit_const: c={:?}", c);
|
||||
if !c.has_param_types_or_consts() {
|
||||
return ControlFlow::CONTINUE;
|
||||
}
|
||||
|
||||
match c.val {
|
||||
ty::ConstKind::Param(param) => {
|
||||
debug!("visit_const: param={:?}", param);
|
||||
debug!(?param);
|
||||
self.unused_parameters.clear(param.index);
|
||||
ControlFlow::CONTINUE
|
||||
}
|
||||
|
@ -296,15 +320,15 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
debug!("visit_ty: ty={:?}", ty);
|
||||
if !ty.has_param_types_or_consts() {
|
||||
return ControlFlow::CONTINUE;
|
||||
}
|
||||
|
||||
match *ty.kind() {
|
||||
ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => {
|
||||
debug!("visit_ty: def_id={:?}", def_id);
|
||||
debug!(?def_id);
|
||||
// Avoid cycle errors with generators.
|
||||
if def_id == self.def_id {
|
||||
return ControlFlow::CONTINUE;
|
||||
|
@ -316,7 +340,7 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> {
|
|||
ControlFlow::CONTINUE
|
||||
}
|
||||
ty::Param(param) => {
|
||||
debug!("visit_ty: param={:?}", param);
|
||||
debug!(?param);
|
||||
self.unused_parameters.clear(param.index);
|
||||
ControlFlow::CONTINUE
|
||||
}
|
||||
|
@ -333,8 +357,8 @@ struct HasUsedGenericParams<'a> {
|
|||
impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
|
||||
type BreakTy = ();
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn visit_const(&mut self, c: &'tcx Const<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
debug!("visit_const: c={:?}", c);
|
||||
if !c.has_param_types_or_consts() {
|
||||
return ControlFlow::CONTINUE;
|
||||
}
|
||||
|
@ -351,8 +375,8 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for HasUsedGenericParams<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
debug!("visit_ty: ty={:?}", ty);
|
||||
if !ty.has_param_types_or_consts() {
|
||||
return ControlFlow::CONTINUE;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
- [Documentation tests](documentation-tests.md)
|
||||
- [Linking to items by name](linking-to-items-by-name.md)
|
||||
- [Lints](lints.md)
|
||||
- [Passes](passes.md)
|
||||
- [Advanced features](advanced-features.md)
|
||||
- [Unstable features](unstable-features.md)
|
||||
- [Passes](passes.md)
|
||||
- [References](references.md)
|
||||
|
|
|
@ -57,23 +57,6 @@ release: 1.17.0
|
|||
LLVM version: 3.9
|
||||
```
|
||||
|
||||
## `-r`/`--input-format`: input format
|
||||
|
||||
This flag is currently ignored; the idea is that `rustdoc` would support various
|
||||
input formats, and you could specify them via this flag.
|
||||
|
||||
Rustdoc only supports Rust source code and Markdown input formats. If the
|
||||
file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file.
|
||||
Otherwise, it assumes that the input file is Rust.
|
||||
|
||||
|
||||
## `-w`/`--output-format`: output format
|
||||
|
||||
This flag is currently ignored; the idea is that `rustdoc` would support
|
||||
various output formats, and you could specify them via this flag.
|
||||
|
||||
Rustdoc only supports HTML output, and so this flag is redundant today.
|
||||
|
||||
## `-o`/`--output`: output path
|
||||
|
||||
Using this flag looks like this:
|
||||
|
@ -100,6 +83,25 @@ By default, `rustdoc` assumes that the name of your crate is the same name
|
|||
as the `.rs` file. `--crate-name` lets you override this assumption with
|
||||
whatever name you choose.
|
||||
|
||||
## `--document-private-items`: Show items that are not public
|
||||
|
||||
Using this flag looks like this:
|
||||
|
||||
```bash
|
||||
$ rustdoc src/lib.rs --document-private-items
|
||||
```
|
||||
|
||||
By default, `rustdoc` only documents items that are publicly reachable.
|
||||
|
||||
```rust
|
||||
pub fn public() {} // this item is public and will documented
|
||||
mod private { // this item is private and will not be documented
|
||||
pub fn unreachable() {} // this item is public, but unreachable, so it will not be documented
|
||||
}
|
||||
```
|
||||
|
||||
`--document-private-items` documents all items, even if they're not public.
|
||||
|
||||
## `-L`/`--library-path`: where to look for dependencies
|
||||
|
||||
Using this flag looks like this:
|
||||
|
@ -166,38 +168,6 @@ affect that.
|
|||
The arguments to this flag are the same as those for the `-C` flag on rustc. Run `rustc -C help` to
|
||||
get the full list.
|
||||
|
||||
## `--passes`: add more rustdoc passes
|
||||
|
||||
Using this flag looks like this:
|
||||
|
||||
```bash
|
||||
$ rustdoc --passes list
|
||||
$ rustdoc src/lib.rs --passes strip-priv-imports
|
||||
```
|
||||
|
||||
An argument of "list" will print a list of possible "rustdoc passes", and other
|
||||
arguments will be the name of which passes to run in addition to the defaults.
|
||||
|
||||
For more details on passes, see [the chapter on them](passes.md).
|
||||
|
||||
See also `--no-defaults`.
|
||||
|
||||
## `--no-defaults`: don't run default passes
|
||||
|
||||
Using this flag looks like this:
|
||||
|
||||
```bash
|
||||
$ rustdoc src/lib.rs --no-defaults
|
||||
```
|
||||
|
||||
By default, `rustdoc` will run several passes over your code. This
|
||||
removes those defaults, allowing you to use `--passes` to specify
|
||||
exactly which passes you want.
|
||||
|
||||
For more details on passes, see [the chapter on them](passes.md).
|
||||
|
||||
See also `--passes`.
|
||||
|
||||
## `--test`: run code examples as tests
|
||||
|
||||
Using this flag looks like this:
|
||||
|
@ -429,3 +399,21 @@ If you specify `@path` on the command-line, then it will open `path` and read
|
|||
command line options from it. These options are one per line; a blank line indicates
|
||||
an empty option. The file can use Unix or Windows style line endings, and must be
|
||||
encoded as UTF-8.
|
||||
|
||||
## `--passes`: add more rustdoc passes
|
||||
|
||||
This flag is **deprecated**.
|
||||
For more details on passes, see [the chapter on them](passes.md).
|
||||
|
||||
## `--no-defaults`: don't run default passes
|
||||
|
||||
This flag is **deprecated**.
|
||||
For more details on passes, see [the chapter on them](passes.md).
|
||||
|
||||
## `-r`/`--input-format`: input format
|
||||
|
||||
This flag is **deprecated** and **has no effect**.
|
||||
|
||||
Rustdoc only supports Rust source code and Markdown input formats. If the
|
||||
file ends in `.md` or `.markdown`, `rustdoc` treats it as a Markdown file.
|
||||
Otherwise, it assumes that the input file is Rust.
|
||||
|
|
|
@ -3,86 +3,9 @@
|
|||
Rustdoc has a concept called "passes". These are transformations that
|
||||
`rustdoc` runs on your documentation before producing its final output.
|
||||
|
||||
In addition to the passes below, check out the docs for these flags:
|
||||
Customizing passes is **deprecated**. The available passes are not considered stable and may
|
||||
change in any release.
|
||||
|
||||
* [`--passes`](command-line-arguments.md#--passes-add-more-rustdoc-passes)
|
||||
* [`--no-defaults`](command-line-arguments.md#--no-defaults-dont-run-default-passes)
|
||||
|
||||
## Default passes
|
||||
|
||||
By default, rustdoc will run some passes, namely:
|
||||
|
||||
* `strip-hidden`
|
||||
* `strip-private`
|
||||
* `collapse-docs`
|
||||
* `unindent-comments`
|
||||
|
||||
However, `strip-private` implies `strip-priv-imports`, and so effectively,
|
||||
all passes are run by default.
|
||||
|
||||
## `strip-hidden`
|
||||
|
||||
This pass implements the `#[doc(hidden)]` attribute. When this pass runs, it
|
||||
checks each item, and if it is annotated with this attribute, it removes it
|
||||
from `rustdoc`'s output.
|
||||
|
||||
Without this pass, these items will remain in the output.
|
||||
|
||||
## `unindent-comments`
|
||||
|
||||
When you write a doc comment like this:
|
||||
|
||||
```rust,no_run
|
||||
/// This is a documentation comment.
|
||||
# fn f() {}
|
||||
```
|
||||
|
||||
There's a space between the `///` and that `T`. That spacing isn't intended
|
||||
to be a part of the output; it's there for humans, to help separate the doc
|
||||
comment syntax from the text of the comment. This pass is what removes that
|
||||
space.
|
||||
|
||||
The exact rules are left under-specified so that we can fix issues that we find.
|
||||
|
||||
Without this pass, the exact number of spaces is preserved.
|
||||
|
||||
## `collapse-docs`
|
||||
|
||||
With this pass, multiple `#[doc]` attributes are converted into one single
|
||||
documentation string.
|
||||
|
||||
For example:
|
||||
|
||||
```rust,no_run
|
||||
#[doc = "This is the first line."]
|
||||
#[doc = "This is the second line."]
|
||||
# fn f() {}
|
||||
```
|
||||
|
||||
Gets collapsed into a single doc string of
|
||||
|
||||
```text
|
||||
This is the first line.
|
||||
This is the second line.
|
||||
```
|
||||
|
||||
## `strip-private`
|
||||
|
||||
This removes documentation for any non-public items, so for example:
|
||||
|
||||
```rust,no_run
|
||||
/// These are private docs.
|
||||
struct Private;
|
||||
|
||||
/// These are public docs.
|
||||
pub struct Public;
|
||||
```
|
||||
|
||||
This pass removes the docs for `Private`, since they're not public.
|
||||
|
||||
This pass implies `strip-priv-imports`.
|
||||
|
||||
## `strip-priv-imports`
|
||||
|
||||
This is the same as `strip-private`, but for `extern crate` and `use`
|
||||
statements instead of items.
|
||||
In the past the most common use case for customizing passes was to omit the `strip-private` pass.
|
||||
You can do this more easily, and without risk of the pass being changed, by passing
|
||||
[`--document-private-items`](./unstable-features.md#--document-private-items).
|
||||
|
|
|
@ -340,6 +340,30 @@ Some methodology notes about what rustdoc counts in this metric:
|
|||
Public items that are not documented can be seen with the built-in `missing_docs` lint. Private
|
||||
items that are not documented can be seen with Clippy's `missing_docs_in_private_items` lint.
|
||||
|
||||
## `-w`/`--output-format`: output format
|
||||
|
||||
When using
|
||||
[`--show-coverage`](https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html#--show-coverage-get-statistics-about-code-documentation-coverage),
|
||||
passing `--output-format json` will display the coverage information in JSON format. For example,
|
||||
here is the JSON for a file with one documented item and one undocumented item:
|
||||
|
||||
```rust
|
||||
/// This item has documentation
|
||||
pub fn foo() {}
|
||||
|
||||
pub fn no_documentation() {}
|
||||
```
|
||||
|
||||
```json
|
||||
{"no_std.rs":{"total":3,"with_docs":1,"total_examples":3,"with_examples":0}}
|
||||
```
|
||||
|
||||
Note that the third item is the crate root, which in this case is undocumented.
|
||||
|
||||
When not using `--show-coverage`, `--output-format json` emits documentation in the experimental
|
||||
[JSON format](https://github.com/rust-lang/rfcs/pull/2963). `--output-format html` has no effect,
|
||||
and is also accepted on stable toolchains.
|
||||
|
||||
### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
|
||||
|
||||
Using this flag looks like this:
|
||||
|
|
617
src/librustdoc/html/render/context.rs
Normal file
617
src/librustdoc/html/render/context.rs
Normal file
|
@ -0,0 +1,617 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::source_map::FileName;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::cache::{build_index, ExternalLocation};
|
||||
use super::print_item::{full_path, item_path, print_item};
|
||||
use super::write_shared::write_shared;
|
||||
use super::{
|
||||
print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS,
|
||||
CURRENT_DEPTH, INITIAL_IDS,
|
||||
};
|
||||
|
||||
use crate::clean::{self, AttributesExt};
|
||||
use crate::config::RenderOptions;
|
||||
use crate::docfs::{DocFS, PathError};
|
||||
use crate::error::Error;
|
||||
use crate::formats::cache::Cache;
|
||||
use crate::formats::item_type::ItemType;
|
||||
use crate::formats::FormatRenderer;
|
||||
use crate::html::escape::Escape;
|
||||
use crate::html::format::Buffer;
|
||||
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
|
||||
use crate::html::{layout, sources};
|
||||
|
||||
/// Major driving force in all rustdoc rendering. This contains information
|
||||
/// about where in the tree-like hierarchy rendering is occurring and controls
|
||||
/// how the current page is being rendered.
|
||||
///
|
||||
/// It is intended that this context is a lightweight object which can be fairly
|
||||
/// easily cloned because it is cloned per work-job (about once per item in the
|
||||
/// rustdoc tree).
|
||||
#[derive(Clone)]
|
||||
crate struct Context<'tcx> {
|
||||
/// Current hierarchy of components leading down to what's currently being
|
||||
/// rendered
|
||||
crate current: Vec<String>,
|
||||
/// The current destination folder of where HTML artifacts should be placed.
|
||||
/// This changes as the context descends into the module hierarchy.
|
||||
crate dst: PathBuf,
|
||||
/// A flag, which when `true`, will render pages which redirect to the
|
||||
/// real location of an item. This is used to allow external links to
|
||||
/// publicly reused items to redirect to the right location.
|
||||
crate render_redirect_pages: bool,
|
||||
/// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set
|
||||
/// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
|
||||
/// the crate.
|
||||
crate redirections: Option<Rc<RefCell<FxHashMap<String, String>>>>,
|
||||
/// The map used to ensure all generated 'id=' attributes are unique.
|
||||
pub(super) id_map: Rc<RefCell<IdMap>>,
|
||||
/// Tracks section IDs for `Deref` targets so they match in both the main
|
||||
/// body and the sidebar.
|
||||
pub(super) deref_id_map: Rc<RefCell<FxHashMap<DefId, String>>>,
|
||||
crate shared: Arc<SharedContext<'tcx>>,
|
||||
all: Rc<RefCell<AllTypes>>,
|
||||
/// Storage for the errors produced while generating documentation so they
|
||||
/// can be printed together at the end.
|
||||
crate errors: Rc<Receiver<String>>,
|
||||
crate cache: Rc<Cache>,
|
||||
}
|
||||
|
||||
impl<'tcx> Context<'tcx> {
|
||||
pub(super) fn path(&self, filename: &str) -> PathBuf {
|
||||
// We use splitn vs Path::extension here because we might get a filename
|
||||
// like `style.min.css` and we want to process that into
|
||||
// `style-suffix.min.css`. Path::extension would just return `css`
|
||||
// which would result in `style.min-suffix.css` which isn't what we
|
||||
// want.
|
||||
let (base, ext) = filename.split_once('.').unwrap();
|
||||
let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext);
|
||||
self.dst.join(&filename)
|
||||
}
|
||||
|
||||
pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
self.shared.tcx
|
||||
}
|
||||
|
||||
fn sess(&self) -> &'tcx Session {
|
||||
&self.shared.tcx.sess
|
||||
}
|
||||
|
||||
pub(super) fn derive_id(&self, id: String) -> String {
|
||||
let mut map = self.id_map.borrow_mut();
|
||||
map.derive(id)
|
||||
}
|
||||
|
||||
/// 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(&self, it: &clean::Item, pushname: bool) -> String {
|
||||
// A little unfortunate that this is done like this, but it sure
|
||||
// does make formatting *a lot* nicer.
|
||||
CURRENT_DEPTH.with(|slot| {
|
||||
slot.set(self.current.len());
|
||||
});
|
||||
|
||||
let mut title = if it.is_primitive() || it.is_keyword() {
|
||||
// No need to include the namespace for primitive types and keywords
|
||||
String::new()
|
||||
} else {
|
||||
self.current.join("::")
|
||||
};
|
||||
if pushname {
|
||||
if !title.is_empty() {
|
||||
title.push_str("::");
|
||||
}
|
||||
title.push_str(&it.name.unwrap().as_str());
|
||||
}
|
||||
title.push_str(" - Rust");
|
||||
let tyname = it.type_();
|
||||
let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc));
|
||||
let desc = if let Some(desc) = desc {
|
||||
desc
|
||||
} else if it.is_crate() {
|
||||
format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate)
|
||||
} else {
|
||||
format!(
|
||||
"API documentation for the Rust `{}` {} in crate `{}`.",
|
||||
it.name.as_ref().unwrap(),
|
||||
tyname,
|
||||
self.shared.layout.krate
|
||||
)
|
||||
};
|
||||
let keywords = make_item_keywords(it);
|
||||
let page = layout::Page {
|
||||
css_class: tyname.as_str(),
|
||||
root_path: &self.root_path(),
|
||||
static_root_path: self.shared.static_root_path.as_deref(),
|
||||
title: &title,
|
||||
description: &desc,
|
||||
keywords: &keywords,
|
||||
resource_suffix: &self.shared.resource_suffix,
|
||||
extra_scripts: &[],
|
||||
static_extra_scripts: &[],
|
||||
};
|
||||
|
||||
{
|
||||
self.id_map.borrow_mut().reset();
|
||||
self.id_map.borrow_mut().populate(&INITIAL_IDS);
|
||||
}
|
||||
|
||||
if !self.render_redirect_pages {
|
||||
layout::render(
|
||||
&self.shared.layout,
|
||||
&page,
|
||||
|buf: &mut _| print_sidebar(self, it, buf),
|
||||
|buf: &mut _| print_item(self, it, buf),
|
||||
&self.shared.style_files,
|
||||
)
|
||||
} else {
|
||||
if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) {
|
||||
let mut path = String::new();
|
||||
for name in &names[..names.len() - 1] {
|
||||
path.push_str(name);
|
||||
path.push('/');
|
||||
}
|
||||
path.push_str(&item_path(ty, names.last().unwrap()));
|
||||
match self.redirections {
|
||||
Some(ref redirections) => {
|
||||
let mut current_path = String::new();
|
||||
for name in &self.current {
|
||||
current_path.push_str(name);
|
||||
current_path.push('/');
|
||||
}
|
||||
current_path.push_str(&item_path(ty, names.last().unwrap()));
|
||||
redirections.borrow_mut().insert(current_path, path);
|
||||
}
|
||||
None => return layout::redirect(&format!("{}{}", self.root_path(), path)),
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a map of items shown in the sidebar to a plain-text summary of their docs.
|
||||
fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> {
|
||||
// BTreeMap instead of HashMap to get a sorted output
|
||||
let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
|
||||
for item in &m.items {
|
||||
if item.is_stripped() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let short = item.type_();
|
||||
let myname = match item.name {
|
||||
None => continue,
|
||||
Some(ref s) => s.to_string(),
|
||||
};
|
||||
let short = short.to_string();
|
||||
map.entry(short).or_default().push((
|
||||
myname,
|
||||
Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))),
|
||||
));
|
||||
}
|
||||
|
||||
if self.shared.sort_modules_alphabetically {
|
||||
for items in map.values_mut() {
|
||||
items.sort();
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
/// Generates a url appropriate for an `href` attribute back to the source of
|
||||
/// this item.
|
||||
///
|
||||
/// The url generated, when clicked, will redirect the browser back to the
|
||||
/// original source code.
|
||||
///
|
||||
/// If `None` is returned, then a source link couldn't be generated. This
|
||||
/// may happen, for example, with externally inlined items where the source
|
||||
/// of their crate documentation isn't known.
|
||||
pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
|
||||
if item.source.is_dummy() {
|
||||
return None;
|
||||
}
|
||||
let mut root = self.root_path();
|
||||
let mut path = String::new();
|
||||
let cnum = item.source.cnum(self.sess());
|
||||
|
||||
// We can safely ignore synthetic `SourceFile`s.
|
||||
let file = match item.source.filename(self.sess()) {
|
||||
FileName::Real(ref path) => path.local_path().to_path_buf(),
|
||||
_ => return None,
|
||||
};
|
||||
let file = &file;
|
||||
|
||||
let symbol;
|
||||
let (krate, path) = if cnum == LOCAL_CRATE {
|
||||
if let Some(path) = self.shared.local_sources.get(file) {
|
||||
(self.shared.layout.krate.as_str(), path)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? {
|
||||
(name, ref src, ExternalLocation::Local) => (name, src),
|
||||
(name, ref src, ExternalLocation::Remote(ref s)) => {
|
||||
root = s.to_string();
|
||||
(name, src)
|
||||
}
|
||||
(_, _, ExternalLocation::Unknown) => return None,
|
||||
};
|
||||
|
||||
sources::clean_path(&src_root, file, false, |component| {
|
||||
path.push_str(&component.to_string_lossy());
|
||||
path.push('/');
|
||||
});
|
||||
let mut fname = file.file_name().expect("source has no filename").to_os_string();
|
||||
fname.push(".html");
|
||||
path.push_str(&fname.to_string_lossy());
|
||||
symbol = krate.as_str();
|
||||
(&*symbol, &path)
|
||||
};
|
||||
|
||||
let loline = item.source.lo(self.sess()).line;
|
||||
let hiline = item.source.hi(self.sess()).line;
|
||||
let lines =
|
||||
if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
|
||||
Some(format!(
|
||||
"{root}src/{krate}/{path}#{lines}",
|
||||
root = Escape(&root),
|
||||
krate = krate,
|
||||
path = path,
|
||||
lines = lines
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the documentation for `crate` into the directory `dst`
|
||||
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
||||
fn descr() -> &'static str {
|
||||
"html"
|
||||
}
|
||||
|
||||
fn init(
|
||||
mut krate: clean::Crate,
|
||||
options: RenderOptions,
|
||||
edition: Edition,
|
||||
mut cache: Cache,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
) -> Result<(Self, clean::Crate), Error> {
|
||||
// need to save a copy of the options for rendering the index page
|
||||
let md_opts = options.clone();
|
||||
let RenderOptions {
|
||||
output,
|
||||
external_html,
|
||||
id_map,
|
||||
playground_url,
|
||||
sort_modules_alphabetically,
|
||||
themes: style_files,
|
||||
default_settings,
|
||||
extension_css,
|
||||
resource_suffix,
|
||||
static_root_path,
|
||||
generate_search_filter,
|
||||
unstable_features,
|
||||
generate_redirect_map,
|
||||
..
|
||||
} = options;
|
||||
|
||||
let src_root = match krate.src {
|
||||
FileName::Real(ref p) => match p.local_path().parent() {
|
||||
Some(p) => p.to_path_buf(),
|
||||
None => PathBuf::new(),
|
||||
},
|
||||
_ => PathBuf::new(),
|
||||
};
|
||||
// If user passed in `--playground-url` arg, we fill in crate name here
|
||||
let mut playground = None;
|
||||
if let Some(url) = playground_url {
|
||||
playground =
|
||||
Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url });
|
||||
}
|
||||
let mut layout = layout::Layout {
|
||||
logo: String::new(),
|
||||
favicon: String::new(),
|
||||
external_html,
|
||||
default_settings,
|
||||
krate: krate.name.to_string(),
|
||||
css_file_extension: extension_css,
|
||||
generate_search_filter,
|
||||
};
|
||||
let mut issue_tracker_base_url = None;
|
||||
let mut include_sources = true;
|
||||
|
||||
// Crawl the crate attributes looking for attributes which control how we're
|
||||
// going to emit HTML
|
||||
if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) {
|
||||
for attr in attrs.lists(sym::doc) {
|
||||
match (attr.name_or_empty(), attr.value_str()) {
|
||||
(sym::html_favicon_url, Some(s)) => {
|
||||
layout.favicon = s.to_string();
|
||||
}
|
||||
(sym::html_logo_url, Some(s)) => {
|
||||
layout.logo = s.to_string();
|
||||
}
|
||||
(sym::html_playground_url, Some(s)) => {
|
||||
playground = Some(markdown::Playground {
|
||||
crate_name: Some(krate.name.to_string()),
|
||||
url: s.to_string(),
|
||||
});
|
||||
}
|
||||
(sym::issue_tracker_base_url, Some(s)) => {
|
||||
issue_tracker_base_url = Some(s.to_string());
|
||||
}
|
||||
(sym::html_no_source, None) if attr.is_word() => {
|
||||
include_sources = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (sender, receiver) = channel();
|
||||
let mut scx = SharedContext {
|
||||
tcx,
|
||||
collapsed: krate.collapsed,
|
||||
src_root,
|
||||
include_sources,
|
||||
local_sources: Default::default(),
|
||||
issue_tracker_base_url,
|
||||
layout,
|
||||
created_dirs: Default::default(),
|
||||
sort_modules_alphabetically,
|
||||
style_files,
|
||||
resource_suffix,
|
||||
static_root_path,
|
||||
fs: DocFS::new(sender),
|
||||
edition,
|
||||
codes: ErrorCodes::from(unstable_features.is_nightly_build()),
|
||||
playground,
|
||||
};
|
||||
|
||||
// Add the default themes to the `Vec` of stylepaths
|
||||
//
|
||||
// Note that these must be added before `sources::render` is called
|
||||
// so that the resulting source pages are styled
|
||||
//
|
||||
// `light.css` is not disabled because it is the stylesheet that stays loaded
|
||||
// by the browser as the theme stylesheet. The theme system (hackily) works by
|
||||
// changing the href to this stylesheet. All other themes are disabled to
|
||||
// prevent rule conflicts
|
||||
scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false });
|
||||
scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true });
|
||||
scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true });
|
||||
|
||||
let dst = output;
|
||||
scx.ensure_dir(&dst)?;
|
||||
krate = sources::render(&dst, &mut scx, krate)?;
|
||||
|
||||
// Build our search index
|
||||
let index = build_index(&krate, &mut cache, tcx);
|
||||
|
||||
let mut cx = Context {
|
||||
current: Vec::new(),
|
||||
dst,
|
||||
render_redirect_pages: false,
|
||||
id_map: Rc::new(RefCell::new(id_map)),
|
||||
deref_id_map: Rc::new(RefCell::new(FxHashMap::default())),
|
||||
shared: Arc::new(scx),
|
||||
all: Rc::new(RefCell::new(AllTypes::new())),
|
||||
errors: Rc::new(receiver),
|
||||
cache: Rc::new(cache),
|
||||
redirections: if generate_redirect_map { Some(Default::default()) } else { None },
|
||||
};
|
||||
|
||||
CURRENT_DEPTH.with(|s| s.set(0));
|
||||
|
||||
// Write shared runs within a flock; disable thread dispatching of IO temporarily.
|
||||
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
|
||||
write_shared(&cx, &krate, index, &md_opts)?;
|
||||
Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
|
||||
Ok((cx, krate))
|
||||
}
|
||||
|
||||
fn after_krate(
|
||||
&mut self,
|
||||
krate: &clean::Crate,
|
||||
diag: &rustc_errors::Handler,
|
||||
) -> Result<(), Error> {
|
||||
let final_file = self.dst.join(&*krate.name.as_str()).join("all.html");
|
||||
let settings_file = self.dst.join("settings.html");
|
||||
let crate_name = krate.name;
|
||||
|
||||
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
|
||||
if !root_path.ends_with('/') {
|
||||
root_path.push('/');
|
||||
}
|
||||
let mut page = layout::Page {
|
||||
title: "List of all items in this crate",
|
||||
css_class: "mod",
|
||||
root_path: "../",
|
||||
static_root_path: self.shared.static_root_path.as_deref(),
|
||||
description: "List of all items in this crate",
|
||||
keywords: BASIC_KEYWORDS,
|
||||
resource_suffix: &self.shared.resource_suffix,
|
||||
extra_scripts: &[],
|
||||
static_extra_scripts: &[],
|
||||
};
|
||||
let sidebar = if let Some(ref version) = self.cache.crate_version {
|
||||
format!(
|
||||
"<p class=\"location\">Crate {}</p>\
|
||||
<div class=\"block version\">\
|
||||
<p>Version {}</p>\
|
||||
</div>\
|
||||
<a id=\"all-types\" href=\"index.html\"><p>Back to index</p></a>",
|
||||
crate_name,
|
||||
Escape(version),
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let all = self.all.replace(AllTypes::new());
|
||||
let v = layout::render(
|
||||
&self.shared.layout,
|
||||
&page,
|
||||
sidebar,
|
||||
|buf: &mut Buffer| all.print(buf),
|
||||
&self.shared.style_files,
|
||||
);
|
||||
self.shared.fs.write(&final_file, v.as_bytes())?;
|
||||
|
||||
// Generating settings page.
|
||||
page.title = "Rustdoc settings";
|
||||
page.description = "Settings of Rustdoc";
|
||||
page.root_path = "./";
|
||||
|
||||
let mut style_files = self.shared.style_files.clone();
|
||||
let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>";
|
||||
style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false });
|
||||
let v = layout::render(
|
||||
&self.shared.layout,
|
||||
&page,
|
||||
sidebar,
|
||||
settings(
|
||||
self.shared.static_root_path.as_deref().unwrap_or("./"),
|
||||
&self.shared.resource_suffix,
|
||||
&self.shared.style_files,
|
||||
)?,
|
||||
&style_files,
|
||||
);
|
||||
self.shared.fs.write(&settings_file, v.as_bytes())?;
|
||||
if let Some(redirections) = self.redirections.take() {
|
||||
if !redirections.borrow().is_empty() {
|
||||
let redirect_map_path =
|
||||
self.dst.join(&*krate.name.as_str()).join("redirect-map.json");
|
||||
let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
|
||||
self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?;
|
||||
self.shared.fs.write(&redirect_map_path, paths.as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush pending errors.
|
||||
Arc::get_mut(&mut self.shared).unwrap().fs.close();
|
||||
let nb_errors = self.errors.iter().map(|err| diag.struct_err(&err).emit()).count();
|
||||
if nb_errors > 0 {
|
||||
Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), ""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> {
|
||||
// Stripped modules survive the rustdoc passes (i.e., `strip-private`)
|
||||
// if they contain impls for public types. These modules can also
|
||||
// contain items such as publicly re-exported structures.
|
||||
//
|
||||
// External crates will provide links to these structures, so
|
||||
// these modules are recursed into, but not rendered normally
|
||||
// (a flag on the context).
|
||||
if !self.render_redirect_pages {
|
||||
self.render_redirect_pages = item.is_stripped();
|
||||
}
|
||||
let scx = &self.shared;
|
||||
self.dst.push(item_name);
|
||||
self.current.push(item_name.to_owned());
|
||||
|
||||
info!("Recursing into {}", self.dst.display());
|
||||
|
||||
let buf = self.render_item(item, false);
|
||||
// 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)?;
|
||||
let joint_dst = self.dst.join("index.html");
|
||||
scx.fs.write(&joint_dst, buf.as_bytes())?;
|
||||
}
|
||||
|
||||
// Render sidebar-items.js used throughout this module.
|
||||
if !self.render_redirect_pages {
|
||||
let module = match *item.kind {
|
||||
clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let items = self.build_sidebar_items(module);
|
||||
let js_dst = self.dst.join("sidebar-items.js");
|
||||
let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap());
|
||||
scx.fs.write(&js_dst, &v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> {
|
||||
info!("Recursed; leaving {}", self.dst.display());
|
||||
|
||||
// Go back to where we were at
|
||||
self.dst.pop();
|
||||
self.current.pop();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn item(&mut self, item: clean::Item) -> Result<(), Error> {
|
||||
// Stripped modules survive the rustdoc passes (i.e., `strip-private`)
|
||||
// if they contain impls for public types. These modules can also
|
||||
// contain items such as publicly re-exported structures.
|
||||
//
|
||||
// External crates will provide links to these structures, so
|
||||
// these modules are recursed into, but not rendered normally
|
||||
// (a flag on the context).
|
||||
if !self.render_redirect_pages {
|
||||
self.render_redirect_pages = item.is_stripped();
|
||||
}
|
||||
|
||||
let buf = self.render_item(&item, true);
|
||||
// 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 = &item_path(item_type, &name.as_str());
|
||||
self.shared.ensure_dir(&self.dst)?;
|
||||
let joint_dst = self.dst.join(file_name);
|
||||
self.shared.fs.write(&joint_dst, buf.as_bytes())?;
|
||||
|
||||
if !self.render_redirect_pages {
|
||||
self.all.borrow_mut().append(full_path(self, &item), &item_type);
|
||||
}
|
||||
// If the item is a macro, redirect from the old macro URL (with !)
|
||||
// to the new one (without).
|
||||
if item_type == ItemType::Macro {
|
||||
let redir_name = format!("{}.{}!.html", item_type, name);
|
||||
if let Some(ref redirections) = self.redirections {
|
||||
let crate_name = &self.shared.layout.krate;
|
||||
redirections.borrow_mut().insert(
|
||||
format!("{}/{}", crate_name, redir_name),
|
||||
format!("{}/{}", crate_name, file_name),
|
||||
);
|
||||
} else {
|
||||
let v = layout::redirect(file_name);
|
||||
let redir_dst = self.dst.join(redir_name);
|
||||
self.shared.fs.write(&redir_dst, v.as_bytes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cache(&self) -> &Cache {
|
||||
&self.cache
|
||||
}
|
||||
}
|
||||
|
||||
fn make_item_keywords(it: &clean::Item) -> String {
|
||||
format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap())
|
||||
}
|
File diff suppressed because it is too large
Load diff
1420
src/librustdoc/html/render/print_item.rs
Normal file
1420
src/librustdoc/html/render/print_item.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,7 @@
|
|||
use super::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::print_item::compare_names;
|
||||
use super::{AllTypes, Buffer};
|
||||
|
||||
#[test]
|
||||
fn test_compare_names() {
|
||||
|
|
542
src/librustdoc/html/render/write_shared.rs
Normal file
542
src/librustdoc/html/render/write_shared.rs
Normal file
|
@ -0,0 +1,542 @@
|
|||
use std::ffi::OsStr;
|
||||
use std::fmt::Write;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_data_structures::flock;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS};
|
||||
use crate::clean::Crate;
|
||||
use crate::config::RenderOptions;
|
||||
use crate::docfs::{DocFS, PathError};
|
||||
use crate::error::Error;
|
||||
use crate::formats::FormatRenderer;
|
||||
use crate::html::{layout, static_files};
|
||||
|
||||
pub(super) fn write_shared(
|
||||
cx: &Context<'_>,
|
||||
krate: &Crate,
|
||||
search_index: String,
|
||||
options: &RenderOptions,
|
||||
) -> Result<(), Error> {
|
||||
// Write out the shared files. Note that these are shared among all rustdoc
|
||||
// docs placed in the output directory, so this needs to be a synchronized
|
||||
// operation with respect to all other rustdocs running around.
|
||||
let lock_file = cx.dst.join(".lock");
|
||||
let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
|
||||
|
||||
// Add all the static files. These may already exist, but we just
|
||||
// overwrite them anyway to make sure that they're fresh and up-to-date.
|
||||
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("rustdoc.css"),
|
||||
static_files::RUSTDOC_CSS,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("settings.css"),
|
||||
static_files::SETTINGS_CSS,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("noscript.css"),
|
||||
static_files::NOSCRIPT_CSS,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
|
||||
// To avoid "light.css" to be overwritten, we'll first run over the received themes and only
|
||||
// then we'll run over the "official" styles.
|
||||
let mut themes: FxHashSet<String> = FxHashSet::default();
|
||||
|
||||
for entry in &cx.shared.style_files {
|
||||
let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path);
|
||||
let extension =
|
||||
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
|
||||
|
||||
// Handle the official themes
|
||||
match theme {
|
||||
"light" => write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("light.css"),
|
||||
static_files::themes::LIGHT,
|
||||
options.enable_minification,
|
||||
)?,
|
||||
"dark" => write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("dark.css"),
|
||||
static_files::themes::DARK,
|
||||
options.enable_minification,
|
||||
)?,
|
||||
"ayu" => write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("ayu.css"),
|
||||
static_files::themes::AYU,
|
||||
options.enable_minification,
|
||||
)?,
|
||||
_ => {
|
||||
// Handle added third-party themes
|
||||
let content = try_err!(fs::read(&entry.path), &entry.path);
|
||||
cx.shared
|
||||
.fs
|
||||
.write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?;
|
||||
}
|
||||
};
|
||||
|
||||
themes.insert(theme.to_owned());
|
||||
}
|
||||
|
||||
let write = |p, c| cx.shared.fs.write(p, c);
|
||||
if (*cx.shared).layout.logo.is_empty() {
|
||||
write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?;
|
||||
}
|
||||
if (*cx.shared).layout.favicon.is_empty() {
|
||||
write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?;
|
||||
write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?;
|
||||
write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?;
|
||||
}
|
||||
write(cx.path("brush.svg"), static_files::BRUSH_SVG)?;
|
||||
write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?;
|
||||
write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?;
|
||||
|
||||
let mut themes: Vec<&String> = themes.iter().collect();
|
||||
themes.sort();
|
||||
// To avoid theme switch latencies as much as possible, we put everything theme related
|
||||
// at the beginning of the html files into another js file.
|
||||
let theme_js = format!(
|
||||
r#"var themes = document.getElementById("theme-choices");
|
||||
var themePicker = document.getElementById("theme-picker");
|
||||
|
||||
function showThemeButtonState() {{
|
||||
themes.style.display = "block";
|
||||
themePicker.style.borderBottomRightRadius = "0";
|
||||
themePicker.style.borderBottomLeftRadius = "0";
|
||||
}}
|
||||
|
||||
function hideThemeButtonState() {{
|
||||
themes.style.display = "none";
|
||||
themePicker.style.borderBottomRightRadius = "3px";
|
||||
themePicker.style.borderBottomLeftRadius = "3px";
|
||||
}}
|
||||
|
||||
function switchThemeButtonState() {{
|
||||
if (themes.style.display === "block") {{
|
||||
hideThemeButtonState();
|
||||
}} else {{
|
||||
showThemeButtonState();
|
||||
}}
|
||||
}};
|
||||
|
||||
function handleThemeButtonsBlur(e) {{
|
||||
var active = document.activeElement;
|
||||
var related = e.relatedTarget;
|
||||
|
||||
if (active.id !== "theme-picker" &&
|
||||
(!active.parentNode || active.parentNode.id !== "theme-choices") &&
|
||||
(!related ||
|
||||
(related.id !== "theme-picker" &&
|
||||
(!related.parentNode || related.parentNode.id !== "theme-choices")))) {{
|
||||
hideThemeButtonState();
|
||||
}}
|
||||
}}
|
||||
|
||||
themePicker.onclick = switchThemeButtonState;
|
||||
themePicker.onblur = handleThemeButtonsBlur;
|
||||
{}.forEach(function(item) {{
|
||||
var but = document.createElement("button");
|
||||
but.textContent = item;
|
||||
but.onclick = function(el) {{
|
||||
switchTheme(currentTheme, mainTheme, item, true);
|
||||
useSystemTheme(false);
|
||||
}};
|
||||
but.onblur = handleThemeButtonsBlur;
|
||||
themes.appendChild(but);
|
||||
}});"#,
|
||||
serde_json::to_string(&themes).unwrap()
|
||||
);
|
||||
|
||||
write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?;
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("main.js"),
|
||||
static_files::MAIN_JS,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("settings.js"),
|
||||
static_files::SETTINGS_JS,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
if cx.shared.include_sources {
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("source-script.js"),
|
||||
static_files::sidebar::SOURCE_SCRIPT,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
}
|
||||
|
||||
{
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("storage.js"),
|
||||
&format!(
|
||||
"var resourcesSuffix = \"{}\";{}",
|
||||
cx.shared.resource_suffix,
|
||||
static_files::STORAGE_JS
|
||||
),
|
||||
options.enable_minification,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(ref css) = cx.shared.layout.css_file_extension {
|
||||
let out = cx.path("theme.css");
|
||||
let buffer = try_err!(fs::read_to_string(css), css);
|
||||
if !options.enable_minification {
|
||||
cx.shared.fs.write(&out, &buffer)?;
|
||||
} else {
|
||||
write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
|
||||
}
|
||||
}
|
||||
write_minify(
|
||||
&cx.shared.fs,
|
||||
cx.path("normalize.css"),
|
||||
static_files::NORMALIZE_CSS,
|
||||
options.enable_minification,
|
||||
)?;
|
||||
write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?;
|
||||
write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?;
|
||||
write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?;
|
||||
write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?;
|
||||
write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?;
|
||||
write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?;
|
||||
write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?;
|
||||
write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?;
|
||||
write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?;
|
||||
write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?;
|
||||
write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?;
|
||||
write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?;
|
||||
write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?;
|
||||
write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?;
|
||||
write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?;
|
||||
|
||||
fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> {
|
||||
let mut ret = Vec::new();
|
||||
let mut krates = Vec::new();
|
||||
|
||||
if path.exists() {
|
||||
let prefix = format!(r#"{}["{}"]"#, key, krate);
|
||||
for line in BufReader::new(File::open(path)?).lines() {
|
||||
let line = line?;
|
||||
if !line.starts_with(key) {
|
||||
continue;
|
||||
}
|
||||
if line.starts_with(&prefix) {
|
||||
continue;
|
||||
}
|
||||
ret.push(line.to_string());
|
||||
krates.push(
|
||||
line[key.len() + 2..]
|
||||
.split('"')
|
||||
.next()
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(String::new),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok((ret, krates))
|
||||
}
|
||||
|
||||
fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
|
||||
let mut ret = Vec::new();
|
||||
let mut krates = Vec::new();
|
||||
|
||||
if path.exists() {
|
||||
let prefix = format!("\"{}\"", krate);
|
||||
for line in BufReader::new(File::open(path)?).lines() {
|
||||
let line = line?;
|
||||
if !line.starts_with('"') {
|
||||
continue;
|
||||
}
|
||||
if line.starts_with(&prefix) {
|
||||
continue;
|
||||
}
|
||||
if line.ends_with(",\\") {
|
||||
ret.push(line[..line.len() - 2].to_string());
|
||||
} else {
|
||||
// Ends with "\\" (it's the case for the last added crate line)
|
||||
ret.push(line[..line.len() - 1].to_string());
|
||||
}
|
||||
krates.push(
|
||||
line.split('"')
|
||||
.find(|s| !s.is_empty())
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(String::new),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok((ret, krates))
|
||||
}
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Hierarchy {
|
||||
elem: OsString,
|
||||
children: FxHashMap<OsString, Hierarchy>,
|
||||
elems: FxHashSet<OsString>,
|
||||
}
|
||||
|
||||
impl Hierarchy {
|
||||
fn new(elem: OsString) -> Hierarchy {
|
||||
Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
|
||||
}
|
||||
|
||||
fn to_json_string(&self) -> String {
|
||||
let mut subs: Vec<&Hierarchy> = self.children.values().collect();
|
||||
subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
|
||||
let mut files = self
|
||||
.elems
|
||||
.iter()
|
||||
.map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
|
||||
.collect::<Vec<_>>();
|
||||
files.sort_unstable();
|
||||
let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().join(",");
|
||||
let dirs =
|
||||
if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) };
|
||||
let files = files.join(",");
|
||||
let files =
|
||||
if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) };
|
||||
format!(
|
||||
"{{\"name\":\"{name}\"{dirs}{files}}}",
|
||||
name = self.elem.to_str().expect("invalid osstring conversion"),
|
||||
dirs = dirs,
|
||||
files = files
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if cx.shared.include_sources {
|
||||
let mut hierarchy = Hierarchy::new(OsString::new());
|
||||
for source in cx
|
||||
.shared
|
||||
.local_sources
|
||||
.iter()
|
||||
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
|
||||
{
|
||||
let mut h = &mut hierarchy;
|
||||
let mut elems = source
|
||||
.components()
|
||||
.filter_map(|s| match s {
|
||||
Component::Normal(s) => Some(s.to_owned()),
|
||||
_ => None,
|
||||
})
|
||||
.peekable();
|
||||
loop {
|
||||
let cur_elem = elems.next().expect("empty file path");
|
||||
if elems.peek().is_none() {
|
||||
h.elems.insert(cur_elem);
|
||||
break;
|
||||
} else {
|
||||
let e = cur_elem.clone();
|
||||
h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
|
||||
let (mut all_sources, _krates) =
|
||||
try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst);
|
||||
all_sources.push(format!(
|
||||
"sourcesIndex[\"{}\"] = {};",
|
||||
&krate.name,
|
||||
hierarchy.to_json_string()
|
||||
));
|
||||
all_sources.sort();
|
||||
let v = format!(
|
||||
"var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n",
|
||||
all_sources.join("\n")
|
||||
);
|
||||
cx.shared.fs.write(&dst, v.as_bytes())?;
|
||||
}
|
||||
|
||||
// Update the search index and crate list.
|
||||
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
|
||||
let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
|
||||
all_indexes.push(search_index);
|
||||
krates.push(krate.name.to_string());
|
||||
krates.sort();
|
||||
|
||||
// Sort the indexes by crate so the file will be generated identically even
|
||||
// with rustdoc running in parallel.
|
||||
all_indexes.sort();
|
||||
{
|
||||
let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
|
||||
v.push_str(&all_indexes.join(",\\\n"));
|
||||
v.push_str("\\\n}');\ninitSearch(searchIndex);");
|
||||
cx.shared.fs.write(&dst, &v)?;
|
||||
}
|
||||
|
||||
let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
|
||||
let crate_list =
|
||||
format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
|
||||
cx.shared.fs.write(&crate_list_dst, &crate_list)?;
|
||||
|
||||
if options.enable_index_page {
|
||||
if let Some(index_page) = options.index_page.clone() {
|
||||
let mut md_opts = options.clone();
|
||||
md_opts.output = cx.dst.clone();
|
||||
md_opts.external_html = (*cx.shared).layout.external_html.clone();
|
||||
|
||||
crate::markdown::render(&index_page, md_opts, cx.shared.edition)
|
||||
.map_err(|e| Error::new(e, &index_page))?;
|
||||
} else {
|
||||
let dst = cx.dst.join("index.html");
|
||||
let page = layout::Page {
|
||||
title: "Index of crates",
|
||||
css_class: "mod",
|
||||
root_path: "./",
|
||||
static_root_path: cx.shared.static_root_path.as_deref(),
|
||||
description: "List of crates",
|
||||
keywords: BASIC_KEYWORDS,
|
||||
resource_suffix: &cx.shared.resource_suffix,
|
||||
extra_scripts: &[],
|
||||
static_extra_scripts: &[],
|
||||
};
|
||||
|
||||
let content = format!(
|
||||
"<h1 class=\"fqn\">\
|
||||
<span class=\"in-band\">List of all crates</span>\
|
||||
</h1><ul class=\"crate mod\">{}</ul>",
|
||||
krates
|
||||
.iter()
|
||||
.map(|s| {
|
||||
format!(
|
||||
"<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
|
||||
ensure_trailing_slash(s),
|
||||
s
|
||||
)
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
|
||||
cx.shared.fs.write(&dst, v.as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the list of all implementors for traits
|
||||
let dst = cx.dst.join("implementors");
|
||||
for (&did, imps) in &cx.cache.implementors {
|
||||
// Private modules can leak through to this phase of rustdoc, which
|
||||
// could contain implementations for otherwise private types. In some
|
||||
// rare cases we could find an implementation for an item which wasn't
|
||||
// indexed, so we just skip this step in that case.
|
||||
//
|
||||
// FIXME: this is a vague explanation for why this can't be a `get`, in
|
||||
// theory it should be...
|
||||
let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
|
||||
Some(p) => p,
|
||||
None => match cx.cache.external_paths.get(&did) {
|
||||
Some(p) => p,
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Implementor {
|
||||
text: String,
|
||||
synthetic: bool,
|
||||
types: Vec<String>,
|
||||
}
|
||||
|
||||
let implementors = imps
|
||||
.iter()
|
||||
.filter_map(|imp| {
|
||||
// If the trait and implementation are in the same crate, then
|
||||
// there's no need to emit information about it (there's inlining
|
||||
// going on). If they're in different crates then the crate defining
|
||||
// the trait will be interested in our implementation.
|
||||
//
|
||||
// If the implementation is from another crate then that crate
|
||||
// should add it.
|
||||
if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() {
|
||||
None
|
||||
} else {
|
||||
Some(Implementor {
|
||||
text: imp.inner_impl().print(cx.cache(), false).to_string(),
|
||||
synthetic: imp.inner_impl().synthetic,
|
||||
types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Only create a js file if we have impls to add to it. If the trait is
|
||||
// documented locally though we always create the file to avoid dead
|
||||
// links.
|
||||
if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let implementors = format!(
|
||||
r#"implementors["{}"] = {};"#,
|
||||
krate.name,
|
||||
serde_json::to_string(&implementors).unwrap()
|
||||
);
|
||||
|
||||
let mut mydst = dst.clone();
|
||||
for part in &remote_path[..remote_path.len() - 1] {
|
||||
mydst.push(part);
|
||||
}
|
||||
cx.shared.ensure_dir(&mydst)?;
|
||||
mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));
|
||||
|
||||
let (mut all_implementors, _) =
|
||||
try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst);
|
||||
all_implementors.push(implementors);
|
||||
// Sort the implementors by crate so the file will be generated
|
||||
// identically even with rustdoc running in parallel.
|
||||
all_implementors.sort();
|
||||
|
||||
let mut v = String::from("(function() {var implementors = {};\n");
|
||||
for implementor in &all_implementors {
|
||||
writeln!(v, "{}", *implementor).unwrap();
|
||||
}
|
||||
v.push_str(
|
||||
"if (window.register_implementors) {\
|
||||
window.register_implementors(implementors);\
|
||||
} else {\
|
||||
window.pending_implementors = implementors;\
|
||||
}",
|
||||
);
|
||||
v.push_str("})()");
|
||||
cx.shared.fs.write(&mydst, &v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_minify(
|
||||
fs: &DocFS,
|
||||
dst: PathBuf,
|
||||
contents: &str,
|
||||
enable_minification: bool,
|
||||
) -> Result<(), Error> {
|
||||
if enable_minification {
|
||||
if dst.extension() == Some(&OsStr::new("css")) {
|
||||
let res = try_none!(minifier::css::minify(contents).ok(), &dst);
|
||||
fs.write(dst, res.as_bytes())
|
||||
} else {
|
||||
fs.write(dst, minifier::js::minify(contents).as_bytes())
|
||||
}
|
||||
} else {
|
||||
fs.write(dst, contents.as_bytes())
|
||||
}
|
||||
}
|
|
@ -9,8 +9,10 @@ use crate::clean::*;
|
|||
use crate::core::DocContext;
|
||||
use crate::fold::DocFolder;
|
||||
use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
|
||||
use crate::visit_ast::inherits_doc_hidden;
|
||||
use rustc_middle::lint::LintLevelSource;
|
||||
use rustc_session::lint;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
|
||||
name: "check-private-items-doc-tests",
|
||||
|
@ -51,7 +53,8 @@ impl crate::doctest::Tester for Tests {
|
|||
}
|
||||
|
||||
crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
|
||||
if matches!(
|
||||
if !cx.cache.access_levels.is_public(item.def_id)
|
||||
|| matches!(
|
||||
*item.kind,
|
||||
clean::StructFieldItem(_)
|
||||
| clean::VariantItem(_)
|
||||
|
@ -64,10 +67,16 @@ crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> boo
|
|||
| clean::ImportItem(_)
|
||||
| clean::PrimitiveItem(_)
|
||||
| clean::KeywordItem(_)
|
||||
) {
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_local());
|
||||
if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden)
|
||||
|| inherits_doc_hidden(cx.tcx, hir_id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id);
|
||||
level != lint::Level::Allow || matches!(source, LintLevelSource::Default)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,16 @@ fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
|
|||
std::iter::once(crate_name).chain(relative).collect()
|
||||
}
|
||||
|
||||
crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool {
|
||||
while let Some(id) = tcx.hir().get_enclosing_scope(node) {
|
||||
node = id;
|
||||
if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// Also, is there some reason that this doesn't use the 'visit'
|
||||
// framework from syntax?.
|
||||
|
||||
|
@ -158,19 +168,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
|||
om: &mut Module<'tcx>,
|
||||
please_inline: bool,
|
||||
) -> bool {
|
||||
fn inherits_doc_hidden(cx: &core::DocContext<'_>, mut node: hir::HirId) -> bool {
|
||||
while let Some(id) = cx.tcx.hir().get_enclosing_scope(node) {
|
||||
node = id;
|
||||
if cx.tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
|
||||
return true;
|
||||
}
|
||||
if node == hir::CRATE_HIR_ID {
|
||||
break;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
debug!("maybe_inline_local res: {:?}", res);
|
||||
|
||||
let tcx = self.cx.tcx;
|
||||
|
@ -212,7 +209,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
|
|||
};
|
||||
|
||||
let is_private = !self.cx.cache.access_levels.is_public(res_did);
|
||||
let is_hidden = inherits_doc_hidden(self.cx, res_hir_id);
|
||||
let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id);
|
||||
|
||||
// Only inline if requested or if the item would otherwise be stripped.
|
||||
if (!please_inline && !is_private && !is_hidden) || is_no_inline {
|
||||
|
|
|
@ -12,16 +12,16 @@
|
|||
/// ```
|
||||
/// println!("hello");
|
||||
/// ```
|
||||
fn test() {
|
||||
pub fn test() {
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
mod module1 { //~ ERROR
|
||||
pub mod module1 { //~ ERROR
|
||||
}
|
||||
|
||||
#[allow(rustdoc::missing_doc_code_examples)]
|
||||
/// doc
|
||||
mod module2 {
|
||||
pub mod module2 {
|
||||
|
||||
/// doc
|
||||
pub fn test() {}
|
||||
|
@ -63,9 +63,22 @@ pub enum Enum {
|
|||
/// Doc
|
||||
//~^ ERROR
|
||||
#[repr(C)]
|
||||
union Union {
|
||||
pub union Union {
|
||||
/// Doc, but no code example and it's fine!
|
||||
a: i32,
|
||||
/// Doc, but no code example and it's fine!
|
||||
b: f32,
|
||||
}
|
||||
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod foo {
|
||||
pub fn bar() {}
|
||||
}
|
||||
|
||||
fn babar() {}
|
||||
|
||||
|
||||
mod fofoo {
|
||||
pub fn tadam() {}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
error: missing code example in this documentation
|
||||
--> $DIR/lint-missing-doc-code-example.rs:19:1
|
||||
|
|
||||
LL | / mod module1 {
|
||||
LL | / pub mod module1 {
|
||||
LL | | }
|
||||
| |_^
|
||||
|
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Regression test for #81712.
|
||||
|
||||
#![feature(generic_associated_types)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
trait A {
|
||||
type BType: B<AType = Self>;
|
||||
}
|
||||
|
||||
trait B {
|
||||
type AType: A<BType = Self>;
|
||||
}
|
||||
trait C {
|
||||
type DType<T>: D<T, CType = Self>;
|
||||
//~^ ERROR: missing generics for associated type `C::DType` [E0107]
|
||||
}
|
||||
trait D<T> {
|
||||
type CType: C<DType = Self>;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,19 @@
|
|||
error[E0107]: missing generics for associated type `C::DType`
|
||||
--> $DIR/issue-81712-cyclic-traits.rs:14:10
|
||||
|
|
||||
LL | type DType<T>: D<T, CType = Self>;
|
||||
| ^^^^^ expected 1 type argument
|
||||
|
|
||||
note: associated type defined here, with 1 type parameter: `T`
|
||||
--> $DIR/issue-81712-cyclic-traits.rs:14:10
|
||||
|
|
||||
LL | type DType<T>: D<T, CType = Self>;
|
||||
| ^^^^^ -
|
||||
help: use angle brackets to add missing type argument
|
||||
|
|
||||
LL | type DType<T><T>: D<T, CType = Self>;
|
||||
| ^^^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0107`.
|
Loading…
Add table
Add a link
Reference in a new issue