]>
Commit | Line | Data |
---|---|---|
532ac7d7 XL |
1 | use crate::clean; |
2 | use crate::core::DocContext; | |
3 | use crate::fold::{self, DocFolder}; | |
3dfed10e XL |
4 | use crate::html::markdown::{find_testable_code, ErrorCodes}; |
5 | use crate::passes::doc_test_lints::{should_have_doc_example, Tests}; | |
532ac7d7 | 6 | use crate::passes::Pass; |
29967ef6 | 7 | use rustc_lint::builtin::MISSING_DOCS; |
fc512014 | 8 | use rustc_middle::lint::LintLevelSource; |
29967ef6 | 9 | use rustc_session::lint; |
dfeec247 | 10 | use rustc_span::FileName; |
ba9703b0 | 11 | use serde::Serialize; |
532ac7d7 XL |
12 | |
13 | use std::collections::BTreeMap; | |
14 | use std::ops; | |
15 | ||
fc512014 | 16 | crate const CALCULATE_DOC_COVERAGE: Pass = Pass { |
532ac7d7 | 17 | name: "calculate-doc-coverage", |
60c5eb7d | 18 | run: calculate_doc_coverage, |
532ac7d7 XL |
19 | description: "counts the number of items with and without documentation", |
20 | }; | |
21 | ||
6a06907d XL |
22 | fn calculate_doc_coverage(krate: clean::Crate, ctx: &mut DocContext<'_>) -> clean::Crate { |
23 | let mut calc = CoverageCalculator { items: Default::default(), ctx }; | |
532ac7d7 XL |
24 | let krate = calc.fold_crate(krate); |
25 | ||
29967ef6 | 26 | calc.print_results(); |
532ac7d7 XL |
27 | |
28 | krate | |
29 | } | |
30 | ||
3dfed10e | 31 | #[derive(Default, Copy, Clone, Serialize, Debug)] |
532ac7d7 XL |
32 | struct ItemCount { |
33 | total: u64, | |
34 | with_docs: u64, | |
3dfed10e XL |
35 | total_examples: u64, |
36 | with_examples: u64, | |
532ac7d7 XL |
37 | } |
38 | ||
39 | impl ItemCount { | |
3dfed10e XL |
40 | fn count_item( |
41 | &mut self, | |
42 | has_docs: bool, | |
43 | has_doc_example: bool, | |
44 | should_have_doc_examples: bool, | |
29967ef6 | 45 | should_have_docs: bool, |
3dfed10e | 46 | ) { |
29967ef6 XL |
47 | if has_docs || should_have_docs { |
48 | self.total += 1; | |
49 | } | |
532ac7d7 XL |
50 | |
51 | if has_docs { | |
52 | self.with_docs += 1; | |
53 | } | |
3dfed10e XL |
54 | if should_have_doc_examples || has_doc_example { |
55 | self.total_examples += 1; | |
56 | } | |
57 | if has_doc_example { | |
58 | self.with_examples += 1; | |
59 | } | |
532ac7d7 XL |
60 | } |
61 | ||
62 | fn percentage(&self) -> Option<f64> { | |
63 | if self.total > 0 { | |
64 | Some((self.with_docs as f64 * 100.0) / self.total as f64) | |
65 | } else { | |
66 | None | |
67 | } | |
68 | } | |
3dfed10e XL |
69 | |
70 | fn examples_percentage(&self) -> Option<f64> { | |
71 | if self.total_examples > 0 { | |
72 | Some((self.with_examples as f64 * 100.0) / self.total_examples as f64) | |
73 | } else { | |
74 | None | |
75 | } | |
76 | } | |
532ac7d7 XL |
77 | } |
78 | ||
79 | impl ops::Sub for ItemCount { | |
80 | type Output = Self; | |
81 | ||
82 | fn sub(self, rhs: Self) -> Self { | |
3dfed10e XL |
83 | ItemCount { |
84 | total: self.total - rhs.total, | |
85 | with_docs: self.with_docs - rhs.with_docs, | |
86 | total_examples: self.total_examples - rhs.total_examples, | |
87 | with_examples: self.with_examples - rhs.with_examples, | |
88 | } | |
532ac7d7 XL |
89 | } |
90 | } | |
91 | ||
92 | impl ops::AddAssign for ItemCount { | |
93 | fn add_assign(&mut self, rhs: Self) { | |
94 | self.total += rhs.total; | |
95 | self.with_docs += rhs.with_docs; | |
3dfed10e XL |
96 | self.total_examples += rhs.total_examples; |
97 | self.with_examples += rhs.with_examples; | |
532ac7d7 XL |
98 | } |
99 | } | |
100 | ||
29967ef6 | 101 | struct CoverageCalculator<'a, 'b> { |
532ac7d7 | 102 | items: BTreeMap<FileName, ItemCount>, |
6a06907d | 103 | ctx: &'a mut DocContext<'b>, |
532ac7d7 XL |
104 | } |
105 | ||
ba9703b0 XL |
106 | fn limit_filename_len(filename: String) -> String { |
107 | let nb_chars = filename.chars().count(); | |
108 | if nb_chars > 35 { | |
109 | "...".to_string() | |
110 | + &filename[filename.char_indices().nth(nb_chars - 32).map(|x| x.0).unwrap_or(0)..] | |
111 | } else { | |
112 | filename | |
113 | } | |
114 | } | |
115 | ||
29967ef6 | 116 | impl<'a, 'b> CoverageCalculator<'a, 'b> { |
ba9703b0 XL |
117 | fn to_json(&self) -> String { |
118 | serde_json::to_string( | |
119 | &self | |
120 | .items | |
121 | .iter() | |
122 | .map(|(k, v)| (k.to_string(), v)) | |
123 | .collect::<BTreeMap<String, &ItemCount>>(), | |
124 | ) | |
125 | .expect("failed to convert JSON data to string") | |
126 | } | |
127 | ||
29967ef6 | 128 | fn print_results(&self) { |
6a06907d | 129 | let output_format = self.ctx.output_format; |
5869c6ff | 130 | if output_format.is_json() { |
ba9703b0 XL |
131 | println!("{}", self.to_json()); |
132 | return; | |
133 | } | |
532ac7d7 XL |
134 | let mut total = ItemCount::default(); |
135 | ||
136 | fn print_table_line() { | |
3dfed10e | 137 | println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", ""); |
532ac7d7 XL |
138 | } |
139 | ||
3dfed10e XL |
140 | fn print_table_record( |
141 | name: &str, | |
142 | count: ItemCount, | |
143 | percentage: f64, | |
144 | examples_percentage: f64, | |
145 | ) { | |
60c5eb7d | 146 | println!( |
3dfed10e XL |
147 | "| {:<35} | {:>10} | {:>9.1}% | {:>10} | {:>9.1}% |", |
148 | name, count.with_docs, percentage, count.with_examples, examples_percentage, | |
60c5eb7d | 149 | ); |
532ac7d7 XL |
150 | } |
151 | ||
152 | print_table_line(); | |
60c5eb7d | 153 | println!( |
3dfed10e XL |
154 | "| {:<35} | {:>10} | {:>10} | {:>10} | {:>10} |", |
155 | "File", "Documented", "Percentage", "Examples", "Percentage", | |
60c5eb7d | 156 | ); |
532ac7d7 XL |
157 | print_table_line(); |
158 | ||
159 | for (file, &count) in &self.items { | |
160 | if let Some(percentage) = count.percentage() { | |
3dfed10e XL |
161 | print_table_record( |
162 | &limit_filename_len(file.to_string()), | |
163 | count, | |
164 | percentage, | |
165 | count.examples_percentage().unwrap_or(0.), | |
166 | ); | |
532ac7d7 XL |
167 | |
168 | total += count; | |
169 | } | |
170 | } | |
171 | ||
172 | print_table_line(); | |
3dfed10e XL |
173 | print_table_record( |
174 | "Total", | |
175 | total, | |
176 | total.percentage().unwrap_or(0.0), | |
177 | total.examples_percentage().unwrap_or(0.0), | |
178 | ); | |
532ac7d7 XL |
179 | print_table_line(); |
180 | } | |
181 | } | |
182 | ||
29967ef6 | 183 | impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> { |
532ac7d7 | 184 | fn fold_item(&mut self, i: clean::Item) -> Option<clean::Item> { |
5869c6ff | 185 | match *i.kind { |
532ac7d7 XL |
186 | _ if !i.def_id.is_local() => { |
187 | // non-local items are skipped because they can be out of the users control, | |
188 | // especially in the case of trait impls, which rustdoc eagerly inlines | |
189 | return Some(i); | |
190 | } | |
191 | clean::StrippedItem(..) => { | |
192 | // don't count items in stripped modules | |
193 | return Some(i); | |
194 | } | |
cdc7bbd5 XL |
195 | // docs on `use` and `extern crate` statements are not displayed, so they're not |
196 | // worth counting | |
197 | clean::ImportItem(..) | clean::ExternCrateItem { .. } => {} | |
198 | // Don't count trait impls, the missing-docs lint doesn't so we shouldn't either. | |
199 | // Inherent impls *can* be documented, and those docs show up, but in most cases it | |
200 | // doesn't make sense, as all methods on a type are in one single impl block | |
201 | clean::ImplItem(_) => {} | |
532ac7d7 | 202 | _ => { |
3dfed10e XL |
203 | let has_docs = !i.attrs.doc_strings.is_empty(); |
204 | let mut tests = Tests { found_tests: 0 }; | |
205 | ||
206 | find_testable_code( | |
5869c6ff | 207 | &i.attrs.collapsed_doc_value().unwrap_or_default(), |
3dfed10e XL |
208 | &mut tests, |
209 | ErrorCodes::No, | |
210 | false, | |
211 | None, | |
212 | ); | |
213 | ||
cdc7bbd5 | 214 | let filename = i.span(self.ctx.tcx).filename(self.ctx.sess()); |
3dfed10e | 215 | let has_doc_example = tests.found_tests != 0; |
29967ef6 XL |
216 | let hir_id = self.ctx.tcx.hir().local_def_id_to_hir_id(i.def_id.expect_local()); |
217 | let (level, source) = self.ctx.tcx.lint_level_at_node(MISSING_DOCS, hir_id); | |
218 | // `missing_docs` is allow-by-default, so don't treat this as ignoring the item | |
219 | // unless the user had an explicit `allow` | |
220 | let should_have_docs = | |
fc512014 XL |
221 | level != lint::Level::Allow || matches!(source, LintLevelSource::Default); |
222 | debug!("counting {:?} {:?} in {}", i.type_(), i.name, filename); | |
223 | self.items.entry(filename).or_default().count_item( | |
3dfed10e XL |
224 | has_docs, |
225 | has_doc_example, | |
29967ef6 XL |
226 | should_have_doc_example(self.ctx, &i), |
227 | should_have_docs, | |
3dfed10e | 228 | ); |
532ac7d7 XL |
229 | } |
230 | } | |
231 | ||
fc512014 | 232 | Some(self.fold_item_recur(i)) |
532ac7d7 XL |
233 | } |
234 | } |