Skip to content

Fixes Parser & Printer for SDL Functionality #138

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

Merged
Merged
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
60 changes: 60 additions & 0 deletions Sources/GraphQL/Language/BlockString.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation

/**
* Print a block string in the indented block form by adding a leading and
* trailing blank line. However, if a block string starts with whitespace and is
* a single-line, adding a leading blank line would strip that whitespace.
*
* @internal
*/
func printBlockString(
_ value: String,
minimize: Bool = false
) -> String {
let escapedValue = value.replacingOccurrences(of: "\"\"\"", with: "\\\"\"\"")

// Expand a block string's raw value into independent lines.
let lines = splitLines(string: escapedValue)
let isSingleLine = lines.count == 1

// If common indentation is found we can fix some of those cases by adding leading new line
let forceLeadingNewLine =
lines.count > 1 &&
lines[1 ... (lines.count - 1)].allSatisfy { line in
line.count == 0 || isWhiteSpace(line.charCode(at: 0))
}

// Trailing triple quotes just looks confusing but doesn't force trailing new line
let hasTrailingTripleQuotes = escapedValue.hasSuffix("\\\"\"\"")

// Trailing quote (single or double) or slash forces trailing new line
let hasTrailingQuote = value.hasSuffix("\"") && !hasTrailingTripleQuotes
let hasTrailingSlash = value.hasSuffix("\\")
let forceTrailingNewline = hasTrailingQuote || hasTrailingSlash

let printAsMultipleLines =
!minimize &&
// add leading and trailing new lines only if it improves readability
(
!isSingleLine ||
value.count > 70 ||
forceTrailingNewline ||
forceLeadingNewLine ||
hasTrailingTripleQuotes
)

var result = ""

// Format a multi-line block quote to account for leading space.
let skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCode(at: 0))
if (printAsMultipleLines && !skipLeadingNewLine) || forceLeadingNewLine {
result += "\n"
}

result += escapedValue
if printAsMultipleLines || forceTrailingNewline {
result += "\n"
}

return "\"\"\"" + result + "\"\"\""
}
14 changes: 14 additions & 0 deletions Sources/GraphQL/Language/CharacterClasses.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* ```
* WhiteSpace ::
* - "Horizontal Tab (U+0009)"
* - "Space (U+0020)"
* ```
* @internal
*/
func isWhiteSpace(_ code: UInt8?) -> Bool {
guard let code = code else {
return false
}
return code == 0x0009 || code == 0x0020
}
228 changes: 159 additions & 69 deletions Sources/GraphQL/Language/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,24 +173,45 @@ func parseDefinition(lexer: Lexer) throws -> Definition {
return try parseOperationDefinition(lexer: lexer)
}

