Skip to content

Support the '&' character for types implementing multiple interfaces #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/GraphQL/Language/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ final public class Token {
case eof = "<EOF>"
case bang = "!"
case dollar = "$"
case amp = "&"
case openingParenthesis = "("
case closingParenthesis = ")"
case spread = "..."
Expand Down
10 changes: 10 additions & 0 deletions Sources/GraphQL/Language/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,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(
Expand Down
25 changes: 22 additions & 3 deletions Sources/GraphQL/Language/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -761,17 +761,22 @@ func parseObjectTypeDefinition(lexer: Lexer) throws -> ObjectTypeDefinition {
}

/**
* ImplementsInterfaces : implements NamedType+
* ImplementsInterfaces :
* - implements &? NamedType
* - ImplementsInterfaces & NamedType
*/
func parseImplementsInterfaces(lexer: Lexer) throws -> [NamedType] {
var types: [NamedType] = []

if lexer.token.value == "implements" {
try lexer.advance()

// Optional leading ampersand
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
// Legacy support for the SDL?
|| peek(lexer: lexer, kind: .name)
}

return types
Expand Down Expand Up @@ -1079,6 +1084,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
Expand Down
29 changes: 29 additions & 0 deletions Tests/GraphQLTests/LanguageTests/ParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,41 @@ class ParserTests : XCTestCase {
"Syntax Error GraphQL (1:39) Unexpected Name \"null\""
))
}

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 {
_ = try parse(source: "{ field(complex: { a: { b: [ $var ] } }) }")
}

func testImplementsInterface() throws {
_ = try parse(source: "type Swallow implements Animal {}")
_ = try parse(source: "type Swallow implements Animal & Bird {}")
_ = try parse(source: "type Swallow implements & Animal & Bird {}")
_ = try parse(source: "interface Bird implements Animal {}")
_ = try parse(source: "interface Bird implements Animal & Lifeform {}")
_ = try parse(source: "interface Bird implements & Animal {}")
}

// it('parses multi-byte characters', async () => {
// // Note: \u0A0A could be naively interpretted as two line-feed chars.
// expect(
Expand Down