1
Fork 0

add option to calculate documentation coverage

This commit is contained in:
QuietMisdreavus 2019-01-30 14:04:56 -06:00
parent 1999a22881
commit 009c91a294
6 changed files with 167 additions and 4 deletions

View file

@ -428,3 +428,24 @@ $ rustdoc src/lib.rs --test -Z unstable-options --persist-doctests target/rustdo
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.

View file

@ -85,6 +85,9 @@ pub struct Options {
/// 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
@ -128,6 +131,7 @@ impl fmt::Debug for Options {
.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()
@ -224,6 +228,10 @@ impl Options {
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);
}
@ -415,12 +423,15 @@ impl Options {
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");
@ -463,6 +474,7 @@ impl Options {
default_passes,
manual_passes,
display_warnings,
show_coverage,
crate_version,
persist_doctests,
render_options: RenderOptions {

View file

@ -605,10 +605,13 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
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),
}
}

View file

@ -347,6 +347,11 @@ fn opts() -> Vec<RustcOptGroup> {
"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")
}),
]
}
@ -391,7 +396,14 @@ fn main_args(args: &[String]) -> isize {
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;

View file

@ -0,0 +1,101 @@
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)
}
}

View file

@ -45,6 +45,9 @@ pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
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.
@ -67,6 +70,7 @@ pub const PASSES: &'static [Pass] = &[
COLLECT_INTRA_DOC_LINKS,
CHECK_CODE_BLOCK_SYNTAX,
COLLECT_TRAIT_IMPLS,
CALCULATE_DOC_COVERAGE,
];
/// The list of passes run by default.
@ -94,12 +98,21 @@ pub const DEFAULT_PRIVATE_PASSES: &[&str] = &[
"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,
}
@ -108,6 +121,7 @@ pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] {
match default_set {
DefaultPassOption::Default => DEFAULT_PASSES,
DefaultPassOption::Private => DEFAULT_PRIVATE_PASSES,
DefaultPassOption::Coverage => DEFAULT_COVERAGE_PASSES,
DefaultPassOption::None => &[],
}
}