Skip to content

Commit f317a65

Browse files
committed
RFC: SDL - Separate multiple inherited interfaces with &
This replaces: ```graphql type Foo implements Bar, Baz { field: Type } ``` With: ```graphql type Foo implements Bar & Baz { field: Type } ``` With no changes to the common case of implementing a single interface. This is more consistent with other trailing lists of values which either have an explicit separator (union members) or are prefixed with a sigil (directives). This avoids parse ambiguity in the case of an omitted field set, illustrated by #1166 This is a breaking change for existing uses of multiple inheritence. To allow for an adaptive migration, this adds a parse option to continue to support the existing experimental SDL: `parse(source, {allowLegacySDLImplementsInterfaces: true})`
1 parent b03b19c commit f317a65

File tree

8 files changed

+85
-15
lines changed

8 files changed

+85
-15
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ schema {
1212
This is a description
1313
of the `Foo` type.
1414
"""
15-
type Foo implements Bar {
15+
type Foo implements Bar & Baz {
1616
one: Type
1717
two(argument: InputType!): Type
1818
three(argument: InputType, other: String): Int

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

+52-7
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ type Hello {
291291
});
292292

293293
it('Simple type inheriting multiple interfaces', () => {
294-
const body = 'type Hello implements Wo, rld { field: String }';
294+
const body = 'type Hello implements Wo & rld { field: String }';
295295
const doc = parse(body);
296296
const expected = {
297297
kind: 'Document',
@@ -301,20 +301,49 @@ type Hello {
301301
name: nameNode('Hello', { start: 5, end: 10 }),
302302
interfaces: [
303303
typeNode('Wo', { start: 22, end: 24 }),
304-
typeNode('rld', { start: 26, end: 29 }),
304+
typeNode('rld', { start: 27, end: 30 }),
305305
],
306306
directives: [],
307307
fields: [
308308
fieldNode(
309-
nameNode('field', { start: 32, end: 37 }),
310-
typeNode('String', { start: 39, end: 45 }),
311-
{ start: 32, end: 45 },
309+
nameNode('field', { start: 33, end: 38 }),
310+
typeNode('String', { start: 40, end: 46 }),
311+
{ start: 33, end: 46 },
312312
),
313313
],
314-
loc: { start: 0, end: 47 },
314+
loc: { start: 0, end: 48 },
315315
},
316316
],
317-
loc: { start: 0, end: 47 },
317+
loc: { start: 0, end: 48 },
318+
};
319+
expect(printJson(doc)).to.equal(printJson(expected));
320+
});
321+
322+
it('Simple type inheriting multiple interfaces with leading ampersand', () => {
323+
const body = 'type Hello implements & Wo & rld { field: String }';
324+
const doc = parse(body);
325+
const expected = {
326+
kind: 'Document',
327+
definitions: [
328+
{
329+
kind: 'ObjectTypeDefinition',
330+
name: nameNode('Hello', { start: 5, end: 10 }),
331+
interfaces: [
332+
typeNode('Wo', { start: 24, end: 26 }),
333+
typeNode('rld', { start: 29, end: 32 }),
334+
],
335+
directives: [],
336+
fields: [
337+
fieldNode(
338+
nameNode('field', { start: 35, end: 40 }),
339+
typeNode('String', { start: 42, end: 48 }),
340+
{ start: 35, end: 48 },
341+
),
342+
],
343+
loc: { start: 0, end: 50 },
344+
},
345+
],
346+
loc: { start: 0, end: 50 },
318347
};
319348
expect(printJson(doc)).to.equal(printJson(expected));
320349
});
@@ -721,4 +750,20 @@ input Hello {
721750
],
722751
});
723752
});
753+
754+
it('Option: allowLegacySDLImplementsInterfaces', () => {
755+
const body = 'type Hello implements Wo rld { field: String }';
756+
expect(() => parse(body)).to.throw('Syntax Error: Unexpected Name "rld"');
757+
const doc = parse(body, { allowLegacySDLImplementsInterfaces: true });
758+
expect(doc).to.containSubset({
759+
definitions: [
760+
{
761+
interfaces: [
762+
typeNode('Wo', { start: 22, end: 24 }),
763+
typeNode('rld', { start: 25, end: 28 }),
764+
],
765+
},
766+
],
767+
});
768+
});
724769
});

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('Printer', () => {
5656
This is a description
5757
of the \`Foo\` type.
5858
"""
59-
type Foo implements Bar {
59+
type Foo implements Bar & Baz {
6060
one: Type
6161
two(argument: InputType!): Type
6262
three(argument: InputType, other: String): Int

src/language/ast.js

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type TokenKind =
5050
| '<EOF>'
5151
| '!'
5252
| '$'
53+
| '&'
5354
| '('
5455
| ')'
5556
| '...'

src/language/lexer.js

+5
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const SOF = '<SOF>';
9999
const EOF = '<EOF>';
100100
const BANG = '!';
101101
const DOLLAR = '$';
102+
const AMP = '&';
102103
const PAREN_L = '(';
103104
const PAREN_R = ')';
104105
const SPREAD = '...';
@@ -126,6 +127,7 @@ export const TokenKind = {
126127
EOF,
127128
BANG,
128129
DOLLAR,
130+
AMP,
129131
PAREN_L,
130132
PAREN_R,
131133
SPREAD,
@@ -242,6 +244,9 @@ function readToken(lexer: Lexer<*>, prev: Token): Token {
242244
// $
243245
case 36:
244246
return new Tok(DOLLAR, position, position + 1, line, col, prev);
247+
// &
248+
case 38:
249+
return new Tok(AMP, position, position + 1, line, col, prev);
245250
// (
246251
case 40:
247252
return new Tok(PAREN_L, position, position + 1, line, col, prev);

src/language/parser.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ export type ParseOptions = {
129129
*/
130130
allowLegacySDLEmptyFields?: boolean,
131131

132+
/**
133+
* If enabled, the parser will parse implemented interfaces with no `&`
134+
* character between each interface. Otherwise, the parser will follow the
135+
* current specification.
136+
*
137+
* This option is provided to ease adoption of the final SDL specification
138+
* and will be removed in a future major release.
139+
*/
140+
allowLegacySDLImplementsInterfaces?: boolean,
141+
132142
/**
133143
* EXPERIMENTAL:
134144
*
@@ -922,15 +932,24 @@ function parseObjectTypeDefinition(lexer: Lexer<*>): ObjectTypeDefinitionNode {
922932
}
923933

924934
/**
925-
* ImplementsInterfaces : implements NamedType+
935+
* ImplementsInterfaces :
936+
* - implements `&`? NamedType
937+
* - ImplementsInterfaces & NamedType
926938
*/
927939
function parseImplementsInterfaces(lexer: Lexer<*>): Array<NamedTypeNode> {
928940
const types = [];
929941
if (lexer.token.value === 'implements') {
930942
lexer.advance();
943+
// Optional leading ampersand
944+
skip(lexer, TokenKind.AMP);
931945
do {
932946
types.push(parseNamedType(lexer));
933-
} while (peek(lexer, TokenKind.NAME));
947+
} while (
948+
skip(lexer, TokenKind.AMP) ||
949+
// Legacy support for the SDL?
950+
(lexer.options.allowLegacySDLImplementsInterfaces &&
951+
peek(lexer, TokenKind.NAME))
952+
);
934953
}
935954
return types;
936955
}

src/language/printer.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ const printDocASTReducer = {
130130
[
131131
'type',
132132
name,
133-
wrap('implements ', join(interfaces, ', ')),
133+
wrap('implements ', join(interfaces, ' & ')),
134134
join(directives, ' '),
135135
block(fields),
136136
],
@@ -226,7 +226,7 @@ const printDocASTReducer = {
226226
[
227227
'extend type',
228228
name,
229-
wrap('implements ', join(interfaces, ', ')),
229+
wrap('implements ', join(interfaces, ' & ')),
230230
join(directives, ' '),
231231
block(fields),
232232
],

src/type/__tests__/validation-test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -822,14 +822,14 @@ describe('Type System: Objects can only implement unique interfaces', () => {
822822
field: String
823823
}
824824
825-
type AnotherObject implements AnotherInterface, AnotherInterface {
825+
type AnotherObject implements AnotherInterface & AnotherInterface {
826826
field: String
827827
}
828828
`);
829829
expect(validateSchema(schema)).to.containSubset([
830830
{
831831
message: 'Type AnotherObject can only implement AnotherInterface once.',
832-
locations: [{ line: 10, column: 37 }, { line: 10, column: 55 }],
832+
locations: [{ line: 10, column: 37 }, { line: 10, column: 56 }],
833833
},
834834
]);
835835
});

0 commit comments

Comments
 (0)