Skip to content

Commit 37717b8

Browse files
authored
RFC: Descriptions as strings. (#927)
As discussed in graphql/graphql-spec#90 This proposes replacing leading comment blocks as descriptions in the schema definition language with leading strings (typically block strings). While I think there is some reduced ergonomics of using a string literal instead of a comment to write descriptions (unless perhaps you are accustomed to Python or Clojure), there are some compelling advantages: * Descriptions are first-class in the AST of the schema definition language. * Comments can remain "ignored" characters. * No ambiguity between commented out regions and descriptions. Specific to this reference implementation, since this is a breaking change and comment descriptions in the experimental SDL have fairly wide usage, I've left the comment description implementation intact and allow it to be enabled via an option. This should help with allowing upgrading with minimal impact on existing codebases and aid in automated transforms.
1 parent bf4a25a commit 37717b8

14 files changed

+792
-195
lines changed

src/language/__tests__/schema-kitchen-sink.graphql

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ schema {
88
mutation: MutationType
99
}
1010

11+
"""
12+
This is a description
13+
of the `Foo` type.
14+
"""
1115
type Foo implements Bar {
1216
one: Type
1317
two(argument: InputType!): Type

src/language/__tests__/schema-parser-test.js

+58
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,55 @@ type Hello {
9494
expect(printJson(doc)).to.equal(printJson(expected));
9595
});
9696

97+
it('parses type with description string', () => {
98+
const doc = parse(`
99+
"Description"
100+
type Hello {
101+
world: String
102+
}`);
103+
expect(doc).to.containSubset({
104+
kind: 'Document',
105+
definitions: [
106+
{
107+
kind: 'ObjectTypeDefinition',
108+
name: nameNode('Hello', { start: 20, end: 25 }),
109+
description: {
110+
kind: 'StringValue',
111+
value: 'Description',
112+
loc: { start: 1, end: 14 },
113+
}
114+
}
115+
],
116+
loc: { start: 0, end: 45 },
117+
});
118+
});
119+
120+
it('parses type with description multi-line string', () => {
121+
const doc = parse(`
122+
"""
123+
Description
124+
"""
125+
# Even with comments between them
126+
type Hello {
127+
world: String
128+
}`);
129+
expect(doc).to.containSubset({
130+
kind: 'Document',
131+
definitions: [
132+
{
133+
kind: 'ObjectTypeDefinition',
134+
name: nameNode('Hello', { start: 60, end: 65 }),
135+
description: {
136+
kind: 'StringValue',
137+
value: 'Description',
138+
loc: { start: 1, end: 20 },
139+
}
140+
}
141+
],
142+
loc: { start: 0, end: 85 },
143+
});
144+
});
145+
97146
it('Simple extension', () => {
98147
const body = `
99148
extend type Hello {
@@ -128,6 +177,15 @@ extend type Hello {
128177
expect(printJson(doc)).to.equal(printJson(expected));
129178
});
130179

180+
it('Extension do not include descriptions', () => {
181+
expect(() => parse(`
182+
"Description"
183+
extend type Hello {
184+
world: String
185+
}
186+
`)).to.throw('Syntax Error GraphQL request (2:7)');
187+
});
188+
131189
it('Simple non-null type', () => {
132190
const body = `
133191
type Hello {

src/language/__tests__/schema-printer-test.js

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ describe('Printer', () => {
5454
mutation: MutationType
5555
}
5656
57+
"""
58+
This is a description
59+
of the \`Foo\` type.
60+
"""
5761
type Foo implements Bar {
5862
one: Type
5963
two(argument: InputType!): Type

src/language/ast.js

+10
Original file line numberDiff line numberDiff line change
@@ -396,13 +396,15 @@ export type TypeDefinitionNode =
396396
export type ScalarTypeDefinitionNode = {
397397
kind: 'ScalarTypeDefinition';
398398
loc?: Location;
399+
description?: ?StringValueNode;
399400
name: NameNode;
400401
directives?: ?Array<DirectiveNode>;
401402
};
402403

403404
export type ObjectTypeDefinitionNode = {
404405
kind: 'ObjectTypeDefinition';
405406
loc?: Location;
407+
description?: ?StringValueNode;
406408
name: NameNode;
407409
interfaces?: ?Array<NamedTypeNode>;
408410
directives?: ?Array<DirectiveNode>;
@@ -412,6 +414,7 @@ export type ObjectTypeDefinitionNode = {
412414
export type FieldDefinitionNode = {
413415
kind: 'FieldDefinition';
414416
loc?: Location;
417+
description?: ?StringValueNode;
415418
name: NameNode;
416419
arguments: Array<InputValueDefinitionNode>;
417420
type: TypeNode;
@@ -421,6 +424,7 @@ export type FieldDefinitionNode = {
421424
export type InputValueDefinitionNode = {
422425
kind: 'InputValueDefinition';
423426
loc?: Location;
427+
description?: ?StringValueNode;
424428
name: NameNode;
425429
type: TypeNode;
426430
defaultValue?: ?ValueNode;
@@ -430,6 +434,7 @@ export type InputValueDefinitionNode = {
430434
export type InterfaceTypeDefinitionNode = {
431435
kind: 'InterfaceTypeDefinition';
432436
loc?: Location;
437+
description?: ?StringValueNode;
433438
name: NameNode;
434439
directives?: ?Array<DirectiveNode>;
435440
fields: Array<FieldDefinitionNode>;
@@ -438,6 +443,7 @@ export type InterfaceTypeDefinitionNode = {
438443
export type UnionTypeDefinitionNode = {
439444
kind: 'UnionTypeDefinition';
440445
loc?: Location;
446+
description?: ?StringValueNode;
441447
name: NameNode;
442448
directives?: ?Array<DirectiveNode>;
443449
types: Array<NamedTypeNode>;
@@ -446,6 +452,7 @@ export type UnionTypeDefinitionNode = {
446452
export type EnumTypeDefinitionNode = {
447453
kind: 'EnumTypeDefinition';
448454
loc?: Location;
455+
description?: ?StringValueNode;
449456
name: NameNode;
450457
directives?: ?Array<DirectiveNode>;
451458
values: Array<EnumValueDefinitionNode>;
@@ -454,13 +461,15 @@ export type EnumTypeDefinitionNode = {
454461
export type EnumValueDefinitionNode = {
455462
kind: 'EnumValueDefinition';
456463
loc?: Location;
464+
description?: ?StringValueNode;
457465
name: NameNode;
458466
directives?: ?Array<DirectiveNode>;
459467
};
460468

461469
export type InputObjectTypeDefinitionNode = {
462470
kind: 'InputObjectTypeDefinition';
463471
loc?: Location;
472+
description?: ?StringValueNode;
464473
name: NameNode;
465474
directives?: ?Array<DirectiveNode>;
466475
fields: Array<InputValueDefinitionNode>;
@@ -475,6 +484,7 @@ export type TypeExtensionDefinitionNode = {
475484
export type DirectiveDefinitionNode = {
476485
kind: 'DirectiveDefinition';
477486
loc?: Location;
487+
description?: ?StringValueNode;
478488
name: NameNode;
479489
arguments?: ?Array<InputValueDefinitionNode>;
480490
locations: Array<NameNode>;

src/language/lexer.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,24 @@ export function createLexer<TOptions>(
3232
token: startOfFileToken,
3333
line: 1,
3434
lineStart: 0,
35-
advance: advanceLexer
35+
advance: advanceLexer,
36+
lookahead
3637
};
3738
return lexer;
3839
}
3940

4041
function advanceLexer() {
41-
let token = this.lastToken = this.token;
42+
this.lastToken = this.token;
43+
const token = this.token = this.lookahead();
44+
return token;
45+
}
46+
47+
function lookahead() {
48+
let token = this.token;
4249
if (token.kind !== EOF) {
4350
do {
44-
token = token.next = readToken(this, token);
51+
token = token.next || (token.next = readToken(this, token));
4552
} while (token.kind === COMMENT);
46-
this.token = token;
4753
}
4854
return token;
4955
}
@@ -79,6 +85,12 @@ export type Lexer<TOptions> = {
7985
* Advances the token stream to the next non-ignored token.
8086
*/
8187
advance(): Token;
88+
89+
/**
90+
* Looks ahead and returns the next non-ignored token, but does not change
91+
* the Lexer's state.
92+
*/
93+
lookahead(): Token;
8294
};
8395

8496
// Each kind of token.

0 commit comments

Comments
 (0)