From 80ac72858b62bdb267cb6d70ea43d02bbd1680fd Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Mon, 20 Jul 2015 23:12:30 -0700 Subject: [PATCH] Docs and Alterations to Schema Grammar This PR tackles a few things: 1. Adds doc block illustrating grammar for every parse rule. 2. Changes Union syntax to match what we use in GraphQL spec. 3. Add default value to field arguments, along with test. 4. Edit tests to be of form "expect test-value to be expected-value" 5. Consistency through rules w.r.t `loc` vs `location` and short-hand object definitions. --- src/language/parser.js | 2 +- src/language/schema/__tests__/parser.js | 96 ++++++++++---- src/language/schema/ast.js | 4 +- src/language/schema/parser.js | 158 ++++++++++++++++-------- 4 files changed, 184 insertions(+), 76 deletions(-) diff --git a/src/language/parser.js b/src/language/parser.js index 4b11f2d07c..64b24ca778 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -311,7 +311,7 @@ function parseVariableValue(parser): Value { return parseValue(parser, false); } -function parseConstValue(parser): Value { +export function parseConstValue(parser): Value { return parseValue(parser, true); } diff --git a/src/language/schema/__tests__/parser.js b/src/language/schema/__tests__/parser.js index d4917e50a8..7521696536 100644 --- a/src/language/schema/__tests__/parser.js +++ b/src/language/schema/__tests__/parser.js @@ -50,8 +50,16 @@ function fieldNodeWithArgs(name, type, args, loc) { return { kind: 'FieldDefinition', name: name, - type: type, arguments: args, + type: type, + loc: loc, + }; +} + +function enumValueNode(name, loc) { + return { + kind: 'EnumValueDefinition', + name: nameNode(name, loc), loc: loc, }; } @@ -92,7 +100,7 @@ type Hello { ], loc: loc(1, 31), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple non-null type', () => { @@ -125,7 +133,7 @@ type Hello { ], loc: loc(1, 32), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); @@ -172,14 +180,6 @@ type Hello { expect(printJson(doc)).to.equal(printJson(expected)); }); - function enumValueNode(name, loc) { - return { - kind: 'EnumValueDefinition', - name: nameNode(name, loc), - loc: loc, - }; - } - it('Single value enum', () => { var body = `enum Hello { WORLD }`; var loc = createLocFn(body); @@ -246,7 +246,7 @@ interface Hello { ], loc: loc(1, 36), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple field with arg', () => { @@ -272,6 +272,7 @@ type Hello { kind: 'ArgumentDefinition', name: nameNode('flag', loc(22, 26)), type: typeNode('Boolean', loc(28, 35)), + defaultValue: null, loc: loc(22, 35), } ], @@ -283,7 +284,49 @@ type Hello { ], loc: loc(1, 46), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); + }); + + it('Simple field with arg with default value', () => { + var body = ` +type Hello { + world(flag: Boolean = true): String +}`; + var doc = parseSchema(body); + var loc = createLocFn(body); + var expected = { + kind: 'SchemaDocument', + definitions: [ + { + kind: 'TypeDefinition', + name: nameNode('Hello', loc(6, 11)), + interfaces: [], + fields: [ + fieldNodeWithArgs( + nameNode('world', loc(16, 21)), + typeNode('String', loc(45, 51)), + [ + { + kind: 'ArgumentDefinition', + name: nameNode('flag', loc(22, 26)), + type: typeNode('Boolean', loc(28, 35)), + defaultValue: { + kind: 'BooleanValue', + value: true, + loc: loc(38, 42), + }, + loc: loc(22, 42), + } + ], + loc(16, 51) + ) + ], + loc: loc(1, 53), + } + ], + loc: loc(1, 53), + }; + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple field with list arg', () => { @@ -313,6 +356,7 @@ type Hello { type: typeNode('String', loc(31, 37)), loc: loc(30, 38) }, + defaultValue: null, loc: loc(22, 38), } ], @@ -324,7 +368,7 @@ type Hello { ], loc: loc(1, 49), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple field with two args', () => { @@ -350,12 +394,14 @@ type Hello { kind: 'ArgumentDefinition', name: nameNode('argOne', loc(22, 28)), type: typeNode('Boolean', loc(30, 37)), + defaultValue: null, loc: loc(22, 37), }, { kind: 'ArgumentDefinition', name: nameNode('argTwo', loc(39, 45)), type: typeNode('Int', loc(47, 50)), + defaultValue: null, loc: loc(39, 50), }, ], @@ -367,11 +413,11 @@ type Hello { ], loc: loc(1, 61), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple union', () => { - var body = `union Hello { World }`; + var body = `union Hello = World`; var doc = parseSchema(body); var loc = createLocFn(body); var expected = { @@ -381,16 +427,16 @@ type Hello { kind: 'UnionDefinition', name: nameNode('Hello', loc(6, 11)), types: [typeNode('World', loc(14, 19))], - loc: loc(0, 21), + loc: loc(0, 19), } ], - loc: loc(0, 21), + loc: loc(0, 19), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Union with two types', () => { - var body = `union Hello { Wo | Rld }`; + var body = `union Hello = Wo | Rld`; var doc = parseSchema(body); var loc = createLocFn(body); var expected = { @@ -403,12 +449,12 @@ type Hello { typeNode('Wo', loc(14, 16)), typeNode('Rld', loc(19, 22)), ], - loc: loc(0, 24), + loc: loc(0, 22), } ], - loc: loc(0, 24), + loc: loc(0, 22), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Scalar', () => { @@ -426,7 +472,7 @@ type Hello { ], loc: loc(0, 12), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple input object', () => { @@ -454,7 +500,7 @@ input Hello { ], loc: loc(1, 32), }; - expect(printJson(expected)).to.equal(printJson(doc)); + expect(printJson(doc)).to.equal(printJson(expected)); }); it('Simple input object with args should fail', () => { diff --git a/src/language/schema/ast.js b/src/language/schema/ast.js index 591acc04dd..f861ab98a2 100644 --- a/src/language/schema/ast.js +++ b/src/language/schema/ast.js @@ -11,6 +11,7 @@ import type { Location, Name, + Value, Type, NamedType } from '../ast'; @@ -41,8 +42,8 @@ export type FieldDefinition = { kind: 'FieldDefinition'; loc?: ?Location; name: Name; - type: Type; arguments: Array; + type: Type; } export type ArgumentDefinition = { @@ -50,6 +51,7 @@ export type ArgumentDefinition = { loc?: ?Location; name: Name; type: Type; + defaultValue?: ?Value; } export type InterfaceDefinition = { diff --git a/src/language/schema/parser.js b/src/language/schema/parser.js index c45c0ab3a9..12ad7afc7c 100644 --- a/src/language/schema/parser.js +++ b/src/language/schema/parser.js @@ -28,6 +28,7 @@ import { import { parseName, + parseConstValue, parseType, parseNamedType, } from '../parser'; @@ -72,6 +73,9 @@ export function parseSchema( return parseSchemaDocument(parser); } +/** + * SchemaDocument : SchemaDefinition+ + */ function parseSchemaDocument(parser): SchemaDocument { var start = parser.token.start; var definitions = []; @@ -86,6 +90,15 @@ function parseSchemaDocument(parser): SchemaDocument { }; } +/** + * SchemaDefinition : + * - TypeDefinition + * - InterfaceDefinition + * - UnionDefinition + * - ScalarDefinition + * - EnumDefinition + * - InputObjectDefinition + */ function parseSchemaDefinition(parser): SchemaDefinition { if (!peek(parser, TokenKind.NAME)) { throw unexpected(parser); @@ -108,26 +121,35 @@ function parseSchemaDefinition(parser): SchemaDefinition { } } +/** + * TypeDefinition : TypeName ImplementsInterfaces? { FieldDefinition+ } + * + * TypeName : Name + */ function parseTypeDefinition(parser): TypeDefinition { var start = parser.token.start; expectKeyword(parser, 'type'); var name = parseName(parser); - var interfaces = parseInterfaces(parser); + var interfaces = parseImplementsInterfaces(parser); var fields = any( parser, TokenKind.BRACE_L, parseFieldDefinition, - TokenKind.BRACE_R); + TokenKind.BRACE_R + ); return { kind: TYPE_DEFINITION, name, - interfaces: interfaces, - fields: fields, + interfaces, + fields, loc: loc(parser, start), }; } -function parseInterfaces(parser): Array { +/** + * ImplementsInterfaces : `implements` NamedType+ + */ +function parseImplementsInterfaces(parser): Array { var types = []; if (parser.token.value === 'implements') { advance(parser); @@ -138,48 +160,64 @@ function parseInterfaces(parser): Array { return types; } +/** + * FieldDefinition : FieldName ArgumentsDefinition? : Type + * + * FieldName : Name + */ function parseFieldDefinition(parser): FieldDefinition { var start = parser.token.start; var name = parseName(parser); var args = parseArgumentDefs(parser); expect(parser, TokenKind.COLON); var type = parseType(parser); - var location = loc(parser, start); return { kind: FIELD_DEFINITION, - name: name, - type: type, + name, arguments: args, - loc: location, + type, + loc: loc(parser, start), }; } +/** + * ArgumentsDefinition : ( ArgumentDefinition+ ) + */ function parseArgumentDefs(parser): Array { if (!peek(parser, TokenKind.PAREN_L)) { return []; } - return many( - parser, - TokenKind.PAREN_L, - parseArgumentDef, - TokenKind.PAREN_R - ); + return many(parser, TokenKind.PAREN_L, parseArgumentDef, TokenKind.PAREN_R); } +/** + * ArgumentDefinition : ArgumentName : Value[Const] DefaultValue? + * + * ArgumentName : Name + * + * DefaultValue : = Value[Const] + */ function parseArgumentDef(parser): ArgumentDefinition { var start = parser.token.start; var name = parseName(parser); expect(parser, TokenKind.COLON); var type = parseType(parser, false); - var location = loc(parser, start); + var defaultValue = null; + if (skip(parser, TokenKind.EQUALS)) { + defaultValue = parseConstValue(parser); + } return { kind: ARGUMENT_DEFINITION, - name: name, - type: type, - loc: location, + name, + type, + defaultValue, + loc: loc(parser, start), }; } +/** + * InterfaceDefinition : `interface` TypeName { Fields+ } + */ function parseInterfaceDefinition(parser): InterfaceDefinition { var start = parser.token.start; expectKeyword(parser, 'interface'); @@ -188,51 +226,63 @@ function parseInterfaceDefinition(parser): InterfaceDefinition { parser, TokenKind.BRACE_L, parseFieldDefinition, - TokenKind.BRACE_R); + TokenKind.BRACE_R + ); return { kind: INTERFACE_DEFINITION, name, - fields: fields, + fields, loc: loc(parser, start), }; } +/** + * UnionDefinition : `union` TypeName = UnionMembers + */ function parseUnionDefinition(parser): UnionDefinition { var start = parser.token.start; expectKeyword(parser, 'union'); var name = parseName(parser); + expect(parser, TokenKind.EQUALS); var types = parseUnionMembers(parser); - var location = loc(parser, start); return { kind: UNION_DEFINITION, - name: name, - types: types, - loc: location, + name, + types, + loc: loc(parser, start), }; } -function parseUnionMembers(parser) { - expect(parser, TokenKind.BRACE_L); - var members = [(parseNamedType(parser))]; - while (!skip(parser, TokenKind.BRACE_R)) { - expect(parser, TokenKind.PIPE); - members.push((parseNamedType(parser))); - } +/** + * UnionMembers : + * - NamedType + * - UnionMembers | NamedType + */ +function parseUnionMembers(parser): Array { + var members = []; + do { + members.push(parseNamedType(parser)); + } while (skip(parser, TokenKind.PIPE)); return members; } +/** + * ScalarDefinition : `scalar` TypeName + */ function parseScalarDefinition(parser): ScalarDefinition { var start = parser.token.start; expectKeyword(parser, 'scalar'); var name = parseName(parser); - var location = loc(parser, start); return { kind: SCALAR_DEFINITION, - name: name, - loc: location, + name, + loc: loc(parser, start), }; } +/** + * EnumDefinition : `enum` TypeName { EnumValueDefinition+ } + */ function parseEnumDefinition(parser): EnumDefinition { var start = parser.token.start; expectKeyword(parser, 'enum'); @@ -241,27 +291,34 @@ function parseEnumDefinition(parser): EnumDefinition { parser, TokenKind.BRACE_L, parseEnumValueDefinition, - TokenKind.BRACE_R); - var location = loc(parser, start); + TokenKind.BRACE_R + ); return { kind: ENUM_DEFINITION, - name: name, - values: values, - loc: location, + name, + values, + loc: loc(parser, start), }; } +/** + * EnumValueDefinition : EnumValue + * + * EnumValue : Name + */ function parseEnumValueDefinition(parser) : EnumValueDefinition { var start = parser.token.start; var name = parseName(parser); - var location = loc(parser, start); return { kind: ENUM_VALUE_DEFINITION, - name: name, - loc: location, + name, + loc: loc(parser, start), }; } +/** + * InputObjectDefinition : `input` TypeName { InputFieldDefinition+ } + */ function parseInputObjectDefinition(parser): InputObjectDefinition { var start = parser.token.start; expectKeyword(parser, 'input'); @@ -270,25 +327,28 @@ function parseInputObjectDefinition(parser): InputObjectDefinition { parser, TokenKind.BRACE_L, parseInputFieldDefinition, - TokenKind.BRACE_R); + TokenKind.BRACE_R + ); return { kind: INPUT_OBJECT_DEFINITION, name, - fields: fields, + fields, loc: loc(parser, start), }; } +/** + * InputFieldDefinition : FieldName : Type + */ function parseInputFieldDefinition(parser): InputFieldDefinition { var start = parser.token.start; var name = parseName(parser); expect(parser, TokenKind.COLON); var type = parseType(parser); - var location = loc(parser, start); return { kind: INPUT_FIELD_DEFINITION, - name: name, - type: type, - loc: location, + name, + type, + loc: loc(parser, start), }; }