diff --git a/src/language/__tests__/kitchen-sink.graphql b/src/language/__tests__/kitchen-sink.graphql index 0e04e2e42d..4536d2e7f3 100644 --- a/src/language/__tests__/kitchen-sink.graphql +++ b/src/language/__tests__/kitchen-sink.graphql @@ -55,3 +55,8 @@ fragment frag on Friend { unnamed(truthy: true, falsey: false), query } + +extend type User { + @iAmAnAnnotation(default: "Foo") + name: String +} diff --git a/src/language/__tests__/printer.js b/src/language/__tests__/printer.js index c6216ddc4c..57d9b9a870 100644 --- a/src/language/__tests__/printer.js +++ b/src/language/__tests__/printer.js @@ -97,6 +97,11 @@ fragment frag on Friend { unnamed(truthy: true, falsey: false) query } + +extend type User { + @iAmAnAnnotation(default: "Foo") + name: String +} `); }); diff --git a/src/language/__tests__/schema-parser.js b/src/language/__tests__/schema-parser.js index 8b4754ac17..d8d3eda70c 100644 --- a/src/language/__tests__/schema-parser.js +++ b/src/language/__tests__/schema-parser.js @@ -42,17 +42,31 @@ function nameNode(name, loc) { }; } +function annotationNode(name, args, loc) { + return { + kind: 'Annotation', + name, + arguments: args, + loc + }; +} + function fieldNode(name, type, loc) { return fieldNodeWithArgs(name, type, [], loc); } function fieldNodeWithArgs(name, type, args, loc) { + return fieldNodeWithArgsAndAnnotations(name, type, args, [], loc); +} + +function fieldNodeWithArgsAndAnnotations(name, type, args, annotations, loc) { return { kind: 'FieldDefinition', name, arguments: args, type, loc, + annotations, }; } @@ -541,4 +555,86 @@ input Hello { expect(() => parse(body)).to.throw('Error'); }); + it('Simple fields with annotations', () => { + var body = ` +type Hello { + @mock(value: "hello") + world: String + @ignore + @mock(value: 2) + hello: Int +}`; + var doc = parse(body); + var loc = createLocFn(body); + var expected = { + kind: 'Document', + definitions: [ + { + kind: 'ObjectTypeDefinition', + name: nameNode('Hello', loc(6, 11)), + interfaces: [], + fields: [ + fieldNodeWithArgsAndAnnotations( + nameNode('world', loc(40, 45)), + typeNode('String', loc(47, 53)), + [], + [ + annotationNode( + nameNode('mock', loc(17, 21)), + [ + { + kind: 'Argument', + name: nameNode('value', loc(22, 27)), + value: { + kind: 'StringValue', + value: 'hello', + loc: loc(29, 36), + }, + loc: loc(22, 36), + } + ], + loc(16, 37) + ), + ], + loc(16, 53) + ), + fieldNodeWithArgsAndAnnotations( + nameNode('hello', loc(84, 89)), + typeNode('Int', loc(91, 94)), + [], + [ + annotationNode( + nameNode('ignore', loc(57, 63)), + [], + loc(56, 63) + ), + annotationNode( + nameNode('mock', loc(67, 71)), + [ + { + kind: 'Argument', + name: nameNode('value', loc(72, 77)), + value: { + kind: 'IntValue', + value: '2', + loc: loc(79, 80), + }, + loc: loc(72, 80), + } + ], + loc(66, 81) + ), + ], + loc(56, 94) + ) + ], + loc: loc(1, 96), + } + ], + loc: loc(1, 96), + }; + expect(printJson(doc)).to.equal(printJson(expected)); + }); + + }); diff --git a/src/language/__tests__/visitor.js b/src/language/__tests__/visitor.js index f0265d7b04..660abe9553 100644 --- a/src/language/__tests__/visitor.js +++ b/src/language/__tests__/visitor.js @@ -547,7 +547,42 @@ describe('Visitor', () => { [ 'leave', 'Field', 1, undefined ], [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ], [ 'leave', 'OperationDefinition', 4, undefined ], - [ 'leave', 'Document', undefined, undefined ] ]); + [ 'enter', 'TypeExtensionDefinition', 5, undefined ], + [ + 'enter', + 'ObjectTypeDefinition', + 'definition', + 'TypeExtensionDefinition', + ], + [ 'enter', 'Name', 'name', 'ObjectTypeDefinition' ], + [ 'leave', 'Name', 'name', 'ObjectTypeDefinition' ], + [ 'enter', 'FieldDefinition', 0, undefined ], + [ 'enter', 'Name', 'name', 'FieldDefinition' ], + [ 'leave', 'Name', 'name', 'FieldDefinition' ], + [ 'enter', 'NamedType', 'type', 'FieldDefinition' ], + [ 'enter', 'Name', 'name', 'NamedType' ], + [ 'leave', 'Name', 'name', 'NamedType' ], + [ 'leave', 'NamedType', 'type', 'FieldDefinition' ], + [ 'enter', 'Annotation', 0, undefined ], + [ 'enter', 'Name', 'name', 'Annotation' ], + [ 'leave', 'Name', 'name', 'Annotation' ], + [ 'enter', 'Argument', 0, undefined ], + [ 'enter', 'Name', 'name', 'Argument' ], + [ 'leave', 'Name', 'name', 'Argument' ], + [ 'enter', 'StringValue', 'value', 'Argument' ], + [ 'leave', 'StringValue', 'value', 'Argument' ], + [ 'leave', 'Argument', 0, undefined ], + [ 'leave', 'Annotation', 0, undefined ], + [ 'leave', 'FieldDefinition', 0, undefined ], + [ + 'leave', + 'ObjectTypeDefinition', + 'definition', + 'TypeExtensionDefinition', + ], + [ 'leave', 'TypeExtensionDefinition', 5, undefined ], + [ 'leave', 'Document', undefined, undefined ], + ]); }); describe('visitInParallel', () => { diff --git a/src/language/ast.js b/src/language/ast.js index 28c42a864c..77eb6f336c 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -44,6 +44,7 @@ export type Node = Name | ObjectValue | ObjectField | Directive + | Annotation | ListType | NonNullType | ObjectTypeDefinition @@ -227,6 +228,14 @@ export type Directive = { arguments?: ?Array; } +// Annotation + +export type Annotation = { + kind: 'Annotation'; + loc?: ?Location; + name: Name; + arguments?: ?Array; +} // Type Reference @@ -276,6 +285,7 @@ export type FieldDefinition = { name: Name; arguments: Array; type: Type; + annotations?: ?Array; } export type InputValueDefinition = { diff --git a/src/language/kinds.js b/src/language/kinds.js index 41d28e8fa5..11eb4c4e9c 100644 --- a/src/language/kinds.js +++ b/src/language/kinds.js @@ -42,6 +42,10 @@ export const OBJECT_FIELD = 'ObjectField'; export const DIRECTIVE = 'Directive'; +// Annotation + +export const ANNOTATION = 'Annotation'; + // Types export const NAMED_TYPE = 'NamedType'; diff --git a/src/language/parser.js b/src/language/parser.js index 80daaaa906..d1a8aba0e0 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -34,6 +34,7 @@ import type { ObjectField, Directive, + Annotation, Type, NamedType, @@ -77,6 +78,7 @@ import { OBJECT_FIELD, DIRECTIVE, + ANNOTATION, NAMED_TYPE, LIST_TYPE, @@ -589,6 +591,33 @@ function parseDirective(parser): Directive { }; } +// Implements the parsing rules in the Annotations section. + +/** + * Annotations : Annotation+ + */ +function parseAnnotations(parser): Array { + var annotations = []; + while (peek(parser, TokenKind.AT)) { + annotations.push(parseAnnotation(parser)); + } + return annotations; +} + +/** + * Annotation : @ Name Arguments? + */ +function parseAnnotation(parser): Annotation { + var start = parser.token.start; + expect(parser, TokenKind.AT); + return { + kind: ANNOTATION, + name: parseName(parser), + arguments: parseArguments(parser), + loc: loc(parser, start) + }; +} + // Implements the parsing rules in the Types section. @@ -709,10 +738,11 @@ function parseImplementsInterfaces(parser): Array { } /** - * FieldDefinition : Name ArgumentsDefinition? : Type + * FieldDefinition : Annotations? Name ArgumentsDefinition? : Type */ function parseFieldDefinition(parser): FieldDefinition { var start = parser.token.start; + var annotations = parseAnnotations(parser); var name = parseName(parser); var args = parseArgumentDefs(parser); expect(parser, TokenKind.COLON); @@ -723,6 +753,7 @@ function parseFieldDefinition(parser): FieldDefinition { arguments: args, type, loc: loc(parser, start), + annotations, }; } diff --git a/src/language/printer.js b/src/language/printer.js index eaaa3e511d..6bdd8cd530 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -83,6 +83,11 @@ var printDocASTReducer = { Directive: ({ name, arguments: args }) => '@' + name + wrap('(', join(args, ', '), ')'), + // Annotation + + Annotation: ({ name, arguments: args }) => + '@' + name + wrap('(', join(args, ', '), ')'), + // Type NamedType: ({ name }) => name, @@ -96,8 +101,10 @@ var printDocASTReducer = { wrap('implements ', join(interfaces, ', '), ' ') + block(fields), - FieldDefinition: ({ name, arguments: args, type }) => - name + wrap('(', join(args, ', '), ')') + ': ' + type, + FieldDefinition: ({ name, arguments: args, type, annotations }) => + wrap('', join(annotations, '\n'), '\n') + + name + wrap('(', join(args, ', '), ')') + ': ' + + type, InputValueDefinition: ({ name, type, defaultValue }) => name + ': ' + type + wrap(' = ', defaultValue), diff --git a/src/language/visitor.js b/src/language/visitor.js index 9aa47ef77a..ab58991471 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -33,13 +33,14 @@ export var QueryDocumentKeys = { ObjectField: [ 'name', 'value' ], Directive: [ 'name', 'arguments' ], + Annotation: [ 'name', 'arguments' ], NamedType: [ 'name' ], ListType: [ 'type' ], NonNullType: [ 'type' ], ObjectTypeDefinition: [ 'name', 'interfaces', 'fields' ], - FieldDefinition: [ 'name', 'arguments', 'type' ], + FieldDefinition: [ 'name', 'arguments', 'type', 'annotations' ], InputValueDefinition: [ 'name', 'type', 'defaultValue' ], InterfaceTypeDefinition: [ 'name', 'fields' ], UnionTypeDefinition: [ 'name', 'types' ],