Skip to content

Missing doc examples lint improvements #75776

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
33 changes: 33 additions & 0 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,36 @@ $ rustdoc src/lib.rs -Z unstable-options --runtool valgrind
```

Another use case would be to run a test inside an emulator, or through a Virtual Machine.

### `--show-coverage`: get statistics about code documentation coverage

This option allows you to get a nice overview over your code documentation coverage, including both
doc-comments and code examples in the doc-comments. Example:

```bash
$ rustdoc src/lib.rs -Z unstable-options --show-coverage
+-------------------------------------+------------+------------+------------+------------+
| File | Documented | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+
| lib.rs | 4 | 100.0% | 1 | 25.0% |
+-------------------------------------+------------+------------+------------+------------+
| Total | 4 | 100.0% | 1 | 25.0% |
+-------------------------------------+------------+------------+------------+------------+
```

You can also use this option with the `--output-format` one:

```bash
$ rustdoc src/lib.rs -Z unstable-options --show-coverage --output-format json
{"lib.rs":{"total":4,"with_docs":4,"total_examples":4,"with_examples":1}}
```

Calculating code examples follows these rules:

1. These items aren't accounted by default:
* struct/union field
* enum variant
* constant
* static
* typedef
2. If one of the previously listed items has a code example, then it'll be counted.
76 changes: 40 additions & 36 deletions src/librustdoc/passes/calculate_doc_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::config::OutputFormat;
use crate::core::DocContext;
use crate::fold::{self, DocFolder};
use crate::html::markdown::{find_testable_code, ErrorCodes};
use crate::passes::doc_test_lints::Tests;
use crate::passes::doc_test_lints::{should_have_doc_example, Tests};
use crate::passes::Pass;
use rustc_span::symbol::sym;
use rustc_span::FileName;
Expand All @@ -27,20 +27,29 @@ fn calculate_doc_coverage(krate: clean::Crate, ctx: &DocContext<'_>) -> clean::C
krate
}

#[derive(Default, Copy, Clone, Serialize)]
#[derive(Default, Copy, Clone, Serialize, Debug)]
struct ItemCount {
total: u64,
with_docs: u64,
total_examples: u64,
with_examples: u64,
}

impl ItemCount {
fn count_item(&mut self, has_docs: bool, has_doc_example: bool) {
fn count_item(
&mut self,
has_docs: bool,
has_doc_example: bool,
should_have_doc_examples: bool,
) {
self.total += 1;

if has_docs {
self.with_docs += 1;
}
if should_have_doc_examples || has_doc_example {
self.total_examples += 1;
}
if has_doc_example {
self.with_examples += 1;
}
Expand All @@ -55,8 +64,8 @@ impl ItemCount {
}

fn examples_percentage(&self) -> Option<f64> {
if self.total > 0 {
Some((self.with_examples as f64 * 100.0) / self.total as f64)
if self.total_examples > 0 {
Some((self.with_examples as f64 * 100.0) / self.total_examples as f64)
} else {
None
}
Expand All @@ -70,6 +79,7 @@ impl ops::Sub for ItemCount {
ItemCount {
total: self.total - rhs.total,
with_docs: self.with_docs - rhs.with_docs,
total_examples: self.total_examples - rhs.total_examples,
with_examples: self.with_examples - rhs.with_examples,
}
}
Expand All @@ -79,6 +89,7 @@ impl ops::AddAssign for ItemCount {
fn add_assign(&mut self, rhs: Self) {
self.total += rhs.total;
self.with_docs += rhs.with_docs;
self.total_examples += rhs.total_examples;
self.with_examples += rhs.with_examples;
}
}
Expand Down Expand Up @@ -121,7 +132,7 @@ impl CoverageCalculator {
let mut total = ItemCount::default();

fn print_table_line() {
println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "");
println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "");
}

fn print_table_record(
Expand All @@ -131,32 +142,25 @@ impl CoverageCalculator {
examples_percentage: f64,
) {
println!(
"| {:<35} | {:>10} | {:>10} | {:>9.1}% | {:>10} | {:>9.1}% |",
name,
count.with_docs,
count.total,
percentage,
count.with_examples,
examples_percentage,
"| {:<35} | {:>10} | {:>9.1}% | {:>10} | {:>9.1}% |",
name, count.with_docs, percentage, count.with_examples, examples_percentage,
);
}

print_table_line();
println!(
"| {:<35} | {:>10} | {:>10} | {:>10} | {:>10} | {:>10} |",
"File", "Documented", "Total", "Percentage", "Examples", "Percentage",
"| {:<35} | {:>10} | {:>10} | {:>10} | {:>10} |",
"File", "Documented", "Percentage", "Examples", "Percentage",
);
print_table_line();

for (file, &count) in &self.items {
if let (Some(percentage), Some(examples_percentage)) =
(count.percentage(), count.examples_percentage())
{
if let Some(percentage) = count.percentage() {
print_table_record(
&limit_filename_len(file.to_string()),
count,
percentage,
examples_percentage,
count.examples_percentage().unwrap_or(0.),
);

total += count;
Expand All @@ -176,19 +180,6 @@ impl CoverageCalculator {

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();
let mut tests = Tests { found_tests: 0 };

find_testable_code(
&i.attrs.doc_strings.iter().map(|d| d.as_str()).collect::<Vec<_>>().join("\n"),
&mut tests,
ErrorCodes::No,
false,
None,
);

let has_doc_example = tests.found_tests != 0;

match i.inner {
_ if !i.def_id.is_local() => {
// non-local items are skipped because they can be out of the users control,
Expand Down Expand Up @@ -237,11 +228,24 @@ impl fold::DocFolder for CoverageCalculator {
}
}
_ => {
let has_docs = !i.attrs.doc_strings.is_empty();
let mut tests = Tests { found_tests: 0 };

find_testable_code(
&i.attrs.doc_strings.iter().map(|d| d.as_str()).collect::<Vec<_>>().join("\n"),
&mut tests,
ErrorCodes::No,
false,
None,
);

let has_doc_example = tests.found_tests != 0;
debug!("counting {:?} {:?} in {}", i.type_(), i.name, i.source.filename);
self.items
.entry(i.source.filename.clone())
.or_default()
.count_item(has_docs, has_doc_example);
self.items.entry(i.source.filename.clone()).or_default().count_item(
has_docs,
has_doc_example,
should_have_doc_example(&i.inner),
);
}
}

Expand Down
25 changes: 18 additions & 7 deletions src/librustdoc/passes/doc_test_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! - PRIVATE_DOC_TESTS: this looks for private items with doc-tests.

use super::{span_of_attrs, Pass};
use crate::clean;
use crate::clean::*;
use crate::core::DocContext;
use crate::fold::DocFolder;
Expand Down Expand Up @@ -59,6 +60,22 @@ impl crate::test::Tester for Tests {
}
}

pub fn should_have_doc_example(item_kind: &clean::ItemEnum) -> bool {
!matches!(item_kind,
clean::StructFieldItem(_)
| clean::VariantItem(_)
| clean::AssocConstItem(_, _)
| clean::AssocTypeItem(_, _)
| clean::TypedefItem(_, _)
| clean::StaticItem(_)
| clean::ConstantItem(_)
| clean::ExternCrateItem(_, _)
| clean::ImportItem(_)
| clean::PrimitiveItem(_)
| clean::KeywordItem(_)
)
}

pub fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
let hir_id = match cx.as_local_hir_id(item.def_id) {
Some(hir_id) => hir_id,
Expand All @@ -73,13 +90,7 @@ pub fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
find_testable_code(&dox, &mut tests, ErrorCodes::No, false, None);

if tests.found_tests == 0 {
use ItemEnum::*;

let should_report = match item.inner {
ExternCrateItem(_, _) | ImportItem(_) | PrimitiveItem(_) | KeywordItem(_) => false,
_ => true,
};
if should_report {
if should_have_doc_example(&item.inner) {
debug!("reporting error for {:?} (hir_id={:?})", item, hir_id);
let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());
cx.tcx.struct_span_lint_hir(
Expand Down
14 changes: 7 additions & 7 deletions src/test/rustdoc-ui/coverage/basic.stdout
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
+-------------------------------------+------------+------------+------------+------------+------------+
| File | Documented | Total | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+------------+
| ...est/rustdoc-ui/coverage/basic.rs | 7 | 14 | 50.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
| Total | 7 | 14 | 50.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
+-------------------------------------+------------+------------+------------+------------+
| File | Documented | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+
| ...est/rustdoc-ui/coverage/basic.rs | 7 | 50.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
| Total | 7 | 50.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
13 changes: 13 additions & 0 deletions src/test/rustdoc-ui/coverage/doc-examples-json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// check-pass
// compile-flags:-Z unstable-options --output-format json --show-coverage

// This check ensures that only one doc example is counted since they're "optional" on
// certain items.

/// ```
/// let x = 12;
/// ```
pub const Foo: u32 = 0;

