Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,15 @@ Calculating code examples follows these rules:
* typedef
2. If one of the previously listed items has a code example, then it'll be counted.

If you use the `-o` option with it, it will generate the file into the given older name. For example:

```shell
rustdoc foo.rs --show-coverage -o doc
```

Will generate a `foo.txt` into the `doc` folder. If the `-o` option isn't passed, it will display
on stdout.

### JSON output

When using `--output-format json` with this option, it will display the coverage information in
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Calculates information used for the --show-coverage flag.

use std::collections::BTreeMap;
use std::fs::{File, create_dir_all};
use std::io::{self, BufWriter, Write, stdout};
use std::ops;

use rustc_hir as hir;
Expand All @@ -10,27 +12,39 @@ use rustc_span::{FileName, RemapPathScopeComponents};
use serde::Serialize;
use tracing::debug;

use crate::clean;
use crate::config::OutputFormat;
use crate::config::{OutputFormat, RenderOptions};
use crate::core::DocContext;
use crate::docfs::PathError;
use crate::error::Error;
use crate::html::markdown::{ErrorCodes, find_testable_code};
use crate::passes::Pass;
use crate::passes::check_doc_test_visibility::{Tests, should_have_doc_example};
use crate::passes::{Tests, should_have_doc_example};
use crate::visit::DocVisitor;

pub(crate) const CALCULATE_DOC_COVERAGE: Pass = Pass {
name: "calculate-doc-coverage",
run: Some(calculate_doc_coverage),
description: "counts the number of items with and without documentation",
};

fn calculate_doc_coverage(krate: clean::Crate, ctx: &mut DocContext<'_>) -> clean::Crate {
use crate::{clean, try_err};

pub(crate) fn run(
krate: &clean::Crate,
ctx: &mut DocContext<'_>,
options: &RenderOptions,
) -> Result<(), Error> {
let is_json = ctx.output_format == OutputFormat::CoverageJson;
let tcx = ctx.tcx;
let mut calc = CoverageCalculator { items: Default::default(), ctx };
calc.visit_crate(&krate);

calc.print_results();

krate
if options.output_to_stdout {
calc.print_results(BufWriter::new(stdout().lock()))
.map_err(|error| Error::new(error, "<stdout>"))
} else {
let out_dir = &options.output;
try_err!(create_dir_all(out_dir), out_dir);
let name = krate.name(tcx);
let mut out_file = out_dir.join(name.as_str());
out_file.set_extension(if is_json { "json" } else { "txt" });
let buf = try_err!(File::create_buffered(&out_file), out_file);
calc.print_results(buf).map_err(|error| Error::new(error, &out_file))?;
println!("Generated output into {out_file:?}");
Ok(())
}
}

#[derive(Default, Copy, Clone, Serialize, Debug)]
Expand Down Expand Up @@ -130,62 +144,66 @@ impl CoverageCalculator<'_, '_> {
.expect("failed to convert JSON data to string")
}

fn print_results(&self) {
fn print_results(&self, mut buf: impl Write) -> io::Result<()> {
let output_format = self.ctx.output_format;
if output_format == OutputFormat::CoverageJson {
println!("{}", self.to_json());
return;
return writeln!(buf, "{}", self.to_json());
}
let mut total = ItemCount::default();

fn print_table_line() {
println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "");
fn print_table_line(buf: &mut impl Write) -> io::Result<()> {
writeln!(buf, "+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "")
}

fn print_table_record(
buf: &mut impl Write,
name: &str,
count: ItemCount,
percentage: f64,
examples_percentage: f64,
) {
println!(
) -> io::Result<()> {
writeln!(
buf,
"| {name:<35} | {with_docs:>10} | {percentage:>9.1}% | {with_examples:>10} | \
{examples_percentage:>9.1}% |",
{examples_percentage:>9.1}% |",
with_docs = count.with_docs,
with_examples = count.with_examples,
);
)
}

print_table_line();
println!(
print_table_line(&mut buf)?;
writeln!(
buf,
"| {:<35} | {:>10} | {:>10} | {:>10} | {:>10} |",
"File", "Documented", "Percentage", "Examples", "Percentage",
);
print_table_line();
)?;
print_table_line(&mut buf)?;

for (file, &count) in &self.items {
if let Some(percentage) = count.percentage() {
print_table_record(
&mut buf,
&limit_filename_len(
file.display(RemapPathScopeComponents::COVERAGE).to_string(),
),
count,
percentage,
count.examples_percentage().unwrap_or(0.),
);
)?;

total += count;
}
}

print_table_line();
print_table_line(&mut buf)?;
print_table_record(
&mut buf,
"Total",
total,
total.percentage().unwrap_or(0.0),
total.examples_percentage().unwrap_or(0.0),
);
print_table_line();
)?;
print_table_line(&mut buf)
}
}

