diff --git a/packages/graphql_codegen/lib/src/config/config.dart b/packages/graphql_codegen/lib/src/config/config.dart index 306df58..4a0a541 100644 --- a/packages/graphql_codegen/lib/src/config/config.dart +++ b/packages/graphql_codegen/lib/src/config/config.dart @@ -52,6 +52,23 @@ class GraphQLCodegenConfigEnum { Map toJson() => _$GraphQLCodegenConfigEnumToJson(this); } +@JsonSerializable() +class GraphQLCodegenConfigImplements { + final String parent; + final String? import; + + const GraphQLCodegenConfigImplements({ + required this.parent, + this.import, + }); + + @override + factory GraphQLCodegenConfigImplements.fromJson(Map json) => + _$GraphQLCodegenConfigImplementsFromJson(json); + + Map toJson() => _$GraphQLCodegenConfigImplementsToJson(this); +} + @JsonSerializable() class GraphQLCodegenConfig { final Set clients; @@ -67,6 +84,7 @@ class GraphQLCodegenConfig { final String outputDirectory; final bool disableContextReplacement; final bool disableCopyWithGeneration; + final Map> typeImplements; GraphQLCodegenConfig({ this.clients = const {}, @@ -82,6 +100,7 @@ class GraphQLCodegenConfig { this.extraKeywords = const [], this.outputDirectory = '.', this.disableCopyWithGeneration = false, + this.typeImplements = const {}, }); @override diff --git a/packages/graphql_codegen/lib/src/config/config.g.dart b/packages/graphql_codegen/lib/src/config/config.g.dart index 5944ec6..1bce168 100644 --- a/packages/graphql_codegen/lib/src/config/config.g.dart +++ b/packages/graphql_codegen/lib/src/config/config.g.dart @@ -44,6 +44,20 @@ Map _$GraphQLCodegenConfigEnumToJson( 'fallbackEnumValue': instance.fallbackEnumValue, }; +GraphQLCodegenConfigImplements _$GraphQLCodegenConfigImplementsFromJson( + Map json) => + GraphQLCodegenConfigImplements( + parent: json['parent'] as String, + import: json['import'] as String?, + ); + +Map _$GraphQLCodegenConfigImplementsToJson( + GraphQLCodegenConfigImplements instance) => + { + 'parent': instance.parent, + 'import': instance.import, + }; + GraphQLCodegenConfig _$GraphQLCodegenConfigFromJson( Map json) => GraphQLCodegenConfig( @@ -84,6 +98,16 @@ GraphQLCodegenConfig _$GraphQLCodegenConfigFromJson( outputDirectory: json['outputDirectory'] as String? ?? '.', disableCopyWithGeneration: json['disableCopyWithGeneration'] as bool? ?? false, + typeImplements: + (json['typeImplements'] as Map?)?.map( + (k, e) => MapEntry( + k, + (e as List) + .map((e) => GraphQLCodegenConfigImplements.fromJson( + e as Map)) + .toList()), + ) ?? + const {}, ); Map _$GraphQLCodegenConfigToJson( @@ -104,6 +128,7 @@ Map _$GraphQLCodegenConfigToJson( 'outputDirectory': instance.outputDirectory, 'disableContextReplacement': instance.disableContextReplacement, 'disableCopyWithGeneration': instance.disableCopyWithGeneration, + 'typeImplements': instance.typeImplements, }; const _$GraphQLCodegenConfigClientEnumMap = { diff --git a/packages/graphql_codegen/lib/src/printer/base/document.dart b/packages/graphql_codegen/lib/src/printer/base/document.dart index e13c6ab..a521c10 100644 --- a/packages/graphql_codegen/lib/src/printer/base/document.dart +++ b/packages/graphql_codegen/lib/src/printer/base/document.dart @@ -110,6 +110,17 @@ Class printContext(PrintContext c) { c.addDependency(extendContext.path); } final properties = c.context.properties; + + final typeImplements = + context.config.typeImplements[c.namePrinter.printClassName(context.path)]; + if (typeImplements != null) { + for (final t in typeImplements) { + if (t.import != null) { + c.addPackage(t.import!); + } + } + } + return Class( (b) => b ..name = c.namePrinter.printClassName(context.path) @@ -120,6 +131,8 @@ Class printContext(PrintContext c) { .map(refer), if (extendContext != null) refer(c.namePrinter.printClassName(extendContext.path)), + if (typeImplements != null) + ...typeImplements.map((e) => Reference(e.parent, e.import)) ]) ..constructors = ListBuilder([ _printConstructor(c, properties), diff --git a/packages/graphql_codegen/test/assets/fragments_implements/document.graphql b/packages/graphql_codegen/test/assets/fragments_implements/document.graphql new file mode 100644 index 0000000..4d4a7ad --- /dev/null +++ b/packages/graphql_codegen/test/assets/fragments_implements/document.graphql @@ -0,0 +1,19 @@ +type Query { + t: T +} + +type T { + id: String! + name: String +} + +fragment F on T { + id + name +} + +query Q { + t { + ...F + } +} diff --git a/packages/graphql_codegen/test/assets/fragments_implements/document.graphql.dart b/packages/graphql_codegen/test/assets/fragments_implements/document.graphql.dart new file mode 100644 index 0000000..ec28e9b --- /dev/null +++ b/packages/graphql_codegen/test/assets/fragments_implements/document.graphql.dart @@ -0,0 +1,523 @@ +import 'implements_import.dart'; +import 'implements_import2.dart'; +import 'package:gql/ast.dart'; + +enum Enum$__TypeKind { + SCALAR, + OBJECT, + INTERFACE, + UNION, + ENUM, + INPUT_OBJECT, + LIST, + NON_NULL, + $unknown; + + factory Enum$__TypeKind.fromJson(String value) => + fromJson$Enum$__TypeKind(value); + + String toJson() => toJson$Enum$__TypeKind(this); +} + +String toJson$Enum$__TypeKind(Enum$__TypeKind e) { + switch (e) { + case Enum$__TypeKind.SCALAR: + return r'SCALAR'; + case Enum$__TypeKind.OBJECT: + return r'OBJECT'; + case Enum$__TypeKind.INTERFACE: + return r'INTERFACE'; + case Enum$__TypeKind.UNION: + return r'UNION'; + case Enum$__TypeKind.ENUM: + return r'ENUM'; + case Enum$__TypeKind.INPUT_OBJECT: + return r'INPUT_OBJECT'; + case Enum$__TypeKind.LIST: + return r'LIST'; + case Enum$__TypeKind.NON_NULL: + return r'NON_NULL'; + case Enum$__TypeKind.$unknown: + return r'$unknown'; + } +} + +Enum$__TypeKind fromJson$Enum$__TypeKind(String value) { + switch (value) { + case r'SCALAR': + return Enum$__TypeKind.SCALAR; + case r'OBJECT': + return Enum$__TypeKind.OBJECT; + case r'INTERFACE': + return Enum$__TypeKind.INTERFACE; + case r'UNION': + return Enum$__TypeKind.UNION; + case r'ENUM': + return Enum$__TypeKind.ENUM; + case r'INPUT_OBJECT': + return Enum$__TypeKind.INPUT_OBJECT; + case r'LIST': + return Enum$__TypeKind.LIST; + case r'NON_NULL': + return Enum$__TypeKind.NON_NULL; + default: + return Enum$__TypeKind.$unknown; + } +} + +enum Enum$__DirectiveLocation { + QUERY, + MUTATION, + SUBSCRIPTION, + FIELD, + FRAGMENT_DEFINITION, + FRAGMENT_SPREAD, + INLINE_FRAGMENT, + VARIABLE_DEFINITION, + SCHEMA, + SCALAR, + OBJECT, + FIELD_DEFINITION, + ARGUMENT_DEFINITION, + INTERFACE, + UNION, + ENUM, + ENUM_VALUE, + INPUT_OBJECT, + INPUT_FIELD_DEFINITION, + $unknown; + + factory Enum$__DirectiveLocation.fromJson(String value) => + fromJson$Enum$__DirectiveLocation(value); + + String toJson() => toJson$Enum$__DirectiveLocation(this); +} + +String toJson$Enum$__DirectiveLocation(Enum$__DirectiveLocation e) { + switch (e) { + case Enum$__DirectiveLocation.QUERY: + return r'QUERY'; + case Enum$__DirectiveLocation.MUTATION: + return r'MUTATION'; + case Enum$__DirectiveLocation.SUBSCRIPTION: + return r'SUBSCRIPTION'; + case Enum$__DirectiveLocation.FIELD: + return r'FIELD'; + case Enum$__DirectiveLocation.FRAGMENT_DEFINITION: + return r'FRAGMENT_DEFINITION'; + case Enum$__DirectiveLocation.FRAGMENT_SPREAD: + return r'FRAGMENT_SPREAD'; + case Enum$__DirectiveLocation.INLINE_FRAGMENT: + return r'INLINE_FRAGMENT'; + case Enum$__DirectiveLocation.VARIABLE_DEFINITION: + return r'VARIABLE_DEFINITION'; + case Enum$__DirectiveLocation.SCHEMA: + return r'SCHEMA'; + case Enum$__DirectiveLocation.SCALAR: + return r'SCALAR'; + case Enum$__DirectiveLocation.OBJECT: + return r'OBJECT'; + case Enum$__DirectiveLocation.FIELD_DEFINITION: + return r'FIELD_DEFINITION'; + case Enum$__DirectiveLocation.ARGUMENT_DEFINITION: + return r'ARGUMENT_DEFINITION'; + case Enum$__DirectiveLocation.INTERFACE: + return r'INTERFACE'; + case Enum$__DirectiveLocation.UNION: + return r'UNION'; + case Enum$__DirectiveLocation.ENUM: + return r'ENUM'; + case Enum$__DirectiveLocation.ENUM_VALUE: + return r'ENUM_VALUE'; + case Enum$__DirectiveLocation.INPUT_OBJECT: + return r'INPUT_OBJECT'; + case Enum$__DirectiveLocation.INPUT_FIELD_DEFINITION: + return r'INPUT_FIELD_DEFINITION'; + case Enum$__DirectiveLocation.$unknown: + return r'$unknown'; + } +} + +Enum$__DirectiveLocation fromJson$Enum$__DirectiveLocation(String value) { + switch (value) { + case r'QUERY': + return Enum$__DirectiveLocation.QUERY; + case r'MUTATION': + return Enum$__DirectiveLocation.MUTATION; + case r'SUBSCRIPTION': + return Enum$__DirectiveLocation.SUBSCRIPTION; + case r'FIELD': + return Enum$__DirectiveLocation.FIELD; + case r'FRAGMENT_DEFINITION': + return Enum$__DirectiveLocation.FRAGMENT_DEFINITION; + case r'FRAGMENT_SPREAD': + return Enum$__DirectiveLocation.FRAGMENT_SPREAD; + case r'INLINE_FRAGMENT': + return Enum$__DirectiveLocation.INLINE_FRAGMENT; + case r'VARIABLE_DEFINITION': + return Enum$__DirectiveLocation.VARIABLE_DEFINITION; + case r'SCHEMA': + return Enum$__DirectiveLocation.SCHEMA; + case r'SCALAR': + return Enum$__DirectiveLocation.SCALAR; + case r'OBJECT': + return Enum$__DirectiveLocation.OBJECT; + case r'FIELD_DEFINITION': + return Enum$__DirectiveLocation.FIELD_DEFINITION; + case r'ARGUMENT_DEFINITION': + return Enum$__DirectiveLocation.ARGUMENT_DEFINITION; + case r'INTERFACE': + return Enum$__DirectiveLocation.INTERFACE; + case r'UNION': + return Enum$__DirectiveLocation.UNION; + case r'ENUM': + return Enum$__DirectiveLocation.ENUM; + case r'ENUM_VALUE': + return Enum$__DirectiveLocation.ENUM_VALUE; + case r'INPUT_OBJECT': + return Enum$__DirectiveLocation.INPUT_OBJECT; + case r'INPUT_FIELD_DEFINITION': + return Enum$__DirectiveLocation.INPUT_FIELD_DEFINITION; + default: + return Enum$__DirectiveLocation.$unknown; + } +} + +class Fragment$F implements Parent, Parent2, Other { + Fragment$F({ + required this.id, + this.name, + this.$__typename = 'T', + }); + + factory Fragment$F.fromJson(Map json) { + final l$id = json['id']; + final l$name = json['name']; + final l$$__typename = json['__typename']; + return Fragment$F( + id: (l$id as String), + name: (l$name as String?), + $__typename: (l$$__typename as String), + ); + } + + final String id; + + final String? name; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$id = id; + _resultData['id'] = l$id; + final l$name = name; + _resultData['name'] = l$name; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$id = id; + final l$name = name; + final l$$__typename = $__typename; + return Object.hashAll([ + l$id, + l$name, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Fragment$F) || runtimeType != other.runtimeType) { + return false; + } + final l$id = id; + final lOther$id = other.id; + if (l$id != lOther$id) { + return false; + } + final l$name = name; + final lOther$name = other.name; + if (l$name != lOther$name) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Fragment$F on Fragment$F { + CopyWith$Fragment$F get copyWith => CopyWith$Fragment$F( + this, + (i) => i, + ); +} + +abstract class CopyWith$Fragment$F { + factory CopyWith$Fragment$F( + Fragment$F instance, + TRes Function(Fragment$F) then, + ) = _CopyWithImpl$Fragment$F; + + factory CopyWith$Fragment$F.stub(TRes res) = _CopyWithStubImpl$Fragment$F; + + TRes call({ + String? id, + String? name, + String? $__typename, + }); +} + +class _CopyWithImpl$Fragment$F implements CopyWith$Fragment$F { + _CopyWithImpl$Fragment$F( + this._instance, + this._then, + ); + + final Fragment$F _instance; + + final TRes Function(Fragment$F) _then; + + static const _undefined = {}; + + TRes call({ + Object? id = _undefined, + Object? name = _undefined, + Object? $__typename = _undefined, + }) => + _then(Fragment$F( + id: id == _undefined || id == null ? _instance.id : (id as String), + name: name == _undefined ? _instance.name : (name as String?), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Fragment$F implements CopyWith$Fragment$F { + _CopyWithStubImpl$Fragment$F(this._res); + + TRes _res; + + call({ + String? id, + String? name, + String? $__typename, + }) => + _res; +} + +const fragmentDefinitionF = FragmentDefinitionNode( + name: NameNode(value: 'F'), + typeCondition: TypeConditionNode( + on: NamedTypeNode( + name: NameNode(value: 'T'), + isNonNull: false, + )), + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'id'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), +); +const documentNodeFragmentF = DocumentNode(definitions: [ + fragmentDefinitionF, +]); + +class Query$Q { + Query$Q({ + this.t, + this.$__typename = 'Query', + }); + + factory Query$Q.fromJson(Map json) { + final l$t = json['t']; + final l$$__typename = json['__typename']; + return Query$Q( + t: l$t == null + ? null + : Fragment$F.fromJson((l$t as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Fragment$F? t; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$t = t; + _resultData['t'] = l$t?.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$t = t; + final l$$__typename = $__typename; + return Object.hashAll([ + l$t, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Query$Q) || runtimeType != other.runtimeType) { + return false; + } + final l$t = t; + final lOther$t = other.t; + if (l$t != lOther$t) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Query$Q on Query$Q { + CopyWith$Query$Q get copyWith => CopyWith$Query$Q( + this, + (i) => i, + ); +} + +abstract class CopyWith$Query$Q { + factory CopyWith$Query$Q( + Query$Q instance, + TRes Function(Query$Q) then, + ) = _CopyWithImpl$Query$Q; + + factory CopyWith$Query$Q.stub(TRes res) = _CopyWithStubImpl$Query$Q; + + TRes call({ + Fragment$F? t, + String? $__typename, + }); + CopyWith$Fragment$F get t; +} + +class _CopyWithImpl$Query$Q implements CopyWith$Query$Q { + _CopyWithImpl$Query$Q( + this._instance, + this._then, + ); + + final Query$Q _instance; + + final TRes Function(Query$Q) _then; + + static const _undefined = {}; + + TRes call({ + Object? t = _undefined, + Object? $__typename = _undefined, + }) => + _then(Query$Q( + t: t == _undefined ? _instance.t : (t as Fragment$F?), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Fragment$F get t { + final local$t = _instance.t; + return local$t == null + ? CopyWith$Fragment$F.stub(_then(_instance)) + : CopyWith$Fragment$F(local$t, (e) => call(t: e)); + } +} + +class _CopyWithStubImpl$Query$Q implements CopyWith$Query$Q { + _CopyWithStubImpl$Query$Q(this._res); + + TRes _res; + + call({ + Fragment$F? t, + String? $__typename, + }) => + _res; + + CopyWith$Fragment$F get t => CopyWith$Fragment$F.stub(_res); +} + +const documentNodeQueryQ = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.query, + name: NameNode(value: 'Q'), + variableDefinitions: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 't'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'F'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitionF, +]); +const possibleTypesMap = >{}; diff --git a/packages/graphql_codegen/test/assets/fragments_implements/implements_import.dart b/packages/graphql_codegen/test/assets/fragments_implements/implements_import.dart new file mode 100644 index 0000000..bbbb0d5 --- /dev/null +++ b/packages/graphql_codegen/test/assets/fragments_implements/implements_import.dart @@ -0,0 +1,8 @@ +abstract class Parent { + String get id; + String? get name; +} + +abstract class Parent2 { + String? get name; +} \ No newline at end of file diff --git a/packages/graphql_codegen/test/assets/fragments_implements/implements_import2.dart b/packages/graphql_codegen/test/assets/fragments_implements/implements_import2.dart new file mode 100644 index 0000000..0db109e --- /dev/null +++ b/packages/graphql_codegen/test/assets/fragments_implements/implements_import2.dart @@ -0,0 +1,3 @@ +abstract class Other { + String get id; +} \ No newline at end of file diff --git a/packages/graphql_codegen/test/assets/fragments_implements/options.json b/packages/graphql_codegen/test/assets/fragments_implements/options.json new file mode 100644 index 0000000..d7f725f --- /dev/null +++ b/packages/graphql_codegen/test/assets/fragments_implements/options.json @@ -0,0 +1,18 @@ +{ + "typeImplements": { + "Fragment$F": [ + { + "parent": "Parent", + "import": "implements_import.dart" + }, + { + "parent": "Parent2", + "import": "implements_import.dart" + }, + { + "parent": "Other", + "import": "implements_import2.dart" + } + ] + } +} \ No newline at end of file