diff --git a/codegen/end_to_end_test/test/operation/variables_test.dart b/codegen/end_to_end_test/test/operation/variables_test.dart index f0d0a9ea..e3227c6d 100644 --- a/codegen/end_to_end_test/test/operation/variables_test.dart +++ b/codegen/end_to_end_test/test/operation/variables_test.dart @@ -1,8 +1,7 @@ -import "package:test/test.dart"; - import 'package:end_to_end_test/graphql/__generated__/schema.schema.gql.dart'; -import 'package:end_to_end_test/variables/__generated__/human_with_args.var.gql.dart'; import 'package:end_to_end_test/variables/__generated__/create_review.var.gql.dart'; +import 'package:end_to_end_test/variables/__generated__/human_with_args.var.gql.dart'; +import "package:test/test.dart"; void main() { group("Basic Args", () { diff --git a/codegen/end_to_end_test/test/schema/input_test.dart b/codegen/end_to_end_test/test/schema/input_test.dart index 1d9bf6f6..9dcb1d7c 100644 --- a/codegen/end_to_end_test/test/schema/input_test.dart +++ b/codegen/end_to_end_test/test/schema/input_test.dart @@ -1,6 +1,5 @@ -import "package:test/test.dart"; - import 'package:end_to_end_test/graphql/__generated__/schema.schema.gql.dart'; +import "package:test/test.dart"; void main() { group("Inputs", () { diff --git a/codegen/end_to_end_test/test/schema/scalars_test.dart b/codegen/end_to_end_test/test/schema/scalars_test.dart index 5224ac69..5a18b244 100644 --- a/codegen/end_to_end_test/test/schema/scalars_test.dart +++ b/codegen/end_to_end_test/test/schema/scalars_test.dart @@ -1,11 +1,10 @@ -import "package:test/test.dart"; - +import 'package:end_to_end_test/custom_field.dart'; +import 'package:end_to_end_test/custom_field_serializer.dart'; import 'package:end_to_end_test/graphql/__generated__/schema.schema.gql.dart'; import 'package:end_to_end_test/graphql/__generated__/serializers.gql.dart'; import 'package:end_to_end_test/scalars/__generated__/review_with_date.data.gql.dart'; import 'package:end_to_end_test/scalars/__generated__/review_with_date.var.gql.dart'; -import 'package:end_to_end_test/custom_field.dart'; -import 'package:end_to_end_test/custom_field_serializer.dart'; +import "package:test/test.dart"; void main() { group("Custom scalars from non-standard dart types", () { diff --git a/codegen/gql_build/README.md b/codegen/gql_build/README.md index 86acaf98..7ce876c6 100644 --- a/codegen/gql_build/README.md +++ b/codegen/gql_build/README.md @@ -63,6 +63,20 @@ new enum values are added to the schema and the client has not updated to the ne `global_enum_fallbacks`: \[bool\] Add a generated fallback value for each enum value (except for ones that have a custom fallback value specified in the enum_fallbacks map). Defaults to false. +`when_extensions`: [Map\] whether to enable the `when`/`maybeWhen` extension on the generated data classes +from inline fragment spreads with type conditions. +Supported keys are `when` and `maybeWhen`, and the values are booleans indicating whether to enable +generation of the extension method not. + + +Example: + +```yaml +when_extensions: + when: true # enable the `when` extension method + maybeWhen: true # enable the `maybeWhen` extension method +``` + Example: ```yaml diff --git a/codegen/gql_build/lib/src/schema_builder.dart b/codegen/gql_build/lib/src/schema_builder.dart index b7054dd5..d85096f9 100644 --- a/codegen/gql_build/lib/src/schema_builder.dart +++ b/codegen/gql_build/lib/src/schema_builder.dart @@ -2,6 +2,7 @@ import "dart:async"; import "package:build/build.dart"; import "package:code_builder/code_builder.dart"; +import "package:gql_build/src/allocators/gql_allocator.dart"; import "package:gql_code_builder/schema.dart"; import "package:path/path.dart"; @@ -14,11 +15,8 @@ class SchemaBuilder implements Builder { final EnumFallbackConfig enumFallbackConfig; final bool generatePossibleTypesMap; - SchemaBuilder( - this.typeOverrides, - this.enumFallbackConfig, - this.generatePossibleTypesMap, - ); + SchemaBuilder(this.typeOverrides, this.enumFallbackConfig, + this.generatePossibleTypesMap); @override Map> get buildExtensions => { @@ -34,19 +32,20 @@ class SchemaBuilder implements Builder { .uri .path; - final library = buildSchemaLibrary( - doc, - basename(generatedPartUrl), - typeOverrides, - enumFallbackConfig, - generatePossibleTypesMap: generatePossibleTypesMap, + final schemaUrl = + outputAssetId(buildStep.inputId, schemaExtension).uri.toString(); + final allocator = GqlAllocator( + buildStep.inputId.uri.toString(), + outputAssetId(buildStep.inputId, schemaExtension).uri.toString(), + schemaUrl, ); + final library = buildSchemaLibrary( + doc, basename(generatedPartUrl), typeOverrides, enumFallbackConfig, + generatePossibleTypesMap: generatePossibleTypesMap, + allocator: allocator); + return writeDocument( - library, - buildStep, - schemaExtension, - outputAssetId(buildStep.inputId, schemaExtension).uri.toString(), - ); + library, buildStep, schemaExtension, schemaUrl, allocator); } } diff --git a/codegen/gql_build/lib/src/serializer_builder.dart b/codegen/gql_build/lib/src/serializer_builder.dart index 22028a04..16d60280 100644 --- a/codegen/gql_build/lib/src/serializer_builder.dart +++ b/codegen/gql_build/lib/src/serializer_builder.dart @@ -42,6 +42,14 @@ class SerializerBuilder implements Builder { @override FutureOr build(BuildStep buildStep) async { + final allocator = PickAllocator( + doNotPick: ["package:built_value/serializer.dart"], + include: [ + "package:built_collection/built_collection.dart", + ...typeOverrides.values.map((ref) => ref.url).whereType() + ], + ); + /// BuiltValue classes with serializers. These will be added automatically /// using `@SerializersFor`. final builtClasses = @@ -102,13 +110,7 @@ class SerializerBuilder implements Builder { ); final _emitter = DartEmitter( - allocator: PickAllocator( - doNotPick: ["package:built_value/serializer.dart"], - include: [ - "package:built_collection/built_collection.dart", - ...typeOverrides.values.map((ref) => ref.url).whereType() - ], - ), + allocator: allocator, orderDirectives: true, useNullSafetySyntax: true, ); diff --git a/codegen/gql_build/lib/src/utils/writer.dart b/codegen/gql_build/lib/src/utils/writer.dart index a1285154..ba901931 100644 --- a/codegen/gql_build/lib/src/utils/writer.dart +++ b/codegen/gql_build/lib/src/utils/writer.dart @@ -14,18 +14,21 @@ Future writeDocument( BuildStep buildStep, String extension, [ String? schemaUrl, + Allocator? allocator, ]) { if (library.body.isEmpty) return Future.value(null); + allocator ??= GqlAllocator( + buildStep.inputId.uri.toString(), + outputAssetId(buildStep.inputId, extension).uri.toString(), + schemaUrl, + ); + final generatedAsset = outputAssetId(buildStep.inputId, extension); final genSrc = _dartfmt.format("${library.accept( DartEmitter( - allocator: GqlAllocator( - buildStep.inputId.uri.toString(), - generatedAsset.uri.toString(), - schemaUrl, - ), + allocator: allocator, orderDirectives: true, useNullSafetySyntax: true, ), diff --git a/codegen/gql_build/lib/src/var_builder.dart b/codegen/gql_build/lib/src/var_builder.dart index 74ba44dc..8733b6b5 100644 --- a/codegen/gql_build/lib/src/var_builder.dart +++ b/codegen/gql_build/lib/src/var_builder.dart @@ -2,6 +2,7 @@ import "dart:async"; import "package:build/build.dart"; import "package:code_builder/code_builder.dart"; +import "package:gql_build/src/allocators/gql_allocator.dart"; import "package:gql_code_builder/var.dart"; import "package:path/path.dart"; @@ -34,18 +35,22 @@ class VarBuilder implements Builder { .uri .path; - final library = buildVarLibrary( - doc, - addTypenames(schema), - basename(generatedPartUrl), - typeOverrides, + final schemaUrl = outputAssetId(schemaId, schemaExtension).uri.toString(); + final allocator = GqlAllocator( + buildStep.inputId.uri.toString(), + outputAssetId(buildStep.inputId, schemaExtension).uri.toString(), + schemaUrl, ); + final library = buildVarLibrary(doc, addTypenames(schema), + basename(generatedPartUrl), typeOverrides, allocator); + return writeDocument( library, buildStep, varExtension, - outputAssetId(schemaId, schemaExtension).uri.toString(), + schemaUrl, + allocator, ); } } diff --git a/codegen/gql_code_builder/lib/schema.dart b/codegen/gql_code_builder/lib/schema.dart index 61a57957..99da105a 100644 --- a/codegen/gql_code_builder/lib/schema.dart +++ b/codegen/gql_code_builder/lib/schema.dart @@ -7,17 +7,14 @@ import "package:gql_code_builder/src/utils/possible_types.dart"; export "package:gql_code_builder/src/config/enum_fallback_config.dart"; -Library buildSchemaLibrary( - SourceNode schemaSource, - String partUrl, - Map typeOverrides, - EnumFallbackConfig enumFallbackConfig, { - bool generatePossibleTypesMap = false, -}) { +Library buildSchemaLibrary(SourceNode schemaSource, String partUrl, + Map typeOverrides, EnumFallbackConfig enumFallbackConfig, + {bool generatePossibleTypesMap = false, Allocator? allocator}) { final lib = buildSchema( schemaSource, typeOverrides, enumFallbackConfig, + allocator ?? Allocator(), ) as Library; final Code? possibleTypes; diff --git a/codegen/gql_code_builder/lib/src/built_class.dart b/codegen/gql_code_builder/lib/src/built_class.dart index 631ba97b..9b92906e 100644 --- a/codegen/gql_code_builder/lib/src/built_class.dart +++ b/codegen/gql_code_builder/lib/src/built_class.dart @@ -11,6 +11,7 @@ Class builtClass({ Map? initializers, Map superclassSelections = const {}, List methods = const [], + bool hasCustomSerializer = false, }) { final className = builtClassName(name); return Class( @@ -76,10 +77,11 @@ Class builtClass({ ).code, ), if (getters != null) ...getters, - // Serlialization methods - buildSerializerGetter(className).rebuild( - (b) => b..body = Code("_\$${toCamelCase(className)}Serializer"), - ), + // Serialization methods + if (!hasCustomSerializer) + buildSerializerGetter(className).rebuild( + (b) => b..body = Code("_\$${toCamelCase(className)}Serializer"), + ), buildToJsonGetter( className, isOverride: superclassSelections.isNotEmpty, diff --git a/codegen/gql_code_builder/lib/src/common.dart b/codegen/gql_code_builder/lib/src/common.dart index 7cbcd01a..3df0ea8b 100644 --- a/codegen/gql_code_builder/lib/src/common.dart +++ b/codegen/gql_code_builder/lib/src/common.dart @@ -93,9 +93,6 @@ Reference _typeRef(TypeNode type, Map typeMap) { (b) => b ..url = ref.url ..symbol = ref.symbol - - /// TODO: remove `inList` check - /// https://github.com/google/built_value.dart/issues/1011#issuecomment-804843573 ..isNullable = !type.isNonNull, ); } else if (type is ListTypeNode) { diff --git a/codegen/gql_code_builder/lib/src/schema.dart b/codegen/gql_code_builder/lib/src/schema.dart index aa9eb78b..0b9d5f23 100644 --- a/codegen/gql_code_builder/lib/src/schema.dart +++ b/codegen/gql_code_builder/lib/src/schema.dart @@ -1,5 +1,6 @@ import "package:code_builder/code_builder.dart"; import "package:gql/ast.dart"; +import "package:gql_code_builder/var.dart"; import "./schema/enum.dart"; import "./schema/input.dart"; @@ -12,69 +13,67 @@ Spec? buildSchema( SourceNode schemaSource, Map typeOverrides, EnumFallbackConfig enumFallbackConfig, + Allocator allocator, ) => - schemaSource.document.accept( - _SchemaBuilderVisitor( - schemaSource, - typeOverrides, - enumFallbackConfig, - ), - ); + schemaSource.document + .accept( + _SchemaBuilderVisitor( + schemaSource, + typeOverrides, + enumFallbackConfig, + allocator, + ), + ) + ?.first; -class _SchemaBuilderVisitor extends SimpleVisitor { +class _SchemaBuilderVisitor extends SimpleVisitor?> { final SourceNode schemaSource; final Map typeOverrides; final EnumFallbackConfig enumFallbackConfig; + final Allocator allocator; - _SchemaBuilderVisitor( - this.schemaSource, - this.typeOverrides, - this.enumFallbackConfig, - ); - - Spec? _acceptOne( - Node? node, - ) => - node != null ? node.accept(this) : literalNull; - - List _acceptMany( - List nodes, - ) => - nodes.map(_acceptOne).toList( - growable: false, - ); + _SchemaBuilderVisitor(this.schemaSource, this.typeOverrides, + this.enumFallbackConfig, this.allocator); @override - Spec visitDocumentNode( + List visitDocumentNode( DocumentNode node, ) => - Library( - (b) => b.body.addAll( - _acceptMany(node.definitions).whereType(), - ), - ); + [ + Library( + (b) => b.body.addAll( + node.definitions.expand( + (node) => node.accept(this) ?? [], + ), + ), + ) + ]; @override - Spec visitInputObjectTypeDefinitionNode( + List visitInputObjectTypeDefinitionNode( InputObjectTypeDefinitionNode node, - ) => - buildInputClass( - node, - schemaSource, - typeOverrides, - ); + ) { + final inputClass = buildInputClass( + node, + schemaSource, + typeOverrides, + ); + final serializer = nullAwareJsonSerializerClass( + inputClass, allocator, schemaSource, typeOverrides); + return [inputClass, serializer]; + } @override - Spec? visitScalarTypeDefinitionNode( + List visitScalarTypeDefinitionNode( ScalarTypeDefinitionNode node, ) => typeOverrides.containsKey(node.name.value) - ? null - : buildScalarClass(node); + ? [] + : [buildScalarClass(node)]; @override - Spec visitEnumTypeDefinitionNode( + List visitEnumTypeDefinitionNode( EnumTypeDefinitionNode node, ) => - buildEnumClass(node, enumFallbackConfig); + [buildEnumClass(node, enumFallbackConfig)]; } diff --git a/codegen/gql_code_builder/lib/src/schema/input.dart b/codegen/gql_code_builder/lib/src/schema/input.dart index bce9faa7..7fff98d7 100644 --- a/codegen/gql_code_builder/lib/src/schema/input.dart +++ b/codegen/gql_code_builder/lib/src/schema/input.dart @@ -1,5 +1,6 @@ import "package:code_builder/code_builder.dart"; import "package:gql/ast.dart"; +import "package:gql_code_builder/var.dart"; import "../../source.dart"; import "../built_class.dart"; @@ -8,17 +9,20 @@ import "../common.dart"; List buildInputClasses( SourceNode schemaSource, Map typeOverrides, + Allocator allocator, ) => schemaSource.document.definitions .whereType() - .map( - (InputObjectTypeDefinitionNode node) => buildInputClass( - node, - schemaSource, - typeOverrides, - ), - ) - .toList(); + .expand((InputObjectTypeDefinitionNode node) { + final inputClass = buildInputClass( + node, + schemaSource, + typeOverrides, + ); + final serializer = nullAwareJsonSerializerClass( + inputClass, allocator, schemaSource, typeOverrides); + return [inputClass, serializer]; + }).toList(); Class buildInputClass( InputObjectTypeDefinitionNode node, @@ -35,4 +39,8 @@ Class buildInputClass( typeOverrides: typeOverrides, ), ), + hasCustomSerializer: true, + methods: [ + nullAwareJsonSerializerField(node, "G${node.name.value}"), + ], ); diff --git a/codegen/gql_code_builder/lib/var.dart b/codegen/gql_code_builder/lib/var.dart index 80f553c7..13396672 100644 --- a/codegen/gql_code_builder/lib/var.dart +++ b/codegen/gql_code_builder/lib/var.dart @@ -1,4 +1,5 @@ import "package:code_builder/code_builder.dart"; +import "package:collection/collection.dart"; import "package:gql/ast.dart"; import "./source.dart"; @@ -11,22 +12,37 @@ Library buildVarLibrary( SourceNode schemaSource, String partUrl, Map typeOverrides, + bool serializeNulls, + Allocator allocator, ) { final operationVarClasses = docSource.document.definitions .whereType() - .map( - (op) => builtClass( - name: "${op.name!.value}Vars", - getters: op.variableDefinitions.map( - (node) => buildGetter( - nameNode: node.variable.name, - typeNode: node.type, - schemaSource: schemaSource, - typeOverrides: typeOverrides, - ), - ), - ), - ) + .map((op) => serializeNulls + ? builtClass( + name: "${op.name!.value}Vars", + getters: op.variableDefinitions.map( + (node) => buildGetter( + nameNode: node.variable.name, + typeNode: node.type, + schemaSource: schemaSource, + typeOverrides: typeOverrides, + ), + ), + hasCustomSerializer: true, + methods: [ + nullAwareJsonSerializerField(op, "G${op.name!.value}Vars"), + ]) + : builtClass( + name: "${op.name!.value}Vars", + getters: op.variableDefinitions.map( + (node) => buildGetter( + nameNode: node.variable.name, + typeNode: node.type, + schemaSource: schemaSource, + typeOverrides: typeOverrides, + ), + ), + )) .toList(); Map _fragmentMap(SourceNode source) => { @@ -54,6 +70,10 @@ Library buildVarLibrary( typeOverrides: typeOverrides, ), ), + hasCustomSerializer: true, + methods: [ + nullAwareJsonSerializerField(frag, "G${frag.name.value}Vars"), + ], ); }).toList(); @@ -63,6 +83,216 @@ Library buildVarLibrary( ..body.addAll([ ...operationVarClasses, ...fragmentVarClasses, + for (var op in operationVarClasses) + nullAwareJsonSerializerClass( + op, + allocator, + schemaSource, + typeOverrides, + ), + for (var frag in fragmentVarClasses) + nullAwareJsonSerializerClass( + frag, + allocator, + schemaSource, + typeOverrides, + ), ]), ); } + +Method nullAwareJsonSerializerField(Node op, String className) => + Method((b) => b + ..annotations.add(CodeExpression( + Code("BuiltValueSerializer(custom: true, serializeNulls: true)"))) + ..static = true + ..type = MethodType.getter + ..lambda = true + ..returns = TypeReference((b2) => b2 + ..symbol = "Serializer" + ..url = "package:built_value/serializer.dart" + ..types.add(refer(className))) + ..name = "serializer" + ..body = Code("${className}Serializer()")); + +Class nullAwareJsonSerializerClass( + Class base, + Allocator allocator, + SourceNode schemaSource, + Map typeOverrides, +) => + Class((b) => b + ..name = "${base.name}Serializer" + ..extend = TypeReference((b) => b + ..symbol = "StructuredSerializer" + ..url = "package:built_value/serializer.dart" + ..types.add(refer(base.name))) + ..fields.addAll([ + Field( + (b) => b + ..name = "wireName" + ..modifier = FieldModifier.final$ + ..type = refer("String") + ..assignment = (literalString(base.name)).code, + ), + Field((b) => b + ..name = "types" + ..modifier = FieldModifier.final$ + ..type = TypeReference((b2) => b2..symbol = "Iterable") + ..assignment = Code("const [${base.name}, _\$${base.name}]")) + ]) + ..methods.addAll([ + Method((b) => b + ..name = "serialize" + ..returns = refer("Iterable") + ..requiredParameters.add(Parameter((b) => b + ..name = "serializers" + ..type = + refer("Serializers", "package:built_value/serializer.dart"))) + ..requiredParameters.add(Parameter((b) => b + ..name = "object" + ..type = refer(base.name))) + ..optionalParameters.add(Parameter((b) => b + ..name = "specifiedType" + ..named = true + ..type = refer("FullType", "package:built_value/serializer.dart") + ..defaultTo = Code("FullType.unspecified"))) + ..body = + _serializerBody(base, allocator, schemaSource, typeOverrides)), + Method((b) => b + ..name = "deserialize" + ..returns = refer(base.name) + ..requiredParameters.add(Parameter((b) => b + ..name = "serializers" + ..type = + refer("Serializers", "package:built_value/serializer.dart"))) + ..requiredParameters.add(Parameter((b) => b + ..name = "serialized" + ..type = refer("Iterable"))) + ..optionalParameters.add(Parameter((b) => b + ..name = "specifiedType" + ..named = true + ..type = refer("FullType", "package:built_value/serializer.dart") + ..defaultTo = Code("FullType.unspecified"))) + ..body = + _deserializerBody(base, allocator, schemaSource, typeOverrides)), + ])); + +Code _serializerBody(Class base, Allocator allocator, SourceNode schemaSource, + Map typeOverrides) { + final vars = []; + + for (final field in base.methods + .where((field) => !field.static && field.type == MethodType.getter)) { + final statements = []; + + statements.add(Code("result.add('${_getWireName(field)}');")); + statements.add(Code( + "result.add(serializers.serialize(object.${field.name}, specifiedType: const ${_generateFullType(field.returns as TypeReference, allocator)}));")); + + vars.add(Block.of(statements)); + } + + final body = Block.of([ + Code("final result = [];"), + ...vars, + Code("return result;"), + ]); + + return body; +} + +Code _deserializerBody(Class base, Allocator allocator, SourceNode schemaSource, + Map typeOverrides) => + Code("""final builder = ${base.name}Builder(); + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + ${_generateFieldDeserializers( + base, + allocator, + schemaSource, + typeOverrides, + )} + } + } + return builder.build(); + """); + +String _generateFieldDeserializers( + Class clazz, + Allocator allocator, + SourceNode schemaSource, + Map typeOverrides, +) => + clazz.methods + .where((field) => field.type == MethodType.getter && !field.static) + .map((field) { + final type = field.returns!; + + final fullType = _generateFullType(type as TypeReference, allocator); + + final typeDefNode = getTypeDefinitionNode( + schemaSource.document, type.symbol.substring(1)); + + print(typeDefNode.runtimeType.toString() + + " " + + (typeDefNode?.name.value.toString() ?? "")); + + //TODO this feels flaky, find a better way + final isBuilder = type.url != null && + (typeDefNode is! ScalarTypeDefinitionNode && + typeDefNode is! EnumTypeDefinitionNode); + + /// TODO check for wireName + + var base = """ +case '${_getWireName(field)}': + var fieldValue = serializers.deserialize( + value, specifiedType: const $fullType) as ${_generateTypeCast(type, allocator)};"""; + + if (isBuilder) { + base += """ + builder.${field.name}.replace(fieldValue); + """; + } else { + base += """ + builder.${field.name} = ${"fieldValue"}; + """; + } + + return base + + """ +break; +"""; + }).join(); + +Code _generateFullType(TypeReference ref, Allocator allocator) { + if (ref.types.isEmpty) { + return Code("FullType(${allocator.allocate(ref)})"); + } else { + return Code( + "FullType(${allocator.allocate(ref)}, [${ref.types.map((t) => _generateFullType(t as TypeReference, allocator)).join(",")}])"); + } +} + +String _generateTypeCast(TypeReference ref, Allocator allocator) { + if (ref.types.isEmpty) { + return allocator.allocate(ref); + } else { + return "${allocator.allocate(ref)}<${ref.types.map((t) => _generateTypeCast(t as TypeReference, allocator)).join(",")}>"; + } +} + +String _getWireName(Method m) { + final annotation = m.annotations.firstWhereOrNull( + (a) => a is InvokeExpression && a.name == "BuiltValueField") + as InvokeExpression?; + if (annotation == null) return m.name!; + return (annotation.namedArguments["wireName"] as LiteralExpression?) + ?.literal ?? + m.name!; +} diff --git a/gql/lib/src/operation/definitions/selections.dart b/gql/lib/src/operation/definitions/selections.dart index eab6af9e..451607aa 100644 --- a/gql/lib/src/operation/definitions/selections.dart +++ b/gql/lib/src/operation/definitions/selections.dart @@ -56,7 +56,6 @@ abstract class Selection extends ExecutableWithResolver { static Selection fromNode( SelectionNode astNode, [ - /// The [schemaType] of the containing element TypeDefinition? schemaType, GetExecutableType? getType, diff --git a/links/gql_link/lib/src/link.dart b/links/gql_link/lib/src/link.dart index 2e1f355c..73af9085 100644 --- a/links/gql_link/lib/src/link.dart +++ b/links/gql_link/lib/src/link.dart @@ -89,7 +89,6 @@ abstract class Link { Stream request( /// An incoming [Request] Request request, [ - /// Function that invokes the [request] function of /// the next [Link] ///