Skip to content

Commit c28c87f

Browse files
authored
Unrolled build for rust-lang#134531
Rollup merge of rust-lang#134531 - GuillaumeGomez:extract-doctests, r=notriddle,aDotInTheVoid [rustdoc] Add `--extract-doctests` command-line flag Part of rust-lang#134529. It was discussed with the Rust-for-Linux project recently that they needed a way to extract doctests so they can modify them and then run them more easily (look for "a way to extract doctests" [here](Rust-for-Linux/linux#2)). For now, I output most of `ScrapedDoctest` fields in JSON format with `serde_json`. So it outputs the following information: * filename * line * langstr * text cc `@ojeda` r? `@notriddle`
2 parents aa4cfd0 + b795138 commit c28c87f

File tree

10 files changed

+308
-39
lines changed

10 files changed

+308
-39
lines changed

Diff for: src/doc/rustdoc/src/unstable-features.md

+64
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ use `-o -`.
524524

525525
## `-w`/`--output-format`: output format
526526

527+
### json
528+
527529
`--output-format json` emits documentation in the experimental
528530
[JSON format](https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc_json_types/). `--output-format html` has no effect,
529531
and is also accepted on stable toolchains.
@@ -542,6 +544,68 @@ It can also be used with `--show-coverage`. Take a look at its
542544
[documentation](#--show-coverage-calculate-the-percentage-of-items-with-documentation) for more
543545
information.
544546

547+
### doctest
548+
549+
`--output-format doctest` emits JSON on stdout which gives you information about doctests in the
550+
provided crate.
551+
552+
Tracking issue: [#134529](https://github.com/rust-lang/rust/issues/134529)
553+
554+
You can use this option like this:
555+
556+
```bash
557+
rustdoc -Zunstable-options --output-format=doctest src/lib.rs
558+
```
559+
560+
For this rust code:
561+
562+
```rust
563+
/// ```
564+
/// let x = 12;
565+
/// ```
566+
pub trait Trait {}
567+
```
568+
569+
The generated output (formatted) will look like this:
570+
571+
```json
572+
{
573+
"format_version": 1,
574+
"doctests": [
575+
{
576+
"file": "foo.rs",
577+
"line": 1,
578+
"doctest_attributes": {
579+
"original": "",
580+
"should_panic": false,
581+
"no_run": false,
582+
"ignore": "None",
583+
"rust": true,
584+
"test_harness": false,
585+
"compile_fail": false,
586+
"standalone_crate": false,
587+
"error_codes": [],
588+
"edition": null,
589+
"added_css_classes": [],
590+
"unknown": []
591+
},
592+
"original_code": "let x = 12;",
593+
"doctest_code": "#![allow(unused)]\nfn main() {\nlet x = 12;\n}",
594+
"name": "foo.rs - Trait (line 1)"
595+
}
596+
]
597+
}
598+
```
599+
600+
* `format_version` gives you the current version of the generated JSON. If we change the output in any way, the number will increase.
601+
* `doctests` contains the list of doctests present in the crate.
602+
* `file` is the file path where the doctest is located.
603+
* `line` is the line where the doctest starts (so where the \`\`\` is located in the current code).
604+
* `doctest_attributes` contains computed information about the attributes used on the doctests. For more information about doctest attributes, take a look [here](write-documentation/documentation-tests.html#attributes).
605+
* `original_code` is the code as written in the source code before rustdoc modifies it.
606+
* `doctest_code` is the code modified by rustdoc that will be run. If there is a fatal syntax error, this field will not be present.
607+
* `name` is the name generated by rustdoc which represents this doctest.
608+
545609
## `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
546610

547611
* Tracking issue: [#64245](https://github.com/rust-lang/rust/issues/64245)

Diff for: src/librustdoc/config.rs

+37-23
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub(crate) enum OutputFormat {
3333
Json,
3434
#[default]
3535
Html,
36+
Doctest,
3637
}
3738

3839
impl OutputFormat {
@@ -48,6 +49,7 @@ impl TryFrom<&str> for OutputFormat {
4849
match value {
4950
"json" => Ok(OutputFormat::Json),
5051
"html" => Ok(OutputFormat::Html),
52+
"doctest" => Ok(OutputFormat::Doctest),
5153
_ => Err(format!("unknown output format `{value}`")),
5254
}
5355
}
@@ -445,14 +447,42 @@ impl Options {
445447
}
446448
}
447449

450+
let show_coverage = matches.opt_present("show-coverage");
451+
let output_format_s = matches.opt_str("output-format");
452+
let output_format = match output_format_s {
453+
Some(ref s) => match OutputFormat::try_from(s.as_str()) {
454+
Ok(out_fmt) => out_fmt,
455+
Err(e) => dcx.fatal(e),
456+
},
457+
None => OutputFormat::default(),
458+
};
459+
448460
// check for `--output-format=json`
449-
if !matches!(matches.opt_str("output-format").as_deref(), None | Some("html"))
450-
&& !matches.opt_present("show-coverage")
451-
&& !nightly_options::is_unstable_enabled(matches)
452-
{
453-
dcx.fatal(
454-
"the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)",
455-
);
461+
match (
462+
output_format_s.as_ref().map(|_| output_format),
463+
show_coverage,
464+
nightly_options::is_unstable_enabled(matches),
465+
) {
466+
(None | Some(OutputFormat::Json), true, _) => {}
467+
(_, true, _) => {
468+
dcx.fatal(format!(
469+
"`--output-format={}` is not supported for the `--show-coverage` option",
470+
output_format_s.unwrap_or_default(),
471+
));
472+
}
473+
// If `-Zunstable-options` is used, nothing to check after this point.
474+
(_, false, true) => {}
475+
(None | Some(OutputFormat::Html), false, _) => {}
476+
(Some(OutputFormat::Json), false, false) => {
477+
dcx.fatal(
478+
"the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)",
479+
);
480+
}
481+
(Some(OutputFormat::Doctest), false, false) => {
482+
dcx.fatal(
483+
"the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/134529)",
484+
);
485+
}
456486
}
457487

458488
let to_check = matches.opt_strs("check-theme");
@@ -704,29 +734,13 @@ impl Options {
704734
})
705735
.collect();
706736

707-
let show_coverage = matches.opt_present("show-coverage");
708-
709737
let crate_types = match parse_crate_types_from_list(matches.opt_strs("crate-type")) {
710738
Ok(types) => types,
711739
Err(e) => {
712740
dcx.fatal(format!("unknown crate type: {e}"));
713741
}
714742
};
715743

716-
let output_format = match matches.opt_str("output-format") {
717-
Some(s) => match OutputFormat::try_from(s.as_str()) {
718-
Ok(out_fmt) => {
719-
if !out_fmt.is_json() && show_coverage {
720-
dcx.fatal(
721-
"html output format isn't supported for the --show-coverage option",
722-
);
723-
}
724-
out_fmt
725-
}
726-
Err(e) => dcx.fatal(e),
727-
},
728-
None => OutputFormat::default(),
729-
};
730744
let crate_name = matches.opt_str("crate-name");
731745
let bin_crate = crate_types.contains(&CrateType::Executable);
732746
let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);

Diff for: src/librustdoc/doctest.rs

+38-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod extracted;
12
mod make;
23
mod markdown;
34
mod runner;
@@ -30,7 +31,7 @@ use tempfile::{Builder as TempFileBuilder, TempDir};
3031
use tracing::debug;
3132

3233
use self::rust::HirCollector;
33-
use crate::config::Options as RustdocOptions;
34+
use crate::config::{Options as RustdocOptions, OutputFormat};
3435
use crate::html::markdown::{ErrorCodes, Ignore, LangString, MdRelLine};
3536
use crate::lint::init_lints;
3637

@@ -209,15 +210,8 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
209210
let args_path = temp_dir.path().join("rustdoc-cfgs");
210211
crate::wrap_return(dcx, generate_args_file(&args_path, &options));
211212

212-
let CreateRunnableDocTests {
213-
standalone_tests,
214-
mergeable_tests,
215-
rustdoc_options,
216-
opts,
217-
unused_extern_reports,
218-
compiling_test_count,
219-
..
220-
} = interface::run_compiler(config, |compiler| {
213+
let extract_doctests = options.output_format == OutputFormat::Doctest;
214+
let result = interface::run_compiler(config, |compiler| {
221215
let krate = rustc_interface::passes::parse(&compiler.sess);
222216

223217
let collector = rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| {
@@ -226,22 +220,53 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
226220
let opts = scrape_test_config(crate_name, crate_attrs, args_path);
227221
let enable_per_target_ignores = options.enable_per_target_ignores;
228222

229-
let mut collector = CreateRunnableDocTests::new(options, opts);
230223
let hir_collector = HirCollector::new(
231224
ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()),
232225
enable_per_target_ignores,
233226
tcx,
234227
);
235228
let tests = hir_collector.collect_crate();
236-
tests.into_iter().for_each(|t| collector.add_test(t));
229+
if extract_doctests {
230+
let mut collector = extracted::ExtractedDocTests::new();
231+
tests.into_iter().for_each(|t| collector.add_test(t, &opts, &options));
232+
233+
let stdout = std::io::stdout();
234+
let mut stdout = stdout.lock();
235+
if let Err(error) = serde_json::ser::to_writer(&mut stdout, &collector) {
236+
eprintln!();
237+
Err(format!("Failed to generate JSON output for doctests: {error:?}"))
238+
} else {
239+
Ok(None)
240+
}
241+
} else {
242+
let mut collector = CreateRunnableDocTests::new(options, opts);
243+
tests.into_iter().for_each(|t| collector.add_test(t));
237244

238-
collector
245+
Ok(Some(collector))
246+
}
239247
});
240248
compiler.sess.dcx().abort_if_errors();
241249

242250
collector
243251
});
244252

253+
let CreateRunnableDocTests {
254+
standalone_tests,
255+
mergeable_tests,
256+
rustdoc_options,
257+
opts,
258+
unused_extern_reports,
259+
compiling_test_count,
260+
..
261+
} = match result {
262+
Ok(Some(collector)) => collector,
263+
Ok(None) => return,
264+
Err(error) => {
265+
eprintln!("{error}");
266+
std::process::exit(1);
267+
}
268+
};
269+
245270
run_tests(opts, &rustdoc_options, &unused_extern_reports, standalone_tests, mergeable_tests);
246271

247272
let compiling_test_count = compiling_test_count.load(Ordering::SeqCst);

0 commit comments

Comments
 (0)