checkValidity static method

void checkValidity(
  1. MethodInvocation node,
  2. List<Expression> arguments,
  3. String? outerName,
  4. List<FormalParameter> 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

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');
      }
    }
  }
}