checkValidity method

String? checkValidity(
  1. MethodInvocation node,
  2. List arguments,
  3. String? outerName,
  4. FormalParameterList outerArgs, {
  5. bool nameAndArgsGenerated = false,
  6. bool examplesRequired = false,
})

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

String? checkValidity(MethodInvocation node, List arguments,
    String? outerName, FormalParameterList outerArgs,
    {bool nameAndArgsGenerated: false, bool examplesRequired: false}) {
  // If we have parameters, we must specify args and name.
  NamedExpression? args = arguments.firstWhereOrNull(
      (each) => each is NamedExpression && each.name.label.name == 'args');
  var parameterNames =
      outerArgs.parameters.map((x) => x.name!.lexeme).toList();
  var hasArgs = args != null;
  var hasParameters = !outerArgs.parameters.isEmpty;
  if (!nameAndArgsGenerated && !hasArgs && hasParameters) {
    return "The 'args' argument for Intl.message must be specified for "
        "messages with parameters. Consider using rewrite_intl_messages.dart";
  }
  if (!checkArgs(args, parameterNames)) {
    return "The 'args' argument must match the message arguments,"
        " e.g. args: ${parameterNames}";
  }
  var messageNameArgument = arguments.firstWhereOrNull((eachArg) =>
      eachArg is NamedExpression && eachArg.name.label.name == 'name');
  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 (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. Consider using "
            "rewrite_intl_messages.dart";
      }
    }
  } else {
    // Name argument is supplied, use it.
    givenName = _evaluateAsString(nameExpression);
    messageName = givenName;
  }

  if (messageName == null) {
    return "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);
  if (!(hasOuterName && (simpleMatch || classMatch))) {
    return "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 simpleArguments = arguments.where((each) =>
      each is NamedExpression &&
      ["desc", "name"].contains(each.name.label.name));
  var values = simpleArguments.map((each) => each.expression).toList();
  for (var arg in values) {
    if (_evaluateAsString(arg) == null) {
      return ("Intl.message arguments must be string literals: $arg");
    }
  }

  if (hasParameters) {
    var exampleArg = arguments.where((each) =>
        each is NamedExpression && each.name.label.name == "examples");
    var examples = exampleArg.map((each) => each.expression).toList();
    if (examples.isEmpty && examplesRequired) {
      return "Examples must be provided for messages with parameters";
    }
    if (examples.isNotEmpty) {
      var example = examples.first;
      var map = _evaluateAsMap(example);
      if (map == null) {
        return "Examples must be a const Map literal.";
      }
      if (example.constKeyword == null) {
        return "Examples must be const.";
      }
    }
  }

  return null;
}