Enforce names and set up to be able to enforce examples for Intl messages with parameters
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=131323872
diff --git a/lib/extract_messages.dart b/lib/extract_messages.dart
index 306b5d4..b9c1222 100644
--- a/lib/extract_messages.dart
+++ b/lib/extract_messages.dart
@@ -60,6 +60,9 @@
/// if [allowEmbeddedPluralsAndGenders] is false.
bool allowEmbeddedPluralsAndGenders = true;
+ /// Are examples required on all messages.
+ bool examplesRequired = false;
+
/// Parse the source of the Dart program file [file] and return a Map from
/// message names to [IntlMessage] instances.
///
@@ -171,7 +174,8 @@
var arguments = node.argumentList.arguments;
var instance = _expectedInstance(node.methodName.name);
return instance.checkValidity(node, arguments, name, parameters,
- nameAndArgsGenerated: generateNameAndArgs);
+ nameAndArgsGenerated: generateNameAndArgs,
+ examplesRequired: extraction.examplesRequired);
}
/// Record the parameters of the function or method declaration we last
@@ -278,7 +282,8 @@
// If there's no name, and the message text is a simple string, compute
// a name based on that plus meaning, if present.
var simpleName = (arguments.first as StringLiteral).stringValue;
- message.name = computeMessageName(message.name, simpleName, message.meaning);
+ message.name =
+ computeMessageName(message.name, simpleName, message.meaning);
}
}
return message;
diff --git a/lib/src/intl_message.dart b/lib/src/intl_message.dart
index a52fedd..45ac177 100644
--- a/lib/src/intl_message.dart
+++ b/lib/src/intl_message.dart
@@ -73,9 +73,36 @@
}
}
+ Map _evaluateAsMap(expression) {
+ var result = expression.accept(_evaluator);
+ if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! Map) {
+ return null;
+ } else {
+ return result;
+ }
+ }
+
+ /// Verify that this looks like a correct
+ /// Intl.message/plural/gender/... invocation.
+ ///
+ /// We expect an invocation like
+ ///
+ /// outerName(x) => Intl.message("foo \$x", ...)
+ ///
+ /// The [node] parameter is the Intl.message invocation node in the AST,
+ /// [arguments] is the list of arguments to that node (also reachable as
+ /// node.argumentList.arguments), [outerName] is the name of the containing
+ /// function, e.g. "outerName" in this case and [outerArgs] is the list of
+ /// arguments to that function. Of the optional parameters
+ /// [nameAndArgsGenerated] indicates if we are generating names and arguments
+ /// while rewriting the code in the transformer or a development-time rewrite,
+ /// so we should not expect them to be present. The [examplesRequired]
+ /// parameter indicates if we will fail if parameter examples are not provided
+ /// for messages with parameters.
String checkValidity(MethodInvocation node, List arguments, String outerName,
FormalParameterList outerArgs,
- {bool nameAndArgsGenerated: false}) {
+ {bool nameAndArgsGenerated: false, bool examplesRequired: false}) {
+ // If we have parameters, we must specify args and name.
var hasArgs = arguments.any(
(each) => each is NamedExpression && each.name.label.name == 'args');
var hasParameters = !outerArgs.parameters.isEmpty;
@@ -83,26 +110,42 @@
return "The 'args' argument for Intl.message must be specified";
}
- bool useMessageAsName = false;
- var messageName = arguments.firstWhere(
+ var messageNameArgument = arguments.firstWhere(
(eachArg) =>
eachArg is NamedExpression && eachArg.name.label.name == 'name',
orElse: () => null);
- messageName = messageName?.expression;
+ var nameExpression = messageNameArgument?.expression;
+ String messageName;
+ String givenName;
+
//TODO(alanknight): If we generalize this to messages with parameters
// this check will need to change.
- if (!nameAndArgsGenerated && messageName == null && !hasParameters) {
- messageName = arguments[0];
- useMessageAsName = true;
+ if (nameExpression == null) {
+ if (!hasParameters) {
+ // No name supplied, no parameters. Use the message as the name.
+ messageName = _evaluateAsString(arguments[0]);
+ outerName = messageName;
+ } else {
+ // We have no name and parameters, but the transformer generates the
+ // name.
+ if (nameAndArgsGenerated) {
+ givenName = outerName;
+ messageName = givenName;
+ } else {
+ return "The 'name' argument for Intl.message must be supplied for "
+ "messages with parameters";
+ }
+ }
+ } else {
+ // Name argument is supplied, use it.
+ givenName = _evaluateAsString(nameExpression);
+ messageName = givenName;
}
- var givenName = messageName == null ? null : _evaluateAsString(messageName);
- if (messageName != null && givenName == null) {
+ if (messageName == null) {
return "The 'name' argument for Intl.message must be a string literal";
}
- if (useMessageAsName) {
- outerName = givenName;
- }
+
var hasOuterName = outerName != null;
var simpleMatch = outerName == givenName || givenName == null;
@@ -123,6 +166,20 @@
return ("Intl.message arguments must be string literals: $arg");
}
}
+
+ if (hasParameters && examplesRequired) {
+ var exampleArg = arguments.where((each) =>
+ each is NamedExpression && each.name.label.name == "examples");
+ var examples = exampleArg.map((each) => each.expression).toList();
+ if (examples.isEmpty) {
+ return "Examples must be provided for messages with parameters";
+ }
+ var map = _evaluateAsMap(examples.first);
+ if (map == null) {
+ return "Examples must be a Map literal, preferably const";
+ }
+ }
+
return null;
}
@@ -316,13 +373,14 @@
/// Verify that this looks like a correct Intl.message invocation.
String checkValidity(MethodInvocation node, List arguments, String outerName,
FormalParameterList outerArgs,
- {nameAndArgsGenerated: false}) {
+ {bool nameAndArgsGenerated: false, bool examplesRequired: false}) {
if (arguments.first is! StringLiteral) {
return "Intl.message messages must be string literals";
}
return super.checkValidity(node, arguments, outerName, outerArgs,
- nameAndArgsGenerated: nameAndArgsGenerated);
+ nameAndArgsGenerated: nameAndArgsGenerated,
+ examplesRequired: examplesRequired);
}
void addPieces(List<Object> messages) {
@@ -413,11 +471,15 @@
out.write("', ");
out.write("name: '$name', ");
out.write(locale == null ? "" : "locale: '$locale', ");
- out.write(description == null ? "" : "desc: '${escapeAndValidateString(description)}', ");
+ out.write(description == null
+ ? ""
+ : "desc: '${escapeAndValidateString(description)}', ");
// json is already mostly-escaped, but we need to handle interpolations.
var json = JSON.encode(examples).replaceAll(r"$", r"\$");
out.write(examples == null ? "" : "examples: const ${json}, ");
- out.write(meaning == null ? "" : "meaning: '${escapeAndValidateString(meaning)}', ");
+ out.write(meaning == null
+ ? ""
+ : "meaning: '${escapeAndValidateString(meaning)}', ");
out.write("args: [${arguments.join(', ')}]");
out.write(")");
return out.toString();