@@ -102,6 +102,7 @@ pub(crate) fn runnable(
102102) -> Option < Runnable > {
103103 match_ast ! {
104104 match item {
105+ ast:: Struct ( it) => runnable_struct( sema, it, file_id) ,
105106 ast:: Fn ( it) => runnable_fn( sema, it, file_id) ,
106107 ast:: Module ( it) => runnable_mod( sema, it, file_id) ,
107108 _ => None ,
@@ -182,6 +183,43 @@ fn runnable_fn(
182183 Some ( Runnable { nav, kind, cfg } )
183184}
184185
186+ fn runnable_struct (
187+ sema : & Semantics < RootDatabase > ,
188+ struct_def : ast:: Struct ,
189+ file_id : FileId ,
190+ ) -> Option < Runnable > {
191+ if !has_runnable_doc_test ( & struct_def) {
192+ return None ;
193+ }
194+ let name_string = struct_def. name ( ) ?. text ( ) . to_string ( ) ;
195+
196+ let attrs =
197+ Attrs :: from_attrs_owner ( sema. db , InFile :: new ( HirFileId :: from ( file_id) , & struct_def) ) ;
198+ let cfg = attrs. cfg ( ) ;
199+
200+ let test_id = match sema. to_def ( & struct_def) . map ( |def| def. module ( sema. db ) ) {
201+ Some ( module) => {
202+ let path_iter = module
203+ . path_to_root ( sema. db )
204+ . into_iter ( )
205+ . rev ( )
206+ . filter_map ( |it| it. name ( sema. db ) )
207+ . map ( |name| name. to_string ( ) ) ;
208+ let path = path_iter. chain ( std:: iter:: once ( name_string) ) . join ( "::" ) ;
209+
210+ TestId :: Path ( path)
211+ }
212+ None => TestId :: Name ( name_string) ,
213+ } ;
214+
215+ let nav = NavigationTarget :: from_doc_commented (
216+ sema. db ,
217+ InFile :: new ( file_id. into ( ) , & struct_def) ,
218+ InFile :: new ( file_id. into ( ) , & struct_def) ,
219+ ) ;
220+ Some ( Runnable { nav, kind : RunnableKind :: DocTest { test_id } , cfg } )
221+ }
222+
185223#[ derive( Debug , Copy , Clone ) ]
186224pub struct TestAttr {
187225 pub ignore : bool ,
@@ -215,8 +253,8 @@ const RUSTDOC_FENCE: &str = "```";
215253const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE : & [ & str ] =
216254 & [ "" , "rust" , "should_panic" , "edition2015" , "edition2018" ] ;
217255
218- fn has_runnable_doc_test ( fn_def : & ast :: Fn ) -> bool {
219- fn_def . doc_comment_text ( ) . map_or ( false , |comments_text| {
256+ fn has_runnable_doc_test ( def : & dyn DocCommentsOwner ) -> bool {
257+ def . doc_comment_text ( ) . map_or ( false , |comments_text| {
220258 let mut in_code_block = false ;
221259
222260 for line in comments_text. lines ( ) {
@@ -487,8 +525,14 @@ fn should_have_no_runnable_5() {}
487525/// let z = 55;
488526/// ```
489527fn should_have_no_runnable_6() {}
528+
529+ /// ```
530+ /// let x = 5;
531+ /// ```
532+ struct StructWithRunnable(String);
533+
490534"# ,
491- & [ & BIN , & DOCTEST , & DOCTEST , & DOCTEST ] ,
535+ & [ & BIN , & DOCTEST , & DOCTEST , & DOCTEST , & DOCTEST ] ,
492536 expect ! [ [ r#"
493537 [
494538 Runnable {
@@ -569,6 +613,26 @@ fn should_have_no_runnable_6() {}
569613 },
570614 cfg: None,
571615 },
616+ Runnable {
617+ nav: NavigationTarget {
618+ file_id: FileId(
619+ 0,
620+ ),
621+ full_range: 756..821,
622+ focus_range: None,
623+ name: "StructWithRunnable",
624+ kind: STRUCT,
625+ container_name: None,
626+ description: None,
627+ docs: None,
628+ },
629+ kind: DocTest {
630+ test_id: Path(
631+ "StructWithRunnable",
632+ ),
633+ },
634+ cfg: None,
635+ },
572636 ]
573637 "# ] ] ,
574638 ) ;
0 commit comments