/// doc
pub const Bar: u32 = 0;
1 change: 1 addition & 0 deletions src/test/rustdoc-ui/coverage/doc-examples-json.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"$DIR/doc-examples-json.rs":{"total":3,"with_docs":2,"total_examples":2,"with_examples":1}}
14 changes: 7 additions & 7 deletions src/test/rustdoc-ui/coverage/doc-examples.stdout
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
+-------------------------------------+------------+------------+------------+------------+------------+
| File | Documented | Total | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+------------+
| ...tdoc-ui/coverage/doc-examples.rs | 4 | 4 | 100.0% | 2 | 50.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
| Total | 4 | 4 | 100.0% | 2 | 50.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
+-------------------------------------+------------+------------+------------+------------+
| File | Documented | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+
| ...tdoc-ui/coverage/doc-examples.rs | 4 | 100.0% | 2 | 50.0% |
+-------------------------------------+------------+------------+------------+------------+
| Total | 4 | 100.0% | 2 | 50.0% |
+-------------------------------------+------------+------------+------------+------------+
14 changes: 7 additions & 7 deletions src/test/rustdoc-ui/coverage/empty.stdout
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
+-------------------------------------+------------+------------+------------+------------+------------+
| File | Documented | Total | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+------------+
| ...est/rustdoc-ui/coverage/empty.rs | 0 | 1 | 0.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
| Total | 0 | 1 | 0.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
+-------------------------------------+------------+------------+------------+------------+
| File | Documented | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+
| ...est/rustdoc-ui/coverage/empty.rs | 0 | 0.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
| Total | 0 | 0.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
14 changes: 7 additions & 7 deletions src/test/rustdoc-ui/coverage/enums.stdout
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
+-------------------------------------+------------+------------+------------+------------+------------+
| File | Documented | Total | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+------------+
| ...est/rustdoc-ui/coverage/enums.rs | 6 | 8 | 75.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
| Total | 6 | 8 | 75.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
+-------------------------------------+------------+------------+------------+------------+
| File | Documented | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+
| ...est/rustdoc-ui/coverage/enums.rs | 6 | 75.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
| Total | 6 | 75.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
16 changes: 8 additions & 8 deletions src/test/rustdoc-ui/coverage/exotic.stdout
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
+-------------------------------------+------------+------------+------------+------------+------------+
| File | Documented | Total | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+------------+
| ...st/rustdoc-ui/coverage/exotic.rs | 1 | 1 | 100.0% | 0 | 0.0% |
| <anon> | 2 | 2 | 100.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
| Total | 3 | 3 | 100.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+------------+
+-------------------------------------+------------+------------+------------+------------+
| File | Documented | Percentage | Examples | Percentage |
+-------------------------------------+------------+------------+------------+------------+
| ...st/rustdoc-ui/coverage/exotic.rs | 1 | 100.0% | 0 | 0.0% |
| <anon> | 2 | 100.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
| Total | 3 | 100.0% | 0 | 0.0% |
+-------------------------------------+------------+------------+------------+------------+
40 changes: 39 additions & 1 deletion src/test/rustdoc-ui/coverage/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,54 @@ pub mod foo {
pub struct X;

/// Bar
///
/// ```
/// let x = 12;
/// ```
pub mod bar {
/// bar
pub struct Bar;
/// X
pub enum X { Y }
pub enum X {
/// ```
/// let x = "should be ignored!";
/// ```
Y
}
}

/// yolo
///
/// ```text
/// should not be counted as a code example!
/// ```
pub enum Yolo { X }

impl Yolo {
/// ```
/// let x = "should be ignored!";
/// ```
pub const Const: u32 = 0;
}

pub struct Xo<T: Clone> {
/// ```
/// let x = "should be ignored!";
/// ```
x: T,
}

/// ```
/// let x = "should be ignored!";
/// ```
pub static StaticFoo: u32 = 0;

/// ```
/// let x = "should be ignored!";
/// ```
pub const ConstFoo: u32 = 0;

/// ```
/// let x = "should be ignored!";
/// ```
pub type TypeFoo = u32;
2 changes: 1 addition & 1 deletion src/test/rustdoc-ui/coverage/json.stdout
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"$DIR/json.rs":{"total":13,"with_docs":7,"with_examples":0}}
{"$DIR/json.rs":{"total":17,"with_docs":12,"total_examples":15,"with_examples":6}}
Loading