This flag allows you to keep doctest executables around after they're compiled or run.
Usually, rustdoc will immediately discard a compiled doctest after it's been tested, but
with this option, you can keep those binaries around for farther testing.
+
+### `--show-coverage`: calculate the percentage of items with documentation
+
+Using this flag looks like this:
+
+```bash
+$ rustdoc src/lib.rs -Z unstable-options --show-coverage
+```
+
+If you want to determine how many items in your crate are documented, pass this flag to rustdoc.
+When it receives this flag, it will count the public items in your crate that have documentation,
+and print out the counts and a percentage instead of generating docs.
+
+Some methodology notes about what rustdoc counts in this metric:
+
+* Rustdoc will only count items from your crate (i.e. items re-exported from other crates don't
+ count).
+* Since trait implementations can inherit documentation from their trait, it will count trait impl
+ blocks separately, and show totals both with and without trait impls included.
+* Inherent impl blocks are not counted, even though their doc comments are displayed, because the
+ common pattern in Rust code is to write all inherent methods into the same impl block.
/// Whether to display warnings during doc generation or while gathering doctests. By default,
/// all non-rustdoc-specific lints are allowed when generating docs.
pub display_warnings: bool,
+ /// Whether to run the `calculate-doc-coverage` pass, which counts the number of public items
+ /// with and without documentation.
+ pub show_coverage: bool,
// Options that alter generated documentation pages
.field("default_passes", &self.default_passes)
.field("manual_passes", &self.manual_passes)
.field("display_warnings", &self.display_warnings)
+ .field("show_coverage", &self.show_coverage)
.field("crate_version", &self.crate_version)
.field("render_options", &self.render_options)
.finish()
for &name in passes::DEFAULT_PRIVATE_PASSES {
println!("{:>20}", name);
}
+ println!("\nPasses run with `--show-coverage`:");
+ for &name in passes::DEFAULT_COVERAGE_PASSES {
+ println!("{:>20}", name);
+ }
return Err(0);
}
let default_passes = if matches.opt_present("no-defaults") {
passes::DefaultPassOption::None
+ } else if matches.opt_present("show-coverage") {
+ passes::DefaultPassOption::Coverage
} else if matches.opt_present("document-private-items") {
passes::DefaultPassOption::Private
} else {
passes::DefaultPassOption::Default
};
let manual_passes = matches.opt_strs("passes");
+ let show_coverage = matches.opt_present("show-coverage");
let crate_name = matches.opt_str("crate-name");
let playground_url = matches.opt_str("playground-url");
default_passes,
manual_passes,
display_warnings,
+ show_coverage,
crate_version,
persist_doctests,
render_options: RenderOptions {
info!("Executing passes");
- for pass in &passes {
- match passes::find_pass(pass).map(|p| p.pass) {
- Some(pass) => krate = pass(krate, &ctxt),
- None => error!("unknown pass {}, skipping", *pass),
+ for pass_name in &passes {
+ match passes::find_pass(pass_name).map(|p| p.pass) {
+ Some(pass) => {
+ debug!("running pass {}", pass_name);
+ krate = pass(krate, &ctxt);
+ }
+ None => error!("unknown pass {}, skipping", *pass_name),
}
}
"generate-redirect-pages",
"Generate extra pages to support legacy URLs and tool links")
}),
+ unstable("show-coverage", |o| {
+ o.optflag("",
+ "show-coverage",
+ "calculate percentage of public items with documentation")
+ }),
]
}
let diag_opts = (options.error_format,
options.debugging_options.treat_err_as_bug,
options.debugging_options.ui_testing);
+ let show_coverage = options.show_coverage;
rust_input(options, move |out| {
+ if show_coverage {
+ // if we ran coverage, bail early, we don't need to also generate docs at this point
+ // (also we didn't load in any of the useful passes)
+ return rustc_driver::EXIT_SUCCESS;
+ }
+
let Output { krate, passes, renderinfo, renderopts } = out;
info!("going to format");
let (error_format, treat_err_as_bug, ui_testing) = diag_opts;
--- /dev/null
+use crate::clean;
+use crate::core::DocContext;
+use crate::fold::{self, DocFolder};
+use crate::passes::Pass;
+
+use syntax::attr;
+
+pub const CALCULATE_DOC_COVERAGE: Pass = Pass {
+ name: "calculate-doc-coverage",
+ pass: calculate_doc_coverage,
+ description: "counts the number of items with and without documentation",
+};
+
+fn calculate_doc_coverage(krate: clean::Crate, _: &DocContext<'_, '_, '_>) -> clean::Crate {
+ let mut calc = CoverageCalculator::default();
+ let krate = calc.fold_crate(krate);
+
+ let total_minus_traits = calc.total - calc.total_trait_impls;
+ let docs_minus_traits = calc.with_docs - calc.trait_impls_with_docs;
+
+ print!("Rustdoc found {}/{} items with documentation", calc.with_docs, calc.total);
+ println!(" ({}/{} not counting trait impls)", docs_minus_traits, total_minus_traits);
+
+ if calc.total > 0 {
+ let percentage = (calc.with_docs as f64 * 100.0) / calc.total as f64;
+ let percentage_minus_traits =
+ (docs_minus_traits as f64 * 100.0) / total_minus_traits as f64;
+ println!(" Score: {:.1}% ({:.1}% not counting trait impls)",
+ percentage, percentage_minus_traits);
+ }
+
+ krate
+}
+
+#[derive(Default)]
+struct CoverageCalculator {
+ total: usize,
+ with_docs: usize,
+ total_trait_impls: usize,
+ trait_impls_with_docs: usize,
+}
+
+impl fold::DocFolder for CoverageCalculator {
+ fn fold_item(&mut self, i: clean::Item) -> Option<clean::Item> {
+ match i.inner {
+ clean::StrippedItem(..) => {}
+ clean::ImplItem(ref impl_)
+ if attr::contains_name(&i.attrs.other_attrs, "automatically_derived")
+ || impl_.synthetic || impl_.blanket_impl.is_some() =>
+ {
+ // skip counting anything inside these impl blocks
+ // FIXME(misdreavus): need to also find items that came out of a derive macro
+ return Some(i);
+ }
+ // non-local items are skipped because they can be out of the users control, especially
+ // in the case of trait impls, which rustdoc eagerly inlines
+ _ => if i.def_id.is_local() {
+ let has_docs = !i.attrs.doc_strings.is_empty();
+
+ if let clean::ImplItem(ref i) = i.inner {
+ if let Some(ref tr) = i.trait_ {
+ debug!("counting impl {:#} for {:#}", tr, i.for_);
+
+ self.total += 1;
+ if has_docs {
+ self.with_docs += 1;
+ }
+
+ // trait impls inherit their docs from the trait definition, so documenting
+ // them can be considered optional
+
+ self.total_trait_impls += 1;
+ if has_docs {
+ self.trait_impls_with_docs += 1;
+ }
+
+ for it in &i.items {
+ self.total_trait_impls += 1;
+ if !it.attrs.doc_strings.is_empty() {
+ self.trait_impls_with_docs += 1;
+ }
+ }
+ } else {
+ // inherent impls *can* be documented, and those docs show up, but in most
+ // cases it doesn't make sense, as all methods on a type are in one single
+ // impl block
+ debug!("not counting impl {:#}", i.for_);
+ }
+ } else {
+ debug!("counting {} {:?}", i.type_(), i.name);
+ self.total += 1;
+ if has_docs {
+ self.with_docs += 1;
+ }
+ }
+ }
+ }
+
+ self.fold_item_recur(i)
+ }
+}
mod check_code_block_syntax;
pub use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX;
+mod calculate_doc_coverage;
+pub use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE;
+
/// A single pass over the cleaned documentation.
///
/// Runs in the compiler context, so it has access to types and traits and the like.
COLLECT_INTRA_DOC_LINKS,
CHECK_CODE_BLOCK_SYNTAX,
COLLECT_TRAIT_IMPLS,
+ CALCULATE_DOC_COVERAGE,
];
/// The list of passes run by default.
"propagate-doc-cfg",
];
+/// The list of default passes run when `--doc-coverage` is passed to rustdoc.
+pub const DEFAULT_COVERAGE_PASSES: &'static [&'static str] = &[
+ "collect-trait-impls",
+ "strip-hidden",
+ "strip-private",
+ "calculate-doc-coverage",
+];
+
/// A shorthand way to refer to which set of passes to use, based on the presence of
/// `--no-defaults` or `--document-private-items`.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum DefaultPassOption {
Default,
Private,
+ Coverage,
None,
}
match default_set {
DefaultPassOption::Default => DEFAULT_PASSES,
DefaultPassOption::Private => DEFAULT_PRIVATE_PASSES,
+ DefaultPassOption::Coverage => DEFAULT_COVERAGE_PASSES,
DefaultPassOption::None => &[],
}
}