Skip to content

Commit 0fee9b5

Browse files
Merge pull request #138 from NeedleInAJayStack/fix/sdl-printer
Fixes Parser & Printer for SDL Functionality
2 parents 75cfce2 + 83b2c70 commit 0fee9b5

10 files changed

+1009
-136
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Foundation
2+
3+
/**
4+
* Print a block string in the indented block form by adding a leading and
5+
* trailing blank line. However, if a block string starts with whitespace and is
6+
* a single-line, adding a leading blank line would strip that whitespace.
7+
*
8+
* @internal
9+
*/
10+
func printBlockString(
11+
_ value: String,
12+
minimize: Bool = false
13+
) -> String {
14+
let escapedValue = value.replacingOccurrences(of: "\"\"\"", with: "\\\"\"\"")
15+
16+
// Expand a block string's raw value into independent lines.
17+
let lines = splitLines(string: escapedValue)
18+
let isSingleLine = lines.count == 1
19+
20+
// If common indentation is found we can fix some of those cases by adding leading new line
21+
let forceLeadingNewLine =
22+
lines.count > 1 &&
23+
lines[1 ... (lines.count - 1)].allSatisfy { line in
24+
line.count == 0 || isWhiteSpace(line.charCode(at: 0))
25+
}
26+
27+
// Trailing triple quotes just looks confusing but doesn't force trailing new line
28+
let hasTrailingTripleQuotes = escapedValue.hasSuffix("\\\"\"\"")
29+
30+
// Trailing quote (single or double) or slash forces trailing new line
31+
let hasTrailingQuote = value.hasSuffix("\"") && !hasTrailingTripleQuotes
32+
let hasTrailingSlash = value.hasSuffix("\\")
33+
let forceTrailingNewline = hasTrailingQuote || hasTrailingSlash
34+
35+
let printAsMultipleLines =
36+
!minimize &&
37+
// add leading and trailing new lines only if it improves readability
38+
(
39+
!isSingleLine ||
40+
value.count > 70 ||
41+
forceTrailingNewline ||
42+
forceLeadingNewLine ||
43+
hasTrailingTripleQuotes
44+
)
45+
46+
var result = ""
47+
48+
// Format a multi-line block quote to account for leading space.
49+
let skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCode(at: 0))
50+
if (printAsMultipleLines && !skipLeadingNewLine) || forceLeadingNewLine {
51+
result += "\n"
52+
}
53+
54+
result += escapedValue
55+
if printAsMultipleLines || forceTrailingNewline {
56+
result += "\n"
57+
}
58+
59+
return "\"\"\"" + result + "\"\"\""
60+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* ```
3+
* WhiteSpace ::
4+
* - "Horizontal Tab (U+0009)"
5+
* - "Space (U+0020)"
6+
* ```
7+
* @internal
8+
*/
9+
func isWhiteSpace(_ code: UInt8?) -> Bool {
10+
guard let code = code else {
11+
return false
12+
}
13+
return code == 0x0009 || code == 0x0020
14+
}

Sources/GraphQL/Language/Parser.swift

Lines changed: 159 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -173,24 +173,45 @@ func parseDefinition(lexer: Lexer) throws -> Definition {
173173
return try parseOperationDefinition(lexer: lexer)
174174
}
175175

