From 5c1320e5b98aefbcb13b40264acd4a6575d15ff9 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 1 Nov 2017 16:14:10 -0700 Subject: [PATCH] Validate the @foo resources in material_en.arb (#12824) --- dev/tools/gen_date_localizations.dart | 4 +- dev/tools/gen_localizations.dart | 9 +++- dev/tools/localizations_utils.dart | 10 ++-- dev/tools/localizations_validator.dart | 68 +++++++++++++++++++++----- 4 files changed, 73 insertions(+), 18 deletions(-) diff --git a/dev/tools/gen_date_localizations.dart b/dev/tools/gen_date_localizations.dart index e93ecf493d7..ad81532d4bf 100644 --- a/dev/tools/gen_date_localizations.dart +++ b/dev/tools/gen_date_localizations.dart @@ -44,7 +44,7 @@ Future main(List rawArgs) async { final bool dotPackagesExists = dotPackagesFile.existsSync(); if (!dotPackagesExists) { - fatal( + exitWithError( 'File not found: ${dotPackagesFile.path}. $_kCommandName must be run ' 'after a successful "flutter update-packages".' ); @@ -56,7 +56,7 @@ Future main(List rawArgs) async { .firstWhere( (String line) => line.startsWith('intl:'), orElse: () { - fatal('intl dependency not found in ${dotPackagesFile.path}'); + exitWithError('intl dependency not found in ${dotPackagesFile.path}'); }, ) .split(':') diff --git a/dev/tools/gen_localizations.dart b/dev/tools/gen_localizations.dart index 8bdda808856..53476858152 100644 --- a/dev/tools/gen_localizations.dart +++ b/dev/tools/gen_localizations.dart @@ -142,6 +142,10 @@ void main(List rawArgs) { final Directory directory = new Directory(pathlib.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n')); final RegExp filenameRE = new RegExp(r'material_(\w+)\.arb$'); + exitWithError( + validateEnglishLocalizations(new File(pathlib.join(directory.path, 'material_en.arb'))) + ); + for (FileSystemEntity entity in directory.listSync()) { final String path = entity.path; if (FileSystemEntity.isFileSync(path) && filenameRE.hasMatch(path)) { @@ -149,7 +153,10 @@ void main(List rawArgs) { processBundle(new File(path), locale); } } - validateLocalizations(localeToResources, localeToResourceAttributes); + + exitWithError( + validateLocalizations(localeToResources, localeToResourceAttributes) + ); final String regenerate = 'dart dev/tools/gen_localizations.dart --overwrite'; final StringBuffer buffer = new StringBuffer(); diff --git a/dev/tools/localizations_utils.dart b/dev/tools/localizations_utils.dart index a2825e9d814..e2faef52695 100644 --- a/dev/tools/localizations_utils.dart +++ b/dev/tools/localizations_utils.dart @@ -7,8 +7,10 @@ import 'dart:io'; import 'package:args/args.dart' as argslib; import 'package:meta/meta.dart'; -void fatal(String message) { - stderr.writeln(message); +void exitWithError(String errorMessage) { + if (errorMessage == null) + return; + stderr.writeln('Fatal Error: $errorMessage'); exit(1); } @@ -16,7 +18,7 @@ void checkCwdIsRepoRoot(String commandName) { final bool isRepoRoot = new Directory('.git').existsSync(); if (!isRepoRoot) { - fatal( + exitWithError( '$commandName must be run from the root of the Flutter repository. The ' 'current working directory is: ${Directory.current.path}' ); @@ -32,7 +34,7 @@ GeneratorOptions parseArgs(List rawArgs) { ); final argslib.ArgResults args = argParser.parse(rawArgs); final bool writeToFile = args['overwrite']; - + return new GeneratorOptions(writeToFile: writeToFile); } diff --git a/dev/tools/localizations_validator.dart b/dev/tools/localizations_validator.dart index 90aec93b4ec..a0acba600dc 100644 --- a/dev/tools/localizations_validator.dart +++ b/dev/tools/localizations_validator.dart @@ -2,19 +2,67 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert' show JSON; import 'dart:io'; +/// Sanity checking of the @foo metadata in the English translations, +/// material_en.arb. +/// +/// - For each @foo resource, there must be a corresponding foo, except +/// for plurals, for which there must be a fooOther. +/// - Each @foo resource must have a Map value with a String valued +/// description entry. +/// +/// Returns an error message upon failure, null on success. +String validateEnglishLocalizations(File file) { + final StringBuffer errorMessages = new StringBuffer(); + + if (!file.existsSync()) { + errorMessages.writeln('English localizations do not exist: $file'); + return errorMessages.toString(); + } + + final Map bundle = JSON.decode(file.readAsStringSync()); + for (String atResourceId in bundle.keys) { + if (!atResourceId.startsWith('@')) + continue; + + final dynamic atResourceValue = bundle[atResourceId]; + final Map atResource = atResourceValue is Map ? atResourceValue : null; + if (atResource == null) { + errorMessages.writeln('A map value was not specified for $atResourceId'); + continue; + } + + final String description = atResource['description']; + if (description == null) + errorMessages.writeln('No description specified for $atResourceId'); + + final String plural = atResource['plural']; + final String resourceId = atResourceId.substring(1); + if (plural != null) { + final String resourceIdOther = '${resourceId}Other'; + if (!bundle.containsKey(resourceIdOther)) + errorMessages.writeln('Default plural resource $resourceIdOther undefined'); + } else { + if (!bundle.containsKey(resourceId)) + errorMessages.writeln('No matching $resourceId defined for $atResourceId'); + } + } + + return errorMessages.isEmpty ? null : errorMessages.toString(); +} + /// Enforces the following invariants in our localizations: -/// +/// /// - Resource keys are valid, i.e. they appear in the canonical list. /// - Resource keys are complete for language-level locales, e.g. "es", "he". -/// +/// /// Uses "en" localizations as the canonical source of locale keys that other /// locales are compared against. -/// -/// If validation fails, print an error message to STDERR and quit with exit -/// code 1. -void validateLocalizations( +/// +/// If validation fails, return an error message, otherwise return null. +String validateLocalizations( Map> localeToResources, Map> localeToAttributes, ) { @@ -33,7 +81,7 @@ void validateLocalizations( bool isPluralVariation(String key) { final RegExp pluralRegexp = new RegExp(r'(\w*)(Zero|One|Two|Few|Many)$'); final Match pluralMatch = pluralRegexp.firstMatch(key); - + if (pluralMatch == null) return false; @@ -83,9 +131,7 @@ void validateLocalizations( ..writeln(' "notUsed": "Sindhi time format does not use a.m. indicator"') ..writeln('}'); } - - stderr.writeln('ERROR:'); - stderr.writeln(errorMessages); - exit(1); + return errorMessages.toString(); } + return null; }