From 872f837fd6d661abdda3cacace81f161d7f075d2 Mon Sep 17 00:00:00 2001 From: Tyler Morrison Date: Tue, 25 Aug 2020 21:09:56 -0700 Subject: [PATCH 1/4] add support for optional Description defined for most TypeDefinitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doesn’t support multi-line descriptions because string parser currently doesn’t support multi-line strings. Tests for all additions. Two strategies used for two different kind of places for descriptions: 1) for items in lists (EnumValueDefinition, FieldDefinition, InputValueDefinition) uses new parseDescription(…) to get the optional description if present. 2) for object definitions where a bunch at the same level have optional descriptions followed by a distinguishing token (SchemaDefinition, ScalarTypeDefintion, ObjectTypeDefinition, InterfaceTypeDefinition, UnionTypeDefinition, EnumTypeDefinition, InputObjectTypeDefinition, DirectiveDefinition) we have to advance the lexer to the distinguishing token so we replace the .string Token in lastToken with a copy with only the token kind set to new Token kind, .description. Then each of the specific parsers for those Definitions checks .lastToken to see if it’s a kind is .description and if so, uses that for it’s optional description field. This was the approach that modified the existing code the least and seemed to keep in the spirit of the current implementation. --- Sources/GraphQL/Language/AST.swift | 97 ++++-- Sources/GraphQL/Language/Parser.swift | 54 ++- .../LanguageTests/SchemaParserTests.swift | 329 ++++++++++++++++++ 3 files changed, 453 insertions(+), 27 deletions(-) diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index 1c618752..e57e7003 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -56,6 +56,7 @@ final public class Token { case float = "Float" case string = "String" case comment = "Comment" + case description = "Description" // string in a description position public var description: String { return rawValue @@ -132,6 +133,19 @@ extension Token : CustomStringConvertible { } } +extension Token { + convenience init(from token: Token, as kind: Kind ) { + self.init(kind: kind, + start: token.start, + end: token.end, + line: token.line, + column: token.column, + value: token.value, + prev: token.prev, + next: token.next) + } +} + public enum NodeResult { case node(Node) case array([Node]) @@ -1075,11 +1089,13 @@ public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool { public final class SchemaDefinition { public let kind: Kind = .schemaDefinition public let loc: Location? + public let description: String? public let directives: [Directive] public let operationTypes: [OperationTypeDefinition] - init(loc: Location? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { + init(loc: Location? = nil, description: String? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { self.loc = loc + self.description = description self.directives = directives self.operationTypes = operationTypes } @@ -1087,8 +1103,9 @@ public final class SchemaDefinition { extension SchemaDefinition : Equatable { public static func == (lhs: SchemaDefinition, rhs: SchemaDefinition) -> Bool { - return lhs.directives == rhs.directives && - lhs.operationTypes == rhs.operationTypes + return lhs.description == rhs.description && + lhs.directives == rhs.directives && + lhs.operationTypes == rhs.operationTypes } } @@ -1156,11 +1173,13 @@ public func == (lhs: TypeDefinition, rhs: TypeDefinition) -> Bool { public final class ScalarTypeDefinition { public let kind: Kind = .scalarTypeDefinition public let loc: Location? + public let description: String? public let name: Name public let directives: [Directive] - init(loc: Location? = nil, name: Name, directives: [Directive] = []) { + init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.directives = directives } @@ -1168,7 +1187,8 @@ public final class ScalarTypeDefinition { extension ScalarTypeDefinition : Equatable { public static func == (lhs: ScalarTypeDefinition, rhs: ScalarTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives } } @@ -1176,13 +1196,15 @@ extension ScalarTypeDefinition : Equatable { public final class ObjectTypeDefinition { public let kind: Kind = .objectTypeDefinition public let loc: Location? + public let description: String? public let name: Name public let interfaces: [NamedType] public let directives: [Directive] public let fields: [FieldDefinition] - init(loc: Location? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { + init(loc: Location? = nil, description: String? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { self.loc = loc + self.description = description self.name = name self.interfaces = interfaces self.directives = directives @@ -1192,7 +1214,8 @@ public final class ObjectTypeDefinition { extension ObjectTypeDefinition : Equatable { public static func == (lhs: ObjectTypeDefinition, rhs: ObjectTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.interfaces == rhs.interfaces && lhs.directives == rhs.directives && lhs.fields == rhs.fields @@ -1202,13 +1225,15 @@ extension ObjectTypeDefinition : Equatable { public final class FieldDefinition { public let kind: Kind = .fieldDefinition public let loc: Location? + public let description: String? public let name: Name public let arguments: [InputValueDefinition] public let type: Type public let directives: [Directive] - init(loc: Location? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { + init(loc: Location? = nil, description: String? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.arguments = arguments self.type = type @@ -1218,7 +1243,8 @@ public final class FieldDefinition { extension FieldDefinition : Equatable { public static func == (lhs: FieldDefinition, rhs: FieldDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.arguments == rhs.arguments && lhs.type == rhs.type && lhs.directives == rhs.directives @@ -1228,13 +1254,15 @@ extension FieldDefinition : Equatable { public final class InputValueDefinition { public let kind: Kind = .inputValueDefinition public let loc: Location? + public let description: String? public let name: Name public let type: Type public let defaultValue: Value? public let directives: [Directive] - init(loc: Location? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { + init(loc: Location? = nil, description: String? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.type = type self.defaultValue = defaultValue @@ -1271,6 +1299,7 @@ extension InputValueDefinition : Equatable { public final class InterfaceTypeDefinition { public let kind: Kind = .interfaceTypeDefinition public let loc: Location? + public let description: String? public let name: Name public let interfaces: [NamedType] public let directives: [Directive] @@ -1278,12 +1307,14 @@ public final class InterfaceTypeDefinition { init( loc: Location? = nil, + description: String? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] ) { self.loc = loc + self.description = description self.name = name self.interfaces = interfaces self.directives = directives @@ -1293,7 +1324,8 @@ public final class InterfaceTypeDefinition { extension InterfaceTypeDefinition : Equatable { public static func == (lhs: InterfaceTypeDefinition, rhs: InterfaceTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives && lhs.fields == rhs.fields } @@ -1302,12 +1334,14 @@ extension InterfaceTypeDefinition : Equatable { public final class UnionTypeDefinition { public let kind: Kind = .unionTypeDefinition public let loc: Location? + public let description: String? public let name: Name public let directives: [Directive] public let types: [NamedType] - init(loc: Location? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { + init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { self.loc = loc + self.description = description self.name = name self.directives = directives self.types = types @@ -1316,7 +1350,8 @@ public final class UnionTypeDefinition { extension UnionTypeDefinition : Equatable { public static func == (lhs: UnionTypeDefinition, rhs: UnionTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives && lhs.types == rhs.types } @@ -1325,12 +1360,14 @@ extension UnionTypeDefinition : Equatable { public final class EnumTypeDefinition { public let kind: Kind = .enumTypeDefinition public let loc: Location? + public let description: String? public let name: Name public let directives: [Directive] public let values: [EnumValueDefinition] - init(loc: Location? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { + init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { self.loc = loc + self.description = description self.name = name self.directives = directives self.values = values @@ -1339,7 +1376,8 @@ public final class EnumTypeDefinition { extension EnumTypeDefinition : Equatable { public static func == (lhs: EnumTypeDefinition, rhs: EnumTypeDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives && lhs.values == rhs.values } @@ -1348,11 +1386,13 @@ extension EnumTypeDefinition : Equatable { public final class EnumValueDefinition { public let kind: Kind = .enumValueDefinition public let loc: Location? + public let description: String? public let name: Name public let directives: [Directive] - init(loc: Location? = nil, name: Name, directives: [Directive] = []) { + init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = []) { self.loc = loc + self.description = description self.name = name self.directives = directives } @@ -1360,7 +1400,8 @@ public final class EnumValueDefinition { extension EnumValueDefinition : Equatable { public static func == (lhs: EnumValueDefinition, rhs: EnumValueDefinition) -> Bool { - return lhs.name == rhs.name && + return lhs.description == rhs.description && + lhs.name == rhs.name && lhs.directives == rhs.directives } } @@ -1368,12 +1409,14 @@ extension EnumValueDefinition : Equatable { public final class InputObjectTypeDefinition { public let kind: Kind = .inputObjectTypeDefinition public let loc: Location? + public let description: String? public let name: Name public let directives: [Directive] public let fields: [InputValueDefinition] - init(loc: Location? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { + init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { self.loc = loc + self.description = description self.name = name self.directives = directives self.fields = fields @@ -1382,9 +1425,10 @@ public final class InputObjectTypeDefinition { extension InputObjectTypeDefinition : Equatable { public static func == (lhs: InputObjectTypeDefinition, rhs: InputObjectTypeDefinition) -> Bool { - return lhs.name == rhs.name && - lhs.directives == rhs.directives && - lhs.fields == rhs.fields + return lhs.description == rhs.description && + lhs.name == rhs.name && + lhs.directives == rhs.directives && + lhs.fields == rhs.fields } } @@ -1408,13 +1452,15 @@ extension TypeExtensionDefinition : Equatable { public final class DirectiveDefinition { public let kind: Kind = .directiveDefinition public let loc: Location? + public let description: String? public let name: Name public let arguments: [InputValueDefinition] public let locations: [Name] - init(loc: Location? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { + init(loc: Location? = nil, description: String? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { self.loc = loc self.name = name + self.description = description self.arguments = arguments self.locations = locations } @@ -1422,8 +1468,9 @@ public final class DirectiveDefinition { extension DirectiveDefinition : Equatable { public static func == (lhs: DirectiveDefinition, rhs: DirectiveDefinition) -> Bool { - return lhs.name == rhs.name && - lhs.arguments == rhs.arguments && - lhs.locations == rhs.locations + return lhs.description == rhs.description && + lhs.name == rhs.name && + lhs.arguments == rhs.arguments && + lhs.locations == rhs.locations } } diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 5f928350..492269f8 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -124,6 +124,23 @@ func parseName(lexer: Lexer) throws -> Name { ) } +/** + * Description is optional string + * Returns the string and advances the lexer if current token is a string + * otherwise returns nil without advancing the lexer + */ + +func parseDescription(lexer: Lexer) throws -> String? { + if peek(lexer: lexer, kind: .string) { + let token = lexer.token + try lexer.advance() + return token.value + } else { + return nil + } +} + + // Implements the parsing rules in the Document section. /** @@ -155,7 +172,11 @@ func parseDefinition(lexer: Lexer) throws -> Definition { return try parseOperationDefinition(lexer: lexer) } - if peek(lexer: lexer, kind: .name) { + // Only TypeSystemDefinitions have an optional Description string so test for that string + if peek(lexer: lexer, kind: .string) { + return try parseTypeSystemDefinition(lexer: lexer) + } + else if peek(lexer: lexer, kind: .name) { switch lexer.token.value! { // Note: subscription is an experimental non-spec addition. case "query", "mutation", "subscription": @@ -163,7 +184,8 @@ func parseDefinition(lexer: Lexer) throws -> Definition { case "fragment": return try parseFragmentDefinition(lexer: lexer) // Note: the Type System IDL is an experimental non-spec addition. - case "schema", "scalar", "type", "interface", "union", "enum", "input", "extend", "directive": return try parseTypeSystemDefinition(lexer: lexer) + case "schema", "scalar", "type", "interface", "union", "enum", "input", "extend", "directive": + return try parseTypeSystemDefinition(lexer: lexer) default: break } @@ -631,6 +653,12 @@ func parseNamedType(lexer: Lexer) throws -> NamedType { * - InputObjectTypeDefinition */ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { + if peek(lexer: lexer, kind: .string) { + lexer.token = Token(from: lexer.token, as: .description) + lexer.token.prev?.next = lexer.token + _ = try lexer.advance() // so next peek will work + } + if peek(lexer: lexer, kind: .name) { switch lexer.token.value! { case "schema": return try parseSchemaDefinition(lexer: lexer); @@ -656,6 +684,7 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { */ func parseSchemaDefinition(lexer: Lexer) throws -> SchemaDefinition { let start = lexer.token + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "schema") let directives = try parseDirectives(lexer: lexer) let operationTypes = try many( @@ -666,6 +695,7 @@ func parseSchemaDefinition(lexer: Lexer) throws -> SchemaDefinition { ) return SchemaDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, directives: directives, operationTypes: operationTypes ) @@ -688,11 +718,13 @@ func parseOperationTypeDefinition(lexer: Lexer) throws -> OperationTypeDefinitio */ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { let start = lexer.token + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "scalar") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) return ScalarTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives ) @@ -704,6 +736,7 @@ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { */ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let start = lexer.token; + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "type") let name = try parseName(lexer: lexer); let interfaces = try parseImplementsInterfaces(lexer: lexer); @@ -716,6 +749,7 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { ) return ObjectTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, interfaces: interfaces, directives: directives, @@ -745,6 +779,7 @@ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { */ func parseFieldDefinition(lexer: Lexer) throws -> FieldDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) let name = try parseName(lexer: lexer) let args = try parseArgumentDefs(lexer: lexer) try expect(lexer: lexer, kind: .colon) @@ -752,6 +787,7 @@ func parseFieldDefinition(lexer: Lexer) throws -> FieldDefinition { let directives = try parseDirectives(lexer: lexer) return FieldDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, arguments: args, type: type, @@ -779,6 +815,7 @@ func parseArgumentDefs(lexer: Lexer) throws -> [InputValueDefinition] { */ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { let start = lexer.token + let description = try parseDescription(lexer: lexer) let name = try parseName(lexer: lexer) try expect(lexer: lexer, kind: .colon) let type = try parseTypeReference(lexer: lexer) @@ -792,6 +829,7 @@ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { return InputValueDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, type: type, defaultValue: defaultValue, @@ -804,6 +842,7 @@ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { */ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinition { let start = lexer.token + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "interface") let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) @@ -816,6 +855,7 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio ) return InterfaceTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, interfaces: interfaces, directives: directives, @@ -828,6 +868,7 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio */ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { let start = lexer.token; + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "union") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -835,6 +876,7 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { let types = try parseUnionMembers(lexer: lexer) return UnionTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives, types: types @@ -861,6 +903,7 @@ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { */ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { let start = lexer.token; + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "enum"); let name = try parseName(lexer: lexer); let directives = try parseDirectives(lexer: lexer); @@ -872,6 +915,7 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { ) return EnumTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives, values: values @@ -885,10 +929,12 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { */ func parseEnumValueDefinition(lexer: Lexer) throws -> EnumValueDefinition { let start = lexer.token; + let description = try parseDescription(lexer: lexer) let name = try parseName(lexer: lexer); let directives = try parseDirectives(lexer: lexer); return EnumValueDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives ) @@ -899,6 +945,7 @@ func parseEnumValueDefinition(lexer: Lexer) throws -> EnumValueDefinition { */ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefinition { let start = lexer.token + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "input") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -910,6 +957,7 @@ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefin ) return InputObjectTypeDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, directives: directives, fields: fields @@ -935,6 +983,7 @@ func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinitio */ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { let start = lexer.token; + let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil try expectKeyword(lexer: lexer, value: "directive"); try expect(lexer: lexer, kind: .at) let name = try parseName(lexer: lexer); @@ -943,6 +992,7 @@ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { let locations = try parseDirectiveLocations(lexer: lexer); return DirectiveDefinition( loc: loc(lexer: lexer, startToken: start), + description: description, name: name, arguments: args, locations: locations diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index a96d3079..b5248777 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -9,6 +9,10 @@ func fieldNode(_ name: Name, _ type: Type) -> FieldDefinition { return FieldDefinition(name: name, type: type) } +func fieldNodeWithDescription(_ description: String? = nil, _ name: Name, _ type: Type) -> FieldDefinition { + return FieldDefinition(description: description, name: name, type: type) +} + func typeNode(_ name: String) -> NamedType { return NamedType(name: nameNode(name)) } @@ -17,6 +21,10 @@ func enumValueNode(_ name: String) -> EnumValueDefinition { return EnumValueDefinition(name: nameNode(name)) } +func enumValueWithDescriptionNode(_ description: String, _ name: String) -> EnumValueDefinition { + return EnumValueDefinition(description: description, name: nameNode(name)) +} + func fieldNodeWithArgs(_ name: Name, _ type: Type, _ args: [InputValueDefinition]) -> FieldDefinition { return FieldDefinition(name: name, arguments: args, type: type) } @@ -25,6 +33,17 @@ func inputValueNode(_ name: Name, _ type: Type, _ defaultValue: Value? = nil) -> return InputValueDefinition(name: name, type: type, defaultValue: defaultValue) } +func inputValueWithDescriptionNode(_ description: String, + _ name: Name, + _ type: Type, + _ defaultValue: Value? = nil) -> InputValueDefinition { + return InputValueDefinition(description: description, name: name, type: type, defaultValue: defaultValue) +} + +func namedTypeNode(_ name: String ) -> NamedType { + return NamedType(name: nameNode(name)) +} + class SchemaParserTests : XCTestCase { func testSimpleType() throws { let source = "type Hello { world: String }" @@ -376,4 +395,314 @@ class SchemaParserTests : XCTestCase { let source = "input Hello { world(foo: Int): String }" XCTAssertThrowsError(try parse(source: source)) } + + func testSimpleSchema() throws { + let source = "schema { query: Hello }" + let expected = SchemaDefinition( + directives: [], + operationTypes: [ + OperationTypeDefinition( + operation: .query, + type: namedTypeNode("Hello") + ) + ] + ) + let result = try parse(source: source) + XCTAssert(result.definitions[0] == expected) + } + + // Description tests + + func testTypeWithDescription() throws { + let source = #""The Hello type" type Hello { world: String }"# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + description: "The Hello type", + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func fails_testTypeWitMultilinehDescription() throws { + let source = #""" + """ + The Hello type. + Multi-line description + """ + type Hello { + world: String + } + """# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testDirectiveDesciption() throws { + let source = #""directive description" directive @Test(a: String = "hello") on FIELD"# + + let expected: Document = Document( + definitions: [ + DirectiveDefinition(loc: nil, + description: "directive description", + name: nameNode("Test"), + arguments: [ + inputValueNode( + nameNode("a"), + typeNode("String"), + StringValue(value: "hello") + ) + ], + locations: [ + nameNode("FIELD") + ]) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func fails_testDirectiveMultilineDesciption() throws { + let source = #""" + """ + directive description + """ + directive @Test(a: String = "hello") on FIELD + """# + let expected: Document = Document( + definitions: [ + DirectiveDefinition(loc: nil, + description: "directive description", + name: nameNode("Test"), + arguments: [ + inputValueNode( + nameNode("a"), + typeNode("String"), + StringValue(value: "hello") + ) + ], + locations: [ + nameNode("FIELD") + ]) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleSchemaWithDescription() throws { + let source = #""Hello Schema" schema { query: Hello } "# + + let expected = SchemaDefinition( + description: "Hello Schema", + directives: [], + operationTypes: [ + OperationTypeDefinition( + operation: .query, + type: namedTypeNode("Hello") + ) + ] + ) + let result = try parse(source: source) + XCTAssert(result.definitions[0] == expected) + } + + func testScalarWithDescription() throws { + let source = #""Hello Scaler Test" scalar Hello"# + + let expected = Document( + definitions: [ + ScalarTypeDefinition( + description: "Hello Scaler Test", + name: nameNode("Hello") + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleInterfaceWithDescription() throws { + let source = #""Hello World Interface" interface Hello { world: String } "# + + let expected = Document( + definitions: [ + InterfaceTypeDefinition( + description: "Hello World Interface", + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleUnionWithDescription() throws { + let source = #""Hello World Union!" union Hello = World "# + + let expected = Document( + definitions: [ + UnionTypeDefinition( + description: "Hello World Union!", + name: nameNode("Hello"), + types: [ + typeNode("World"), + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSingleValueEnumDescription() throws { + let source = #""Hello World Enum..." enum Hello { WORLD } "# + + let expected = Document( + definitions: [ + EnumTypeDefinition( + description: "Hello World Enum...", + name: nameNode("Hello"), + values: [ + enumValueNode("WORLD"), + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleInputObjectWithDescription() throws { + let source = #""Hello Input Object" input Hello { world: String }"# + + let expected = Document( + definitions: [ + InputObjectTypeDefinition( + description: "Hello Input Object", + name: nameNode("Hello"), + fields: [ + inputValueNode( + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + // Test Fields and values with optional descriptions + + func testSingleValueEnumWithDescription() throws { + let source = """ + enum Hello { + "world description" + WORLD + "Hello there" + HELLO + } + """ + + let expected = Document( + definitions: [ + EnumTypeDefinition( + name: nameNode("Hello"), + values: [ + enumValueWithDescriptionNode("world description", "WORLD"), + enumValueWithDescriptionNode("Hello there", "HELLO") + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testTypeFieldWithDescription() throws { + let source = #"type Hello { "The world field." world: String } "# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + fieldNodeWithDescription( + "The world field.", + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testSimpleInputObjectFieldWithDescription() throws { + let source = #"input Hello { "World field" world: String }"# + + let expected = Document( + definitions: [ + InputObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + inputValueWithDescriptionNode( + "World field", + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + } From 5e66d3dfc4fdf2417b59c2e4dea3c1ecc2377302 Mon Sep 17 00:00:00 2001 From: Tyler Morrison Date: Wed, 26 Aug 2020 00:02:57 -0700 Subject: [PATCH 2/4] merge in the fixed tests that will pass once multi-line blockstrings are merged in. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prefixed with “fails_” currently so will not be run. --- .../LanguageTests/SchemaParserTests.swift | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index b5248777..8772df22 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -444,11 +444,12 @@ class SchemaParserTests : XCTestCase { type Hello { world: String } - """# + """# let expected = Document( definitions: [ ObjectTypeDefinition( + description: "The Hello type.\nMulti-line description", name: nameNode("Hello"), fields: [ fieldNode( @@ -495,7 +496,7 @@ class SchemaParserTests : XCTestCase { directive description """ directive @Test(a: String = "hello") on FIELD - """# + """# let expected: Document = Document( definitions: [ DirectiveDefinition(loc: nil, @@ -682,6 +683,37 @@ class SchemaParserTests : XCTestCase { let result = try parse(source: source) XCTAssert(result == expected) } + + func fails_testTypeFieldWithMultilineDescription() throws { + let source = #""" + type Hello { + """ + The world + field. + """ + world: String + } + """# + + let expected = Document( + definitions: [ + ObjectTypeDefinition( + name: nameNode("Hello"), + fields: [ + fieldNodeWithDescription( + "The world\nfield.", + nameNode("world"), + typeNode("String") + ) + ] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected, "\(dump(result)) \n\n\(dump(expected))") + } + func testSimpleInputObjectFieldWithDescription() throws { let source = #"input Hello { "World field" world: String }"# From 38de3f697cae57fa5a21a73a77d90b023ca5af05 Mon Sep 17 00:00:00 2001 From: Tyler Morrison Date: Thu, 27 Aug 2020 11:10:00 -0700 Subject: [PATCH 3/4] enable blockstring description tests now that blockstring support is merged in. --- Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index 8772df22..f13455c0 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -435,7 +435,7 @@ class SchemaParserTests : XCTestCase { XCTAssert(result == expected) } - func fails_testTypeWitMultilinehDescription() throws { + func testTypeWitMultilinehDescription() throws { let source = #""" """ The Hello type. @@ -490,7 +490,7 @@ class SchemaParserTests : XCTestCase { XCTAssert(result == expected) } - func fails_testDirectiveMultilineDesciption() throws { + func testDirectiveMultilineDesciption() throws { let source = #""" """ directive description @@ -684,7 +684,7 @@ class SchemaParserTests : XCTestCase { XCTAssert(result == expected) } - func fails_testTypeFieldWithMultilineDescription() throws { + func testTypeFieldWithMultilineDescription() throws { let source = #""" type Hello { """ From 75cb8718bc2846a9e0ed2123bfccac56de46c986 Mon Sep 17 00:00:00 2001 From: Tyler Morrison Date: Thu, 27 Aug 2020 14:36:14 -0700 Subject: [PATCH 4/4] re-do of description support uses blockstrings2 re-do and matches closer to canonical js approach. cleaned up test names. update tests. --- Sources/GraphQL/Language/AST.swift | 64 ++++++--------- Sources/GraphQL/Language/Lexer.swift | 27 +++++++ Sources/GraphQL/Language/Parser.swift | 79 ++++++++++--------- .../LanguageTests/ParserTests.swift | 2 +- .../LanguageTests/SchemaParserTests.swift | 58 +++++++------- 5 files changed, 122 insertions(+), 108 deletions(-) diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index e0d46fec..3a730e0e 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -57,7 +57,6 @@ final public class Token { case string = "String" case blockstring = "BlockString" case comment = "Comment" - case description = "Description" // string in a description position public var description: String { return rawValue @@ -134,19 +133,6 @@ extension Token : CustomStringConvertible { } } -extension Token { - convenience init(from token: Token, as kind: Kind ) { - self.init(kind: kind, - start: token.start, - end: token.end, - line: token.line, - column: token.column, - value: token.value, - prev: token.prev, - next: token.next) - } -} - public enum NodeResult { case node(Node) case array([Node]) @@ -825,16 +811,18 @@ public final class StringValue { public let kind: Kind = .stringValue public let loc: Location? public let value: String + public let block: Bool? - init(loc: Location? = nil, value: String) { + init(loc: Location? = nil, value: String, block: Bool? = nil) { self.loc = loc self.value = value + self.block = block } } extension StringValue : Equatable { public static func == (lhs: StringValue, rhs: StringValue) -> Bool { - return lhs.value == rhs.value + return lhs.value == rhs.value && lhs.block == rhs.block } } @@ -1090,11 +1078,11 @@ public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool { public final class SchemaDefinition { public let kind: Kind = .schemaDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let directives: [Directive] public let operationTypes: [OperationTypeDefinition] - init(loc: Location? = nil, description: String? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { + init(loc: Location? = nil, description: StringValue? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { self.loc = loc self.description = description self.directives = directives @@ -1174,11 +1162,11 @@ public func == (lhs: TypeDefinition, rhs: TypeDefinition) -> Bool { public final class ScalarTypeDefinition { public let kind: Kind = .scalarTypeDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let directives: [Directive] - init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = []) { self.loc = loc self.description = description self.name = name @@ -1197,13 +1185,13 @@ extension ScalarTypeDefinition : Equatable { public final class ObjectTypeDefinition { public let kind: Kind = .objectTypeDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let interfaces: [NamedType] public let directives: [Directive] public let fields: [FieldDefinition] - init(loc: Location? = nil, description: String? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { self.loc = loc self.description = description self.name = name @@ -1226,13 +1214,13 @@ extension ObjectTypeDefinition : Equatable { public final class FieldDefinition { public let kind: Kind = .fieldDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let arguments: [InputValueDefinition] public let type: Type public let directives: [Directive] - init(loc: Location? = nil, description: String? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { self.loc = loc self.description = description self.name = name @@ -1255,13 +1243,13 @@ extension FieldDefinition : Equatable { public final class InputValueDefinition { public let kind: Kind = .inputValueDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let type: Type public let defaultValue: Value? public let directives: [Directive] - init(loc: Location? = nil, description: String? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { self.loc = loc self.description = description self.name = name @@ -1300,7 +1288,7 @@ extension InputValueDefinition : Equatable { public final class InterfaceTypeDefinition { public let kind: Kind = .interfaceTypeDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let interfaces: [NamedType] public let directives: [Directive] @@ -1308,7 +1296,7 @@ public final class InterfaceTypeDefinition { init( loc: Location? = nil, - description: String? = nil, + description: StringValue? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], @@ -1335,12 +1323,12 @@ extension InterfaceTypeDefinition : Equatable { public final class UnionTypeDefinition { public let kind: Kind = .unionTypeDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let directives: [Directive] public let types: [NamedType] - init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { self.loc = loc self.description = description self.name = name @@ -1361,12 +1349,12 @@ extension UnionTypeDefinition : Equatable { public final class EnumTypeDefinition { public let kind: Kind = .enumTypeDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let directives: [Directive] public let values: [EnumValueDefinition] - init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { self.loc = loc self.description = description self.name = name @@ -1387,11 +1375,11 @@ extension EnumTypeDefinition : Equatable { public final class EnumValueDefinition { public let kind: Kind = .enumValueDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let directives: [Directive] - init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = []) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = []) { self.loc = loc self.description = description self.name = name @@ -1410,12 +1398,12 @@ extension EnumValueDefinition : Equatable { public final class InputObjectTypeDefinition { public let kind: Kind = .inputObjectTypeDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let directives: [Directive] public let fields: [InputValueDefinition] - init(loc: Location? = nil, description: String? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { self.loc = loc self.description = description self.name = name @@ -1453,12 +1441,12 @@ extension TypeExtensionDefinition : Equatable { public final class DirectiveDefinition { public let kind: Kind = .directiveDefinition public let loc: Location? - public let description: String? + public let description: StringValue? public let name: Name public let arguments: [InputValueDefinition] public let locations: [Name] - init(loc: Location? = nil, description: String? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { + init(loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { self.loc = loc self.name = name self.description = description diff --git a/Sources/GraphQL/Language/Lexer.swift b/Sources/GraphQL/Language/Lexer.swift index 40cf4e9b..0501aa74 100644 --- a/Sources/GraphQL/Language/Lexer.swift +++ b/Sources/GraphQL/Language/Lexer.swift @@ -91,6 +91,33 @@ final class Lexer { func advance() throws -> Token { return try advanceFunction(self) } + + /** + * Looks ahead and returns the next non-ignored token, but does not change + * the state of Lexer. + */ + func lookahead() throws -> Token { + var startToken = token + let savedLine = self.line + let savedLineStart = self.lineStart + + guard startToken.kind != .eof else { return startToken } + repeat { + startToken = try startToken.next ?? + { + startToken.next = try readToken(lexer: self, prev: startToken) + return startToken.next! + }() + } while startToken.kind == .comment + + // restore these since both `positionAfterWhitespace` & `readBlockString` + // can potentially modify them and commment for `lookahead` says no lexer modification. + // (the latter is true in the canonical js lexer also and is likely a bug) + self.line = savedLine + self.lineStart = savedLineStart + + return startToken + } } /** diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 492269f8..04ec50aa 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -124,20 +124,19 @@ func parseName(lexer: Lexer) throws -> Name { ) } +func peekDescription(lexer: Lexer) -> Bool { + return peek(lexer: lexer, kind: .string) || peek(lexer: lexer, kind: .blockstring) +} + /** - * Description is optional string - * Returns the string and advances the lexer if current token is a string - * otherwise returns nil without advancing the lexer + * Description is optional StringValue */ -func parseDescription(lexer: Lexer) throws -> String? { - if peek(lexer: lexer, kind: .string) { - let token = lexer.token - try lexer.advance() - return token.value - } else { - return nil +func parseDescription(lexer: Lexer) throws -> StringValue? { + if peekDescription(lexer: lexer) { + return try parseStringLiteral(lexer: lexer, startToken: lexer.token) } + return nil } @@ -171,12 +170,8 @@ func parseDefinition(lexer: Lexer) throws -> Definition { if peek(lexer: lexer, kind: .openingBrace) { return try parseOperationDefinition(lexer: lexer) } - - // Only TypeSystemDefinitions have an optional Description string so test for that string - if peek(lexer: lexer, kind: .string) { - return try parseTypeSystemDefinition(lexer: lexer) - } - else if peek(lexer: lexer, kind: .name) { + + if peek(lexer: lexer, kind: .name) { switch lexer.token.value! { // Note: subscription is an experimental non-spec addition. case "query", "mutation", "subscription": @@ -189,6 +184,8 @@ func parseDefinition(lexer: Lexer) throws -> Definition { default: break } + } else if peekDescription(lexer: lexer) { + return try parseTypeSystemDefinition(lexer: lexer) } throw unexpected(lexer: lexer) @@ -471,12 +468,8 @@ func parseValueLiteral(lexer: Lexer, isConst: Bool) throws -> Value { loc: loc(lexer: lexer, startToken: token), value: token.value! ) - case .string: - try lexer.advance(); - return StringValue( - loc: loc(lexer: lexer, startToken: token), - value: token.value! - ) + case .string, .blockstring: + return try parseStringLiteral(lexer: lexer, startToken: token) case .name: if (token.value == "true" || token.value == "false") { try lexer.advance() @@ -561,6 +554,18 @@ func parseObjectField(lexer: Lexer, isConst: Bool) throws -> ObjectField { ) } +/** + * parseStringLiteral + */ + +func parseStringLiteral(lexer: Lexer, startToken: Token) throws -> StringValue { + try lexer.advance(); + return StringValue( + loc: loc(lexer: lexer, startToken: startToken), + value: startToken.value!, + block: startToken.kind == .blockstring + ) +} // Implements the parsing rules in the Directives section. @@ -653,14 +658,12 @@ func parseNamedType(lexer: Lexer) throws -> NamedType { * - InputObjectTypeDefinition */ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { - if peek(lexer: lexer, kind: .string) { - lexer.token = Token(from: lexer.token, as: .description) - lexer.token.prev?.next = lexer.token - _ = try lexer.advance() // so next peek will work - } + let keywordToken = peekDescription(lexer: lexer) + ? try lexer.lookahead() + : lexer.token - if peek(lexer: lexer, kind: .name) { - switch lexer.token.value! { + if keywordToken.kind == .name { + switch keywordToken.value! { case "schema": return try parseSchemaDefinition(lexer: lexer); case "scalar": return try parseScalarTypeDefinition(lexer: lexer); case "type": return try parseObjectTypeDefinition(lexer: lexer); @@ -674,7 +677,7 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { } } - throw unexpected(lexer: lexer) + throw unexpected(lexer: lexer, atToken: keywordToken) } /** @@ -684,7 +687,7 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { */ func parseSchemaDefinition(lexer: Lexer) throws -> SchemaDefinition { let start = lexer.token - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "schema") let directives = try parseDirectives(lexer: lexer) let operationTypes = try many( @@ -718,7 +721,7 @@ func parseOperationTypeDefinition(lexer: Lexer) throws -> OperationTypeDefinitio */ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { let start = lexer.token - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "scalar") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -736,7 +739,7 @@ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { */ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let start = lexer.token; - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "type") let name = try parseName(lexer: lexer); let interfaces = try parseImplementsInterfaces(lexer: lexer); @@ -842,7 +845,7 @@ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { */ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinition { let start = lexer.token - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "interface") let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) @@ -868,7 +871,7 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio */ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { let start = lexer.token; - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "union") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -903,7 +906,7 @@ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { */ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { let start = lexer.token; - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "enum"); let name = try parseName(lexer: lexer); let directives = try parseDirectives(lexer: lexer); @@ -945,7 +948,7 @@ func parseEnumValueDefinition(lexer: Lexer) throws -> EnumValueDefinition { */ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefinition { let start = lexer.token - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "input") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) @@ -983,7 +986,7 @@ func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinitio */ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { let start = lexer.token; - let description = lexer.lastToken.kind == .description ? lexer.lastToken.value : nil + let description = try parseDescription(lexer: lexer) try expectKeyword(lexer: lexer, value: "directive"); try expect(lexer: lexer, kind: .at) let name = try parseName(lexer: lexer); diff --git a/Tests/GraphQLTests/LanguageTests/ParserTests.swift b/Tests/GraphQLTests/LanguageTests/ParserTests.swift index 47642c24..87feb914 100644 --- a/Tests/GraphQLTests/LanguageTests/ParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/ParserTests.swift @@ -266,7 +266,7 @@ class ParserTests : XCTestCase { let expected: Value = ListValue( values: [ IntValue(value: "123"), - StringValue(value: "abc") + StringValue(value: "abc", block: false) ] ) diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index f13455c0..fa249e4b 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -9,7 +9,7 @@ func fieldNode(_ name: Name, _ type: Type) -> FieldDefinition { return FieldDefinition(name: name, type: type) } -func fieldNodeWithDescription(_ description: String? = nil, _ name: Name, _ type: Type) -> FieldDefinition { +func fieldNodeWithDescription(_ description: StringValue? = nil, _ name: Name, _ type: Type) -> FieldDefinition { return FieldDefinition(description: description, name: name, type: type) } @@ -21,7 +21,7 @@ func enumValueNode(_ name: String) -> EnumValueDefinition { return EnumValueDefinition(name: nameNode(name)) } -func enumValueWithDescriptionNode(_ description: String, _ name: String) -> EnumValueDefinition { +func enumValueWithDescriptionNode(_ description: StringValue?, _ name: String) -> EnumValueDefinition { return EnumValueDefinition(description: description, name: nameNode(name)) } @@ -33,7 +33,7 @@ func inputValueNode(_ name: Name, _ type: Type, _ defaultValue: Value? = nil) -> return InputValueDefinition(name: name, type: type, defaultValue: defaultValue) } -func inputValueWithDescriptionNode(_ description: String, +func inputValueWithDescriptionNode(_ description: StringValue?, _ name: Name, _ type: Type, _ defaultValue: Value? = nil) -> InputValueDefinition { @@ -416,23 +416,19 @@ class SchemaParserTests : XCTestCase { func testTypeWithDescription() throws { let source = #""The Hello type" type Hello { world: String }"# - let expected = Document( - definitions: [ - ObjectTypeDefinition( - description: "The Hello type", - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] + let expected = ObjectTypeDefinition( + description: StringValue(value: "The Hello type", block: false), + name: nameNode("Hello"), + fields: [ + fieldNode( + nameNode("world"), + typeNode("String") ) ] ) let result = try parse(source: source) - XCTAssert(result == expected) + XCTAssertEqual(result.definitions[0] as! ObjectTypeDefinition, expected, "\n\(dump(result.definitions[0]))\n\(dump(expected))\n") } func testTypeWitMultilinehDescription() throws { @@ -449,7 +445,7 @@ class SchemaParserTests : XCTestCase { let expected = Document( definitions: [ ObjectTypeDefinition( - description: "The Hello type.\nMulti-line description", + description: StringValue(value:"The Hello type.\nMulti-line description", block: true), name: nameNode("Hello"), fields: [ fieldNode( @@ -471,13 +467,13 @@ class SchemaParserTests : XCTestCase { let expected: Document = Document( definitions: [ DirectiveDefinition(loc: nil, - description: "directive description", + description: StringValue(value: "directive description", block: false), name: nameNode("Test"), arguments: [ inputValueNode( nameNode("a"), typeNode("String"), - StringValue(value: "hello") + StringValue(value: "hello", block: false) ) ], locations: [ @@ -500,13 +496,13 @@ class SchemaParserTests : XCTestCase { let expected: Document = Document( definitions: [ DirectiveDefinition(loc: nil, - description: "directive description", + description: StringValue(value: "directive description", block: true), name: nameNode("Test"), arguments: [ inputValueNode( nameNode("a"), typeNode("String"), - StringValue(value: "hello") + StringValue(value: "hello", block: false) ) ], locations: [ @@ -523,7 +519,7 @@ class SchemaParserTests : XCTestCase { let source = #""Hello Schema" schema { query: Hello } "# let expected = SchemaDefinition( - description: "Hello Schema", + description: StringValue(value: "Hello Schema", block: false), directives: [], operationTypes: [ OperationTypeDefinition( @@ -542,7 +538,7 @@ class SchemaParserTests : XCTestCase { let expected = Document( definitions: [ ScalarTypeDefinition( - description: "Hello Scaler Test", + description: StringValue(value: "Hello Scaler Test", block: false), name: nameNode("Hello") ) ] @@ -558,7 +554,7 @@ class SchemaParserTests : XCTestCase { let expected = Document( definitions: [ InterfaceTypeDefinition( - description: "Hello World Interface", + description: StringValue(value: "Hello World Interface", block: false), name: nameNode("Hello"), fields: [ fieldNode( @@ -580,7 +576,7 @@ class SchemaParserTests : XCTestCase { let expected = Document( definitions: [ UnionTypeDefinition( - description: "Hello World Union!", + description: StringValue(value: "Hello World Union!", block: false), name: nameNode("Hello"), types: [ typeNode("World"), @@ -599,7 +595,7 @@ class SchemaParserTests : XCTestCase { let expected = Document( definitions: [ EnumTypeDefinition( - description: "Hello World Enum...", + description: StringValue(value: "Hello World Enum...", block: false), name: nameNode("Hello"), values: [ enumValueNode("WORLD"), @@ -618,7 +614,7 @@ class SchemaParserTests : XCTestCase { let expected = Document( definitions: [ InputObjectTypeDefinition( - description: "Hello Input Object", + description: StringValue(value: "Hello Input Object", block: false), name: nameNode("Hello"), fields: [ inputValueNode( @@ -651,8 +647,8 @@ class SchemaParserTests : XCTestCase { EnumTypeDefinition( name: nameNode("Hello"), values: [ - enumValueWithDescriptionNode("world description", "WORLD"), - enumValueWithDescriptionNode("Hello there", "HELLO") + enumValueWithDescriptionNode(StringValue(value: "world description", block: false), "WORLD"), + enumValueWithDescriptionNode(StringValue(value: "Hello there", block: false), "HELLO") ] ) ] @@ -671,7 +667,7 @@ class SchemaParserTests : XCTestCase { name: nameNode("Hello"), fields: [ fieldNodeWithDescription( - "The world field.", + StringValue(value: "The world field.", block: false), nameNode("world"), typeNode("String") ) @@ -701,7 +697,7 @@ class SchemaParserTests : XCTestCase { name: nameNode("Hello"), fields: [ fieldNodeWithDescription( - "The world\nfield.", + StringValue(value: "The world\nfield.", block: true), nameNode("world"), typeNode("String") ) @@ -724,7 +720,7 @@ class SchemaParserTests : XCTestCase { name: nameNode("Hello"), fields: [ inputValueWithDescriptionNode( - "World field", + StringValue(value: "World field", block: false), nameNode("world"), typeNode("String") )