176-
if peek(lexer: lexer, kind: .name) {
177-
guard let value = lexer.token.value else {
176+
// Many definitions begin with a description and require a lookahead.
177+
let hasDescription = peekDescription(lexer: lexer)
178+
let keywordToken = hasDescription
179+
? try lexer.lookahead()
180+
: lexer.token
181+
182+
if keywordToken.kind == .name {
183+
guard let value = keywordToken.value else {
178184
throw GraphQLError(message: "Expected name token to have value: \(lexer.token)")
179185
}
186+
180187
switch value {
181-
case "query", "mutation", "subscription":
182-
return try parseOperationDefinition(lexer: lexer)
183-
case "fragment":
184-
return try parseFragmentDefinition(lexer: lexer)
185-
// Note: the Type System IDL is an experimental non-spec addition.
186-
case "schema", "scalar", "type", "interface", "union", "enum", "input", "extend",
187-
"directive":
188-
return try parseTypeSystemDefinition(lexer: lexer)
188+
case "schema": return try parseSchemaDefinition(lexer: lexer)
189+
case "scalar": return try parseScalarTypeDefinition(lexer: lexer)
190+
case "type": return try parseObjectTypeDefinition(lexer: lexer)
191+
case "interface": return try parseInterfaceTypeDefinition(lexer: lexer)
192+
case "union": return try parseUnionTypeDefinition(lexer: lexer)
193+
case "enum": return try parseEnumTypeDefinition(lexer: lexer)
194+
case "input": return try parseInputObjectTypeDefinition(lexer: lexer)
195+
case "directive": return try parseDirectiveDefinition(lexer: lexer)
189196
default:
190-
break
197+
if hasDescription {
198+
throw syntaxError(
199+
source: lexer.source,
200+
position: lexer.token.start,
201+
description: "Unexpected description, descriptions are supported only on type definitions."
202+
)
203+
}
204+
switch value {
205+
case "query", "mutation", "subscription":
206+
return try parseOperationDefinition(lexer: lexer)
207+
case "fragment":
208+
return try parseFragmentDefinition(lexer: lexer)
209+
case "extend":
210+
return try parseExtensionDefinition(lexer: lexer)
211+
default:
212+
break
213+
}
191214
}
192-
} else if peekDescription(lexer: lexer) {
193-
return try parseTypeSystemDefinition(lexer: lexer)
194215
}
195216

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

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

678-
/**
679-
* TypeSystemDefinition :
680-
* - SchemaDefinition
681-
* - TypeDefinition
682-
* - TypeExtensionDefinition
683-
* - DirectiveDefinition
684-
*
685-
* TypeDefinition :
686-
* - ScalarTypeDefinition
687-
* - ObjectTypeDefinition
688-
* - InterfaceTypeDefinition
689-
* - UnionTypeDefinition
690-
* - EnumTypeDefinition
691-
* - InputObjectTypeDefinition
692-
*/
693-
func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition {
694-
let keywordToken = peekDescription(lexer: lexer)
695-
? try lexer.lookahead()
696-
: lexer.token
697-
698-
if keywordToken.kind == .name {
699-
guard let value = keywordToken.value else {
700-
throw GraphQLError(message: "Expected keyword token to have value: \(keywordToken)")
701-
}
702-
switch value {
703-
case "schema": return try parseSchemaDefinition(lexer: lexer)
704-
case "scalar": return try parseScalarTypeDefinition(lexer: lexer)
705-
case "type": return try parseObjectTypeDefinition(lexer: lexer)
706-
case "interface": return try parseInterfaceTypeDefinition(lexer: lexer)
707-
case "union": return try parseUnionTypeDefinition(lexer: lexer)
708-
case "enum": return try parseEnumTypeDefinition(lexer: lexer)
709-
case "input": return try parseInputObjectTypeDefinition(lexer: lexer)
710-
case "extend": return try parseExtensionDefinition(lexer: lexer)
711-
case "directive": return try parseDirectiveDefinition(lexer: lexer)
712-
default: break
713-
}
714-
}
715-
716-
throw unexpected(lexer: lexer, atToken: keywordToken)
717-
}
718-
719699
/**
720700
* SchemaDefinition : schema Directives? { OperationTypeDefinition+ }
721701
*
@@ -1025,10 +1005,31 @@ func parseExtensionDefinition(lexer: Lexer) throws -> TypeSystemDefinition {
10251005
func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinition {
10261006
let start = lexer.token
10271007
try expectKeyword(lexer: lexer, value: "extend")
1028-
let definition = try parseObjectTypeDefinition(lexer: lexer)
1008+
try expectKeyword(lexer: lexer, value: "type")
1009+
let name = try parseName(lexer: lexer)
1010+
let interfaces = try parseImplementsInterfaces(lexer: lexer)
1011+
let directives = try parseDirectives(lexer: lexer)
1012+
let fields = try optionalMany(
1013+
lexer: lexer,
1014+
openKind: .openingBrace,
1015+
closeKind: .closingBrace,
1016+
parse: parseFieldDefinition
1017+
)
1018+
if
1019+
interfaces.isEmpty,
1020+
directives.isEmpty,
1021+
fields.isEmpty
1022+
{
1023+
throw unexpected(lexer: lexer)
1024+
}
10291025
return TypeExtensionDefinition(
10301026
loc: loc(lexer: lexer, startToken: start),
1031-
definition: definition
1027+
definition: ObjectTypeDefinition(
1028+
name: name,
1029+
interfaces: interfaces,
1030+
directives: directives,
1031+
fields: fields
1032+
)
10321033
)
10331034
}
10341035

@@ -1038,16 +1039,24 @@ func parseTypeExtensionDefinition(lexer: Lexer) throws -> TypeExtensionDefinitio
10381039
func parseSchemaExtensionDefinition(lexer: Lexer) throws -> SchemaExtensionDefinition {
10391040
let start = lexer.token
10401041
try expectKeyword(lexer: lexer, value: "extend")
1041-
let description = try parseDescription(lexer: lexer)
10421042
try expectKeyword(lexer: lexer, value: "schema")
10431043
let directives = try parseDirectives(lexer: lexer)
1044+
let operationTypes = try optionalMany(
1045+
lexer: lexer,
1046+
openKind: .openingBrace,
1047+
closeKind: .closingBrace,
1048+
parse: parseOperationTypeDefinition
1049+
)
1050+
if directives.isEmpty, operationTypes.isEmpty {
1051+
throw unexpected(lexer: lexer)
1052+
}
10441053
return SchemaExtensionDefinition(
10451054
loc: loc(lexer: lexer, startToken: start),
10461055
definition: SchemaDefinition(
10471056
loc: loc(lexer: lexer, startToken: start),
1048-
description: description,
1057+
description: nil,
10491058
directives: directives,
1050-
operationTypes: []
1059+
operationTypes: operationTypes
10511060
)
10521061
)
10531062
}
@@ -1058,10 +1067,31 @@ func parseSchemaExtensionDefinition(lexer: Lexer) throws -> SchemaExtensionDefin
10581067
func parseInterfaceExtensionDefinition(lexer: Lexer) throws -> InterfaceExtensionDefinition {
10591068
let start = lexer.token
10601069
try expectKeyword(lexer: lexer, value: "extend")
1061-
let interfaceDefinition = try parseInterfaceTypeDefinition(lexer: lexer)
1070+
try expectKeyword(lexer: lexer, value: "interface")
1071+
let name = try parseName(lexer: lexer)
1072+
let interfaces = try parseImplementsInterfaces(lexer: lexer)
1073+
let directives = try parseDirectives(lexer: lexer)
1074+
let fields = try optionalMany(
1075+
lexer: lexer,
1076+
openKind: .openingBrace,
1077+
closeKind: .closingBrace,
1078+
parse: parseFieldDefinition
1079+
)
1080+
if
1081+
interfaces.isEmpty,
1082+
directives.isEmpty,
1083+
fields.isEmpty
1084+
{
1085+
throw unexpected(lexer: lexer)
1086+
}
10621087
return InterfaceExtensionDefinition(
10631088
loc: loc(lexer: lexer, startToken: start),
1064-
definition: interfaceDefinition
1089+
definition: InterfaceTypeDefinition(
1090+
name: name,
1091+
interfaces: interfaces,
1092+
directives: directives,
1093+
fields: fields
1094+
)
10651095
)
10661096
}
10671097

@@ -1071,10 +1101,18 @@ func parseInterfaceExtensionDefinition(lexer: Lexer) throws -> InterfaceExtensio
10711101
func parseScalarExtensionDefinition(lexer: Lexer) throws -> ScalarExtensionDefinition {
10721102
let start = lexer.token
10731103
try expectKeyword(lexer: lexer, value: "extend")
1074-
let scalarDefinition = try parseScalarTypeDefinition(lexer: lexer)
1104+
try expectKeyword(lexer: lexer, value: "scalar")
1105+
let name = try parseName(lexer: lexer)
1106+
let directives = try parseDirectives(lexer: lexer)
1107+
if directives.isEmpty {
1108+
throw unexpected(lexer: lexer)
1109+
}
10751110
return ScalarExtensionDefinition(
10761111
loc: loc(lexer: lexer, startToken: start),
1077-
definition: scalarDefinition
1112+
definition: ScalarTypeDefinition(
1113+
name: name,
1114+
directives: directives
1115+
)
10781116
)
10791117
}
10801118

@@ -1084,10 +1122,24 @@ func parseScalarExtensionDefinition(lexer: Lexer) throws -> ScalarExtensionDefin
10841122
func parseUnionExtensionDefinition(lexer: Lexer) throws -> UnionExtensionDefinition {
10851123
let start = lexer.token
10861124
try expectKeyword(lexer: lexer, value: "extend")
1087-
let definition = try parseUnionTypeDefinition(lexer: lexer)
1125+
try expectKeyword(lexer: lexer, value: "union")
1126+
let name = try parseName(lexer: lexer)
1127+
let directives = try parseDirectives(lexer: lexer)
1128+
let types = try parseUnionMembers(lexer: lexer)
1129+
if
1130+
directives.isEmpty,
1131+
types.isEmpty
1132+
{
1133+
throw unexpected(lexer: lexer)
1134+
}
10881135
return UnionExtensionDefinition(
10891136
loc: loc(lexer: lexer, startToken: start),
1090-
definition: definition
1137+
definition: UnionTypeDefinition(
1138+
loc: loc(lexer: lexer, startToken: start),
1139+
name: name,
1140+
directives: directives,
1141+
types: types
1142+
)
10911143
)
10921144
}
10931145

@@ -1097,10 +1149,29 @@ func parseUnionExtensionDefinition(lexer: Lexer) throws -> UnionExtensionDefinit
10971149
func parseEnumExtensionDefinition(lexer: Lexer) throws -> EnumExtensionDefinition {
10981150
let start = lexer.token
10991151
try expectKeyword(lexer: lexer, value: "extend")
1100-
let definition = try parseEnumTypeDefinition(lexer: lexer)
1152+
try expectKeyword(lexer: lexer, value: "enum")
1153+
let name = try parseName(lexer: lexer)
1154+
let directives = try parseDirectives(lexer: lexer)
1155+
let values = try optionalMany(
1156+
lexer: lexer,
1157+
openKind: .openingBrace,
1158+
closeKind: .closingBrace,
1159+
parse: parseEnumValueDefinition
1160+
)
1161+
if
1162+
directives.isEmpty,
1163+
values.isEmpty
1164+
{
1165+
throw unexpected(lexer: lexer)
1166+
}
11011167
return EnumExtensionDefinition(
11021168
loc: loc(lexer: lexer, startToken: start),
1103-
definition: definition
1169+
definition: EnumTypeDefinition(
1170+
loc: loc(lexer: lexer, startToken: start),
1171+
name: name,
1172+
directives: directives,
1173+
values: values
1174+
)
11041175
)
11051176
}
11061177

@@ -1110,10 +1181,29 @@ func parseEnumExtensionDefinition(lexer: Lexer) throws -> EnumExtensionDefinitio
11101181
func parseInputObjectExtensionDefinition(lexer: Lexer) throws -> InputObjectExtensionDefinition {
11111182
let start = lexer.token
11121183
try expectKeyword(lexer: lexer, value: "extend")
1113-
let definition = try parseInputObjectTypeDefinition(lexer: lexer)
1184+
try expectKeyword(lexer: lexer, value: "input")
1185+
let name = try parseName(lexer: lexer)
1186+
let directives = try parseDirectives(lexer: lexer)
1187+
let fields = try optionalMany(
1188+
lexer: lexer,
1189+
openKind: .openingBrace,
1190+
closeKind: .closingBrace,
1191+
parse: parseInputValueDef
1192+
)
1193+
if
1194+
directives.isEmpty,
1195+
fields.isEmpty
1196+
{
1197+
throw unexpected(lexer: lexer)
1198+
}
11141199
return InputObjectExtensionDefinition(
11151200
loc: loc(lexer: lexer, startToken: start),
1116-
definition: definition
1201+
definition: InputObjectTypeDefinition(
1202+
loc: loc(lexer: lexer, startToken: start),
1203+
name: name,
1204+
directives: directives,
1205+
fields: fields
1206+
)
11171207
)
11181208
}
11191209

0 commit comments

Comments
 (0)