@@ -26,11 +26,12 @@ use rustc_span::FileName;
2626use rustc_span:: edition:: Edition ;
2727use rustc_span:: symbol:: sym;
2828use rustc_target:: spec:: { Target , TargetTuple } ;
29+ use serde:: { Serialize , Serializer } ;
2930use tempfile:: { Builder as TempFileBuilder , TempDir } ;
3031use tracing:: debug;
3132
3233use self :: rust:: HirCollector ;
33- use crate :: config:: Options as RustdocOptions ;
34+ use crate :: config:: { Options as RustdocOptions , OutputFormat } ;
3435use crate :: html:: markdown:: { ErrorCodes , Ignore , LangString , MdRelLine } ;
3536use crate :: lint:: init_lints;
3637
@@ -133,6 +134,14 @@ fn get_doctest_dir() -> io::Result<TempDir> {
133134 TempFileBuilder :: new ( ) . prefix ( "rustdoctest" ) . tempdir ( )
134135}
135136
137+ #[ derive( Serialize ) ]
138+ struct ExtractedDoctest {
139+ /// `None` if the code syntax is invalid.
140+ doctest_code : Option < String > ,
141+ #[ serde( flatten) ] // We make all `ScrapedDocTest` fields at the same level as `doctest_code`.
142+ scraped_test : ScrapedDocTest ,
143+ }
144+
136145pub ( crate ) fn run ( dcx : DiagCtxtHandle < ' _ > , input : Input , options : RustdocOptions ) {
137146 let invalid_codeblock_attributes_name = crate :: lint:: INVALID_CODEBLOCK_ATTRIBUTES . name ;
138147
@@ -209,6 +218,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
209218 let args_path = temp_dir. path ( ) . join ( "rustdoc-cfgs" ) ;
210219 crate :: wrap_return ( dcx, generate_args_file ( & args_path, & options) ) ;
211220
221+ let extract_doctests = options. output_format == OutputFormat :: Doctest ;
212222 let CreateRunnableDocTests {
213223 standalone_tests,
214224 mergeable_tests,
@@ -217,7 +227,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
217227 unused_extern_reports,
218228 compiling_test_count,
219229 ..
220- } = interface:: run_compiler ( config, |compiler| {
230+ } = match interface:: run_compiler ( config, |compiler| {
221231 let krate = rustc_interface:: passes:: parse ( & compiler. sess ) ;
222232
223233 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
226236 let opts = scrape_test_config ( crate_name, crate_attrs, args_path) ;
227237 let enable_per_target_ignores = options. enable_per_target_ignores ;
228238
229- let mut collector = CreateRunnableDocTests :: new ( options, opts) ;
230239 let hir_collector = HirCollector :: new (
231240 ErrorCodes :: from ( compiler. sess . opts . unstable_features . is_nightly_build ( ) ) ,
232241 enable_per_target_ignores,
233242 tcx,
234243 ) ;
235244 let tests = hir_collector. collect_crate ( ) ;
236- tests. into_iter ( ) . for_each ( |t| collector. add_test ( t) ) ;
245+ if extract_doctests {
246+ let extracted = tests
247+ . into_iter ( )
248+ . map ( |scraped_test| {
249+ let edition = scraped_test. edition ( & options) ;
250+ let doctest = DocTestBuilder :: new (
251+ & scraped_test. text ,
252+ Some ( & opts. crate_name ) ,
253+ edition,
254+ false ,
255+ None ,
256+ Some ( & scraped_test. langstr ) ,
257+ ) ;
258+ let ( full_test_code, size) = doctest. generate_unique_doctest (
259+ & scraped_test. text ,
260+ scraped_test. langstr . test_harness ,
261+ & opts,
262+ Some ( & opts. crate_name ) ,
263+ ) ;
264+ ExtractedDoctest {
265+ doctest_code : if size != 0 { Some ( full_test_code) } else { None } ,
266+ scraped_test,
267+ }
268+ } )
269+ . collect :: < Vec < _ > > ( ) ;
270+
271+ let stdout = std:: io:: stdout ( ) ;
272+ let mut stdout = stdout. lock ( ) ;
273+ if let Err ( error) = serde_json:: ser:: to_writer ( & mut stdout, & extracted) {
274+ eprintln ! ( ) ;
275+ Err ( format ! ( "Failed to generate JSON output for doctests: {error:?}" ) )
276+ } else {
277+ Ok ( None )
278+ }
279+ } else {
280+ let mut collector = CreateRunnableDocTests :: new ( options, opts) ;
281+ tests. into_iter ( ) . for_each ( |t| collector. add_test ( t) ) ;
237282
238- collector
283+ Ok ( Some ( collector) )
284+ }
239285 } ) ;
240286 compiler. sess . dcx ( ) . abort_if_errors ( ) ;
241287
242288 collector
243- } ) ;
289+ } ) {
290+ Ok ( Some ( collector) ) => collector,
291+ Ok ( None ) => return ,
292+ Err ( error) => {
293+ eprintln ! ( "{error}" ) ;
294+ std:: process:: exit ( 1 ) ;
295+ }
296+ } ;
244297
245298 run_tests ( opts, & rustdoc_options, & unused_extern_reports, standalone_tests, mergeable_tests) ;
246299
@@ -752,6 +805,14 @@ impl IndividualTestOptions {
752805 }
753806}
754807
808+ fn filename_to_string < S : Serializer > (
809+ filename : & FileName ,
810+ serializer : S ,
811+ ) -> Result < S :: Ok , S :: Error > {
812+ let filename = filename. prefer_remapped_unconditionaly ( ) . to_string ( ) ;
813+ serializer. serialize_str ( & filename)
814+ }
815+
755816/// A doctest scraped from the code, ready to be turned into a runnable test.
756817///
757818/// The pipeline goes: [`clean`] AST -> `ScrapedDoctest` -> `RunnableDoctest`.
@@ -761,10 +822,14 @@ impl IndividualTestOptions {
761822/// [`clean`]: crate::clean
762823/// [`run_merged_tests`]: crate::doctest::runner::DocTestRunner::run_merged_tests
763824/// [`generate_unique_doctest`]: crate::doctest::make::DocTestBuilder::generate_unique_doctest
825+ #[ derive( Serialize ) ]
764826pub ( crate ) struct ScrapedDocTest {
827+ #[ serde( serialize_with = "filename_to_string" ) ]
765828 filename : FileName ,
766829 line : usize ,
830+ #[ serde( rename = "doctest_attributes" ) ]
767831 langstr : LangString ,
832+ #[ serde( rename = "original_code" ) ]
768833 text : String ,
769834 name : String ,
770835}
0 commit comments