]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/calculate_doc_coverage.rs
Rollup merge of #69565 - RalfJung:assert, r=eddyb
[rust.git] / src / librustdoc / passes / calculate_doc_coverage.rs
1 use crate::clean;
2 use crate::core::DocContext;
3 use crate::fold::{self, DocFolder};
4 use crate::passes::Pass;
5
6 use rustc_ast::attr;
7 use rustc_span::symbol::sym;
8 use rustc_span::FileName;
9
10 use std::collections::BTreeMap;
11 use std::ops;
12
13 pub const CALCULATE_DOC_COVERAGE: Pass = Pass {
14     name: "calculate-doc-coverage",
15     run: calculate_doc_coverage,
16     description: "counts the number of items with and without documentation",
17 };
18
19 fn calculate_doc_coverage(krate: clean::Crate, _: &DocContext<'_>) -> clean::Crate {
20     let mut calc = CoverageCalculator::default();
21     let krate = calc.fold_crate(krate);
22
23     calc.print_results();
24
25     krate
26 }
27
28 #[derive(Default, Copy, Clone)]
29 struct ItemCount {
30     total: u64,
31     with_docs: u64,
32 }
33
34 impl ItemCount {
35     fn count_item(&mut self, has_docs: bool) {
36         self.total += 1;
37
38         if has_docs {
39             self.with_docs += 1;
40         }
41     }
42
43     fn percentage(&self) -> Option<f64> {
44         if self.total > 0 {
45             Some((self.with_docs as f64 * 100.0) / self.total as f64)
46         } else {
47             None
48         }
49     }
50 }
51
52 impl ops::Sub for ItemCount {
53     type Output = Self;
54
55     fn sub(self, rhs: Self) -> Self {
56         ItemCount { total: self.total - rhs.total, with_docs: self.with_docs - rhs.with_docs }
57     }
58 }
59
60 impl ops::AddAssign for ItemCount {
61     fn add_assign(&mut self, rhs: Self) {
62         self.total += rhs.total;
63         self.with_docs += rhs.with_docs;
64     }
65 }
66
67 #[derive(Default)]
68 struct CoverageCalculator {
69     items: BTreeMap<FileName, ItemCount>,
70 }
71
72 impl CoverageCalculator {
73     fn print_results(&self) {
74         let mut total = ItemCount::default();
75
76         fn print_table_line() {
77             println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "");
78         }
79
80         fn print_table_record(name: &str, count: ItemCount, percentage: f64) {
81             println!(
82                 "| {:<35} | {:>10} | {:>10} | {:>9.1}% |",
83                 name, count.with_docs, count.total, percentage
84             );
85         }
86
87         print_table_line();
88         println!(
89             "| {:<35} | {:>10} | {:>10} | {:>10} |",
90             "File", "Documented", "Total", "Percentage"
91         );
92         print_table_line();
93
94         for (file, &count) in &self.items {
95             if let Some(percentage) = count.percentage() {
96                 let mut name = file.to_string();
97                 // if a filename is too long, shorten it so we don't blow out the table
98                 // FIXME(misdreavus): this needs to count graphemes, and probably also track
99                 // double-wide characters...
100                 if name.len() > 35 {
101                     name = "...".to_string() + &name[name.len() - 32..];
102                 }
103
104                 print_table_record(&name, count, percentage);
105
106                 total += count;
107             }
108         }
109
110         print_table_line();
111         print_table_record("Total", total, total.percentage().unwrap_or(0.0));
112         print_table_line();
113     }
114 }
115
116 impl fold::DocFolder for CoverageCalculator {
117     fn fold_item(&mut self, i: clean::Item) -> Option<clean::Item> {
118         let has_docs = !i.attrs.doc_strings.is_empty();
119
120         match i.inner {
121             _ if !i.def_id.is_local() => {
122                 // non-local items are skipped because they can be out of the users control,
123                 // especially in the case of trait impls, which rustdoc eagerly inlines
124                 return Some(i);
125             }
126             clean::StrippedItem(..) => {
127                 // don't count items in stripped modules
128                 return Some(i);
129             }
130             clean::ImportItem(..) | clean::ExternCrateItem(..) => {
131                 // docs on `use` and `extern crate` statements are not displayed, so they're not
132                 // worth counting
133                 return Some(i);
134             }
135             clean::ImplItem(ref impl_)
136                 if attr::contains_name(&i.attrs.other_attrs, sym::automatically_derived)
137                     || impl_.synthetic
138                     || impl_.blanket_impl.is_some() =>
139             {
140                 // built-in derives get the `#[automatically_derived]` attribute, and
141                 // synthetic/blanket impls are made up by rustdoc and can't be documented
142                 // FIXME(misdreavus): need to also find items that came out of a derive macro
143                 return Some(i);
144             }
145             clean::ImplItem(ref impl_) => {
146                 if let Some(ref tr) = impl_.trait_ {
147                     debug!(
148                         "impl {:#} for {:#} in {}",
149                         tr.print(),
150                         impl_.for_.print(),
151                         i.source.filename
152                     );
153
154                     // don't count trait impls, the missing-docs lint doesn't so we shouldn't
155                     // either
156                     return Some(i);
157                 } else {
158                     // inherent impls *can* be documented, and those docs show up, but in most
159                     // cases it doesn't make sense, as all methods on a type are in one single
160                     // impl block
161                     debug!("impl {:#} in {}", impl_.for_.print(), i.source.filename);
162                 }
163             }
164             _ => {
165                 debug!("counting {:?} {:?} in {}", i.type_(), i.name, i.source.filename);
166                 self.items.entry(i.source.filename.clone()).or_default().count_item(has_docs);
167             }
168         }
169
170         self.fold_item_recur(i)
171     }
172 }