Expand Down
9 changes: 8 additions & 1 deletion src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,14 @@ impl Options {
output_to_stdout = out_dir == "-";
PathBuf::from(out_dir)
}
(None, None) => PathBuf::from("doc"),
(None, None) => {
if show_coverage {
// If no `-o` option is given and we're in the `--show-coverage` mode, by
// default we print on the stdout.
output_to_stdout = true;
}
PathBuf::from("doc")
}
};

let cfgs = matches.opt_strs("cfg");
Expand Down
7 changes: 7 additions & 0 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,13 @@ pub(crate) fn run_global_ctxt(
}
}

if show_coverage
&& let Err(error) = crate::calculate_doc_coverage::run(&krate, &mut ctxt, &render_options)
{
eprintln!("{error}");
std::process::exit(1);
}

tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc)));

krate =
Expand Down
5 changes: 3 additions & 2 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ macro_rules! map {
}}
}

mod calculate_doc_coverage;
mod clean;
mod config;
mod core;
Expand Down Expand Up @@ -955,14 +956,14 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
return scrape_examples::run(krate, render_opts, cache, tcx, options, bin_crate);
}

cache.crate_version = crate_version;

if show_coverage {
// if we ran coverage, bail early, we don't need to also generate docs at this point
// (also we didn't load in any of the useful passes)
return;
}

cache.crate_version = crate_version;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's likely a remain when I was testing things around. Although considering we only need to set this information after the return in the if show_coverage block just above, I guess it's fine.


rustc_interface::passes::emit_delayed_lints(tcx);

