@@ -184,6 +184,73 @@ pub(crate) struct DocTestBuilder {
184184 pub ( crate ) can_be_merged : bool ,
185185}
186186
187+ /// Contains needed information for doctest to be correctly generated with expected "wrapping".
188+ pub ( crate ) struct WrapperInfo {
189+ pub ( crate ) before : String ,
190+ pub ( crate ) after : String ,
191+ pub ( crate ) returns_result : bool ,
192+ insert_indent_space : bool ,
193+ }
194+
195+ impl WrapperInfo {
196+ fn len ( & self ) -> usize {
197+ self . before . len ( ) + self . after . len ( )
198+ }
199+ }
200+
201+ /// Contains a doctest information. Can be converted into code with the `to_string()` method.
202+ pub ( crate ) enum DocTestWrapResult {
203+ Valid {
204+ crate_level_code : String ,
205+ wrapper : Option < WrapperInfo > ,
206+ code : String ,
207+ } ,
208+ /// Contains the original source code.
209+ SyntaxError ( String ) ,
210+ }
211+
212+ impl std:: string:: ToString for DocTestWrapResult {
213+ fn to_string ( & self ) -> String {
214+ match self {
215+ Self :: SyntaxError ( s) => s. clone ( ) ,
216+ Self :: Valid { crate_level_code, wrapper, code } => {
217+ let mut prog_len = code. len ( ) + crate_level_code. len ( ) ;
218+ if let Some ( wrapper) = wrapper {
219+ prog_len += wrapper. len ( ) ;
220+ if wrapper. insert_indent_space {
221+ prog_len += code. lines ( ) . count ( ) * 4 ;
222+ }
223+ }
224+ let mut prog = String :: with_capacity ( prog_len) ;
225+
226+ prog. push_str ( crate_level_code) ;
227+ if let Some ( wrapper) = wrapper {
228+ prog. push_str ( & wrapper. before ) ;
229+
230+ // add extra 4 spaces for each line to offset the code block
231+ if wrapper. insert_indent_space {
232+ write ! (
233+ prog,
234+ "{}" ,
235+ fmt:: from_fn( |f| code
236+ . lines( )
237+ . map( |line| fmt:: from_fn( move |f| write!( f, " {line}" ) ) )
238+ . joined( "\n " , f) )
239+ )
240+ . unwrap ( ) ;
241+ } else {
242+ prog. push_str ( code) ;
243+ }
244+ prog. push_str ( & wrapper. after ) ;
245+ } else {
246+ prog. push_str ( code) ;
247+ }
248+ prog
249+ }
250+ }
251+ }
252+ }
253+
187254impl DocTestBuilder {
188255 fn invalid (
189256 crate_attrs : String ,
@@ -214,49 +281,49 @@ impl DocTestBuilder {
214281 dont_insert_main : bool ,
215282 opts : & GlobalTestOptions ,
216283 crate_name : Option < & str > ,
217- ) -> ( String , usize ) {
284+ ) -> ( DocTestWrapResult , usize ) {
218285 if self . invalid_ast {
219286 // If the AST failed to compile, no need to go generate a complete doctest, the error
220287 // will be better this way.
221288 debug ! ( "invalid AST:\n {test_code}" ) ;
222- return ( test_code. to_string ( ) , 0 ) ;
289+ return ( DocTestWrapResult :: SyntaxError ( test_code. to_string ( ) ) , 0 ) ;
223290 }
224291 let mut line_offset = 0 ;
225- let mut prog = String :: new ( ) ;
226- let everything_else = self . everything_else . trim ( ) ;
292+ let mut crate_level_code = String :: new ( ) ;
293+ let code = self . everything_else . trim ( ) . to_string ( ) ;
227294 if opts. attrs . is_empty ( ) {
228295 // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
229296 // lints that are commonly triggered in doctests. The crate-level test attributes are
230297 // commonly used to make tests fail in case they trigger warnings, so having this there in
231298 // that case may cause some tests to pass when they shouldn't have.
232- prog . push_str ( "#![allow(unused)]\n " ) ;
299+ crate_level_code . push_str ( "#![allow(unused)]\n " ) ;
233300 line_offset += 1 ;
234301 }
235302
236303 // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
237304 for attr in & opts. attrs {
238- prog . push_str ( & format ! ( "#![{attr}]\n " ) ) ;
305+ crate_level_code . push_str ( & format ! ( "#![{attr}]\n " ) ) ;
239306 line_offset += 1 ;
240307 }
241308
242309 // Now push any outer attributes from the example, assuming they
243310 // are intended to be crate attributes.
244311 if !self . crate_attrs . is_empty ( ) {
245- prog . push_str ( & self . crate_attrs ) ;
312+ crate_level_code . push_str ( & self . crate_attrs ) ;
246313 if !self . crate_attrs . ends_with ( '\n' ) {
247- prog . push ( '\n' ) ;
314+ crate_level_code . push ( '\n' ) ;
248315 }
249316 }
250317 if !self . maybe_crate_attrs . is_empty ( ) {
251- prog . push_str ( & self . maybe_crate_attrs ) ;
318+ crate_level_code . push_str ( & self . maybe_crate_attrs ) ;
252319 if !self . maybe_crate_attrs . ends_with ( '\n' ) {
253- prog . push ( '\n' ) ;
320+ crate_level_code . push ( '\n' ) ;
254321 }
255322 }
256323 if !self . crates . is_empty ( ) {
257- prog . push_str ( & self . crates ) ;
324+ crate_level_code . push_str ( & self . crates ) ;
258325 if !self . crates . ends_with ( '\n' ) {
259- prog . push ( '\n' ) ;
326+ crate_level_code . push ( '\n' ) ;
260327 }
261328 }
262329
@@ -274,17 +341,20 @@ impl DocTestBuilder {
274341 {
275342 // rustdoc implicitly inserts an `extern crate` item for the own crate
276343 // which may be unused, so we need to allow the lint.
277- prog . push_str ( "#[allow(unused_extern_crates)]\n " ) ;
344+ crate_level_code . push_str ( "#[allow(unused_extern_crates)]\n " ) ;
278345
279- prog . push_str ( & format ! ( "extern crate r#{crate_name};\n " ) ) ;
346+ crate_level_code . push_str ( & format ! ( "extern crate r#{crate_name};\n " ) ) ;
280347 line_offset += 1 ;
281348 }
282349
283350 // FIXME: This code cannot yet handle no_std test cases yet
284- if dont_insert_main || self . has_main_fn || prog. contains ( "![no_std]" ) {
285- prog. push_str ( everything_else) ;
351+ let wrapper = if dont_insert_main
352+ || self . has_main_fn
353+ || crate_level_code. contains ( "![no_std]" )
354+ {
355+ None
286356 } else {
287- let returns_result = everything_else . ends_with ( "(())" ) ;
357+ let returns_result = code . ends_with ( "(())" ) ;
288358 // Give each doctest main function a unique name.
289359 // This is for example needed for the tooling around `-C instrument-coverage`.
290360 let inner_fn_name = if let Some ( ref test_id) = self . test_id {
@@ -318,28 +388,15 @@ impl DocTestBuilder {
318388 // /// ``` <- end of the inner main
319389 line_offset += 1 ;
320390
321- prog. push_str ( & main_pre) ;
322-
323- // add extra 4 spaces for each line to offset the code block
324- if opts. insert_indent_space {
325- write ! (
326- prog,
327- "{}" ,
328- fmt:: from_fn( |f| everything_else
329- . lines( )
330- . map( |line| fmt:: from_fn( move |f| write!( f, " {line}" ) ) )
331- . joined( "\n " , f) )
332- )
333- . unwrap ( ) ;
334- } else {
335- prog. push_str ( everything_else) ;
336- } ;
337- prog. push_str ( & main_post) ;
338- }
339-
340- debug ! ( "final doctest:\n {prog}" ) ;
391+ Some ( WrapperInfo {
392+ before : main_pre,
393+ after : main_post,
394+ returns_result,
395+ insert_indent_space : opts. insert_indent_space ,
396+ } )
397+ } ;
341398
342- ( prog , line_offset)
399+ ( DocTestWrapResult :: Valid { code , wrapper , crate_level_code } , line_offset)
343400 }
344401}
345402
0 commit comments