From d2ee4ca8929912c33346600a5d6090fe0bf96913 Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Fri, 27 Mar 2026 00:09:58 +0000 Subject: [PATCH] rustdoc: Represent `--output-format=json` coverage and ir differently `--output-format=json` does a few different things: - By itself, it emits a JSON representation of a crate's API ("IR JSON"). - When used with `--show-coverage`, it prints the doc coverage as JSON, instead of an ASCII table ("Coverage JSON"). These two cases need to be handled entirely differently. There's no overlapping code, they just are called the same way. By making these separate variants, we don't need to check the `show_coverage` variable each time we check `is_json`, which makes it harder to write a bug that checks for IR JSON, and accidentally happens in coverage JSON too. Also, as a driveby, improves some instability error messages, and cleans up their tests, and adds other related tests. --- src/librustdoc/config.rs | 76 +++++++++---------- src/librustdoc/core.rs | 5 +- src/librustdoc/json/mod.rs | 2 +- src/librustdoc/lib.rs | 8 +- .../passes/calculate_doc_coverage.rs | 4 +- .../run-make/unstable-flag-required/README.md | 3 - .../output-format-json.stderr | 2 - .../run-make/unstable-flag-required/rmake.rs | 12 --- tests/run-make/unstable-flag-required/x.rs | 1 - ...output-format-coveragejson-emit-depinfo.rs | 2 + ...ut-format-coveragejson-emit-depinfo.stdout | 1 + .../output-format-doctests-unstable.rs | 4 + .../output-format-doctests-unstable.stderr | 2 + .../output-format-irjson-emit-depinfo.rs | 2 + .../output-format-irjson-unstable.rs | 4 + .../output-format-irjson-unstable.stderr | 2 + ...-emit-html.html_non_static_coverage.stderr | 2 + ...json-emit-html.html_static_coverage.stderr | 2 + .../output-format-json-emit-html.rs | 6 +- tests/rustdoc-ui/output-format-unknown.rs | 4 + tests/rustdoc-ui/output-format-unknown.stderr | 2 + 21 files changed, 77 insertions(+), 69 deletions(-) delete mode 100644 tests/run-make/unstable-flag-required/README.md delete mode 100644 tests/run-make/unstable-flag-required/output-format-json.stderr delete mode 100644 tests/run-make/unstable-flag-required/rmake.rs delete mode 100644 tests/run-make/unstable-flag-required/x.rs create mode 100644 tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs create mode 100644 tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.stdout create mode 100644 tests/rustdoc-ui/output-format-doctests-unstable.rs create mode 100644 tests/rustdoc-ui/output-format-doctests-unstable.stderr create mode 100644 tests/rustdoc-ui/output-format-irjson-emit-depinfo.rs create mode 100644 tests/rustdoc-ui/output-format-irjson-unstable.rs create mode 100644 tests/rustdoc-ui/output-format-irjson-unstable.stderr create mode 100644 tests/rustdoc-ui/output-format-json-emit-html.html_non_static_coverage.stderr create mode 100644 tests/rustdoc-ui/output-format-json-emit-html.html_static_coverage.stderr create mode 100644 tests/rustdoc-ui/output-format-unknown.rs create mode 100644 tests/rustdoc-ui/output-format-unknown.stderr diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 3cd34f24c6e3d..a8c27c5615007 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -29,33 +29,18 @@ use crate::passes::{self, Condition}; use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions}; use crate::{html, opts, theme}; -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum OutputFormat { - Json, - #[default] + /// `--output-format=json` without `--show-coverage`. + /// + /// JSON description of crate API. + IrJson, + /// `--output-format=json` with `--show-coverage`. + CoverageJson, Html, Doctest, } -impl OutputFormat { - pub(crate) fn is_json(&self) -> bool { - matches!(self, OutputFormat::Json) - } -} - -impl TryFrom<&str> for OutputFormat { - type Error = String; - - fn try_from(value: &str) -> Result { - match value { - "json" => Ok(OutputFormat::Json), - "html" => Ok(OutputFormat::Html), - "doctest" => Ok(OutputFormat::Doctest), - _ => Err(format!("unknown output format `{value}`")), - } - } -} - /// Either an input crate, markdown file, or nothing (--merge=finalize). pub(crate) enum InputMode { /// The `--merge=finalize` step does not need an input crate to rustdoc. @@ -329,7 +314,8 @@ pub(crate) enum EmitType { HtmlStaticFiles, HtmlNonStaticFiles, // not explicitly nameable by the user for now - JsonFiles, + IrJsonFiles, + CoverageJsonFiles, DepInfo(Option), } @@ -338,7 +324,8 @@ impl fmt::Display for EmitType { f.write_str(match self { Self::HtmlStaticFiles => "html-static-files", Self::HtmlNonStaticFiles => "html-non-static-files", - Self::JsonFiles => "json-files", + Self::IrJsonFiles => "ir-json-files", + Self::CoverageJsonFiles => "coverage-json-files", Self::DepInfo(_) => "dep-info", }) } @@ -479,40 +466,48 @@ impl Options { let show_coverage = matches.opt_present("show-coverage"); let output_format_s = matches.opt_str("output-format"); - let output_format = match output_format_s { - Some(ref s) => match OutputFormat::try_from(s.as_str()) { - Ok(out_fmt) => out_fmt, - Err(e) => dcx.fatal(e), - }, - None => OutputFormat::default(), + let output_format = match output_format_s.as_deref() { + None | Some("html") => OutputFormat::Html, + Some("json") => { + if show_coverage { + OutputFormat::CoverageJson + } else { + OutputFormat::IrJson + } + } + Some("doctest") => OutputFormat::Doctest, + Some(other) => dcx.fatal(format!("unknown output format `{other}`")), }; - // check for `--output-format=json` + // check for `--output-format` stability, and compatibility with `--show-coverage` match ( output_format_s.as_ref().map(|_| output_format), show_coverage, nightly_options::is_unstable_enabled(matches), ) { - (None | Some(OutputFormat::Json), true, _) => {} + (None | Some(OutputFormat::CoverageJson), true, _) => {} (_, true, _) => { dcx.fatal(format!( "`--output-format={}` is not supported for the `--show-coverage` option", - output_format_s.unwrap_or_default(), + output_format_s.expect("checked for none above"), )); } // If `-Zunstable-options` is used, nothing to check after this point. (_, false, true) => {} (None | Some(OutputFormat::Html), false, _) => {} - (Some(OutputFormat::Json), false, false) => { + (Some(OutputFormat::IrJson), false, false) => { dcx.fatal( - "the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)", + "the -Z unstable-options flag must be passed to enable --output-format=json for documentation generation (see https://github.com/rust-lang/rust/issues/76578)", ); } (Some(OutputFormat::Doctest), false, false) => { dcx.fatal( - "the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/134529)", + "the -Z unstable-options flag must be passed to enable --output-format=doctest (see https://github.com/rust-lang/rust/issues/134529)", ); } + (Some(OutputFormat::CoverageJson), false, _) => { + unreachable!("CoverageJson is only possible when show_coverage is true") + } } let mut emit = FxIndexMap::default(); @@ -531,18 +526,18 @@ impl Options { match typ { EmitType::DepInfo(_) => match output_format { - OutputFormat::Json | OutputFormat::Html => {} + OutputFormat::Html | OutputFormat::IrJson | OutputFormat::CoverageJson => {} OutputFormat::Doctest => unreachable!(), }, EmitType::HtmlStaticFiles | EmitType::HtmlNonStaticFiles => match output_format { OutputFormat::Html => {} - OutputFormat::Json => dcx.fatal(format!( + OutputFormat::IrJson | OutputFormat::CoverageJson => dcx.fatal(format!( "the `--emit={typ}` flag is not supported with `--output-format=json`", )), OutputFormat::Doctest => unreachable!(), }, - EmitType::JsonFiles => unreachable!(), + EmitType::IrJsonFiles | EmitType::CoverageJsonFiles => unreachable!(), } // De-duplicate emit types and the last wins. @@ -558,7 +553,8 @@ impl Options { // will have already been rejected above. if emit.is_empty() { match output_format { - OutputFormat::Json => emit.push(EmitType::JsonFiles), + OutputFormat::IrJson => emit.push(EmitType::IrJsonFiles), + OutputFormat::CoverageJson => emit.push(EmitType::CoverageJsonFiles), OutputFormat::Html => { emit.push(EmitType::HtmlStaticFiles); emit.push(EmitType::HtmlNonStaticFiles); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index fccfa3f57e930..5bdedf7ae3d54 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -75,8 +75,6 @@ pub(crate) struct DocContext<'tcx> { pub(crate) inlined: FxHashSet, /// Used by `calculate_doc_coverage`. pub(crate) output_format: OutputFormat, - /// Used by `strip_private`. - pub(crate) show_coverage: bool, } impl<'tcx> DocContext<'tcx> { @@ -139,7 +137,7 @@ impl<'tcx> DocContext<'tcx> { /// /// If another option like `--show-coverage` is enabled, it will return `false`. pub(crate) fn is_json_output(&self) -> bool { - self.output_format.is_json() && !self.show_coverage + self.output_format == OutputFormat::IrJson } /// If `--document-private-items` was passed to rustdoc. @@ -385,7 +383,6 @@ pub(crate) fn run_global_ctxt( cache: Cache::new(render_options.document_private, render_options.document_hidden), inlined: FxHashSet::default(), output_format, - show_coverage, }; for cnum in tcx.crates(()) { diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 433e4487eb9d5..2d6865dc689b6 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -134,7 +134,7 @@ impl<'tcx> JsonRenderer<'tcx> { impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { const DESCR: &'static str = "json"; const RUN_ON_MODULE: bool = false; - const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::JsonFiles; + const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::IrJsonFiles; type ModuleData = (); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index a4f8e3e12e510..17f5af024ac7f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -984,11 +984,13 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { }, ) }), - config::OutputFormat::Json => sess.time("render_json", || { + config::OutputFormat::IrJson => sess.time("render_json", || { run_renderer(krate, render_opts, cache, tcx, json::JsonRenderer::init) }), - // Already handled above with doctest runners. - config::OutputFormat::Doctest => unreachable!(), + // Already handled above with doctest runners or coverage early return + config::OutputFormat::Doctest | config::OutputFormat::CoverageJson => { + unreachable!() + } } }) }) diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 200a5a71b453a..adf98afc46cc0 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -11,6 +11,7 @@ use serde::Serialize; use tracing::debug; use crate::clean; +use crate::config::OutputFormat; use crate::core::DocContext; use crate::html::markdown::{ErrorCodes, find_testable_code}; use crate::passes::Pass; @@ -131,8 +132,7 @@ impl CoverageCalculator<'_, '_> { fn print_results(&self) { let output_format = self.ctx.output_format; - // In this case we want to ensure that the `OutputFormat` is JSON and NOT the `DocContext`. - if output_format.is_json() { + if output_format == OutputFormat::CoverageJson { println!("{}", self.to_json()); return; } diff --git a/tests/run-make/unstable-flag-required/README.md b/tests/run-make/unstable-flag-required/README.md deleted file mode 100644 index e5251fdf9b5d9..0000000000000 --- a/tests/run-make/unstable-flag-required/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This is a collection of tests that verify `--unstable-options` is required. -It should eventually be removed in favor of UI tests once compiletest stops -passing --unstable-options by default (#82639). diff --git a/tests/run-make/unstable-flag-required/output-format-json.stderr b/tests/run-make/unstable-flag-required/output-format-json.stderr deleted file mode 100644 index fb4079beb2799..0000000000000 --- a/tests/run-make/unstable-flag-required/output-format-json.stderr +++ /dev/null @@ -1,2 +0,0 @@ -error: the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578) - diff --git a/tests/run-make/unstable-flag-required/rmake.rs b/tests/run-make/unstable-flag-required/rmake.rs deleted file mode 100644 index c521436c203d5..0000000000000 --- a/tests/run-make/unstable-flag-required/rmake.rs +++ /dev/null @@ -1,12 +0,0 @@ -// The flag `--output-format` is unauthorized on beta and stable releases, which led -// to confusion for maintainers doing testing on nightly. Tying it to an unstable flag -// elucidates this, and this test checks that `--output-format` cannot be passed on its -// own. -// See https://github.com/rust-lang/rust/pull/82497 - -use run_make_support::{diff, rustdoc}; - -fn main() { - let out = rustdoc().output_format("json").input("x.html").run_fail().stderr_utf8(); - diff().expected_file("output-format-json.stderr").actual_text("actual-json", out).run(); -} diff --git a/tests/run-make/unstable-flag-required/x.rs b/tests/run-make/unstable-flag-required/x.rs deleted file mode 100644 index 5df7576133a68..0000000000000 --- a/tests/run-make/unstable-flag-required/x.rs +++ /dev/null @@ -1 +0,0 @@ -// nothing to see here diff --git a/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs b/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs new file mode 100644 index 0000000000000..0609b515e5ca8 --- /dev/null +++ b/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs @@ -0,0 +1,2 @@ +//@ compile-flags: --show-coverage --output-format=json --emit=dep-info -Zunstable-options +//@ build-pass diff --git a/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.stdout b/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.stdout new file mode 100644 index 0000000000000..f3ba8205ddedf --- /dev/null +++ b/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.stdout @@ -0,0 +1 @@ +{"$DIR/output-format-coveragejson-emit-depinfo.rs":{"total":1,"with_docs":0,"total_examples":0,"with_examples":0}} diff --git a/tests/rustdoc-ui/output-format-doctests-unstable.rs b/tests/rustdoc-ui/output-format-doctests-unstable.rs new file mode 100644 index 0000000000000..b409f619fcc42 --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctests-unstable.rs @@ -0,0 +1,4 @@ +//@ compile-flags: --output-format=doctest +pub struct Foo; + +//~? ERROR the -Z unstable-options flag must be passed to enable --output-format=doctest (see https://github.com/rust-lang/rust/issues/134529) diff --git a/tests/rustdoc-ui/output-format-doctests-unstable.stderr b/tests/rustdoc-ui/output-format-doctests-unstable.stderr new file mode 100644 index 0000000000000..b4e920efb2b0f --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctests-unstable.stderr @@ -0,0 +1,2 @@ +error: the -Z unstable-options flag must be passed to enable --output-format=doctest (see https://github.com/rust-lang/rust/issues/134529) + diff --git a/tests/rustdoc-ui/output-format-irjson-emit-depinfo.rs b/tests/rustdoc-ui/output-format-irjson-emit-depinfo.rs new file mode 100644 index 0000000000000..7b5e7eaf998d4 --- /dev/null +++ b/tests/rustdoc-ui/output-format-irjson-emit-depinfo.rs @@ -0,0 +1,2 @@ +//@ compile-flags: --output-format=json --emit=dep-info -Zunstable-options +//@ build-pass diff --git a/tests/rustdoc-ui/output-format-irjson-unstable.rs b/tests/rustdoc-ui/output-format-irjson-unstable.rs new file mode 100644 index 0000000000000..88ba753616bb9 --- /dev/null +++ b/tests/rustdoc-ui/output-format-irjson-unstable.rs @@ -0,0 +1,4 @@ +//@ compile-flags: --output-format=json +pub struct Foo; + +//~? ERROR the -Z unstable-options flag must be passed to enable --output-format=json for documentation diff --git a/tests/rustdoc-ui/output-format-irjson-unstable.stderr b/tests/rustdoc-ui/output-format-irjson-unstable.stderr new file mode 100644 index 0000000000000..dd9cea9301189 --- /dev/null +++ b/tests/rustdoc-ui/output-format-irjson-unstable.stderr @@ -0,0 +1,2 @@ +error: the -Z unstable-options flag must be passed to enable --output-format=json for documentation generation (see https://github.com/rust-lang/rust/issues/76578) + diff --git a/tests/rustdoc-ui/output-format-json-emit-html.html_non_static_coverage.stderr b/tests/rustdoc-ui/output-format-json-emit-html.html_non_static_coverage.stderr new file mode 100644 index 0000000000000..8d8e8c6d12281 --- /dev/null +++ b/tests/rustdoc-ui/output-format-json-emit-html.html_non_static_coverage.stderr @@ -0,0 +1,2 @@ +error: the `--emit=html-non-static-files` flag is not supported with `--output-format=json` + diff --git a/tests/rustdoc-ui/output-format-json-emit-html.html_static_coverage.stderr b/tests/rustdoc-ui/output-format-json-emit-html.html_static_coverage.stderr new file mode 100644 index 0000000000000..bc1ddc1b155d1 --- /dev/null +++ b/tests/rustdoc-ui/output-format-json-emit-html.html_static_coverage.stderr @@ -0,0 +1,2 @@ +error: the `--emit=html-static-files` flag is not supported with `--output-format=json` + diff --git a/tests/rustdoc-ui/output-format-json-emit-html.rs b/tests/rustdoc-ui/output-format-json-emit-html.rs index 7ce3bf756ec92..f97392bab5b68 100644 --- a/tests/rustdoc-ui/output-format-json-emit-html.rs +++ b/tests/rustdoc-ui/output-format-json-emit-html.rs @@ -1,7 +1,11 @@ -//@ revisions: html_static html_non_static +//@ revisions: html_static html_non_static html_static_coverage html_non_static_coverage //@ compile-flags: -Zunstable-options --output-format json //@[html_static] compile-flags: --emit html-static-files +//@[html_static_coverage] compile-flags: --emit html-static-files --show-coverage //@[html_non_static] compile-flags: --emit html-non-static-files +//@[html_non_static_coverage] compile-flags: --emit html-non-static-files --show-coverage //[html_static]~? ERROR the `--emit=html-static-files` flag is not supported with `--output-format=json` +//[html_static_coverage]~? ERROR the `--emit=html-static-files` flag is not supported with `--output-format=json` //[html_non_static]~? ERROR the `--emit=html-non-static-files` flag is not supported with `--output-format=json` +//[html_non_static_coverage]~? ERROR the `--emit=html-non-static-files` flag is not supported with `--output-format=json` diff --git a/tests/rustdoc-ui/output-format-unknown.rs b/tests/rustdoc-ui/output-format-unknown.rs new file mode 100644 index 0000000000000..281a9ceb4cfd7 --- /dev/null +++ b/tests/rustdoc-ui/output-format-unknown.rs @@ -0,0 +1,4 @@ +//@ compile-flags: --output-format=da-doo-ron-ron + +pub struct Foo; +//~? ERROR unknown output format `da-doo-ron-ron` diff --git a/tests/rustdoc-ui/output-format-unknown.stderr b/tests/rustdoc-ui/output-format-unknown.stderr new file mode 100644 index 0000000000000..0f34e62b628ef --- /dev/null +++ b/tests/rustdoc-ui/output-format-unknown.stderr @@ -0,0 +1,2 @@ +error: unknown output format `da-doo-ron-ron` +