forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of rust-lang#58626 - QuietMisdreavus:doc-coverage, r=Gui…
…llaumeGomez rustdoc: add option to calculate "documentation coverage" This PR adds a new flag to rustdoc, `--show-coverage`. When passed, this flag will make rustdoc count the number of items in a crate with documentation instead of generating docs. This count will be output as a table of each file in the crate, like this (when run on my crate `egg-mode`): ``` +-------------------------------------+------------+------------+------------+ | File | Documented | Total | Percentage | +-------------------------------------+------------+------------+------------+ | src/auth.rs | 16 | 16 | 100.0% | | src/common/mod.rs | 1 | 1 | 100.0% | | src/common/response.rs | 9 | 9 | 100.0% | | src/cursor.rs | 24 | 24 | 100.0% | | src/direct/fun.rs | 6 | 6 | 100.0% | | src/direct/mod.rs | 41 | 41 | 100.0% | | src/entities.rs | 50 | 50 | 100.0% | | src/error.rs | 27 | 27 | 100.0% | | src/lib.rs | 1 | 1 | 100.0% | | src/list/fun.rs | 19 | 19 | 100.0% | | src/list/mod.rs | 22 | 22 | 100.0% | | src/media/mod.rs | 27 | 27 | 100.0% | | src/place/fun.rs | 8 | 8 | 100.0% | | src/place/mod.rs | 35 | 35 | 100.0% | | src/search.rs | 26 | 26 | 100.0% | | src/service.rs | 74 | 74 | 100.0% | | src/stream/mod.rs | 49 | 49 | 100.0% | | src/tweet/fun.rs | 15 | 15 | 100.0% | | src/tweet/mod.rs | 73 | 73 | 100.0% | | src/user/fun.rs | 24 | 24 | 100.0% | | src/user/mod.rs | 87 | 87 | 100.0% | +-------------------------------------+------------+------------+------------+ | Total | 634 | 634 | 100.0% | +-------------------------------------+------------+------------+------------+ ``` Trait implementations are not counted because by default they "inherit" the docs from the trait, even though an impl can override those docs. Similarly, inherent impl blocks are not counted at all, because for the majority of cases such docs are not useful. (The usual pattern for inherent impl blocks is to throw all the methods on a type into a single impl block. Any docs you would put on that block would be better served on the type itself.) In addition, `--show-coverage` can be combined with `--document-private-items` to get the coverage counts for everything in the crate, not just public items. The coverage calculation is implemented as a late pass and two new sets of passes which strip out most of the work that rustdoc otherwise does when generating docs. The is because after the new pass is executed, rustdoc immediately closes instead of going on to generate documentation. Many examples of coverage calculations have been included as `rustdoc-ui` tests. r? @rust-lang/rustdoc
- Loading branch information
Showing
21 changed files
with
487 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
use crate::clean; | ||
use crate::core::DocContext; | ||
use crate::fold::{self, DocFolder}; | ||
use crate::passes::Pass; | ||
|
||
use syntax::attr; | ||
use syntax_pos::FileName; | ||
|
||
use std::collections::BTreeMap; | ||
use std::ops; | ||
|
||
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); | ||
|
||
calc.print_results(); | ||
|
||
krate | ||
} | ||
|
||
#[derive(Default, Copy, Clone)] | ||
struct ItemCount { | ||
total: u64, | ||
with_docs: u64, | ||
} | ||
|
||
impl ItemCount { | ||
fn count_item(&mut self, has_docs: bool) { | ||
self.total += 1; | ||
|
||
if has_docs { | ||
self.with_docs += 1; | ||
} | ||
} | ||
|
||
fn percentage(&self) -> Option<f64> { | ||
if self.total > 0 { | ||
Some((self.with_docs as f64 * 100.0) / self.total as f64) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
impl ops::Sub for ItemCount { | ||
type Output = Self; | ||
|
||
fn sub(self, rhs: Self) -> Self { | ||
ItemCount { | ||
total: self.total - rhs.total, | ||
with_docs: self.with_docs - rhs.with_docs, | ||
} | ||
} | ||
} | ||
|
||
impl ops::AddAssign for ItemCount { | ||
fn add_assign(&mut self, rhs: Self) { | ||
self.total += rhs.total; | ||
self.with_docs += rhs.with_docs; | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
struct CoverageCalculator { | ||
items: BTreeMap<FileName, ItemCount>, | ||
} | ||
|
||
impl CoverageCalculator { | ||
fn print_results(&self) { | ||
let mut total = ItemCount::default(); | ||
|
||
fn print_table_line() { | ||
println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", ""); | ||
} | ||
|
||
fn print_table_record(name: &str, count: ItemCount, percentage: f64) { | ||
println!("| {:<35} | {:>10} | {:>10} | {:>9.1}% |", | ||
name, count.with_docs, count.total, percentage); | ||
} | ||
|
||
print_table_line(); | ||
println!("| {:<35} | {:>10} | {:>10} | {:>10} |", | ||
"File", "Documented", "Total", "Percentage"); | ||
print_table_line(); | ||
|
||
for (file, &count) in &self.items { | ||
if let Some(percentage) = count.percentage() { | ||
let mut name = file.to_string(); | ||
// if a filename is too long, shorten it so we don't blow out the table | ||
// FIXME(misdreavus): this needs to count graphemes, and probably also track | ||
// double-wide characters... | ||
if name.len() > 35 { | ||
name = "...".to_string() + &name[name.len()-32..]; | ||
} | ||
|
||
print_table_record(&name, count, percentage); | ||
|
||
total += count; | ||
} | ||
} | ||
|
||
print_table_line(); | ||
print_table_record("Total", total, total.percentage().unwrap_or(0.0)); | ||
print_table_line(); | ||
} | ||
} | ||
|
||
impl fold::DocFolder for CoverageCalculator { | ||
fn fold_item(&mut self, i: clean::Item) -> Option<clean::Item> { | ||
let has_docs = !i.attrs.doc_strings.is_empty(); | ||
|
||
match i.inner { | ||
_ if !i.def_id.is_local() => { | ||
// 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 | ||
return Some(i); | ||
} | ||
clean::StrippedItem(..) => { | ||
// don't count items in stripped modules | ||
return Some(i); | ||
} | ||
clean::ImportItem(..) | clean::ExternCrateItem(..) => { | ||
// docs on `use` and `extern crate` statements are not displayed, so they're not | ||
// worth counting | ||
return Some(i); | ||
} | ||
clean::ImplItem(ref impl_) | ||
if attr::contains_name(&i.attrs.other_attrs, "automatically_derived") | ||
|| impl_.synthetic || impl_.blanket_impl.is_some() => | ||
{ | ||
// built-in derives get the `#[automatically_derived]` attribute, and | ||
// synthetic/blanket impls are made up by rustdoc and can't be documented | ||
// FIXME(misdreavus): need to also find items that came out of a derive macro | ||
return Some(i); | ||
} | ||
clean::ImplItem(ref impl_) => { | ||
if let Some(ref tr) = impl_.trait_ { | ||
debug!("impl {:#} for {:#} in {}", tr, impl_.for_, i.source.filename); | ||
|
||
// don't count trait impls, the missing-docs lint doesn't so we shouldn't | ||
// either | ||
return Some(i); | ||
} 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!("impl {:#} in {}", impl_.for_, i.source.filename); | ||
} | ||
} | ||
_ => { | ||
debug!("counting {} {:?} in {}", i.type_(), i.name, i.source.filename); | ||
self.items.entry(i.source.filename.clone()) | ||
.or_default() | ||
.count_item(has_docs); | ||
} | ||
} | ||
|
||
self.fold_item_recur(i) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.