From d11320e1197c703c2793b8677b0bdc7f3a92b5e2 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:06:28 -0800 Subject: [PATCH 01/13] update kitchen sink schema --- .../LanguageTests/schema-kitchen-sink.graphql | 113 ++++++++++++++++-- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/Tests/GraphQLTests/LanguageTests/schema-kitchen-sink.graphql b/Tests/GraphQLTests/LanguageTests/schema-kitchen-sink.graphql index d1482762..82145538 100644 --- a/Tests/GraphQLTests/LanguageTests/schema-kitchen-sink.graphql +++ b/Tests/GraphQLTests/LanguageTests/schema-kitchen-sink.graphql @@ -10,39 +10,97 @@ schema { mutation: MutationType } -type Foo implements Bar { +""" +This is a description +of the `Foo` type. +""" +type Foo implements Bar & Baz & Two { + "Description of the `one` field." one: Type - two(argument: InputType!): Type + """ + This is a description of the `two` field. + """ + two( + """ + This is a description of the `argument` argument. + """ + argument: InputType! + ): Type + """This is a description of the `three` field.""" three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type + seven(argument: Int = null): Type } type AnnotatedObject @onObject(arg: "value") { - annotatedField(arg: Type = "default" @onArg): Type @onField + annotatedField(arg: Type = "default" @onArgumentDefinition): Type @onField +} + +type UndefinedType + +extend type Foo { + seven(argument: [String]): Type } +extend type Foo @onType + interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { - annotatedField(arg: Type @onArg): Type @onField + annotatedField(arg: Type @onArgumentDefinition): Type @onField } -union Feed = Story | Article | Advert +interface UndefinedInterface + +extend interface Bar implements Two { + two(argument: InputType!): Type +} + +extend interface Bar @onInterface + +interface Baz implements Bar & Two { + one: Type + two(argument: InputType!): Type + four(argument: String = "string"): String +} + +union Feed = + | Story + | Article + | Advert union AnnotatedUnion @onUnion = A | B +union AnnotatedUnionTwo @onUnion = | A | B + +union UndefinedUnion + +extend union Feed = Photo | Video + +extend union Feed @onUnion + scalar CustomScalar scalar AnnotatedScalar @onScalar +extend scalar CustomScalar @onScalar + enum Site { + """ + This is a description of the `DESKTOP` value + """ DESKTOP + + """This is a description of the `MOBILE` value""" MOBILE + + "This is a description of the `WEB` value" + WEB } enum AnnotatedEnum @onEnum { @@ -50,26 +108,55 @@ enum AnnotatedEnum @onEnum { OTHER_VALUE } +enum UndefinedEnum + +extend enum Site { + VR +} + +extend enum Site @onEnum + input InputType { key: String! answer: Int = 42 } -input AnnotatedInput @onInputObjectType { - annotatedField: Type @onField +input AnnotatedInput @onInputObject { + annotatedField: Type @onInputFieldDefinition } -extend type Foo { - seven(argument: [String]): Type -} +input UndefinedInput -extend type Foo @onType {} +extend input InputType { + other: Float = 1.23e4 @onInputFieldDefinition +} -type NoFields {} +extend input InputType @onInputObject -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT +""" +This is a description of the `@skip` directive +""" +directive @skip( + """This is a description of the `if` argument""" + if: Boolean! @onArgumentDefinition +) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include2(if: Boolean!) on + | FIELD + | FRAGMENT_SPREAD + | INLINE_FRAGMENT + +directive @myRepeatableDir(name: String!) repeatable on + | OBJECT + | INTERFACE + +extend schema @onSchema + +extend schema @onSchema { + subscription: SubscriptionType +} From 13d1f73c39cf87acc4f7f6e2b8dd0b34b1868f74 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:07:36 -0800 Subject: [PATCH 02/13] add ampersand support --- Sources/GraphQL/Language/AST.swift | 1 + Sources/GraphQL/Language/Lexer.swift | 10 +++++++++ Sources/GraphQL/Language/Parser.swift | 21 +++++++++++++++++-- .../LanguageTests/ParserTests.swift | 20 ++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index 81fbced0..9eabcd7f 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -39,6 +39,7 @@ public final class Token { case eof = "" case bang = "!" case dollar = "$" + case amp = "&" case openingParenthesis = "(" case closingParenthesis = ")" case spread = "..." diff --git a/Sources/GraphQL/Language/Lexer.swift b/Sources/GraphQL/Language/Lexer.swift index 6e45463d..43acbbe3 100644 --- a/Sources/GraphQL/Language/Lexer.swift +++ b/Sources/GraphQL/Language/Lexer.swift @@ -244,6 +244,16 @@ func readToken(lexer: Lexer, prev: Token) throws -> Token { column: col, prev: prev ) + // & + case 38: + return Token( + kind: .amp, + start: position, + end: position + 1, + line: line, + column: col, + prev: prev + ) // ( case 40: return Token( diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index fb8e3e45..3099c413 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -796,7 +796,9 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { } /** - * ImplementsInterfaces : implements NamedType+ + * ImplementsInterfaces : + * - implements &? NamedType + * - ImplementsInterfaces & NamedType */ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { var types: [NamedType] = [] @@ -804,9 +806,10 @@ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { if lexer.token.value == "implements" { try lexer.advance() + try expectOptional(lexer: lexer, kind: .amp) repeat { types.append(try parseNamedType(lexer: lexer)) - } while peek(lexer: lexer, kind: .name) + } while try expectOptional(lexer: lexer, kind: .amp) != nil || peek(lexer: lexer, kind: .name) } return types @@ -1148,6 +1151,20 @@ func expect(lexer: Lexer, kind: Token.Kind) throws -> Token { return token } +/** + * If the next token is of the given kind, return that token after advancing + * the lexer. Otherwise, do not change the parser state and return nil. + */ +@discardableResult +func expectOptional(lexer: Lexer, kind: Token.Kind) throws -> Token? { + let token = lexer.token + if token.kind == kind { + try lexer.advance() + return token + } + return nil +} + /** * If the next token is a keyword with the given value, return that token after * advancing the lexer. Otherwise, do not change the parser state and return diff --git a/Tests/GraphQLTests/LanguageTests/ParserTests.swift b/Tests/GraphQLTests/LanguageTests/ParserTests.swift index 6365fc35..4a9857ff 100644 --- a/Tests/GraphQLTests/LanguageTests/ParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/ParserTests.swift @@ -110,6 +110,26 @@ class ParserTests: XCTestCase { "Syntax Error GraphQL (1:9) Expected Name, found }" )) } + + XCTAssertThrowsError(try parse(source: "type WithImplementsButNoTypes implements {}")) { error in + guard let error = error as? GraphQLError else { + return XCTFail() + } + + XCTAssert(error.message.contains( + "Syntax Error GraphQL (1:42) Expected Name, found {" + )) + } + + XCTAssertThrowsError(try parse(source: "type WithImplementsWithTrailingAmp implements AInterface & {}")) { error in + guard let error = error as? GraphQLError else { + return XCTFail() + } + + XCTAssert(error.message.contains( + "Syntax Error GraphQL (1:60) Expected Name, found {" + )) + } } func testVariableInlineValues() throws { From b4d5a4420bbf88e5fbf8d0a94bd73c347a88ba46 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:11:16 -0800 Subject: [PATCH 03/13] add more extension definitions --- Sources/GraphQL/Language/AST.swift | 110 ++++++++++++++++++++++++++ Sources/GraphQL/Language/Kinds.swift | 5 ++ Sources/GraphQL/Language/Parser.swift | 72 ++++++++++++++++- 3 files changed, 186 insertions(+), 1 deletion(-) diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index 9eabcd7f..a78313b0 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -1103,6 +1103,11 @@ extension SchemaDefinition: TypeSystemDefinition {} extension TypeExtensionDefinition: TypeSystemDefinition {} extension SchemaExtensionDefinition: TypeSystemDefinition {} extension DirectiveDefinition: TypeSystemDefinition {} +extension InterfaceExtensionDefinition: TypeSystemDefinition {} +extension ScalarExtensionDefinition: TypeSystemDefinition {} +extension UnionExtensionDefinition: TypeSystemDefinition {} +extension EnumExtensionDefinition: TypeSystemDefinition {} +extension InputObjectExtensionDefinition: TypeSystemDefinition {} public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool { switch lhs { @@ -1126,6 +1131,26 @@ public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool { if let r = rhs as? SchemaExtensionDefinition { return l == r } + case let l as InterfaceExtensionDefinition: + if let r = rhs as? InterfaceExtensionDefinition { + return l == r + } + case let l as ScalarExtensionDefinition: + if let r = rhs as? ScalarExtensionDefinition { + return l == r + } + case let l as UnionExtensionDefinition: + if let r = rhs as? UnionExtensionDefinition { + return l == r + } + case let l as EnumExtensionDefinition: + if let r = rhs as? EnumExtensionDefinition { + return l == r + } + case let l as InputObjectExtensionDefinition: + if let r = rhs as? InputObjectExtensionDefinition { + return l == r + } default: return false } @@ -1567,6 +1592,91 @@ extension SchemaExtensionDefinition: Equatable { } } +public final class InterfaceExtensionDefinition { + public let kind: Kind = .interfaceExtensionDefinition + public let loc: Location? + public let definition: InterfaceTypeDefinition + + init(loc: Location? = nil, definition: InterfaceTypeDefinition) { + self.loc = loc + self.definition = definition + } +} + +extension InterfaceExtensionDefinition: Equatable { + public static func == (lhs: InterfaceExtensionDefinition, rhs: InterfaceExtensionDefinition) -> Bool { + return lhs.definition == rhs.definition + } +} + +public final class ScalarExtensionDefinition { + public let kind: Kind = .scalarExtensionDefinition + public let loc: Location? + public let definition: ScalarTypeDefinition + + init(loc: Location? = nil, definition: ScalarTypeDefinition) { + self.loc = loc + self.definition = definition + } +} + +extension ScalarExtensionDefinition: Equatable { + public static func == (lhs: ScalarExtensionDefinition, rhs: ScalarExtensionDefinition) -> Bool { + return lhs.definition == rhs.definition + } +} + +public final class UnionExtensionDefinition { + public let kind: Kind = .unionExtensionDefinition + public let loc: Location? + public let definition: UnionTypeDefinition + + init(loc: Location? = nil, definition: UnionTypeDefinition) { + self.loc = loc + self.definition = definition + } +} + +extension UnionExtensionDefinition: Equatable { + public static func == (lhs: UnionExtensionDefinition, rhs: UnionExtensionDefinition) -> Bool { + return lhs.definition == rhs.definition + } +} + +public final class EnumExtensionDefinition { + public let kind: Kind = .enumExtensionDefinition + public let loc: Location? + public let definition: EnumTypeDefinition + + init(loc: Location? = nil, definition: EnumTypeDefinition) { + self.loc = loc + self.definition = definition + } +} + +extension EnumExtensionDefinition: Equatable { + public static func == (lhs: EnumExtensionDefinition, rhs: EnumExtensionDefinition) -> Bool { + return lhs.definition == rhs.definition + } +} + +public final class InputObjectExtensionDefinition { + public let kind: Kind = .inputObjectExtensionDefinition + public let loc: Location? + public let definition: InputObjectTypeDefinition + + init(loc: Location? = nil, definition: InputObjectTypeDefinition) { + self.loc = loc + self.definition = definition + } +} + +extension InputObjectExtensionDefinition: Equatable { + public static func == (lhs: InputObjectExtensionDefinition, rhs: InputObjectExtensionDefinition) -> Bool { + return lhs.definition == rhs.definition + } +} + public final class DirectiveDefinition { public let kind: Kind = .directiveDefinition public let loc: Location? diff --git a/Sources/GraphQL/Language/Kinds.swift b/Sources/GraphQL/Language/Kinds.swift index e17be574..448db3a7 100644 --- a/Sources/GraphQL/Language/Kinds.swift +++ b/Sources/GraphQL/Language/Kinds.swift @@ -37,4 +37,9 @@ public enum Kind { case typeExtensionDefinition case directiveDefinition case schemaExtensionDefinition + case interfaceExtensionDefinition + case scalarExtensionDefinition + case unionExtensionDefinition + case enumExtensionDefinition + case inputObjectExtensionDefinition } diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 3099c413..8b80f3ef 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -1010,11 +1010,16 @@ func parseExtensionDefinition(lexer: Lexer) throws -> TypeSystemDefinition { switch token.value { case "type": return try parseTypeExtensionDefinition(lexer: lexer) case "schema": return try parseSchemaExtensionDefinition(lexer: lexer) + case "interface": return try parseInterfaceExtensionDefinition(lexer: lexer) + case "scalar": return try parseScalarExtensionDefinition(lexer: lexer) + case "union": return try parseUnionExtensionDefinition(lexer: lexer) + case "enum": return try parseEnumExtensionDefinition(lexer: lexer) + case "input": return try parseInputObjectExtensionDefinition(lexer: lexer) default: throw syntaxError( source: lexer.source, position: token.start, - description: "expected schema or type after extend" + description: "expected schema or type or interface or scalar or union or enum or input after extend" ) } } @@ -1052,6 +1057,71 @@ func parseSchemaExtensionDefinition(lexer: Lexer) throws -> SchemaExtensionDefin ) } +/** + * InterfaceExtensionDefinition: extend InterfaceTypeDefinition + */ +func parseInterfaceExtensionDefinition(lexer: Lexer) throws -> InterfaceExtensionDefinition { + let start = lexer.token + try expectKeyword(lexer: lexer, value: "extend") + let interfaceDefinition = try parseInterfaceTypeDefinition(lexer: lexer) + return InterfaceExtensionDefinition( + loc: loc(lexer: lexer, startToken: start), + definition: interfaceDefinition + ) +} + +/** + * ScalarExtensionDefinition: extend InterfaceTypeDefinition + */ +func parseScalarExtensionDefinition(lexer: Lexer) throws -> ScalarExtensionDefinition { + let start = lexer.token + try expectKeyword(lexer: lexer, value: "extend") + let scalarDefinition = try parseScalarTypeDefinition(lexer: lexer) + return ScalarExtensionDefinition( + loc: loc(lexer: lexer, startToken: start), + definition: scalarDefinition + ) +} + +/** + * UnionExtensionDefinition: extend UnionTypeDefinition + */ +func parseUnionExtensionDefinition(lexer: Lexer) throws -> UnionExtensionDefinition { + let start = lexer.token + try expectKeyword(lexer: lexer, value: "extend") + let definition = try parseUnionTypeDefinition(lexer: lexer) + return UnionExtensionDefinition( + loc: loc(lexer: lexer, startToken: start), + definition: definition + ) +} + +/** + * EnumExtensionDefinition: extend EnumTypeDefinition + */ +func parseEnumExtensionDefinition(lexer: Lexer) throws -> EnumExtensionDefinition { + let start = lexer.token + try expectKeyword(lexer: lexer, value: "extend") + let definition = try parseEnumTypeDefinition(lexer: lexer) + return EnumExtensionDefinition( + loc: loc(lexer: lexer, startToken: start), + definition: definition + ) +} + +/** + * InputObjectExtensionDefinition: extend InputObjectTypeDefinition + */ +func parseInputObjectExtensionDefinition(lexer: Lexer) throws -> InputObjectExtensionDefinition { + let start = lexer.token + try expectKeyword(lexer: lexer, value: "extend") + let definition = try parseInputObjectTypeDefinition(lexer: lexer) + return InputObjectExtensionDefinition( + loc: loc(lexer: lexer, startToken: start), + definition: definition + ) +} + /** * DirectiveDefinition : * - directive @ Name ArgumentsDefinition? on DirectiveLocations From 142d7dd77432cbcfdf1649bf30c31251c9ae878e Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:12:20 -0800 Subject: [PATCH 04/13] support undefined types --- Sources/GraphQL/Language/Parser.swift | 200 +++++++++++++++++--------- 1 file changed, 133 insertions(+), 67 deletions(-) diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 8b80f3ef..f779fa4e 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -771,6 +771,7 @@ func parseScalarTypeDefinition(lexer: Lexer) throws -> ScalarTypeDefinition { /** * ObjectTypeDefinition : * - type Name ImplementsInterfaces? Directives? { FieldDefinition+ } + * - type Name ImplementsInterfaces? Directives? */ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let start = lexer.token @@ -779,20 +780,32 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let fields = try any( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseFieldDefinition - ) - return ObjectTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - interfaces: interfaces, - directives: directives, - fields: fields - ) + + do { + let fields = try any( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseFieldDefinition + ) + return ObjectTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + interfaces: interfaces, + directives: directives, + fields: fields + ) + } catch { + return ObjectTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + interfaces: interfaces, + directives: directives, + fields: [] + ) + } } /** @@ -879,7 +892,9 @@ func parseInputValueDef(lexer: Lexer) throws -> InputValueDefinition { } /** - * InterfaceTypeDefinition : interface Name Directives? { FieldDefinition+ } + * InterfaceTypeDefinition : + * - interface Name Directives? { FieldDefinition+ } + * - interface Name Directives? */ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinition { let start = lexer.token @@ -888,24 +903,38 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let fields = try any( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseFieldDefinition - ) - return InterfaceTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - interfaces: interfaces, - directives: directives, - fields: fields - ) + + do { + let fields = try any( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseFieldDefinition + ) + return InterfaceTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + interfaces: interfaces, + directives: directives, + fields: fields + ) + } catch { + return InterfaceTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + interfaces: interfaces, + directives: directives, + fields: [] + ) + } } /** - * UnionTypeDefinition : union Name Directives? = UnionMembers + * UnionTypeDefinition : + * - union Name Directives? = UnionMembers + * - union Name Directives? */ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { let start = lexer.token @@ -913,15 +942,26 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { try expectKeyword(lexer: lexer, value: "union") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - try expect(lexer: lexer, kind: .equals) - let types = try parseUnionMembers(lexer: lexer) - return UnionTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - types: types - ) + + do { + try expect(lexer: lexer, kind: .equals) + let types = try parseUnionMembers(lexer: lexer) + return UnionTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + types: types + ) + } catch { + return UnionTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + types: [] + ) + } } /** @@ -940,7 +980,9 @@ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { } /** - * EnumTypeDefinition : enum Name Directives? { EnumValueDefinition+ } + * EnumTypeDefinition : + * - enum Name Directives? { EnumValueDefinition+ } + * - enum Name Directives? */ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { let start = lexer.token @@ -948,19 +990,30 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { try expectKeyword(lexer: lexer, value: "enum") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let values = try many( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseEnumValueDefinition - ) - return EnumTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - values: values - ) + + do { + let values = try many( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseEnumValueDefinition + ) + return EnumTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + values: values + ) + } catch { + return EnumTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + values: [] + ) + } } /** @@ -982,7 +1035,9 @@ func parseEnumValueDefinition(lexer: Lexer) throws -> EnumValueDefinition { } /** - * InputObjectTypeDefinition : input Name Directives? { InputValueDefinition+ } + * InputObjectTypeDefinition : + * - input Name Directives? { InputValueDefinition+ } + * - input Name Directives? */ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefinition { let start = lexer.token @@ -990,19 +1045,30 @@ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefin try expectKeyword(lexer: lexer, value: "input") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let fields = try any( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseInputValueDef - ) - return InputObjectTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - fields: fields - ) + + do { + let fields = try any( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseInputValueDef + ) + return InputObjectTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + fields: fields + ) + } catch { + return InputObjectTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + fields: [] + ) + } } func parseExtensionDefinition(lexer: Lexer) throws -> TypeSystemDefinition { From 8e231586ecf7e67bfc507b1acd4a557964b50321 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:12:59 -0800 Subject: [PATCH 05/13] support for repeatable on directives --- Sources/GraphQL/Language/AST.swift | 5 +++- Sources/GraphQL/Language/Parser.swift | 35 +++++++++++++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index a78313b0..59ca698e 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -1684,19 +1684,22 @@ public final class DirectiveDefinition { public let name: Name public let arguments: [InputValueDefinition] public let locations: [Name] + public let repeatable: Bool init( loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], - locations: [Name] + locations: [Name], + repeatable: Bool = false ) { self.loc = loc self.name = name self.description = description self.arguments = arguments self.locations = locations + self.repeatable = repeatable } } diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index f779fa4e..9b6d1065 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -1190,7 +1190,7 @@ func parseInputObjectExtensionDefinition(lexer: Lexer) throws -> InputObjectExte /** * DirectiveDefinition : - * - directive @ Name ArgumentsDefinition? on DirectiveLocations + * - directive @ Name ArgumentsDefinition? repeatable? on DirectiveLocations */ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { let start = lexer.token @@ -1199,15 +1199,30 @@ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { try expect(lexer: lexer, kind: .at) let name = try parseName(lexer: lexer) let args = try parseArgumentDefs(lexer: lexer) - try expectKeyword(lexer: lexer, value: "on") - let locations = try parseDirectiveLocations(lexer: lexer) - return DirectiveDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - arguments: args, - locations: locations - ) + + do { + try expectKeyword(lexer: lexer, value: "repeatable") + try expectKeyword(lexer: lexer, value: "on") + let locations = try parseDirectiveLocations(lexer: lexer) + return DirectiveDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + arguments: args, + locations: locations, + repeatable: true + ) + } catch { + try expectKeyword(lexer: lexer, value: "on") + let locations = try parseDirectiveLocations(lexer: lexer) + return DirectiveDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + arguments: args, + locations: locations + ) + } } /** From 74eddce7add29503afe9cfef83e79b25f086ee29 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:13:13 -0800 Subject: [PATCH 06/13] add support for optional pipe --- Sources/GraphQL/Language/Parser.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 9b6d1065..24af1ad9 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -968,10 +968,12 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { * UnionMembers : * - NamedType * - UnionMembers | NamedType + * - | UnionMembers | NamedType */ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { var members: [NamedType] = [] + try expectOptional(lexer: lexer, kind: .pipe) repeat { members.append(try parseNamedType(lexer: lexer)) } while try skip(lexer: lexer, kind: .pipe) @@ -1203,6 +1205,7 @@ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { do { try expectKeyword(lexer: lexer, value: "repeatable") try expectKeyword(lexer: lexer, value: "on") + try expectOptional(lexer: lexer, kind: .pipe) let locations = try parseDirectiveLocations(lexer: lexer) return DirectiveDefinition( loc: loc(lexer: lexer, startToken: start), @@ -1214,6 +1217,7 @@ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { ) } catch { try expectKeyword(lexer: lexer, value: "on") + try expectOptional(lexer: lexer, kind: .pipe) let locations = try parseDirectiveLocations(lexer: lexer) return DirectiveDefinition( loc: loc(lexer: lexer, startToken: start), From 608c90bf16872600671ff057efb5a7b2872eddca Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:13:21 -0800 Subject: [PATCH 07/13] add tests --- .../LanguageTests/SchemaParserTests.swift | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index d65ae52e..29697f45 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -844,6 +844,250 @@ class SchemaParserTests: XCTestCase { XCTAssert(result == expected) } + func testUndefinedType() throws { + let source = #"type UndefinedType"# + + let expected = Document( + definitions: [ + ObjectTypeDefinition(name: nameNode("UndefinedType")) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testUndefinedInterfaceType() throws { + let source = #"interface UndefinedInterface"# + + let expected = Document( + definitions: [ + InterfaceTypeDefinition( + name: nameNode("UndefinedInterface"), + fields: []) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testInterfaceExtensionType() throws { + let source = #"extend interface Bar @onInterface"# + + let expected = Document( + definitions: [ + InterfaceExtensionDefinition( + definition: InterfaceTypeDefinition( + name: nameNode("Bar"), + directives: [ + Directive(name: nameNode("onInterface")) + ], + fields: [])) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testUnionPipe() throws { + let source = #"union AnnotatedUnionTwo @onUnion = | A | B"# + + let expected = Document( + definitions: [ + UnionTypeDefinition( + name: nameNode("AnnotatedUnionTwo"), + directives: [ + Directive(name: nameNode("onUnion")) + ], + types: [ + NamedType(name: nameNode("A")), + NamedType(name: nameNode("B")), + ]) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testExtendScalar() throws { + let source = #"extend scalar CustomScalar @onScalar"# + + let expected = Document( + definitions: [ + ScalarExtensionDefinition( + definition: ScalarTypeDefinition( + name: nameNode("CustomScalar"), + directives: [ + Directive(name: nameNode("onScalar")) + ] + ) + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testUndefinedUnion() throws { + let source = #"union UndefinedUnion"# + + let expected = Document( + definitions: [ + UnionTypeDefinition( + name: nameNode("UndefinedUnion"), + types: [] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testExtendUnion() throws { + let source = #"extend union Feed = Photo | Video"# + + let expected = Document( + definitions: [ + UnionExtensionDefinition( + definition: UnionTypeDefinition( + name: nameNode("Feed"), + types: [ + namedTypeNode("Photo"), + namedTypeNode("Video"), + ] + ) + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testUndefinedEnum() throws { + let source = #"enum UndefinedEnum"# + + let expected = Document( + definitions: [ + EnumTypeDefinition( + name: nameNode("UndefinedEnum"), + values: [] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testEnumExtension() throws { + let source = #"extend enum Site @onEnum"# + + let expected = Document( + definitions: [ + EnumExtensionDefinition( + definition: EnumTypeDefinition( + name: nameNode("Site"), + directives: [ + Directive(name: nameNode("onEnum")) + ], + values: [] + ) + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testUndefinedInput() throws { + let source = #"input UndefinedInput"# + + let expected = Document( + definitions: [ + InputObjectTypeDefinition( + name: nameNode("UndefinedInput"), + fields: [] + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testInputExtension() throws { + let source = #"extend input InputType"# + + let expected = Document( + definitions: [ + InputObjectExtensionDefinition( + definition: InputObjectTypeDefinition( + name: nameNode("InputType"), + fields: [] + ) + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testDirectivePipe() throws { + let source = """ + directive @include2 on + | FIELD + | FRAGMENT_SPREAD + | INLINE_FRAGMENT + """ + + let expected = Document( + definitions: [ + DirectiveDefinition( + name: nameNode("include2"), + locations: [ + nameNode("FIELD"), + nameNode("FRAGMENT_SPREAD"), + nameNode("INLINE_FRAGMENT") + ]) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + + func testDirectiveRepeatable() throws { + let source = """ + directive @myRepeatableDir repeatable on + | OBJECT + | INTERFACE + """ + + let expected = Document( + definitions: [ + DirectiveDefinition( + name: nameNode("myRepeatableDir"), + locations: [ + nameNode("OBJECT"), + nameNode("INTERFACE"), + ], + repeatable: true + ) + ] + ) + + let result = try parse(source: source) + XCTAssert(result == expected) + } + func testKitchenSink() throws { guard let url = Bundle.module.url( From cf592ad758833b5b7f94e0f5e89be0d0f4112cfb Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Tue, 28 Feb 2023 01:20:15 -0800 Subject: [PATCH 08/13] run swiftformat --- Sources/GraphQL/Language/AST.swift | 10 ++++- Sources/GraphQL/Language/Parser.swift | 5 ++- .../LanguageTests/ParserTests.swift | 4 +- .../LanguageTests/SchemaParserTests.swift | 41 +++++++++++-------- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index 59ca698e..22da2a76 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -1604,7 +1604,10 @@ public final class InterfaceExtensionDefinition { } extension InterfaceExtensionDefinition: Equatable { - public static func == (lhs: InterfaceExtensionDefinition, rhs: InterfaceExtensionDefinition) -> Bool { + public static func == ( + lhs: InterfaceExtensionDefinition, + rhs: InterfaceExtensionDefinition + ) -> Bool { return lhs.definition == rhs.definition } } @@ -1672,7 +1675,10 @@ public final class InputObjectExtensionDefinition { } extension InputObjectExtensionDefinition: Equatable { - public static func == (lhs: InputObjectExtensionDefinition, rhs: InputObjectExtensionDefinition) -> Bool { + public static func == ( + lhs: InputObjectExtensionDefinition, + rhs: InputObjectExtensionDefinition + ) -> Bool { return lhs.definition == rhs.definition } } diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 24af1ad9..821a22b6 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -822,7 +822,8 @@ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { try expectOptional(lexer: lexer, kind: .amp) repeat { types.append(try parseNamedType(lexer: lexer)) - } while try expectOptional(lexer: lexer, kind: .amp) != nil || peek(lexer: lexer, kind: .name) + } while try expectOptional(lexer: lexer, kind: .amp) != nil || + peek(lexer: lexer, kind: .name) } return types @@ -942,7 +943,7 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { try expectKeyword(lexer: lexer, value: "union") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - + do { try expect(lexer: lexer, kind: .equals) let types = try parseUnionMembers(lexer: lexer) diff --git a/Tests/GraphQLTests/LanguageTests/ParserTests.swift b/Tests/GraphQLTests/LanguageTests/ParserTests.swift index 4a9857ff..6eb19fab 100644 --- a/Tests/GraphQLTests/LanguageTests/ParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/ParserTests.swift @@ -121,7 +121,9 @@ class ParserTests: XCTestCase { )) } - XCTAssertThrowsError(try parse(source: "type WithImplementsWithTrailingAmp implements AInterface & {}")) { error in + XCTAssertThrowsError( + try parse(source: "type WithImplementsWithTrailingAmp implements AInterface & {}") + ) { error in guard let error = error as? GraphQLError else { return XCTFail() } diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index 29697f45..332afe1a 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -849,7 +849,7 @@ class SchemaParserTests: XCTestCase { let expected = Document( definitions: [ - ObjectTypeDefinition(name: nameNode("UndefinedType")) + ObjectTypeDefinition(name: nameNode("UndefinedType")), ] ) @@ -864,7 +864,8 @@ class SchemaParserTests: XCTestCase { definitions: [ InterfaceTypeDefinition( name: nameNode("UndefinedInterface"), - fields: []) + fields: [] + ), ] ) @@ -881,9 +882,11 @@ class SchemaParserTests: XCTestCase { definition: InterfaceTypeDefinition( name: nameNode("Bar"), directives: [ - Directive(name: nameNode("onInterface")) + Directive(name: nameNode("onInterface")), ], - fields: [])) + fields: [] + ) + ), ] ) @@ -899,12 +902,13 @@ class SchemaParserTests: XCTestCase { UnionTypeDefinition( name: nameNode("AnnotatedUnionTwo"), directives: [ - Directive(name: nameNode("onUnion")) + Directive(name: nameNode("onUnion")), ], types: [ NamedType(name: nameNode("A")), NamedType(name: nameNode("B")), - ]) + ] + ), ] ) @@ -921,10 +925,10 @@ class SchemaParserTests: XCTestCase { definition: ScalarTypeDefinition( name: nameNode("CustomScalar"), directives: [ - Directive(name: nameNode("onScalar")) + Directive(name: nameNode("onScalar")), ] ) - ) + ), ] ) @@ -940,7 +944,7 @@ class SchemaParserTests: XCTestCase { UnionTypeDefinition( name: nameNode("UndefinedUnion"), types: [] - ) + ), ] ) @@ -961,7 +965,7 @@ class SchemaParserTests: XCTestCase { namedTypeNode("Video"), ] ) - ) + ), ] ) @@ -977,7 +981,7 @@ class SchemaParserTests: XCTestCase { EnumTypeDefinition( name: nameNode("UndefinedEnum"), values: [] - ) + ), ] ) @@ -994,11 +998,11 @@ class SchemaParserTests: XCTestCase { definition: EnumTypeDefinition( name: nameNode("Site"), directives: [ - Directive(name: nameNode("onEnum")) + Directive(name: nameNode("onEnum")), ], values: [] ) - ) + ), ] ) @@ -1014,7 +1018,7 @@ class SchemaParserTests: XCTestCase { InputObjectTypeDefinition( name: nameNode("UndefinedInput"), fields: [] - ) + ), ] ) @@ -1032,7 +1036,7 @@ class SchemaParserTests: XCTestCase { name: nameNode("InputType"), fields: [] ) - ) + ), ] ) @@ -1055,8 +1059,9 @@ class SchemaParserTests: XCTestCase { locations: [ nameNode("FIELD"), nameNode("FRAGMENT_SPREAD"), - nameNode("INLINE_FRAGMENT") - ]) + nameNode("INLINE_FRAGMENT"), + ] + ), ] ) @@ -1080,7 +1085,7 @@ class SchemaParserTests: XCTestCase { nameNode("INTERFACE"), ], repeatable: true - ) + ), ] ) From a9927750af33f5cedf8bedfb060a1da379d6f5c4 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Sat, 4 Mar 2023 22:09:15 -0800 Subject: [PATCH 09/13] update test --- Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index 332afe1a..45d29f4e 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -214,7 +214,7 @@ class SchemaParserTests: XCTestCase { } func testSimpleTypeInheritingMultipleInterfaces() throws { - let source = "type Hello implements Wo, rld { }" + let source = "type Hello implements Wo & rld { }" let expected = Document( definitions: [ From 1aacac484fc8f360101d841dc3aa12f170dd5697 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Sat, 4 Mar 2023 22:09:25 -0800 Subject: [PATCH 10/13] add delimited many --- Sources/GraphQL/Language/Lexer.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/GraphQL/Language/Lexer.swift b/Sources/GraphQL/Language/Lexer.swift index 43acbbe3..3523e060 100644 --- a/Sources/GraphQL/Language/Lexer.swift +++ b/Sources/GraphQL/Language/Lexer.swift @@ -46,6 +46,22 @@ func advanceLexer(lexer: Lexer) throws -> Token { return token } +/** + * Returns a non-empty list of parse nodes, determined by the parseFn. + * This list may begin with a lex token of delimiterKind followed by items separated by lex tokens of tokenKind. + * Advances the parser to the next lex token after last item in the list. + */ +func delimitedMany(lexer: Lexer, kind: Token.Kind, parseFn: (Lexer) throws -> T) throws -> [T] { + _ = try expectOptional(lexer: lexer, kind: kind) + + var nodes: [T] = [] + repeat { + try nodes.append(parseFn(lexer)) + } while (try expectOptional(lexer: lexer, kind: kind) != nil) + + return nodes +} + /** * The return type of createLexer. */ From 4421aa5ba62a3708f438f5c2bafad6d73bfacd03 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Sat, 4 Mar 2023 22:10:01 -0800 Subject: [PATCH 11/13] add expectOptionalKeyword and optionalMany --- Sources/GraphQL/Language/Parser.swift | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 821a22b6..29d1f885 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -1342,6 +1342,20 @@ func expectKeyword(lexer: Lexer, value: String) throws -> Token { return token } +/** +* If the next token is a given keyword, return "true" after advancing the lexer. +* Otherwise, do not change the parser state and return "false". +*/ +@discardableResult +func expectOptionalKeyword(lexer: Lexer, value: String) throws -> Bool { + let token = lexer.token + guard token.kind == .name && token.value == value else { + return false + } + try lexer.advance() + return true +} + /** * Helper func for creating an error when an unexpected lexed token * is encountered. @@ -1377,6 +1391,23 @@ func any( return nodes } +/** +* Returns a list of parse nodes, determined by the parseFn. +* It can be empty only if open token is missing otherwise it will always return non-empty list +* that begins with a lex token of openKind and ends with a lex token of closeKind. +* Advances the parser to the next lex token after the closing token. +*/ +func optionalMany(lexer: Lexer, openKind: Token.Kind, closeKind: Token.Kind, parse: (Lexer) throws -> T) throws -> [T] { + guard try expectOptional(lexer: lexer, kind: openKind) != nil else { + return [] + } + var nodes: [T] = [] + while try !skip(lexer: lexer, kind: closeKind) { + nodes.append(try parse(lexer)) + } + return nodes +} + /** * Returns a non-empty list of parse nodes, determined by * the parseFn. This list begins with a lex token of openKind From 0ea4917b9374b3917ffa8fceb9f9422b4e2a7646 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Sat, 4 Mar 2023 22:10:16 -0800 Subject: [PATCH 12/13] update parser to be closer to graphql-js --- Sources/GraphQL/Language/Parser.swift | 243 +++++++------------------- 1 file changed, 64 insertions(+), 179 deletions(-) diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 29d1f885..01b7c9ab 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -780,32 +780,15 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - - do { - let fields = try any( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseFieldDefinition - ) - return ObjectTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - interfaces: interfaces, - directives: directives, - fields: fields - ) - } catch { - return ObjectTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - interfaces: interfaces, - directives: directives, - fields: [] - ) - } + let fields = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseFieldDefinition) + return ObjectTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + interfaces: interfaces, + directives: directives, + fields: fields + ) } /** @@ -814,19 +797,9 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { * - ImplementsInterfaces & NamedType */ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { - var types: [NamedType] = [] - - if lexer.token.value == "implements" { - try lexer.advance() - - try expectOptional(lexer: lexer, kind: .amp) - repeat { - types.append(try parseNamedType(lexer: lexer)) - } while try expectOptional(lexer: lexer, kind: .amp) != nil || - peek(lexer: lexer, kind: .name) - } - - return types + try expectOptionalKeyword(lexer: lexer, value: "implements") + ? delimitedMany(lexer: lexer, kind: .amp, parseFn: parseNamedType) + : [] } /** @@ -904,32 +877,15 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - - do { - let fields = try any( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseFieldDefinition - ) - return InterfaceTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - interfaces: interfaces, - directives: directives, - fields: fields - ) - } catch { - return InterfaceTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - interfaces: interfaces, - directives: directives, - fields: [] - ) - } + let fields = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseFieldDefinition) + return InterfaceTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + interfaces: interfaces, + directives: directives, + fields: fields + ) } /** @@ -943,43 +899,24 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { try expectKeyword(lexer: lexer, value: "union") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - - do { - try expect(lexer: lexer, kind: .equals) - let types = try parseUnionMembers(lexer: lexer) - return UnionTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - types: types - ) - } catch { - return UnionTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - types: [] - ) - } + return UnionTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + types: try parseUnionMembers(lexer: lexer) + ) } /** * UnionMembers : - * - NamedType - * - UnionMembers | NamedType - * - | UnionMembers | NamedType + * - = |? NamedType + * - UnionMemberTypes | NamedType */ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { - var members: [NamedType] = [] - - try expectOptional(lexer: lexer, kind: .pipe) - repeat { - members.append(try parseNamedType(lexer: lexer)) - } while try skip(lexer: lexer, kind: .pipe) - - return members + try expectOptional(lexer: lexer, kind: .equals) != nil + ? delimitedMany(lexer: lexer, kind: .pipe, parseFn: parseNamedType) + : [] } /** @@ -993,30 +930,14 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { try expectKeyword(lexer: lexer, value: "enum") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - - do { - let values = try many( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseEnumValueDefinition - ) - return EnumTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - values: values - ) - } catch { - return EnumTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - values: [] - ) - } + let values = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseEnumValueDefinition) + return EnumTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + values: values + ) } /** @@ -1048,30 +969,14 @@ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefin try expectKeyword(lexer: lexer, value: "input") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - - do { - let fields = try any( - lexer: lexer, - openKind: .openingBrace, - closeKind: .closingBrace, - parse: parseInputValueDef - ) - return InputObjectTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - fields: fields - ) - } catch { - return InputObjectTypeDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - directives: directives, - fields: [] - ) - } + let fields = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseInputValueDef) + return InputObjectTypeDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + directives: directives, + fields: fields + ) } func parseExtensionDefinition(lexer: Lexer) throws -> TypeSystemDefinition { @@ -1202,47 +1107,27 @@ func parseDirectiveDefinition(lexer: Lexer) throws -> DirectiveDefinition { try expect(lexer: lexer, kind: .at) let name = try parseName(lexer: lexer) let args = try parseArgumentDefs(lexer: lexer) - - do { - try expectKeyword(lexer: lexer, value: "repeatable") - try expectKeyword(lexer: lexer, value: "on") - try expectOptional(lexer: lexer, kind: .pipe) - let locations = try parseDirectiveLocations(lexer: lexer) - return DirectiveDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - arguments: args, - locations: locations, - repeatable: true - ) - } catch { - try expectKeyword(lexer: lexer, value: "on") - try expectOptional(lexer: lexer, kind: .pipe) - let locations = try parseDirectiveLocations(lexer: lexer) - return DirectiveDefinition( - loc: loc(lexer: lexer, startToken: start), - description: description, - name: name, - arguments: args, - locations: locations - ) - } + let repeatable = try expectOptionalKeyword(lexer: lexer, value: "repeatable") + try expectKeyword(lexer: lexer, value: "on") + try expectOptional(lexer: lexer, kind: .pipe) + let locations = try parseDirectiveLocations(lexer: lexer) + return DirectiveDefinition( + loc: loc(lexer: lexer, startToken: start), + description: description, + name: name, + arguments: args, + locations: locations, + repeatable: repeatable + ) } /** * DirectiveLocations : - * - Name - * - DirectiveLocations | Name + * - |? DirectiveLocation + * - DirectiveLocations | DirectiveLocation */ func parseDirectiveLocations(lexer: Lexer) throws -> [Name] { - var locations: [Name] = [] - - repeat { - locations.append(try parseName(lexer: lexer)) - } while try skip(lexer: lexer, kind: .pipe) - - return locations + try delimitedMany(lexer: lexer, kind: .pipe, parseFn: parseName) } // Core parsing utility funcs From 98d563eeaaf9218a7e3986aec99412cf3f64fd85 Mon Sep 17 00:00:00 2001 From: Sami Suteria Date: Sat, 4 Mar 2023 22:10:54 -0800 Subject: [PATCH 13/13] swiftformat --- Sources/GraphQL/Language/Lexer.swift | 10 ++--- Sources/GraphQL/Language/Parser.swift | 61 +++++++++++++++++++-------- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/Sources/GraphQL/Language/Lexer.swift b/Sources/GraphQL/Language/Lexer.swift index 3523e060..415c0cf3 100644 --- a/Sources/GraphQL/Language/Lexer.swift +++ b/Sources/GraphQL/Language/Lexer.swift @@ -47,17 +47,17 @@ func advanceLexer(lexer: Lexer) throws -> Token { } /** - * Returns a non-empty list of parse nodes, determined by the parseFn. - * This list may begin with a lex token of delimiterKind followed by items separated by lex tokens of tokenKind. - * Advances the parser to the next lex token after last item in the list. - */ + * Returns a non-empty list of parse nodes, determined by the parseFn. + * This list may begin with a lex token of delimiterKind followed by items separated by lex tokens of tokenKind. + * Advances the parser to the next lex token after last item in the list. + */ func delimitedMany(lexer: Lexer, kind: Token.Kind, parseFn: (Lexer) throws -> T) throws -> [T] { _ = try expectOptional(lexer: lexer, kind: kind) var nodes: [T] = [] repeat { try nodes.append(parseFn(lexer)) - } while (try expectOptional(lexer: lexer, kind: kind) != nil) + } while try expectOptional(lexer: lexer, kind: kind) != nil return nodes } diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 01b7c9ab..da263553 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -780,7 +780,12 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let fields = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseFieldDefinition) + let fields = try optionalMany( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseFieldDefinition + ) return ObjectTypeDefinition( loc: loc(lexer: lexer, startToken: start), description: description, @@ -798,8 +803,8 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition { */ func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] { try expectOptionalKeyword(lexer: lexer, value: "implements") - ? delimitedMany(lexer: lexer, kind: .amp, parseFn: parseNamedType) - : [] + ? delimitedMany(lexer: lexer, kind: .amp, parseFn: parseNamedType) + : [] } /** @@ -877,7 +882,12 @@ func parseInterfaceTypeDefinition(lexer: Lexer) throws -> InterfaceTypeDefinitio let name = try parseName(lexer: lexer) let interfaces = try parseImplementsInterfaces(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let fields = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseFieldDefinition) + let fields = try optionalMany( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseFieldDefinition + ) return InterfaceTypeDefinition( loc: loc(lexer: lexer, startToken: start), description: description, @@ -915,8 +925,8 @@ func parseUnionTypeDefinition(lexer: Lexer) throws -> UnionTypeDefinition { */ func parseUnionMembers(lexer: Lexer) throws -> [NamedType] { try expectOptional(lexer: lexer, kind: .equals) != nil - ? delimitedMany(lexer: lexer, kind: .pipe, parseFn: parseNamedType) - : [] + ? delimitedMany(lexer: lexer, kind: .pipe, parseFn: parseNamedType) + : [] } /** @@ -930,7 +940,12 @@ func parseEnumTypeDefinition(lexer: Lexer) throws -> EnumTypeDefinition { try expectKeyword(lexer: lexer, value: "enum") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let values = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseEnumValueDefinition) + let values = try optionalMany( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseEnumValueDefinition + ) return EnumTypeDefinition( loc: loc(lexer: lexer, startToken: start), description: description, @@ -969,7 +984,12 @@ func parseInputObjectTypeDefinition(lexer: Lexer) throws -> InputObjectTypeDefin try expectKeyword(lexer: lexer, value: "input") let name = try parseName(lexer: lexer) let directives = try parseDirectives(lexer: lexer) - let fields = try optionalMany(lexer: lexer, openKind: .openingBrace, closeKind: .closingBrace, parse: parseInputValueDef) + let fields = try optionalMany( + lexer: lexer, + openKind: .openingBrace, + closeKind: .closingBrace, + parse: parseInputValueDef + ) return InputObjectTypeDefinition( loc: loc(lexer: lexer, startToken: start), description: description, @@ -1228,13 +1248,13 @@ func expectKeyword(lexer: Lexer, value: String) throws -> Token { } /** -* If the next token is a given keyword, return "true" after advancing the lexer. -* Otherwise, do not change the parser state and return "false". -*/ + * If the next token is a given keyword, return "true" after advancing the lexer. + * Otherwise, do not change the parser state and return "false". + */ @discardableResult func expectOptionalKeyword(lexer: Lexer, value: String) throws -> Bool { let token = lexer.token - guard token.kind == .name && token.value == value else { + guard token.kind == .name, token.value == value else { return false } try lexer.advance() @@ -1277,12 +1297,17 @@ func any( } /** -* Returns a list of parse nodes, determined by the parseFn. -* It can be empty only if open token is missing otherwise it will always return non-empty list -* that begins with a lex token of openKind and ends with a lex token of closeKind. -* Advances the parser to the next lex token after the closing token. -*/ -func optionalMany(lexer: Lexer, openKind: Token.Kind, closeKind: Token.Kind, parse: (Lexer) throws -> T) throws -> [T] { + * Returns a list of parse nodes, determined by the parseFn. + * It can be empty only if open token is missing otherwise it will always return non-empty list + * that begins with a lex token of openKind and ends with a lex token of closeKind. + * Advances the parser to the next lex token after the closing token. + */ +func optionalMany( + lexer: Lexer, + openKind: Token.Kind, + closeKind: Token.Kind, + parse: (Lexer) throws -> T +) throws -> [T] { guard try expectOptional(lexer: lexer, kind: openKind) != nil else { return [] }