4646//! Within each [`Mod`], items are emitted in sort-key order and submodules
4747//! in alphabetical order by name.
4848
49+ #![ forbid( unsafe_code) ]
50+ #![ warn( missing_docs, missing_debug_implementations) ]
51+
4952use std:: {
5053 collections:: { btree_map:: Entry , BTreeMap } ,
5154 path:: { Path , PathBuf } ,
@@ -67,7 +70,7 @@ fn validate_mod_name(name: &str, full: &str) {
6770 // syn must accept `gen` (an identifier before edition 2024), but our
6871 // generated code may land in a 2024 crate, so we reject it ourselves.
6972 if name == "gen" {
70- panic ! ( "module name {:?} (in {:?}) is a Rust keyword" , name , full ) ;
73+ panic ! ( "module name {name :?} (in {full :?}) is a Rust keyword" ) ;
7174 }
7275 if !name. starts_with ( "r#" ) && syn:: parse_str :: < syn:: Ident > ( name) . is_ok ( ) {
7376 return ;
@@ -84,12 +87,11 @@ fn validate_mod_name(name: &str, full: &str) {
8487 )
8588 } ) ;
8689 if is_keyword {
87- panic ! ( "module name {:?} (in {:?}) is a Rust keyword" , name , full ) ;
90+ panic ! ( "module name {name :?} (in {full :?}) is a Rust keyword" ) ;
8891 }
8992 panic ! (
90- "module name {:?} (in {:?}) is not a valid Rust identifier \
93+ "module name {name :?} (in {full :?}) is not a valid Rust identifier \
9194 (raw identifiers are not supported)",
92- name, full,
9395 ) ;
9496}
9597
@@ -505,7 +507,7 @@ impl Mod {
505507 #vis mod #ident;
506508 } ) ;
507509 let ( file, child_dir) = if m. mods . is_empty ( ) {
508- ( dir. join ( format ! ( "{}.rs" , name ) ) , dir. to_path_buf ( ) )
510+ ( dir. join ( format ! ( "{name }.rs" ) ) , dir. to_path_buf ( ) )
509511 } else {
510512 let child_dir = dir. join ( & name) ;
511513 ( child_dir. join ( "mod.rs" ) , child_dir)
@@ -622,7 +624,7 @@ mod tests {
622624 }
623625
624626 #[ test]
625- #[ should_panic]
627+ #[ should_panic( expected = "is not a valid Rust identifier" ) ]
626628 fn invalid_mod_name_panics ( ) {
627629 let mut cs = Codespace :: default ( ) ;
628630 cs. add_item ( "not-valid-ident::key" , quote ! { } ) ;
@@ -671,7 +673,7 @@ mod tests {
671673 }
672674
673675 #[ test]
674- #[ should_panic]
676+ #[ should_panic( expected = "is not a valid Rust identifier" ) ]
675677 fn get_mod_invalid_ident_panics ( ) {
676678 let mut cs = Codespace :: default ( ) ;
677679 cs. get_root_mod ( ) . get_mod ( "not-valid" ) ;
@@ -751,9 +753,9 @@ mod tests {
751753 m. add_attr ( quote ! { allow( dead_code) } ) ;
752754 m. add_item ( "f" , quote ! { pub fn f( ) { } } ) ;
753755 let out = no_ws ( & cs. into_stream ( ) . to_string ( ) ) ;
754- assert ! ( out. contains( r## "#[doc="Helperfunctions."]"# # ) ) ;
756+ assert ! ( out. contains( r#"#[doc="Helperfunctions."]"# ) ) ;
755757 // Metadata appears immediately before the mod, in outer form.
756- assert ! ( out. contains( r## "#[allow(dead_code)]pubmodhelpers{"# # ) ) ;
758+ assert ! ( out. contains( r#"#[allow(dead_code)]pubmodhelpers{"# ) ) ;
757759 assert ! ( !out. contains( "#!" ) ) ;
758760 }
759761
@@ -815,11 +817,56 @@ mod tests {
815817 let mut cs = Codespace :: default ( ) ;
816818 cs. get_root_mod ( ) . add_mod ( "m" , a) ;
817819 let out = no_ws ( & cs. into_stream ( ) . to_string ( ) ) ;
818- assert ! ( out. contains( r## "#[doc="First."]#[doc=""]#[doc="Second."]"# # ) ) ;
820+ assert ! ( out. contains( r#"#[doc="First."]#[doc=""]#[doc="Second."]"# ) ) ;
819821 assert ! ( out. contains( "#[allow(dead_code)]#[allow(unused)]" ) ) ;
820822 assert ! ( out. contains( "pub(crate)modm" ) ) ;
821823 }
822824
825+ #[ test]
826+ fn replace_mod_clobbers_existing ( ) {
827+ let mut root = Mod :: default ( ) ;
828+ // First insertion: nothing to return.
829+ let mut m1 = Mod :: default ( ) ;
830+ m1. add_item ( "f" , quote ! { pub fn f( ) { } } ) ;
831+ assert ! ( root. replace_mod( "client" , m1) . is_none( ) ) ;
832+ // Replacement returns the previous module and clobbers it--no
833+ // merging, in contrast to add_mod.
834+ let mut m2 = Mod :: default ( ) ;
835+ m2. add_item ( "g" , quote ! { pub fn g( ) { } } ) ;
836+ let prev = root. replace_mod ( "client" , m2) . unwrap ( ) ;
837+ assert ! ( prev. items. contains_key( "f" ) ) ;
838+ let mut cs = Codespace :: default ( ) ;
839+ * cs. get_root_mod ( ) = root;
840+ let out = cs. into_stream ( ) . to_string ( ) ;
841+ assert_eq ! ( out. matches( "mod client" ) . count( ) , 1 ) ;
842+ assert ! ( out. contains( "fn g" ) ) ;
843+ assert ! ( !out. contains( "fn f" ) ) ;
844+ }
845+
846+ #[ test]
847+ #[ should_panic( expected = "is a Rust keyword" ) ]
848+ fn replace_mod_keyword_panics ( ) {
849+ let mut root = Mod :: default ( ) ;
850+ root. replace_mod ( "type" , Mod :: default ( ) ) ;
851+ }
852+
853+ #[ test]
854+ fn into_root_mod_mounts_in_another_codespace ( ) {
855+ // Generate a subsystem in its own Codespace, then mount it as a
856+ // submodule of a larger one.
857+ let mut inner = Codespace :: default ( ) ;
858+ inner. add_item ( "Foo" , quote ! { pub struct Foo ; } ) ;
859+ inner. add_item ( "defaults::f" , quote ! { pub fn f( ) { } } ) ;
860+ let mut outer = Codespace :: default ( ) ;
861+ outer. add_item ( "Bar" , quote ! { pub struct Bar ; } ) ;
862+ outer. add_mod ( "subsystem" , inner. into_root_mod ( ) ) ;
863+ let out = no_ws ( & outer. into_stream ( ) . to_string ( ) ) ;
864+ assert ! ( out. contains( "pubstructBar" ) ) ;
865+ assert ! ( out. contains( "pubmodsubsystem{" ) ) ;
866+ assert ! ( out. contains( "pubstructFoo" ) ) ;
867+ assert ! ( out. contains( "pubmoddefaults{" ) ) ;
868+ }
869+
823870 #[ test]
824871 fn add_mod_inserts_new ( ) {
825872 let mut cs = Codespace :: default ( ) ;
@@ -912,14 +959,35 @@ mod tests {
912959 // Docs and attrs are outer, at the declaration site, immediately
913960 // before the visibility and declaration.
914961 assert ! (
915- lib. contains( r## "#[doc="Helperfunctions."]#[allow(dead_code)]pub(crate)modhelpers;"# # )
962+ lib. contains( r#"#[doc="Helperfunctions."]#[allow(dead_code)]pub(crate)modhelpers;"# )
916963 ) ;
917964 // Nothing of the metadata leaks into the child file.
918965 let helpers = no_ws ( & files[ Path :: new ( "helpers.rs" ) ] . to_string ( ) ) ;
919966 assert ! ( !helpers. contains( "doc" ) ) ;
920967 assert ! ( !helpers. contains( "allow" ) ) ;
921968 }
922969
970+ #[ test]
971+ fn into_files_non_leaf_decl_carries_metadata ( ) {
972+ // Like into_files_decl_carries_metadata, but for a module with
973+ // children: metadata belongs on the declaration in the parent, not
974+ // in the child's mod.rs.
975+ let mut cs = Codespace :: default ( ) ;
976+ let m = cs. get_root_mod ( ) . get_mod ( "outer" ) ;
977+ m. set_visibility ( Visibility :: Private ) ;
978+ m. add_docs ( "Outer module." ) ;
979+ m. add_attr ( quote ! { allow( dead_code) } ) ;
980+ m. get_mod ( "inner" ) . add_item ( "f" , quote ! { pub fn f( ) { } } ) ;
981+ let files = cs. into_files ( ) ;
982+ let lib = no_ws ( & files[ Path :: new ( "lib.rs" ) ] . to_string ( ) ) ;
983+ assert ! ( lib. contains( r#"#[doc="Outermodule."]#[allow(dead_code)]modouter;"# ) ) ;
984+ assert ! ( !lib. contains( "pubmodouter" ) ) ;
985+ // The child's own file holds only its contents and its child's
986+ // declaration.
987+ let outer = no_ws ( & files[ Path :: new ( "outer/mod.rs" ) ] . to_string ( ) ) ;
988+ assert_eq ! ( outer, "pubmodinner;" ) ;
989+ }
990+
923991 #[ test]
924992 fn into_files_root_meta_is_inner ( ) {
925993 let mut cs = Codespace :: default ( ) ;
@@ -928,7 +996,7 @@ mod tests {
928996 cs. add_item ( "Foo" , quote ! { pub struct Foo ; } ) ;
929997 let files = cs. into_files ( ) ;
930998 let lib = no_ws ( & files[ Path :: new ( "lib.rs" ) ] . to_string ( ) ) ;
931- assert ! ( lib. starts_with( r## "#![doc="Generatedcode."]#![allow(clippy::all)]"# # ) ) ;
999+ assert ! ( lib. starts_with( r#"#![doc="Generatedcode."]#![allow(clippy::all)]"# ) ) ;
9321000 }
9331001
9341002 #[ test]
@@ -971,7 +1039,7 @@ mod tests {
9711039 // Lines within one call stay in one paragraph; a blank doc line
9721040 // separates calls.
9731041 assert ! ( out. contains(
974- r## "#[doc="Firstparagraph."]#[doc="Stillthefirst."]#[doc=""]#[doc="Secondparagraph."]"# #
1042+ r#"#[doc="Firstparagraph."]#[doc="Stillthefirst."]#[doc=""]#[doc="Secondparagraph."]"#
9751043 ) ) ;
9761044 }
9771045
@@ -982,9 +1050,8 @@ mod tests {
9821050 cs. get_root_mod ( ) . add_attr ( quote ! { allow( clippy:: all) } ) ;
9831051 cs. add_item ( "Foo" , quote ! { pub struct Foo ; } ) ;
9841052 let out = no_ws ( & cs. into_stream ( ) . to_string ( ) ) ;
985- assert ! ( out. starts_with(
986- r##"#![doc="Generatedcode."]#![doc="Donotedit."]#![allow(clippy::all)]"##
987- ) ) ;
1053+ assert ! ( out
1054+ . starts_with( r#"#![doc="Generatedcode."]#![doc="Donotedit."]#![allow(clippy::all)]"# ) ) ;
9881055 assert ! ( out. contains( "pubstructFoo" ) ) ;
9891056 }
9901057}
0 commit comments