Unverified Commit f2db93df authored by Alex Li's avatar Alex Li Committed by GitHub

🐛 Treat empty ARB content as empty map when decoding (#131242)

Fixes #128932.
parent d250fa62
...@@ -566,13 +566,18 @@ class Message { ...@@ -566,13 +566,18 @@ class Message {
} }
} }
// Represents the contents of one ARB file. /// Represents the contents of one ARB file.
class AppResourceBundle { class AppResourceBundle {
/// Assuming that the caller has verified that the file exists and is readable.
factory AppResourceBundle(File file) { factory AppResourceBundle(File file) {
// Assuming that the caller has verified that the file exists and is readable. final Map<String, Object?> resources;
Map<String, Object?> resources;
try { try {
resources = json.decode(file.readAsStringSync()) as Map<String, Object?>; final String content = file.readAsStringSync().trim();
if (content.isEmpty) {
resources = <String, Object?>{};
} else {
resources = json.decode(content) as Map<String, Object?>;
}
} on FormatException catch (e) { } on FormatException catch (e) {
throw L10nException( throw L10nException(
'The arb file ${file.path} has the following formatting issue: \n' 'The arb file ${file.path} has the following formatting issue: \n'
...@@ -657,20 +662,26 @@ class AppResourceBundleCollection { ...@@ -657,20 +662,26 @@ class AppResourceBundleCollection {
final RegExp filenameRE = RegExp(r'(\w+)\.arb$'); final RegExp filenameRE = RegExp(r'(\w+)\.arb$');
final Map<LocaleInfo, AppResourceBundle> localeToBundle = <LocaleInfo, AppResourceBundle>{}; final Map<LocaleInfo, AppResourceBundle> localeToBundle = <LocaleInfo, AppResourceBundle>{};
final Map<String, List<LocaleInfo>> languageToLocales = <String, List<LocaleInfo>>{}; final Map<String, List<LocaleInfo>> languageToLocales = <String, List<LocaleInfo>>{};
final List<File> files = directory.listSync().whereType<File>().toList()..sort(sortFilesByPath); // We require the list of files to be sorted so that
// "languageToLocales[bundle.locale.languageCode]" is not null
// by the time we handle locales with country codes.
final List<File> files = directory
.listSync()
.whereType<File>()
.where((File e) => filenameRE.hasMatch(e.path))
.toList()
..sort(sortFilesByPath);
for (final File file in files) { for (final File file in files) {
if (filenameRE.hasMatch(file.path)) { final AppResourceBundle bundle = AppResourceBundle(file);
final AppResourceBundle bundle = AppResourceBundle(file); if (localeToBundle[bundle.locale] != null) {
if (localeToBundle[bundle.locale] != null) { throw L10nException(
throw L10nException( "Multiple arb files with the same '${bundle.locale}' locale detected. \n"
"Multiple arb files with the same '${bundle.locale}' locale detected. \n" 'Ensure that there is exactly one arb file for each locale.'
'Ensure that there is exactly one arb file for each locale.' );
);
}
localeToBundle[bundle.locale] = bundle;
languageToLocales[bundle.locale.languageCode] ??= <LocaleInfo>[];
languageToLocales[bundle.locale.languageCode]!.add(bundle.locale);
} }
localeToBundle[bundle.locale] = bundle;
languageToLocales[bundle.locale.languageCode] ??= <LocaleInfo>[];
languageToLocales[bundle.locale.languageCode]!.add(bundle.locale);
} }
languageToLocales.forEach((String language, List<LocaleInfo> listOfCorrespondingLocales) { languageToLocales.forEach((String language, List<LocaleInfo> listOfCorrespondingLocales) {
......
...@@ -10,6 +10,7 @@ import 'package:flutter_tools/src/build_system/build_system.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/localizations.dart'; import 'package:flutter_tools/src/build_system/targets/localizations.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/generate_localizations.dart'; import 'package:flutter_tools/src/commands/generate_localizations.dart';
import 'package:flutter_tools/src/localizations/gen_l10n_types.dart';
import '../../integration.shard/test_data/basic_project.dart'; import '../../integration.shard/test_data/basic_project.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -501,4 +502,33 @@ format: true ...@@ -501,4 +502,33 @@ format: true
throwsToolExit(message: 'Unexpected positional argument "false".') throwsToolExit(message: 'Unexpected positional argument "false".')
); );
}); });
group(AppResourceBundle, () {
testWithoutContext("can be parsed without FormatException when it's content is empty", () {
final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
expect(AppResourceBundle(arbFile), isA<AppResourceBundle>());
});
testUsingContext("would not fail the gen-l10n command when it's content is empty", () async {
fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb')).createSync(recursive: true);
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec);
final GenerateLocalizationsCommand command = GenerateLocalizationsCommand(
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
processManager: processManager,
);
await createTestCommandRunner(command).run(<String>['gen-l10n']);
final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n'));
expect(outputDirectory.existsSync(), true);
expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true);
expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment