inferType function

Expression inferType(
  1. List<CustomTypes> customTypes,
  2. Element typeElement,
  3. String name,
  4. DartType type, {
  5. bool? nullable,
  6. String? genericTypeName,
  7. Map<String, TypeParameterElement>? generics,
  8. bool isInput = false,
})

Implementation

Expression inferType(
  List<CustomTypes> customTypes,
  Element typeElement,
  String name,
  DartType type, {
  bool? nullable,
  String? genericTypeName,
  Map<String, TypeParameterElement>? generics,
  bool isInput = false,
}) {
  final docs = getDocumentation(typeElement);
  if (docs?.typeName != null) {
    return refer(docs!.typeName!);
  }

  // If its a Future<T>, Stream<T> or FutureOr<T>, unpack T and infer it
  final genericWhenAsync = genericTypeWhenFutureOrStream(type);
  if (genericWhenAsync != null) {
    return inferType(
      customTypes,
      typeElement,
      name,
      genericWhenAsync,
      generics: generics,
      isInput: isInput,
    );
  }
  final nonNullable = type.nullabilitySuffix == NullabilitySuffix.none;
  Expression _wrapNullability(Expression exp) =>
      nonNullable && nullable != true ? exp.property('nonNull').call([]) : exp;

  // Check customTypes from generator config
  final typeName =
      type.getDisplayString(withNullability: false).split('<').first;
  final customType = customTypes.firstWhereOrNull((t) => t.name == typeName);
  if (customType != null) {
    return _wrapNullability(refer(customType.getter, customType.import));
  }

  Expression _wrapExpression(Expression exp) {
    if (type is InterfaceType && type.typeArguments.isNotEmpty) {
      // Generics
      return _wrapNullability(
        exp.call([
          ...type.typeArguments.map((e) {
            return inferType(
              customTypes,
              typeElement,
              name,
              e,
              generics: generics,
              isInput: isInput,
            );
          }),
        ], {
          if (genericTypeName != null) 'name': literalString(genericTypeName)
        }, [
          ...type.typeArguments.map(getReturnType).map(refer)
        ]),
      );
    }

    return _wrapNullability(exp);
  }

  // `DartType.graphQLType` static getter or method
  final element = type.element;
  if (element is ClassElement) {
    final ExecutableElement? e =
        element.getGetter('graphQLType') ?? element.getMethod('graphQLType');
    if (e != null) {
      if (!e.isStatic) {
        throw Exception(
          'The getter or method "$typeName.graphQLType" should be static.',
        );
      }
      final prop = refer(typeName).property(e.name);
      return _wrapExpression(prop);
    }
  }

  // Next, check to see if it's a List.
  if (type is InterfaceType &&
      type.typeArguments.isNotEmpty &&
      const TypeChecker.fromRuntime(Iterable).isAssignableFromType(type)) {
    final arg = type.typeArguments[0];
    final inner = inferType(
      customTypes,
      typeElement,
      name,
      arg,
      generics: generics,
      isInput: isInput,
    );
    return _wrapNullability(inner.property('list').call([]));
  }

  const primitive = {
    String: 'graphQLString',
    int: 'graphQLInt',
    double: 'graphQLFloat',
    bool: 'graphQLBoolean',
    DateTime: 'graphQLDate',
    Uri: 'graphQLUri',
    BigInt: 'graphQLBigInt',
  };

  // Check to see if it's a primitive type.
  for (final entry in primitive.entries) {
    if (type.element != null &&
        TypeChecker.fromRuntime(entry.key).isAssignableFrom(type.element!)) {
      // Next, check if this is the "id" field of a `Model`.
      // TODO: 1G make the id configurable, maybe with @key(fields: "") directive
      // if (const TypeChecker.fromRuntime(Model).isAssignableFromType(type) &&
      //     name == 'id') {
      //   return refer('graphQLId');
      // }
      if (entry.key == String && name == 'id') {
        return _wrapNullability(refer('graphQLId'));
      }
      return _wrapNullability(refer(entry.value));
    }
  }

  final externalName =
      typeName.startsWith('_') ? typeName.substring(1) : typeName;

  // Firstly, check if it's a GraphQL class.
  if (type is! InterfaceType ||
      !const TypeChecker.fromRuntime(BaseGraphQLTypeDecorator)
          .hasAnnotationOf(type.element)) {
    // If it is not an annotated type, then check if it is a generic
    if (type is TypeParameterType && generics != null) {
      final generic = generics[typeName];
      if (generic != null) {
        final isNonNull = nullable != true &&
            type.nullabilitySuffix == NullabilitySuffix.none &&
            generic.bound?.nullabilitySuffix == NullabilitySuffix.none;
        final nullability = isNonNull
            ? '.nonNull()'
            : type.nullabilitySuffix != NullabilitySuffix.none
                ? '.nullable()'
                : '';
        return refer(
          '${ReCase(externalName).camelCase}$graphqlTypeSuffix$nullability',
        );
      }
    }
    final _namePrefix = typeElement.enclosingElement is ClassElement
        ? '${typeElement.enclosingElement!.name!}.'
        : '';
    log.warning(
      'Cannot infer the GraphQLType for field $_namePrefix$name (type=$type).'
      ' Please annotate the Dart type, provide a $typeName.graphQLType'
      ' static getter or add the type to `build.yaml` "customTypes" property.',
    );
  }

  final _inputSuffix = isInput &&
          type.element != null &&
          const TypeChecker.fromRuntime(GraphQLInput)
              .hasAnnotationOf(type.element!)
      ? 'Input'
      : '';
  return _wrapExpression(
    refer('${ReCase(externalName).camelCase}$graphqlTypeSuffix$_inputSuffix'),
  );

  // Nothing else is allowed.
  // throw 'Cannot infer the GraphQL type for '
  //     'field $className.$name (type=$type).';
}