checkValidity static method
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.
Implementation
static void checkValidity(
MethodInvocation node,
List<Expression> arguments,
String? outerName,
List<FormalParameter> outerArgs, {
bool nameAndArgsGenerated = false,
bool examplesRequired = false,
}) {
// If we have parameters, we must specify args and name.
var argsNamedExps = arguments.whereType<NamedExpression>().where(
(each) => each.name.label.name == 'args',
);
var args = argsNamedExps.isNotEmpty ? argsNamedExps.first : null;
var parameterNames = outerArgs.map((x) => x.name!.lexeme).toList();
var hasParameters = outerArgs.isNotEmpty;
if (!nameAndArgsGenerated && args == null && hasParameters) {
throw MessageExtractionException(
"The 'args' argument for Intl.message must be specified for "
'messages with parameters. Consider using rewrite_intl_messages.dart',
);
}
if (!checkArgs(args, parameterNames)) {
throw MessageExtractionException(
"The 'args' argument must match the message arguments,"
' e.g. args: $parameterNames',
);
}
var nameNamedExps = arguments
.whereType<NamedExpression>()
.where((arg) => arg.name.label.name == 'name')
.map((e) => e.expression);
String? messageName;
String? givenName;
//TODO(alanknight): If we generalize this to messages with parameters
// this check will need to change.
if (nameNamedExps.isEmpty) {
if (!hasParameters) {
// No name supplied, no parameters. Use the message as the name.
var name = _evaluateAsString(arguments[0]);
messageName = name;
outerName = name;
} else {
// We have no name and parameters, but the transformer generates the
// name.
if (nameAndArgsGenerated) {
messageName = outerName;
givenName = outerName;
} else {
throw MessageExtractionException(
"The 'name' argument for Intl.message must be supplied for "
'messages with parameters. Consider using '
'rewrite_intl_messages.dart',
);
}
}
} else {
// Name argument is supplied, use it.
var name = _evaluateAsString(nameNamedExps.first);
messageName = name;
givenName = name;
}
if (messageName == null) {
throw MessageExtractionException(
"The 'name' argument for Intl.message must be a string literal",
);
}
var hasOuterName = outerName != null;
var simpleMatch = outerName == givenName || givenName == null;
var classPlusMethod = Message.classPlusMethodName(node, outerName);
var classMatch = classPlusMethod != null && (givenName == classPlusMethod);
var mapOrListLiteralWithoutParameters =
(node.parent is ListLiteral || node.parent is MapLiteralEntry) &&
!hasParameters;
if (!(hasOuterName &&
(simpleMatch || classMatch || mapOrListLiteralWithoutParameters))) {
throw MessageExtractionException(
"The 'name' argument for Intl.message must match either "
'the name of the containing function or <ClassName>_<methodName> ('
"was '$givenName' but must be '$outerName' or '$classPlusMethod')",
);
}
var values = arguments
.whereType<NamedExpression>()
.where((each) => ['desc', 'name'].contains(each.name.label.name))
.map((each) => each.expression)
.toList();
for (var arg in values) {
if (_evaluateAsString(arg) == null) {
throw MessageExtractionException(
'Intl.message arguments must be string literals: $arg',
);
}
}
if (hasParameters) {
var examples = arguments
.whereType<NamedExpression>()
.where((each) => each.name.label.name == 'examples')
.map((each) => each.expression);
if (examples.isEmpty && examplesRequired) {
throw MessageExtractionException(
'Examples must be provided for messages with parameters',
);
}
if (examples.isNotEmpty) {
var example = examples.first;
if (example is SetOrMapLiteral) {
var map = _evaluateAsMap(example);
if (map == null) {
throw MessageExtractionException(
'Examples must be a const Map literal.',
);
} else if (example.constKeyword == null) {
throw MessageExtractionException('Examples must be const.');
}
} else {
throw MessageExtractionException('Examples must be a map');
}
}
}
}