Skip to content

Commit 6da8c86

Browse files
committed
RFC: Descriptions as strings.
As discussed in graphql/graphql-spec#90 This proposes replacing leading comment blocks as descriptions in the schema definition language with leading 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 f222fa5 commit 6da8c86

14 files changed

+738
-191
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ schema {
1010
mutation: MutationType
1111
}
1212

13+
"""
14+
This is a description
15+
of the `Foo` type.
16+
"""
1317
type Foo implements Bar {
1418
one: Type
1519
two(argument: InputType!): Type

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

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

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

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

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

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ describe('Printer', () => {
5656
mutation: MutationType
5757
}
5858
59+
"""
60+
This is a description
61+
of the \`Foo\` type.
62+
"""
5963
type Foo implements Bar {
6064
one: Type
6165
two(argument: InputType!): Type

src/language/ast.js

+10
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,15 @@ export type TypeDefinitionNode =
398398
export type ScalarTypeDefinitionNode = {
399399
kind: 'ScalarTypeDefinition';
400400
loc?: Location;
401+
description?: ?StringValueNode;
401402
name: NameNode;
402403
directives?: ?Array<DirectiveNode>;
403404
};
404405

405406
export type ObjectTypeDefinitionNode = {
406407
kind: 'ObjectTypeDefinition';
407408
loc?: Location;
409+
description?: ?StringValueNode;
408410
name: NameNode;
409411
interfaces?: ?Array<NamedTypeNode>;
410412
directives?: ?Array<DirectiveNode>;
@@ -414,6 +416,7 @@ export type ObjectTypeDefinitionNode = {
414416
export type FieldDefinitionNode = {
415417
kind: 'FieldDefinition';
416418
loc?: Location;
419+
description?: ?StringValueNode;
417420
name: NameNode;
418421
arguments: Array<InputValueDefinitionNode>;
419422
type: TypeNode;
@@ -423,6 +426,7 @@ export type FieldDefinitionNode = {
423426
export type InputValueDefinitionNode = {
424427
kind: 'InputValueDefinition';
425428
loc?: Location;
429+
description?: ?StringValueNode;
426430
name: NameNode;
427431
type: TypeNode;
428432
defaultValue?: ?ValueNode;
@@ -432,6 +436,7 @@ export type InputValueDefinitionNode = {
432436
export type InterfaceTypeDefinitionNode = {
433437
kind: 'InterfaceTypeDefinition';
434438
loc?: Location;
439+
description?: ?StringValueNode;
435440
name: NameNode;
436441
directives?: ?Array<DirectiveNode>;
437442
fields: Array<FieldDefinitionNode>;
@@ -440,6 +445,7 @@ export type InterfaceTypeDefinitionNode = {
440445
export type UnionTypeDefinitionNode = {
441446
kind: 'UnionTypeDefinition';
442447
loc?: Location;
448+
description?: ?StringValueNode;
443449
name: NameNode;
444450
directives?: ?Array<DirectiveNode>;
445451
types: Array<NamedTypeNode>;
@@ -448,6 +454,7 @@ export type UnionTypeDefinitionNode = {
448454
export type EnumTypeDefinitionNode = {
449455
kind: 'EnumTypeDefinition';
450456
loc?: Location;
457+
description?: ?StringValueNode;
451458
name: NameNode;
452459
directives?: ?Array<DirectiveNode>;
453460
values: Array<EnumValueDefinitionNode>;
@@ -456,13 +463,15 @@ export type EnumTypeDefinitionNode = {
456463
export type EnumValueDefinitionNode = {
457464
kind: 'EnumValueDefinition';
458465
loc?: Location;
466+
description?: ?StringValueNode;
459467
name: NameNode;
460468
directives?: ?Array<DirectiveNode>;
461469
};
462470

463471
export type InputObjectTypeDefinitionNode = {
464472
kind: 'InputObjectTypeDefinition';
465473
loc?: Location;
474+
description?: ?StringValueNode;
466475
name: NameNode;
467476
directives?: ?Array<DirectiveNode>;
468477
fields: Array<InputValueDefinitionNode>;
@@ -477,6 +486,7 @@ export type TypeExtensionDefinitionNode = {
477486
export type DirectiveDefinitionNode = {
478487
kind: 'DirectiveDefinition';
479488
loc?: Location;
489+
description?: ?StringValueNode;
480490
name: NameNode;
481491
arguments?: ?Array<InputValueDefinitionNode>;
482492
locations: Array<NameNode>;

src/language/lexer.js

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

4142
function advanceLexer() {
42-
let token = this.lastToken = this.token;
43+
this.lastToken = this.token;
44+
const token = this.token = this.lookahead();
45+
return token;
46+
}
47+
48+
function lookahead() {
49+
let token = this.token;
4350
if (token.kind !== EOF) {
4451
do {
45-
token = token.next = readToken(this, token);
52+
token = token.next || (token.next = readToken(this, token));
4653
} while (token.kind === COMMENT);
47-
this.token = token;
4854
}
4955
return token;
5056
}
@@ -80,6 +86,12 @@ export type Lexer<TOptions> = {
8086
* Advances the token stream to the next non-ignored token.
8187
*/
8288
advance(): Token;
89+
90+
/**
91+
* Looks ahead and returns the next non-ignored token, but does not change
92+
* the Lexer's state.
93+
*/
94+
lookahead(): Token;
8395
};
8496

8597
// Each kind of token.

0 commit comments

Comments
 (0)