Add new output-format
This commit is contained in:
parent
ccc9ba5c30
commit
0323af93b4
4 changed files with 105 additions and 12 deletions
|
@ -33,6 +33,7 @@ pub(crate) enum OutputFormat {
|
|||
Json,
|
||||
#[default]
|
||||
Html,
|
||||
Doctest,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
|
@ -48,6 +49,7 @@ impl TryFrom<&str> for OutputFormat {
|
|||
match value {
|
||||
"json" => Ok(OutputFormat::Json),
|
||||
"html" => Ok(OutputFormat::Html),
|
||||
"doctest" => Ok(OutputFormat::Doctest),
|
||||
_ => Err(format!("unknown output format `{value}`")),
|
||||
}
|
||||
}
|
||||
|
@ -446,12 +448,20 @@ impl Options {
|
|||
}
|
||||
|
||||
// check for `--output-format=json`
|
||||
if !matches!(matches.opt_str("output-format").as_deref(), None | Some("html"))
|
||||
if let Some(format) = matches.opt_str("output-format").as_deref()
|
||||
&& format != "html"
|
||||
&& !matches.opt_present("show-coverage")
|
||||
&& !nightly_options::is_unstable_enabled(matches)
|
||||
{
|
||||
let extra = if format == "json" {
|
||||
" (see https://github.com/rust-lang/rust/issues/76578)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
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)",
|
||||
format!(
|
||||
"the -Z unstable-options flag must be passed to enable --output-format for documentation generation{extra}",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,11 +26,12 @@ use rustc_span::FileName;
|
|||
use rustc_span::edition::Edition;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_target::spec::{Target, TargetTuple};
|
||||
use serde::{Serialize, Serializer};
|
||||
use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||
use tracing::debug;
|
||||
|
||||
use self::rust::HirCollector;
|
||||
use crate::config::Options as RustdocOptions;
|
||||
use crate::config::{Options as RustdocOptions, OutputFormat};
|
||||
use crate::html::markdown::{ErrorCodes, Ignore, LangString, MdRelLine};
|
||||
use crate::lint::init_lints;
|
||||
|
||||
|
@ -133,6 +134,14 @@ fn get_doctest_dir() -> io::Result<TempDir> {
|
|||
TempFileBuilder::new().prefix("rustdoctest").tempdir()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExtractedDoctest {
|
||||
/// `None` if the code syntax is invalid.
|
||||
doctest_code: Option<String>,
|
||||
#[serde(flatten)] // We make all `ScrapedDocTest` fields at the same level as `doctest_code`.
|
||||
scraped_test: ScrapedDocTest,
|
||||
}
|
||||
|
||||
pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions) {
|
||||
let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
|
||||
|
||||
|
@ -209,6 +218,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
|
|||
let args_path = temp_dir.path().join("rustdoc-cfgs");
|
||||
crate::wrap_return(dcx, generate_args_file(&args_path, &options));
|
||||
|
||||
let extract_doctests = options.output_format == OutputFormat::Doctest;
|
||||
let CreateRunnableDocTests {
|
||||
standalone_tests,
|
||||
mergeable_tests,
|
||||
|
@ -217,7 +227,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
|
|||
unused_extern_reports,
|
||||
compiling_test_count,
|
||||
..
|
||||
} = interface::run_compiler(config, |compiler| {
|
||||
} = match interface::run_compiler(config, |compiler| {
|
||||
let krate = rustc_interface::passes::parse(&compiler.sess);
|
||||
|
||||
let collector = rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| {
|
||||
|
@ -226,21 +236,64 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
|
|||
let opts = scrape_test_config(crate_name, crate_attrs, args_path);
|
||||
let enable_per_target_ignores = options.enable_per_target_ignores;
|
||||
|
||||
let mut collector = CreateRunnableDocTests::new(options, opts);
|
||||
let hir_collector = HirCollector::new(
|
||||
ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()),
|
||||
enable_per_target_ignores,
|
||||
tcx,
|
||||
);
|
||||
let tests = hir_collector.collect_crate();
|
||||
tests.into_iter().for_each(|t| collector.add_test(t));
|
||||
if extract_doctests {
|
||||
let extracted = tests
|
||||
.into_iter()
|
||||
.map(|scraped_test| {
|
||||
let edition = scraped_test.edition(&options);
|
||||
let doctest = DocTestBuilder::new(
|
||||
&scraped_test.text,
|
||||
Some(&opts.crate_name),
|
||||
edition,
|
||||
false,
|
||||
None,
|
||||
Some(&scraped_test.langstr),
|
||||
);
|
||||
let (full_test_code, size) = doctest.generate_unique_doctest(
|
||||
&scraped_test.text,
|
||||
scraped_test.langstr.test_harness,
|
||||
&opts,
|
||||
Some(&opts.crate_name),
|
||||
);
|
||||
ExtractedDoctest {
|
||||
doctest_code: if size != 0 { Some(full_test_code) } else { None },
|
||||
scraped_test,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
collector
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
if let Err(error) = serde_json::ser::to_writer(&mut stdout, &extracted) {
|
||||
eprintln!();
|
||||
Err(format!("Failed to generate JSON output for doctests: {error:?}"))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
let mut collector = CreateRunnableDocTests::new(options, opts);
|
||||
tests.into_iter().for_each(|t| collector.add_test(t));
|
||||
|
||||
Ok(Some(collector))
|
||||
}
|
||||
});
|
||||
compiler.sess.dcx().abort_if_errors();
|
||||
|
||||
collector
|
||||
});
|
||||
}) {
|
||||
Ok(Some(collector)) => collector,
|
||||
Ok(None) => return,
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
run_tests(opts, &rustdoc_options, &unused_extern_reports, standalone_tests, mergeable_tests);
|
||||
|
||||
|
@ -752,6 +805,14 @@ impl IndividualTestOptions {
|
|||
}
|
||||
}
|
||||
|
||||
fn filename_to_string<S: Serializer>(
|
||||
filename: &FileName,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let filename = filename.prefer_remapped_unconditionaly().to_string();
|
||||
serializer.serialize_str(&filename)
|
||||
}
|
||||
|
||||
/// A doctest scraped from the code, ready to be turned into a runnable test.
|
||||
///
|
||||
/// The pipeline goes: [`clean`] AST -> `ScrapedDoctest` -> `RunnableDoctest`.
|
||||
|
@ -761,10 +822,14 @@ impl IndividualTestOptions {
|
|||
/// [`clean`]: crate::clean
|
||||
/// [`run_merged_tests`]: crate::doctest::runner::DocTestRunner::run_merged_tests
|
||||
/// [`generate_unique_doctest`]: crate::doctest::make::DocTestBuilder::generate_unique_doctest
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct ScrapedDocTest {
|
||||
#[serde(serialize_with = "filename_to_string")]
|
||||
filename: FileName,
|
||||
line: usize,
|
||||
#[serde(rename = "doctest_attributes")]
|
||||
langstr: LangString,
|
||||
#[serde(rename = "original_code")]
|
||||
text: String,
|
||||
name: String,
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ pub(crate) use rustc_resolve::rustdoc::main_body_opts;
|
|||
use rustc_resolve::rustdoc::may_be_doc_link;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use serde::{Serialize, Serializer};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::clean::RenderedLink;
|
||||
|
@ -820,7 +821,17 @@ impl<'tcx> ExtraInfo<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||
fn edition_to_string<S: Serializer>(
|
||||
edition: &Option<Edition>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
match edition {
|
||||
Some(edition) => serializer.serialize_some(&edition.to_string()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, Serialize)]
|
||||
pub(crate) struct LangString {
|
||||
pub(crate) original: String,
|
||||
pub(crate) should_panic: bool,
|
||||
|
@ -831,12 +842,13 @@ pub(crate) struct LangString {
|
|||
pub(crate) compile_fail: bool,
|
||||
pub(crate) standalone_crate: bool,
|
||||
pub(crate) error_codes: Vec<String>,
|
||||
#[serde(serialize_with = "edition_to_string")]
|
||||
pub(crate) edition: Option<Edition>,
|
||||
pub(crate) added_classes: Vec<String>,
|
||||
pub(crate) unknown: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||
#[derive(Eq, PartialEq, Clone, Debug, Serialize)]
|
||||
pub(crate) enum Ignore {
|
||||
All,
|
||||
None,
|
||||
|
|
|
@ -814,7 +814,12 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
|
|||
}
|
||||
};
|
||||
|
||||
match (options.should_test, config::markdown_input(&input)) {
|
||||
let output_format = options.output_format;
|
||||
|
||||
match (
|
||||
options.should_test || output_format == config::OutputFormat::Doctest,
|
||||
config::markdown_input(&input),
|
||||
) {
|
||||
(true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(&input, options)),
|
||||
(true, None) => return doctest::run(dcx, input, options),
|
||||
(false, Some(md_input)) => {
|
||||
|
@ -849,7 +854,6 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
|
|||
// plug/cleaning passes.
|
||||
let crate_version = options.crate_version.clone();
|
||||
|
||||
let output_format = options.output_format;
|
||||
let scrape_examples_options = options.scrape_examples_options.clone();
|
||||
let bin_crate = options.bin_crate;
|
||||
|
||||
|
@ -899,6 +903,8 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
|
|||
config::OutputFormat::Json => sess.time("render_json", || {
|
||||
run_renderer::<json::JsonRenderer<'_>>(krate, render_opts, cache, tcx)
|
||||
}),
|
||||
// Already handled above with doctest runners.
|
||||
config::OutputFormat::Doctest => unreachable!(),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue