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