Unverified Commit 38843814 authored by Tae Hyung Kim's avatar Tae Hyung Kim Committed by GitHub

Make gen-l10n error handling independent of logger state (#119644)

* init

* lint

* lint again
parent 8f90e2a7
...@@ -579,6 +579,10 @@ class LocalizationsGenerator { ...@@ -579,6 +579,10 @@ class LocalizationsGenerator {
// Whether we want to use escaping for ICU messages. // Whether we want to use escaping for ICU messages.
bool useEscaping = false; bool useEscaping = false;
/// Whether any errors were caught. This is set after encountering any errors
/// from calling [_generateMethod].
bool hadErrors = false;
/// The list of all arb path strings in [inputDirectory]. /// The list of all arb path strings in [inputDirectory].
List<String> get arbPathStrings { List<String> get arbPathStrings {
return _allBundles.bundles.map((AppResourceBundle bundle) => bundle.file.path).toList(); return _allBundles.bundles.map((AppResourceBundle bundle) => bundle.file.path).toList();
...@@ -1093,151 +1097,152 @@ class LocalizationsGenerator { ...@@ -1093,151 +1097,152 @@ class LocalizationsGenerator {
String _generateMethod(Message message, LocaleInfo locale) { String _generateMethod(Message message, LocaleInfo locale) {
try { try {
// Determine if we must import intl for date or number formatting. // Determine if we must import intl for date or number formatting.
if (message.placeholdersRequireFormatting) { if (message.placeholdersRequireFormatting) {
requiresIntlImport = true; requiresIntlImport = true;
} }
final String translationForMessage = message.messages[locale]!; final String translationForMessage = message.messages[locale]!;
final Node node = message.parsedMessages[locale]!; final Node node = message.parsedMessages[locale]!;
// If parse tree is only a string, then return a getter method. // If parse tree is only a string, then return a getter method.
if (node.children.every((Node child) => child.type == ST.string)) { if (node.children.every((Node child) => child.type == ST.string)) {
// Use the parsed translation to handle escaping with the same behavior. // Use the parsed translation to handle escaping with the same behavior.
return getterTemplate return getterTemplate
.replaceAll('@(name)', message.resourceId) .replaceAll('@(name)', message.resourceId)
.replaceAll('@(message)', "'${generateString(node.children.map((Node child) => child.value!).join())}'"); .replaceAll('@(message)', "'${generateString(node.children.map((Node child) => child.value!).join())}'");
} }
final List<String> tempVariables = <String>[]; final List<String> tempVariables = <String>[];
// Get a unique temporary variable name. // Get a unique temporary variable name.
int variableCount = 0; int variableCount = 0;
String getTempVariableName() { String getTempVariableName() {
return '_temp${variableCount++}'; return '_temp${variableCount++}';
} }
// Do a DFS post order traversal through placeholderExpr, pluralExpr, and selectExpr nodes. // Do a DFS post order traversal through placeholderExpr, pluralExpr, and selectExpr nodes.
// When traversing through a placeholderExpr node, return "$placeholderName". // When traversing through a placeholderExpr node, return "$placeholderName".
// When traversing through a pluralExpr node, return "$tempVarN" and add variable declaration in "tempVariables". // When traversing through a pluralExpr node, return "$tempVarN" and add variable declaration in "tempVariables".
// When traversing through a selectExpr node, return "$tempVarN" and add variable declaration in "tempVariables". // When traversing through a selectExpr node, return "$tempVarN" and add variable declaration in "tempVariables".
// When traversing through a message node, return concatenation of all of "generateVariables(child)" for each child. // When traversing through a message node, return concatenation of all of "generateVariables(child)" for each child.
String generateVariables(Node node, { bool isRoot = false }) { String generateVariables(Node node, { bool isRoot = false }) {
switch (node.type) { switch (node.type) {
case ST.message: case ST.message:
final List<String> expressions = node.children.map<String>((Node node) { final List<String> expressions = node.children.map<String>((Node node) {
if (node.type == ST.string) { if (node.type == ST.string) {
return node.value!; return node.value!;
} }
return generateVariables(node); return generateVariables(node);
}).toList(); }).toList();
return generateReturnExpr(expressions); return generateReturnExpr(expressions);
case ST.placeholderExpr: case ST.placeholderExpr:
assert(node.children[1].type == ST.identifier); assert(node.children[1].type == ST.identifier);
final String identifier = node.children[1].value!; final String identifier = node.children[1].value!;
final Placeholder placeholder = message.placeholders[identifier]!; final Placeholder placeholder = message.placeholders[identifier]!;
if (placeholder.requiresFormatting) { if (placeholder.requiresFormatting) {
return '\$${node.children[1].value}String'; return '\$${node.children[1].value}String';
}
return '\$${node.children[1].value}';
case ST.pluralExpr:
requiresIntlImport = true;
final Map<String, String> pluralLogicArgs = <String, String>{};
// Recall that pluralExpr are of the form
// pluralExpr := "{" ID "," "plural" "," pluralParts "}"
assert(node.children[1].type == ST.identifier);
assert(node.children[5].type == ST.pluralParts);
final Node identifier = node.children[1];
final Node pluralParts = node.children[5];
for (final Node pluralPart in pluralParts.children.reversed) {
String pluralCase;
Node pluralMessage;
if (pluralPart.children[0].value == '=') {
assert(pluralPart.children[1].type == ST.number);
assert(pluralPart.children[3].type == ST.message);
pluralCase = pluralPart.children[1].value!;
pluralMessage = pluralPart.children[3];
} else {
assert(pluralPart.children[0].type == ST.identifier || pluralPart.children[0].type == ST.other);
assert(pluralPart.children[2].type == ST.message);
pluralCase = pluralPart.children[0].value!;
pluralMessage = pluralPart.children[2];
} }
if (!pluralLogicArgs.containsKey(pluralCases[pluralCase])) { return '\$${node.children[1].value}';
final String pluralPartExpression = generateVariables(pluralMessage);
final String? transformedPluralCase = pluralCases[pluralCase]; case ST.pluralExpr:
// A valid plural case is one of "=0", "=1", "=2", "zero", "one", "two", "few", "many", or "other". requiresIntlImport = true;
if (transformedPluralCase == null) { final Map<String, String> pluralLogicArgs = <String, String>{};
throw L10nParserException( // Recall that pluralExpr are of the form
''' // pluralExpr := "{" ID "," "plural" "," pluralParts "}"
assert(node.children[1].type == ST.identifier);
assert(node.children[5].type == ST.pluralParts);
final Node identifier = node.children[1];
final Node pluralParts = node.children[5];
for (final Node pluralPart in pluralParts.children.reversed) {
String pluralCase;
Node pluralMessage;
if (pluralPart.children[0].value == '=') {
assert(pluralPart.children[1].type == ST.number);
assert(pluralPart.children[3].type == ST.message);
pluralCase = pluralPart.children[1].value!;
pluralMessage = pluralPart.children[3];
} else {
assert(pluralPart.children[0].type == ST.identifier || pluralPart.children[0].type == ST.other);
assert(pluralPart.children[2].type == ST.message);
pluralCase = pluralPart.children[0].value!;
pluralMessage = pluralPart.children[2];
}
if (!pluralLogicArgs.containsKey(pluralCases[pluralCase])) {
final String pluralPartExpression = generateVariables(pluralMessage);
final String? transformedPluralCase = pluralCases[pluralCase];
// A valid plural case is one of "=0", "=1", "=2", "zero", "one", "two", "few", "many", or "other".
if (transformedPluralCase == null) {
throw L10nParserException(
'''
The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "many", or "other. The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "many", or "other.
$pluralCase is not a valid plural case.''', $pluralCase is not a valid plural case.''',
_inputFileNames[locale]!, _inputFileNames[locale]!,
message.resourceId, message.resourceId,
translationForMessage, translationForMessage,
pluralPart.positionInMessage, pluralPart.positionInMessage,
); );
} }
pluralLogicArgs[transformedPluralCase] = ' ${pluralCases[pluralCase]}: $pluralPartExpression,'; pluralLogicArgs[transformedPluralCase] = ' ${pluralCases[pluralCase]}: $pluralPartExpression,';
} else if (!suppressWarnings) { } else if (!suppressWarnings) {
logger.printWarning(''' logger.printWarning('''
[${_inputFileNames[locale]}:${message.resourceId}] ICU Syntax Warning: The plural part specified below is overridden by a later plural part. [${_inputFileNames[locale]}:${message.resourceId}] ICU Syntax Warning: The plural part specified below is overridden by a later plural part.
$translationForMessage $translationForMessage
${Parser.indentForError(pluralPart.positionInMessage)}'''); ${Parser.indentForError(pluralPart.positionInMessage)}''');
}
} }
} final String tempVarName = getTempVariableName();
final String tempVarName = getTempVariableName(); tempVariables.add(pluralVariableTemplate
tempVariables.add(pluralVariableTemplate .replaceAll('@(varName)', tempVarName)
.replaceAll('@(varName)', tempVarName) .replaceAll('@(count)', identifier.value!)
.replaceAll('@(count)', identifier.value!) .replaceAll('@(pluralLogicArgs)', pluralLogicArgs.values.join('\n'))
.replaceAll('@(pluralLogicArgs)', pluralLogicArgs.values.join('\n')) );
); return '\$$tempVarName';
return '\$$tempVarName';
case ST.selectExpr:
case ST.selectExpr: requiresIntlImport = true;
requiresIntlImport = true; // Recall that pluralExpr are of the form
// Recall that pluralExpr are of the form // pluralExpr := "{" ID "," "plural" "," pluralParts "}"
// pluralExpr := "{" ID "," "plural" "," pluralParts "}" assert(node.children[1].type == ST.identifier);
assert(node.children[1].type == ST.identifier); assert(node.children[5].type == ST.selectParts);
assert(node.children[5].type == ST.selectParts);
final Node identifier = node.children[1];
final Node identifier = node.children[1]; final List<String> selectLogicArgs = <String>[];
final List<String> selectLogicArgs = <String>[]; final Node selectParts = node.children[5];
final Node selectParts = node.children[5]; for (final Node selectPart in selectParts.children) {
for (final Node selectPart in selectParts.children) { assert(selectPart.children[0].type == ST.identifier || selectPart.children[0].type == ST.other);
assert(selectPart.children[0].type == ST.identifier || selectPart.children[0].type == ST.other); assert(selectPart.children[2].type == ST.message);
assert(selectPart.children[2].type == ST.message); final String selectCase = selectPart.children[0].value!;
final String selectCase = selectPart.children[0].value!; final Node selectMessage = selectPart.children[2];
final Node selectMessage = selectPart.children[2]; final String selectPartExpression = generateVariables(selectMessage);
final String selectPartExpression = generateVariables(selectMessage); selectLogicArgs.add(" '$selectCase': $selectPartExpression,");
selectLogicArgs.add(" '$selectCase': $selectPartExpression,"); }
} final String tempVarName = getTempVariableName();
final String tempVarName = getTempVariableName(); tempVariables.add(selectVariableTemplate
tempVariables.add(selectVariableTemplate .replaceAll('@(varName)', tempVarName)
.replaceAll('@(varName)', tempVarName) .replaceAll('@(choice)', identifier.value!)
.replaceAll('@(choice)', identifier.value!) .replaceAll('@(selectCases)', selectLogicArgs.join('\n'))
.replaceAll('@(selectCases)', selectLogicArgs.join('\n')) );
); return '\$$tempVarName';
return '\$$tempVarName'; // ignore: no_default_cases
// ignore: no_default_cases default:
default: throw Exception('Cannot call "generateHelperMethod" on node type ${node.type}');
throw Exception('Cannot call "generateHelperMethod" on node type ${node.type}'); }
} }
} final String messageString = generateVariables(node, isRoot: true);
final String messageString = generateVariables(node, isRoot: true); final String tempVarLines = tempVariables.isEmpty ? '' : '${tempVariables.join('\n')}\n';
final String tempVarLines = tempVariables.isEmpty ? '' : '${tempVariables.join('\n')}\n'; return methodTemplate
return methodTemplate .replaceAll('@(name)', message.resourceId)
.replaceAll('@(name)', message.resourceId) .replaceAll('@(parameters)', generateMethodParameters(message).join(', '))
.replaceAll('@(parameters)', generateMethodParameters(message).join(', ')) .replaceAll('@(dateFormatting)', generateDateFormattingLogic(message))
.replaceAll('@(dateFormatting)', generateDateFormattingLogic(message)) .replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message))
.replaceAll('@(numberFormatting)', generateNumberFormattingLogic(message)) .replaceAll('@(tempVars)', tempVarLines)
.replaceAll('@(tempVars)', tempVarLines) .replaceAll('@(message)', messageString)
.replaceAll('@(message)', messageString) .replaceAll('@(none)\n', '');
.replaceAll('@(none)\n', '');
} on L10nParserException catch (error) { } on L10nParserException catch (error) {
logger.printError(error.toString()); logger.printError(error.toString());
hadErrors = true;
return ''; return '';
} }
} }
...@@ -1247,7 +1252,7 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " ...@@ -1247,7 +1252,7 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", "
final String generatedLocalizationsFile = _generateCode(); final String generatedLocalizationsFile = _generateCode();
// If there were any syntax errors, don't write to files. // If there were any syntax errors, don't write to files.
if (logger.hadErrorOutput) { if (hadErrors) {
throw L10nException('Found syntax errors.'); throw L10nException('Found syntax errors.');
} }
......
...@@ -785,6 +785,28 @@ void main() { ...@@ -785,6 +785,28 @@ void main() {
}); });
group('generateLocalizations', () { group('generateLocalizations', () {
// Regression test for https://github.com/flutter/flutter/issues/119593
testWithoutContext('other logs from flutter_tools does not affect gen-l10n', () async {
_standardFlutterDirectoryL10nSetup(fs);
final Logger logger = BufferLogger.test();
logger.printError('An error output from a different tool in flutter_tools');
// Should run without error.
generateLocalizations(
fileSystem: fs,
options: LocalizationOptions(
arbDirectory: Uri.directory(defaultL10nPathString),
outputDirectory: Uri.directory(defaultL10nPathString, windows: false),
templateArbFile: Uri.file(defaultTemplateArbFileName, windows: false),
useSyntheticPackage: false,
),
logger: logger,
projectDir: fs.currentDirectory,
dependenciesDir: fs.currentDirectory,
);
});
testWithoutContext('forwards arguments correctly', () async { testWithoutContext('forwards arguments correctly', () async {
_standardFlutterDirectoryL10nSetup(fs); _standardFlutterDirectoryL10nSetup(fs);
final LocalizationOptions options = LocalizationOptions( final LocalizationOptions options = LocalizationOptions(
......
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