Skip to content

Commit

Permalink
Merge pull request #138 from NeedleInAJayStack/fix/sdl-printer
Browse files Browse the repository at this point in the history
Fixes Parser & Printer for SDL Functionality
  • Loading branch information
NeedleInAJayStack committed Dec 26, 2023
2 parents 75cfce2 + 83b2c70 commit 0fee9b5
Show file tree
Hide file tree
Showing 10 changed files with 1,009 additions and 136 deletions.
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

0 comments on commit 0fee9b5

Please sign in to comment.