@@ -308,6 +308,10 @@ pub struct Config {
308308 #[ serde( skip) ]
309309 deprecation_warnings : Vec < DeprecationWarning > ,
310310
311+ // Holds unknown fields we encountered while parsing
312+ #[ serde( skip, default ) ]
313+ unknown_fields : Vec < String > ,
314+
311315 #[ serde( default = "default_path" ) ]
312316 pub path : PathBuf ,
313317}
@@ -435,9 +439,14 @@ impl Config {
435439
436440 /// Try to convert a bsconfig from a string to a bsconfig struct
437441 pub fn new_from_json_string ( config_str : & str ) -> Result < Self > {
438- let mut config = serde_json:: from_str :: < Config > ( config_str) ?;
442+ let mut deserializer = serde_json:: Deserializer :: from_str ( config_str) ;
443+ let mut unknown_fields = Vec :: new ( ) ;
444+ let mut config: Config = serde_ignored:: deserialize ( & mut deserializer, |path| {
445+ unknown_fields. push ( path. to_string ( ) ) ;
446+ } ) ?;
439447
440448 config. handle_deprecations ( ) ?;
449+ config. unknown_fields = unknown_fields;
441450
442451 Ok ( config)
443452 }
@@ -662,6 +671,52 @@ impl Config {
662671 & self . deprecation_warnings
663672 }
664673
674+ pub fn get_unknown_fields ( & self ) -> Vec < String > {
675+ self . unknown_fields
676+ . iter ( )
677+ . filter ( |field| !self . is_unsupported_field ( field) )
678+ . cloned ( )
679+ . collect ( )
680+ }
681+
682+ pub fn get_unsupported_fields ( & self ) -> Vec < String > {
683+ let mut fields = self
684+ . unknown_fields
685+ . iter ( )
686+ . filter ( |field| self . is_unsupported_field ( field) )
687+ . cloned ( )
688+ . collect :: < Vec < _ > > ( ) ;
689+
690+ if self . gentype_config . is_some ( ) {
691+ fields. push ( "gentypeconfig" . to_string ( ) ) ;
692+ }
693+
694+ fields
695+ }
696+
697+ fn is_unsupported_field ( & self , field : & str ) -> bool {
698+ const UNSUPPORTED_TOP_LEVEL_FIELDS : & [ & str ] = & [
699+ "version" ,
700+ "ignored-dirs" ,
701+ "generators" ,
702+ "cut-generators" ,
703+ "pp-flags" ,
704+ "js-post-build" ,
705+ "entries" ,
706+ "use-stdlib" ,
707+ "external-stdlib" ,
708+ "bs-external-includes" ,
709+ "reanalyze" ,
710+ ] ;
711+
712+ let top_level = field
713+ . split ( |c| c == '.' || c == '[' )
714+ . next ( )
715+ . unwrap_or ( field) ;
716+
717+ UNSUPPORTED_TOP_LEVEL_FIELDS . contains ( & top_level)
718+ }
719+
665720 fn handle_deprecations ( & mut self ) -> Result < ( ) > {
666721 if self . dependencies . is_some ( ) && self . bs_dependencies . is_some ( ) {
667722 bail ! ( "dependencies and bs-dependencies are mutually exclusive. Please use 'dependencies'." ) ;
@@ -729,6 +784,7 @@ pub mod tests {
729784 deprecation_warnings : vec ! [ ] ,
730785 experimental_features : None ,
731786 allowed_dependents : args. allowed_dependents ,
787+ unknown_fields : vec ! [ ] ,
732788 path : args. path ,
733789 }
734790 }
@@ -1023,6 +1079,45 @@ pub mod tests {
10231079 assert ! ( config. get_deprecations( ) . is_empty( ) ) ;
10241080 }
10251081
1082+ #[ test]
1083+ fn test_unknown_fields_are_collected ( ) {
1084+ let json = r#"
1085+ {
1086+ "name": "testrepo",
1087+ "sources": {
1088+ "dir": "src",
1089+ "subdirs": true
1090+ },
1091+ "some-new-field": true
1092+ }
1093+ "# ;
1094+
1095+ let config = Config :: new_from_json_string ( json) . expect ( "a valid json string" ) ;
1096+ assert_eq ! ( config. get_unknown_fields( ) , vec![ "some-new-field" . to_string( ) ] ) ;
1097+ assert ! ( config. get_unsupported_fields( ) . is_empty( ) ) ;
1098+ }
1099+
1100+ #[ test]
1101+ fn test_unsupported_fields_are_collected ( ) {
1102+ let json = r#"
1103+ {
1104+ "name": "testrepo",
1105+ "sources": {
1106+ "dir": "src",
1107+ "subdirs": true
1108+ },
1109+ "ignored-dirs": ["scripts"]
1110+ }
1111+ "# ;
1112+
1113+ let config = Config :: new_from_json_string ( json) . expect ( "a valid json string" ) ;
1114+ assert_eq ! (
1115+ config. get_unsupported_fields( ) ,
1116+ vec![ "ignored-dirs" . to_string( ) ]
1117+ ) ;
1118+ assert ! ( config. get_unknown_fields( ) . is_empty( ) ) ;
1119+ }
1120+
10261121 #[ test]
10271122 fn test_compiler_flags ( ) {
10281123 let json = r#"
0 commit comments