if peek(lexer: lexer, kind: .name) {
guard let value = lexer.token.value else {
// Many definitions begin with a description and require a lookahead.
let hasDescription = peekDescription(lexer: lexer)
let keywordToken = hasDescription
? try lexer.lookahead()
: lexer.token

if keywordToken.kind == .name {
guard let value = keywordToken.value else {
throw GraphQLError(message: "Expected name token to have value: \(lexer.token)")
}

switch value {
case "query", "mutation", "subscription":
return try parseOperationDefinition(lexer: lexer)
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": return try parseSchemaDefinition(lexer: lexer)
case "scalar": return try parseScalarTypeDefinition(lexer: lexer)
case "type": return try parseObjectTypeDefinition(lexer: lexer)
case "interface": return try parseInterfaceTypeDefinition(lexer: lexer)
case "union": return try parseUnionTypeDefinition(lexer: lexer)
case "enum": return try parseEnumTypeDefinition(lexer: lexer)
case "input": return try parseInputObjectTypeDefinition(lexer: lexer)
case "directive": return try parseDirectiveDefinition(lexer: lexer)
default:
break
if hasDescription {
throw syntaxError(
source: lexer.source,
position: lexer.token.start,
description: "Unexpected description, descriptions are supported only on type definitions."
)
}
switch value {
case "query", "mutation", "subscription":
return try parseOperationDefinition(lexer: lexer)
case "fragment":
return try parseFragmentDefinition(lexer: lexer)
case "extend":
return try parseExtensionDefinition(lexer: lexer)
default:
break
}
}
} else if peekDescription(lexer: lexer) {
return try parseTypeSystemDefinition(lexer: lexer)
}

throw unexpected(lexer: lexer)
Expand Down Expand Up @@ -675,47 +696,6 @@ func parseNamedType(lexer: Lexer) throws -> NamedType {

// Implements the parsing rules in the Type Definition section.

/**
* TypeSystemDefinition :
* - SchemaDefinition
* - TypeDefinition
* - TypeExtensionDefinition
* - DirectiveDefinition
*
* TypeDefinition :
* - ScalarTypeDefinition
* - ObjectTypeDefinition
* - InterfaceTypeDefinition
* - UnionTypeDefinition
* - EnumTypeDefinition
* - InputObjectTypeDefinition
*/
func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition {
let keywordToken = peekDescription(lexer: lexer)
? try lexer.lookahead()
: lexer.token

if keywordToken.kind == .name {
guard let value = keywordToken.value else {
throw GraphQLError(message: "Expected keyword token to have value: \(keywordToken)")
}
switch value {
case "schema": return try parseSchemaDefinition(lexer: lexer)
case "scalar": return try parseScalarTypeDefinition(lexer: lexer)
case "type": return try parseObjectTypeDefinition(lexer: lexer)
case "interface": return try parseInterfaceTypeDefinition(lexer: lexer)
case "union": return try parseUnionTypeDefinition(lexer: lexer)
case "enum": return try parseEnumTypeDefinition(lexer: lexer)
case "input": return try parseInputObjectTypeDefinition(lexer: lexer)
case "extend": return try parseExtensionDefinition(lexer: lexer)
case "directive": return try parseDirectiveDefinition(lexer: lexer)
default: break
}
}

throw unexpected(lexer: lexer, atToken: keywordToken)
}

/**
* SchemaDefinition : schema Directives? { OperationTypeDefinition+ }
*
Expand Down Expand Up @@ -1025,10 +1005,31 @@ func parseExtensionDefinition(lexer: Lexer) throws -> TypeSystemDefinition {
func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let definition = try parseObjectTypeDefinition(lexer: lexer)
try expectKeyword(lexer: lexer, value: "type")
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
)
if
interfaces.isEmpty,
directives.isEmpty,
fields.isEmpty
{
throw unexpected(lexer: lexer)
}
return TypeExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: definition
definition: ObjectTypeDefinition(
name: name,
interfaces: interfaces,
directives: directives,
fields: fields
)
)
}

Expand All @@ -1038,16 +1039,24 @@ func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinitio
func parseSchemaExtensionDefinition(lexer: Lexer) throws -> SchemaExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let description = try parseDescription(lexer: lexer)
try expectKeyword(lexer: lexer, value: "schema")
let directives = try parseDirectives(lexer: lexer)
let operationTypes = try optionalMany(
lexer: lexer,
openKind: .openingBrace,
closeKind: .closingBrace,
parse: parseOperationTypeDefinition
)
if directives.isEmpty, operationTypes.isEmpty {
throw unexpected(lexer: lexer)
}
return SchemaExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: SchemaDefinition(
loc: loc(lexer: lexer, startToken: start),
description: description,
description: nil,
directives: directives,
operationTypes: []
operationTypes: operationTypes
)
)
}
Expand All @@ -1058,10 +1067,31 @@ func parseSchemaExtensionDefinition(lexer: Lexer) throws -> SchemaExtensionDefin
func parseInterfaceExtensionDefinition(lexer: Lexer) throws -> InterfaceExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let interfaceDefinition = try parseInterfaceTypeDefinition(lexer: lexer)
try expectKeyword(lexer: lexer, value: "interface")
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
)
if
interfaces.isEmpty,
directives.isEmpty,
fields.isEmpty
{
throw unexpected(lexer: lexer)
}
return InterfaceExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: interfaceDefinition
definition: InterfaceTypeDefinition(
name: name,
interfaces: interfaces,
directives: directives,
fields: fields
)
)
}

Expand All @@ -1071,10 +1101,18 @@ func parseInterfaceExtensionDefinition(lexer: Lexer) throws -> InterfaceExtensio
func parseScalarExtensionDefinition(lexer: Lexer) throws -> ScalarExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let scalarDefinition = try parseScalarTypeDefinition(lexer: lexer)
try expectKeyword(lexer: lexer, value: "scalar")
let name = try parseName(lexer: lexer)
let directives = try parseDirectives(lexer: lexer)
if directives.isEmpty {
throw unexpected(lexer: lexer)
}
return ScalarExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: scalarDefinition
definition: ScalarTypeDefinition(
name: name,
directives: directives
)
)
}

Expand All @@ -1084,10 +1122,24 @@ func parseScalarExtensionDefinition(lexer: Lexer) throws -> ScalarExtensionDefin
func parseUnionExtensionDefinition(lexer: Lexer) throws -> UnionExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let definition = try parseUnionTypeDefinition(lexer: lexer)
try expectKeyword(lexer: lexer, value: "union")
let name = try parseName(lexer: lexer)
let directives = try parseDirectives(lexer: lexer)
let types = try parseUnionMembers(lexer: lexer)
if
directives.isEmpty,
types.isEmpty
{
throw unexpected(lexer: lexer)
}
return UnionExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: definition
definition: UnionTypeDefinition(
loc: loc(lexer: lexer, startToken: start),
name: name,
directives: directives,
types: types
)
)
}

Expand All @@ -1097,10 +1149,29 @@ func parseUnionExtensionDefinition(lexer: Lexer) throws -> UnionExtensionDefinit
func parseEnumExtensionDefinition(lexer: Lexer) throws -> EnumExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let definition = try parseEnumTypeDefinition(lexer: lexer)
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
)
if
directives.isEmpty,
values.isEmpty
{
throw unexpected(lexer: lexer)
}
return EnumExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: definition
definition: EnumTypeDefinition(
loc: loc(lexer: lexer, startToken: start),
name: name,
directives: directives,
values: values
)
)
}

Expand All @@ -1110,10 +1181,29 @@ func parseEnumExtensionDefinition(lexer: Lexer) throws -> EnumExtensionDefinitio
func parseInputObjectExtensionDefinition(lexer: Lexer) throws -> InputObjectExtensionDefinition {
let start = lexer.token
try expectKeyword(lexer: lexer, value: "extend")
let definition = try parseInputObjectTypeDefinition(lexer: lexer)
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
)
if
directives.isEmpty,
fields.isEmpty
{
throw unexpected(lexer: lexer)
}
return InputObjectExtensionDefinition(
loc: loc(lexer: lexer, startToken: start),
definition: definition
definition: InputObjectTypeDefinition(
loc: loc(lexer: lexer, startToken: start),
name: name,
directives: directives,
fields: fields
)
)
}

Expand Down
Loading