Unverified Commit 134aa8e9 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[gen-l10n] Add `nullable-getter` flag (#79263)

parent cf6d4a35
......@@ -172,6 +172,17 @@ class GenerateLocalizationsCommand extends FlutterCommand {
'\n'
'Resource attributes are still required for plural messages.'
);
argParser.addFlag(
'nullable-getter',
help: 'Whether or not the localizations class getter is nullable.\n'
'\n'
'By default, this value is set to true so that '
'Localizations.of(context) returns a nullable value '
'for backwards compatibility. If this value is set to true, then '
'a null check is performed on the returned value of '
'Localizations.of(context), removing the need for null checking in '
'user code.'
);
}
final FileSystem _fileSystem;
......@@ -220,6 +231,7 @@ class GenerateLocalizationsCommand extends FlutterCommand {
final bool useSyntheticPackage = boolArg('synthetic-package');
final String projectPathString = stringArg('project-dir');
final bool areResourceAttributesRequired = boolArg('required-resource-attributes');
final bool usesNullableGetter = boolArg('nullable-getter');
final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(_fileSystem);
......@@ -242,6 +254,7 @@ class GenerateLocalizationsCommand extends FlutterCommand {
projectPathString: projectPathString,
areResourceAttributesRequired: areResourceAttributesRequired,
untranslatedMessagesFile: untranslatedMessagesFile,
usesNullableGetter: usesNullableGetter,
)
..loadResources()
..writeOutputFiles(_logger);
......
......@@ -66,6 +66,7 @@ void generateLocalizations({
useSyntheticPackage: options.useSyntheticPackage ?? true,
areResourceAttributesRequired: options.areResourceAttributesRequired ?? false,
untranslatedMessagesFile: options?.untranslatedMessagesFile?.toFilePath(),
usesNullableGetter: options?.usesNullableGetter ?? true,
)
..loadResources()
..writeOutputFiles(logger, isFromYaml: true);
......@@ -545,6 +546,10 @@ class LocalizationsGenerator {
AppResourceBundleCollection _allBundles;
LocaleInfo _templateArbLocale;
bool _useSyntheticPackage = true;
// Used to decide if the generated code is nullable or not
// (whether AppLocalizations? or AppLocalizations is returned from
// `static {name}Localizations{?} of (BuildContext context))`
bool _usesNullableGetter = true;
/// The directory that contains the project's arb files, as well as the
/// header file, if specified.
......@@ -689,8 +694,10 @@ class LocalizationsGenerator {
String projectPathString,
bool areResourceAttributesRequired = false,
String untranslatedMessagesFile,
bool usesNullableGetter = true,
}) {
_useSyntheticPackage = useSyntheticPackage;
_usesNullableGetter = usesNullableGetter;
setProjectDir(projectPathString);
setInputDirectory(inputPathString);
setOutputDirectory(outputPathString ?? inputPathString);
......@@ -1162,7 +1169,9 @@ class LocalizationsGenerator {
.replaceAll('@(supportedLanguageCodes)', supportedLanguageCodes.join(', '))
.replaceAll('@(messageClassImports)', sortedClassImports.join('\n'))
.replaceAll('@(delegateClass)', delegateClass)
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '');
.replaceAll('@(requiresIntlImport)', _containsPluralMessage() ? "import 'package:intl/intl.dart' as intl;" : '')
.replaceAll('@(canBeNullable)', _usesNullableGetter ? '?' : '')
.replaceAll('@(needsNullCheck)', _usesNullableGetter ? '' : '!');
}
bool _containsPluralMessage() => _allMessages.any((Message message) => message.isPlural);
......
......@@ -77,8 +77,8 @@ abstract class @(class) {
// ignore: unused_field
final String localeName;
static @(class)? of(BuildContext context) {
return Localizations.of<@(class)>(context, @(class));
static @(class)@(canBeNullable) of(BuildContext context) {
return Localizations.of<@(class)>(context, @(class))@(needsNullCheck);
}
static const LocalizationsDelegate<@(class)> delegate = _@(class)Delegate();
......
......@@ -304,6 +304,7 @@ class LocalizationOptions {
this.deferredLoading,
this.useSyntheticPackage = true,
this.areResourceAttributesRequired = false,
this.usesNullableGetter = true,
}) : assert(useSyntheticPackage != null);
/// The `--arb-dir` argument.
......@@ -365,6 +366,11 @@ class LocalizationOptions {
/// Whether to require all resource ids to contain a corresponding
/// resource attribute.
final bool areResourceAttributesRequired;
/// The `nullable-getter` argument.
///
/// Whether or not the localizations class getter is nullable.
final bool usesNullableGetter;
}
/// Parse the localizations configuration options from [file].
......@@ -398,6 +404,7 @@ LocalizationOptions parseLocalizationsOptions({
deferredLoading: _tryReadBool(yamlNode, 'use-deferred-loading', logger),
useSyntheticPackage: _tryReadBool(yamlNode, 'synthetic-package', logger) ?? true,
areResourceAttributesRequired: _tryReadBool(yamlNode, 'required-resource-attributes', logger) ?? false,
usesNullableGetter: _tryReadBool(yamlNode, 'nullable-getter', logger) ?? true,
);
}
......
......@@ -44,6 +44,7 @@ void main() {
untranslatedMessagesFile: Uri.file('untranslated'),
useSyntheticPackage: false,
areResourceAttributesRequired: true,
usesNullableGetter: false,
);
final LocalizationsGenerator mockLocalizationsGenerator = MockLocalizationsGenerator();
......@@ -70,6 +71,7 @@ void main() {
projectPathString: '/',
areResourceAttributesRequired: true,
untranslatedMessagesFile: 'untranslated',
usesNullableGetter: false,
),
).called(1);
verify(mockLocalizationsGenerator.loadResources()).called(1);
......@@ -151,6 +153,9 @@ header-file: header
header: HEADER
use-deferred-loading: true
preferred-supported-locales: en_US
synthetic-package: false
required-resource-attributes: false
nullable-getter: false
''');
final LocalizationOptions options = parseLocalizationsOptions(
......@@ -167,6 +172,9 @@ preferred-supported-locales: en_US
expect(options.header, 'HEADER');
expect(options.deferredLoading, true);
expect(options.preferredSupportedLocales, <String>['en_US']);
expect(options.useSyntheticPackage, false);
expect(options.areResourceAttributesRequired, false);
expect(options.usesNullableGetter, false);
});
testWithoutContext('parseLocalizationsOptions handles preferredSupportedLocales as list', () async {
......
......@@ -800,6 +800,82 @@ void main() {
},
);
testUsingContext(
'generates nullable localizations class getter via static `of` method '
'by default',
() {
_standardFlutterDirectoryL10nSetup(fs);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: fs.path.join('lib', 'l10n', 'output'),
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
useSyntheticPackage: false,
)
..loadResources()
..writeOutputFiles(BufferLogger.test());
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output');
expect(outputDirectory.existsSync(), isTrue);
expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue);
expect(
outputDirectory.childFile('output-localization-file.dart').readAsStringSync(),
contains('static AppLocalizations? of(BuildContext context)'),
);
expect(
outputDirectory.childFile('output-localization-file.dart').readAsStringSync(),
contains('return Localizations.of<AppLocalizations>(context, AppLocalizations);'),
);
},
);
testUsingContext(
'can generate non-nullable localizations class getter via static `of` method ',
() {
_standardFlutterDirectoryL10nSetup(fs);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: fs.path.join('lib', 'l10n', 'output'),
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
useSyntheticPackage: false,
usesNullableGetter: false,
)
..loadResources()
..writeOutputFiles(BufferLogger.test());
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output');
expect(outputDirectory.existsSync(), isTrue);
expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue);
expect(
outputDirectory.childFile('output-localization-file.dart').readAsStringSync(),
contains('static AppLocalizations of(BuildContext context)'),
);
expect(
outputDirectory.childFile('output-localization-file.dart').readAsStringSync(),
contains('return Localizations.of<AppLocalizations>(context, AppLocalizations)!;'),
);
},
);
testUsingContext('creates list of inputs and outputs when file path is specified', () {
_standardFlutterDirectoryL10nSetup(fs);
......
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