diff --git a/pkgs/jnigen/lib/src/config/yaml_reader.dart b/pkgs/jnigen/lib/src/config/yaml_reader.dart index c9b77de1d3..aacf24ff12 100644 --- a/pkgs/jnigen/lib/src/config/yaml_reader.dart +++ b/pkgs/jnigen/lib/src/config/yaml_reader.dart @@ -88,17 +88,55 @@ class YamlReader { /// from YAML config. Uri? getPath(String property) => _config.optionalPath(property); - List? getStringList(String property) => _config.optionalStringList( - property, - splitCliPattern: ';', - combineAllConfigs: false, - ); - - List? getPathList(String property) => _config.optionalPathList( - property, - combineAllConfigs: false, - splitCliPattern: ';', - ); + /// Reports an entry left empty in [property], which reads as null. + /// + /// A list read from the config file is cast lazily, so such an entry only + /// fails where something reaches for it, with a message that names neither + /// the property nor the file. A list that came from `-D` holds its own + /// strings and cannot carry a null. + /// + /// Pass the list through `cast()`: reading an element of the cast + /// list throws before this can see it, and casting again unwraps to the + /// source rather than stacking on top of it. + void _checkForEmptyEntries(String property, List values) { + for (var i = 0; i < values.length; ++i) { + if (values[i] == null) { + throw ConfigException( + 'Entry ${i + 1} of "$property" is empty in the config file.'); + } + } + } + + List? getStringList(String property) { + final values = _config.optionalStringList( + property, + splitCliPattern: ';', + combineAllConfigs: false, + ); + if (values == null) return null; + // The cast is what lets the check read a null; see the helper. + _checkForEmptyEntries(property, values.cast()); + return values; + } + + List? getPathList(String property) { + // `optionalPathList` reads the entries itself, so they have to be checked + // before it does. + final values = _config.optionalStringList( + property, + splitCliPattern: ';', + combineAllConfigs: false, + ); + if (values != null) { + // The cast is what lets the check read a null; see the helper. + _checkForEmptyEntries(property, values.cast()); + } + return _config.optionalPathList( + property, + combineAllConfigs: false, + splitCliPattern: ';', + ); + } String? getOneOf(String property, Set values) => _config.optionalString(property, validValues: values); diff --git a/pkgs/jnigen/test/config_test.dart b/pkgs/jnigen/test/config_test.dart index f87c9d12d7..30a5634ed7 100644 --- a/pkgs/jnigen/test/config_test.dart +++ b/pkgs/jnigen/test/config_test.dart @@ -123,5 +123,26 @@ void main() async { name: 'Nested class specified', overrides: ['-Dclasses=com.android.Clock\$Clock'], ); + + for (final property in ['classes', 'source_path']) { + test('Empty entry in $property', () { + // Not expressible as an override: the -D parser requires a value. + final dir = Directory.systemTemp.createTempSync('jnigen_config_test'); + addTearDown(() => dir.deleteSync(recursive: true)); + final yaml = File(join(dir.path, 'jnigen.yaml'))..writeAsStringSync(''' +output: + dart: + path: lib/gen.dart + structure: single_file +$property: + - "com.example.Foo" + - +${property == 'classes' ? '' : 'classes:\n - "com.example.Foo"\n'}'''); + expect( + () => Config.parseArgs(['--config', yaml.path]), + throwsA(isA()), + ); + }); + } }); }