Skip to content

Commit c6bee55

Browse files
committed
Add a concept of annotations to graphql
1 parent a6bcc75 commit c6bee55

17 files changed

+432
-7
lines changed

src/__tests__/starWarsIntrospectionTests.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ describe('Star Wars Introspection Tests', () => {
6666
{
6767
name: '__InputValue'
6868
},
69+
{
70+
name: '__Annotation'
71+
},
72+
{
73+
name: '__AnnotationArgument'
74+
},
6975
{
7076
name: '__EnumValue'
7177
},

src/language/__tests__/kitchen-sink.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,8 @@ fragment frag on Friend {
5555
unnamed(truthy: true, falsey: false),
5656
query
5757
}
58+
59+
extend type User {
60+
@iAmAnAnnotation(default: "Foo")
61+
name: String
62+
}

src/language/__tests__/printer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ fragment frag on Friend {
9797
unnamed(truthy: true, falsey: false)
9898
query
9999
}
100+
101+
extend type User {
102+
@iAmAnAnnotation(default: "Foo")
103+
name: String
104+
}
100105
`);
101106

102107
});

src/language/__tests__/schema-parser.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,31 @@ function nameNode(name, loc) {
4242
};
4343
}
4444

45+
function annotationNode(name, args, loc) {
46+
return {
47+
kind: 'Annotation',
48+
name,
49+
arguments: args,
50+
loc
51+
};
52+
}
53+
4554
function fieldNode(name, type, loc) {
4655
return fieldNodeWithArgs(name, type, [], loc);
4756
}
4857

4958
function fieldNodeWithArgs(name, type, args, loc) {
59+
return fieldNodeWithArgsAndAnnotations(name, type, args, [], loc);
60+
}
61+
62+
function fieldNodeWithArgsAndAnnotations(name, type, args, annotations, loc) {
5063
return {
5164
kind: 'FieldDefinition',
5265
name,
5366
arguments: args,
5467
type,
5568
loc,
69+
annotations,
5670
};
5771
}
5872

@@ -541,4 +555,86 @@ input Hello {
541555
expect(() => parse(body)).to.throw('Error');
542556
});
543557

558+
it('Simple fields with annotations', () => {
559+
var body = `
560+
type Hello {
561+
@mock(value: "hello")
562+
world: String
563+
@ignore
564+
@mock(value: 2)
565+
hello: Int
566+
}`;
567+
var doc = parse(body);
568+
var loc = createLocFn(body);
569+
var expected = {
570+
kind: 'Document',
571+
definitions: [
572+
{
573+
kind: 'ObjectTypeDefinition',
574+
name: nameNode('Hello', loc(6, 11)),
575+
interfaces: [],
576+
fields: [
577+
fieldNodeWithArgsAndAnnotations(
578+
nameNode('world', loc(40, 45)),
579+
typeNode('String', loc(47, 53)),
580+
[],
581+
[
582+
annotationNode(
583+
nameNode('mock', loc(17, 21)),
584+
[
585+
{
586+
kind: 'Argument',
587+
name: nameNode('value', loc(22, 27)),
588+
value: {
589+
kind: 'StringValue',
590+
value: 'hello',
591+
loc: loc(29, 36),
592+
},
593+
loc: loc(22, 36),
594+
}
595+
],
596+
loc(16, 37)
597+
),
598+
],
599+
loc(16, 53)
600+
),
601+
fieldNodeWithArgsAndAnnotations(
602+
nameNode('hello', loc(84, 89)),
603+
typeNode('Int', loc(91, 94)),
604+
[],
605+
[
606+
annotationNode(
607+
nameNode('ignore', loc(57, 63)),
608+
[],
609+
loc(56, 63)
610+
),
611+
annotationNode(
612+
nameNode('mock', loc(67, 71)),
613+
[
614+
{
615+
kind: 'Argument',
616+
name: nameNode('value', loc(72, 77)),
617+
value: {
618+
kind: 'IntValue',
619+
value: '2',
620+
loc: loc(79, 80),
621+
},
622+
loc: loc(72, 80),
623+
}
624+
],
625+
loc(66, 81)
626+
),
627+
],
628+
loc(56, 94)
629+
)
630+
],
631+
loc: loc(1, 96),
632+
}
633+
],
634+
loc: loc(1, 96),
635+
};
636+
expect(printJson(doc)).to.equal(printJson(expected));
637+
});
638+
639+
544640
});

src/language/__tests__/visitor.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,42 @@ describe('Visitor', () => {
547547
[ 'leave', 'Field', 1, undefined ],
548548
[ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
549549
[ 'leave', 'OperationDefinition', 4, undefined ],
550-
[ 'leave', 'Document', undefined, undefined ] ]);
550+
[ 'enter', 'TypeExtensionDefinition', 5, undefined ],
551+
[
552+
'enter',
553+
'ObjectTypeDefinition',
554+
'definition',
555+
'TypeExtensionDefinition',
556+
],
557+
[ 'enter', 'Name', 'name', 'ObjectTypeDefinition' ],
558+
[ 'leave', 'Name', 'name', 'ObjectTypeDefinition' ],
559+
[ 'enter', 'FieldDefinition', 0, undefined ],
560+
[ 'enter', 'Name', 'name', 'FieldDefinition' ],
561+
[ 'leave', 'Name', 'name', 'FieldDefinition' ],
562+
[ 'enter', 'NamedType', 'type', 'FieldDefinition' ],
563+
[ 'enter', 'Name', 'name', 'NamedType' ],
564+
[ 'leave', 'Name', 'name', 'NamedType' ],
565+
[ 'leave', 'NamedType', 'type', 'FieldDefinition' ],
566+
[ 'enter', 'Annotation', 0, undefined ],
567+
[ 'enter', 'Name', 'name', 'Annotation' ],
568+
[ 'leave', 'Name', 'name', 'Annotation' ],
569+
[ 'enter', 'Argument', 0, undefined ],
570+
[ 'enter', 'Name', 'name', 'Argument' ],
571+
[ 'leave', 'Name', 'name', 'Argument' ],
572+
[ 'enter', 'StringValue', 'value', 'Argument' ],
573+
[ 'leave', 'StringValue', 'value', 'Argument' ],
574+
[ 'leave', 'Argument', 0, undefined ],
575+
[ 'leave', 'Annotation', 0, undefined ],
576+
[ 'leave', 'FieldDefinition', 0, undefined ],
577+
[
578+
'leave',
579+
'ObjectTypeDefinition',
580+
'definition',
581+
'TypeExtensionDefinition',
582+
],
583+
[ 'leave', 'TypeExtensionDefinition', 5, undefined ],
584+
[ 'leave', 'Document', undefined, undefined ],
585+
]);
551586
});
552587

553588
describe('visitInParallel', () => {

src/language/ast.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type Node = Name
4444
| ObjectValue
4545
| ObjectField
4646
| Directive
47+
| Annotation
4748
| ListType
4849
| NonNullType
4950
| ObjectTypeDefinition
@@ -227,6 +228,14 @@ export type Directive = {
227228
arguments?: ?Array<Argument>;
228229
}
229230

231+
// Annotation
232+
233+
export type Annotation = {
234+
kind: 'Annotation';
235+
loc?: ?Location;
236+
name: Name;
237+
arguments?: ?Array<Argument>;
238+
}
230239

231240
// Type Reference
232241

@@ -276,6 +285,7 @@ export type FieldDefinition = {
276285
name: Name;
277286
arguments: Array<InputValueDefinition>;
278287
type: Type;
288+
annotations?: ?Array<Annotation>;
279289
}
280290

281291
export type InputValueDefinition = {

src/language/kinds.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export const OBJECT_FIELD = 'ObjectField';
4242

4343
export const DIRECTIVE = 'Directive';
4444

45+
// Annotation
46+
47+
export const ANNOTATION = 'Annotation';
48+
4549
// Types
4650

4751
export const NAMED_TYPE = 'NamedType';

src/language/parser.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type {
3434
ObjectField,
3535

3636
Directive,
37+
Annotation,
3738

3839
Type,
3940
NamedType,
@@ -77,6 +78,7 @@ import {
7778
OBJECT_FIELD,
7879

7980
DIRECTIVE,
81+
ANNOTATION,
8082

8183
NAMED_TYPE,
8284
LIST_TYPE,
@@ -589,6 +591,33 @@ function parseDirective(parser): Directive {
589591
};
590592
}
591593

594+
// Implements the parsing rules in the Annotations section.
595+
596+
/**
597+
* Annotations : Annotation+
598+
*/
599+
function parseAnnotations(parser): Array<Annotation> {
600+
var annotations = [];
601+
while (peek(parser, TokenKind.AT)) {
602+
annotations.push(parseAnnotation(parser));
603+
}
604+
return annotations;
605+
}
606+
607+
/**
608+
* Annotation : @ Name Arguments?
609+
*/
610+
function parseAnnotation(parser): Annotation {
611+
var start = parser.token.start;
612+
expect(parser, TokenKind.AT);
613+
return {
614+
kind: ANNOTATION,
615+
name: parseName(parser),
616+
arguments: parseArguments(parser),
617+
loc: loc(parser, start)
618+
};
619+
}
620+
592621

593622
// Implements the parsing rules in the Types section.
594623

@@ -709,10 +738,11 @@ function parseImplementsInterfaces(parser): Array<NamedType> {
709738
}
710739

711740
/**
712-
* FieldDefinition : Name ArgumentsDefinition? : Type
741+
* FieldDefinition : Annotations? Name ArgumentsDefinition? : Type
713742
*/
714743
function parseFieldDefinition(parser): FieldDefinition {
715744
var start = parser.token.start;
745+
var annotations = parseAnnotations(parser);
716746
var name = parseName(parser);
717747
var args = parseArgumentDefs(parser);
718748
expect(parser, TokenKind.COLON);
@@ -723,6 +753,7 @@ function parseFieldDefinition(parser): FieldDefinition {
723753
arguments: args,
724754
type,
725755
loc: loc(parser, start),
756+
annotations,
726757
};
727758
}
728759

src/language/printer.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ var printDocASTReducer = {
8383
Directive: ({ name, arguments: args }) =>
8484
'@' + name + wrap('(', join(args, ', '), ')'),
8585

86+
// Annotation
87+
88+
Annotation: ({ name, arguments: args }) =>
89+
'@' + name + wrap('(', join(args, ', '), ')'),
90+
8691
// Type
8792

8893
NamedType: ({ name }) => name,
@@ -96,8 +101,10 @@ var printDocASTReducer = {
96101
wrap('implements ', join(interfaces, ', '), ' ') +
97102
block(fields),
98103

99-
FieldDefinition: ({ name, arguments: args, type }) =>
100-
name + wrap('(', join(args, ', '), ')') + ': ' + type,
104+
FieldDefinition: ({ name, arguments: args, type, annotations }) =>
105+
wrap('', join(annotations, '\n'), '\n') +
106+
name + wrap('(', join(args, ', '), ')') + ': ' +
107+
type,
101108

102109
InputValueDefinition: ({ name, type, defaultValue }) =>
103110
name + ': ' + type + wrap(' = ', defaultValue),

src/language/visitor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ export var QueryDocumentKeys = {
3333
ObjectField: [ 'name', 'value' ],
3434

3535
Directive: [ 'name', 'arguments' ],
36+
Annotation: [ 'name', 'arguments' ],
3637

3738
NamedType: [ 'name' ],
3839
ListType: [ 'type' ],
3940
NonNullType: [ 'type' ],
4041

4142
ObjectTypeDefinition: [ 'name', 'interfaces', 'fields' ],
42-
FieldDefinition: [ 'name', 'arguments', 'type' ],
43+
FieldDefinition: [ 'name', 'arguments', 'type', 'annotations' ],
4344
InputValueDefinition: [ 'name', 'type', 'defaultValue' ],
4445
InterfaceTypeDefinition: [ 'name', 'fields' ],
4546
UnionTypeDefinition: [ 'name', 'types' ],

0 commit comments

Comments
 (0)