if render_opts.dep_info().is_some() {
Expand Down
9 changes: 3 additions & 6 deletions src/librustdoc/passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ pub(crate) mod collect_intra_doc_links;
pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;

mod check_doc_test_visibility;
pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY;
pub(crate) use self::check_doc_test_visibility::{
CHECK_DOC_TEST_VISIBILITY, Tests, should_have_doc_example,
};

mod collect_trait_impls;
pub(crate) use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;

mod calculate_doc_coverage;
pub(crate) use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE;

mod lint;
pub(crate) use self::lint::RUN_LINTS;

Expand Down Expand Up @@ -81,7 +80,6 @@ pub(crate) const PASSES: &[Pass] = &[
PROPAGATE_STABILITY,
COLLECT_INTRA_DOC_LINKS,
COLLECT_TRAIT_IMPLS,
CALCULATE_DOC_COVERAGE,
RUN_LINTS,
];

Expand All @@ -103,7 +101,6 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[
pub(crate) const COVERAGE_PASSES: &[ConditionalPass] = &[
ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
ConditionalPass::always(CALCULATE_DOC_COVERAGE),
];

impl ConditionalPass {
Expand Down
5 changes: 5 additions & 0 deletions tests/run-make/rustdoc-show-coverage/foo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub struct Bar;

impl Bar {
pub fn foo() {}
}
56 changes: 56 additions & 0 deletions tests/run-make/rustdoc-show-coverage/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// This test ensures that `-o` option works as expected with `--show-coverage`.
// Regression test for <https://github.com/rust-lang/rust/issues/158929>.

//@ needs-target-std

use run_make_support::assertion_helpers::assert_contains_regex;
use run_make_support::rfs::{read_to_string, remove_file};
use run_make_support::{path, rustdoc};

fn run_rustdoc(extra_args: &[&str]) -> String {
rustdoc()
.input("foo.rs")
.arg("-Zunstable-options")
.arg("--show-coverage")
.args(extra_args)
.run()
.stdout_utf8()
}

fn check_print_stdout(extra_args: &[&str], stdout_check: &str) {
let out = run_rustdoc(extra_args);

// By default, it shouldn't have created a `doc` folder.
assert!(!path("doc").exists(), "`doc` folder created with {extra_args:?}");
// It should have display its output on stdout.
assert!(out.starts_with(stdout_check), "{out:?} doesn't start with {stdout_check:?}");
}

fn check_generate_file(ext: &str, extra_args: &[&str], file_check: &str) {
let mut args = extra_args.to_vec();
args.push("-o");
args.push("doc");
let out = run_rustdoc(&args);

// By default, it shouldn't have created a `doc` folder.
assert!(path("doc").exists(), "`doc` folder not created with {args:?}");
let file = format!("doc/foo.{ext}");
assert!(path(&file).exists());

assert_contains_regex(out, format!("Generated output into \"doc.foo\\.{ext}\"\\s"));

let content = read_to_string(&file);
assert!(content.starts_with(file_check), "{content:?} doesn't start with {file_check:?}");
remove_file(file);
}

fn main() {
check_print_stdout(&[], "+-");
check_print_stdout(&["-o", "-"], "+-");
check_print_stdout(&["--output-format=json"], "{");
check_print_stdout(&["--output-format=json", "-o", "-"], "{");

// Now we check that it works with "-o something".
check_generate_file("txt", &[], "+-");
check_generate_file("json", &["--output-format=json"], "{");
}
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/allow_missing_docs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

//! Make sure to have some docs on your crate root
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/basic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

#![feature(extern_types)]
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/doc-examples-json.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//@ check-pass
//@ compile-flags:-Z unstable-options --output-format json --show-coverage
//@ compile-flags:-Z unstable-options --output-format json --show-coverage -o -

// This check ensures that only one doc example is counted since they're "optional" on
// certain items.
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/doc-examples.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

//! This test ensure that only rust code examples are counted.
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/empty.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

// an empty crate still has one item to document: the crate root
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/enum-tuple-documented.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

// The point of this test is to ensure that the number of "documented" items
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/enum-tuple.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

//! (remember the crate root is still a module)
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/enums.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

//! (remember the crate root is still a module)
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/exotic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage
//@ compile-flags:-Z unstable-options --show-coverage -o -
//@ check-pass

#![feature(rustdoc_internals)]
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/json.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//@ check-pass
//@ compile-flags:-Z unstable-options --output-format json --show-coverage
//@ compile-flags:-Z unstable-options --output-format json --show-coverage -o -

pub mod foo {
/// Hello!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
//@ compile-flags: --show-coverage --output-format=json --emit=dep-info -Zunstable-options
//@ compile-flags: --show-coverage --output-format=json --emit=dep-info -Zunstable-options -o -
//@ build-pass
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/coverage/private.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags:-Z unstable-options --show-coverage --document-private-items
//@ compile-flags:-Z unstable-options --show-coverage --document-private-items -o -
//@ check-pass

#![allow(unused)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags: -Z unstable-options --show-coverage --output-format=json
//@ compile-flags: -Z unstable-options --show-coverage --output-format=json -o -
//@ check-pass

mod bar {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ compile-flags: -Z unstable-options --show-coverage
//@ compile-flags: -Z unstable-options --show-coverage -o -
//@ check-pass

mod bar {
Expand Down
Loading
Loading