]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/passes/calculate_doc_coverage.rs
New upstream version 1.53.0+dfsg1
[rustc.git] / src / librustdoc / passes / calculate_doc_coverage.rs
CommitLineData
532ac7d7
XL
1use crate::clean;
2use crate::core::DocContext;
3use crate::fold::{self, DocFolder};
3dfed10e
XL
4use crate::html::markdown::{find_testable_code, ErrorCodes};
5use crate::passes::doc_test_lints::{should_have_doc_example, Tests};
532ac7d7 6use crate::passes::Pass;
29967ef6 7use rustc_lint::builtin::MISSING_DOCS;
fc512014 8use rustc_middle::lint::LintLevelSource;
29967ef6 9use rustc_session::lint;
dfeec247 10use rustc_span::FileName;
ba9703b0 11use serde::Serialize;
532ac7d7
XL
12
13use std::collections::BTreeMap;
14use std::ops;
15
fc512014 16crate 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
22fn 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
32struct ItemCount {
33 total: u64,
34 with_docs: u64,
3dfed10e
XL
35 total_examples: u64,
36 with_examples: u64,
532ac7d7
XL
37}
38
39impl 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
79impl 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
92impl 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 101struct CoverageCalculator<'a, 'b> {
532ac7d7 102 items: BTreeMap<FileName, ItemCount>,
6a06907d 103 ctx: &'a mut DocContext<'b>,
532ac7d7
XL
104}
105
ba9703b0
XL
106fn 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 116impl<'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 183impl<'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}