diff --git a/src/execution/__tests__/executor.js b/src/execution/__tests__/executor.js index 7659e0c191..26cc5c4c57 100644 --- a/src/execution/__tests__/executor.js +++ b/src/execution/__tests__/executor.js @@ -647,4 +647,32 @@ describe('Execute: Handles basic execution tasks', () => { ]); }); + it('fails to execute a query containing a type definition', async () => { + var query = parse(` + { foo } + + type Query { foo: String } + `); + + var schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + foo: { type: GraphQLString } + } + }) + }); + + var caughtError; + try { + await execute(schema, query); + } catch (error) { + caughtError = error; + } + + expect(caughtError).to.deep.equal({ + message: 'GraphQL cannot execute a request containing a ObjectDefinition.' + }); + }); + }); diff --git a/src/execution/execute.js b/src/execution/execute.js index 9c942413d4..177d1191a6 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -174,6 +174,10 @@ function buildExecutionContext( case Kind.FRAGMENT_DEFINITION: fragments[statement.name.value] = statement; break; + default: throw new GraphQLError( + `GraphQL cannot execute a request containing a ${statement.kind}.`, + statement + ); } }); if (!operationName && Object.keys(operations).length !== 1) { diff --git a/src/language/schema/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql similarity index 100% rename from src/language/schema/__tests__/schema-kitchen-sink.graphql rename to src/language/__tests__/schema-kitchen-sink.graphql diff --git a/src/language/schema/__tests__/parser.js b/src/language/__tests__/schema-parser.js similarity index 86% rename from src/language/schema/__tests__/parser.js rename to src/language/__tests__/schema-parser.js index f80c00b5bf..0f8836cbf2 100644 --- a/src/language/schema/__tests__/parser.js +++ b/src/language/__tests__/schema-parser.js @@ -9,7 +9,7 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; -import { parseSchemaIntoAST } from '../parser'; +import { parse } from '../parser'; function createLocFn(body) { return (start, end) => ({ @@ -80,13 +80,13 @@ describe('Schema Parser', () => { type Hello { world: String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(6, 11)), interfaces: [], fields: [ @@ -110,12 +110,12 @@ type Hello { world: String! }`; var loc = createLocFn(body); - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(6, 11)), interfaces: [], fields: [ @@ -141,12 +141,12 @@ type Hello { it('Simple type inheriting interface', () => { var body = `type Hello implements World { }`; var loc = createLocFn(body); - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(5, 10)), interfaces: [ typeNode('World', loc(22, 27)) ], fields: [], @@ -161,12 +161,12 @@ type Hello { it('Simple type inheriting multiple interfaces', () => { var body = `type Hello implements Wo, rld { }`; var loc = createLocFn(body); - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(5, 10)), interfaces: [ typeNode('Wo', loc(22, 24)), @@ -184,9 +184,9 @@ type Hello { it('Single value enum', () => { var body = `enum Hello { WORLD }`; var loc = createLocFn(body); - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'EnumDefinition', @@ -203,9 +203,9 @@ type Hello { it('Double value enum', () => { var body = `enum Hello { WO, RLD }`; var loc = createLocFn(body); - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'EnumDefinition', @@ -227,10 +227,10 @@ type Hello { interface Hello { world: String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'InterfaceDefinition', @@ -255,13 +255,13 @@ interface Hello { type Hello { world(flag: Boolean): String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(6, 11)), interfaces: [], fields: [ @@ -292,13 +292,13 @@ type Hello { type Hello { world(flag: Boolean = true): String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(6, 11)), interfaces: [], fields: [ @@ -333,13 +333,13 @@ type Hello { type Hello { world(things: [String]): String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(6, 11)), interfaces: [], fields: [ @@ -374,13 +374,13 @@ type Hello { type Hello { world(argOne: Boolean, argTwo: Int): String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { - kind: 'TypeDefinition', + kind: 'ObjectDefinition', name: nameNode('Hello', loc(6, 11)), interfaces: [], fields: [ @@ -414,10 +414,10 @@ type Hello { it('Simple union', () => { var body = `union Hello = World`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'UnionDefinition', @@ -433,10 +433,10 @@ type Hello { it('Union with two types', () => { var body = `union Hello = Wo | Rld`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'UnionDefinition', @@ -455,10 +455,10 @@ type Hello { it('Scalar', () => { var body = `scalar Hello`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'ScalarDefinition', @@ -476,10 +476,10 @@ type Hello { input Hello { world: String }`; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); var loc = createLocFn(body); var expected = { - kind: 'SchemaDocument', + kind: 'Document', definitions: [ { kind: 'InputObjectDefinition', @@ -505,16 +505,7 @@ input Hello { input Hello { world(foo: Int): String }`; - expect(() => parseSchemaIntoAST(body)).to.throw('Error'); + expect(() => parse(body)).to.throw('Error'); }); - it('Reject query keywords', () => { - var body = `query Foo { field }`; - expect(() => parseSchemaIntoAST(body)).to.throw('Error'); - }); - - it('Reject query shorthand', () => { - var body = `{ field }`; - expect(() => parseSchemaIntoAST(body)).to.throw('Error'); - }); }); diff --git a/src/language/schema/__tests__/printer.js b/src/language/__tests__/schema-printer.js similarity index 82% rename from src/language/schema/__tests__/printer.js rename to src/language/__tests__/schema-printer.js index 2f3cc03bef..3d993e43c9 100644 --- a/src/language/schema/__tests__/printer.js +++ b/src/language/__tests__/schema-printer.js @@ -11,8 +11,8 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; import { readFileSync } from 'fs'; import { join } from 'path'; -import { parseSchemaIntoAST } from '../parser'; -import { printSchema } from '../printer'; +import { parse } from '../parser'; +import { print } from '../printer'; describe('Printer', () => { @@ -21,12 +21,12 @@ describe('Printer', () => { kind: 'ScalarDefinition', name: { kind: 'Name', value: 'foo' } }; - expect(printSchema(ast)).to.equal('scalar foo'); + expect(print(ast)).to.equal('scalar foo'); }); it('produces helpful error messages', () => { var badAst1 = { random: 'Data' }; - expect(() => printSchema(badAst1)).to.throw( + expect(() => print(badAst1)).to.throw( 'Invalid AST Node: {"random":"Data"}' ); }); @@ -37,17 +37,17 @@ describe('Printer', () => { ); it('does not alter ast', () => { - var ast = parseSchemaIntoAST(kitchenSink); + var ast = parse(kitchenSink); var astCopy = JSON.parse(JSON.stringify(ast)); - printSchema(ast); + print(ast); expect(ast).to.deep.equal(astCopy); }); it('prints kitchen sink', () => { - var ast = parseSchemaIntoAST(kitchenSink); + var ast = parse(kitchenSink); - var printed = printSchema(ast); + var printed = print(ast); expect(printed).to.equal( `type Foo implements Bar { diff --git a/src/language/ast.js b/src/language/ast.js index 3ca2dd07b0..b0ca27b56c 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -46,6 +46,15 @@ export type Node = Name | Directive | ListType | NonNullType + | ObjectDefinition + | FieldDefinition + | InputValueDefinition + | InterfaceDefinition + | UnionDefinition + | ScalarDefinition + | EnumDefinition + | EnumValueDefinition + | InputObjectDefinition // Name @@ -65,6 +74,7 @@ export type Document = { export type Definition = OperationDefinition | FragmentDefinition + | TypeDefinition export type OperationDefinition = { kind: 'OperationDefinition'; @@ -216,7 +226,7 @@ export type Directive = { } -// Types +// Type Reference export type Type = NamedType | ListType @@ -239,3 +249,77 @@ export type NonNullType = { loc?: ?Location; type: NamedType | ListType; } + +// Type Definition + +export type TypeDefinition = + ObjectDefinition | + InterfaceDefinition | + UnionDefinition | + ScalarDefinition | + EnumDefinition | + InputObjectDefinition + +export type ObjectDefinition = { + kind: 'ObjectDefinition'; + loc?: ?Location; + name: Name; + interfaces?: ?Array; + fields: Array; +} + +export type FieldDefinition = { + kind: 'FieldDefinition'; + loc?: ?Location; + name: Name; + arguments: Array; + type: Type; +} + +export type InputValueDefinition = { + kind: 'InputValueDefinition'; + loc?: ?Location; + name: Name; + type: Type; + defaultValue?: ?Value; +} + +export type InterfaceDefinition = { + kind: 'InterfaceDefinition'; + loc?: ?Location; + name: Name; + fields: Array; +} + +export type UnionDefinition = { + kind: 'UnionDefinition'; + loc?: ?Location; + name: Name; + types: Array; +} + +export type ScalarDefinition = { + kind: 'ScalarDefinition'; + loc?: ?Location; + name: Name; +} + +export type EnumDefinition = { + kind: 'EnumDefinition'; + loc?: ?Location; + name: Name; + values: Array; +} + +export type EnumValueDefinition = { + kind: 'EnumValueDefinition'; + loc?: ?Location; + name: Name; +} + +export type InputObjectDefinition = { + kind: 'InputObjectDefinition'; + loc?: ?Location; + name: Name; + fields: Array; +} diff --git a/src/language/kinds.js b/src/language/kinds.js index 873820bf74..667ad18639 100644 --- a/src/language/kinds.js +++ b/src/language/kinds.js @@ -47,3 +47,15 @@ export const DIRECTIVE = 'Directive'; export const NAMED_TYPE = 'NamedType'; export const LIST_TYPE = 'ListType'; export const NON_NULL_TYPE = 'NonNullType'; + +// Type Definitions + +export const OBJECT_DEFINITION = 'ObjectDefinition'; +export const FIELD_DEFINITION = 'FieldDefinition'; +export const INPUT_VALUE_DEFINITION = 'InputValueDefinition'; +export const INTERFACE_DEFINITION = 'InterfaceDefinition'; +export const UNION_DEFINITION = 'UnionDefinition'; +export const SCALAR_DEFINITION = 'ScalarDefinition'; +export const ENUM_DEFINITION = 'EnumDefinition'; +export const ENUM_VALUE_DEFINITION = 'EnumValueDefinition'; +export const INPUT_OBJECT_DEFINITION = 'InputObjectDefinition'; diff --git a/src/language/parser.js b/src/language/parser.js index 4ba2a1ecd1..255d578575 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -9,12 +9,14 @@ import { Source } from './source'; import { syntaxError } from '../error'; -import { TokenKind } from './lexer'; +import { lex, TokenKind, getTokenKindDesc, getTokenDesc } from './lexer'; +import type { Token } from './lexer'; import type { Name, Variable, Document, + Definition, OperationDefinition, VariableDefinition, SelectionSet, @@ -35,6 +37,17 @@ import type { Type, NamedType, + + TypeDefinition, + ObjectDefinition, + FieldDefinition, + InputValueDefinition, + InterfaceDefinition, + UnionDefinition, + ScalarDefinition, + EnumDefinition, + EnumValueDefinition, + InputObjectDefinition, } from './ast'; import { @@ -66,21 +79,37 @@ import { NAMED_TYPE, LIST_TYPE, NON_NULL_TYPE, + + OBJECT_DEFINITION, + FIELD_DEFINITION, + INPUT_VALUE_DEFINITION, + INTERFACE_DEFINITION, + UNION_DEFINITION, + SCALAR_DEFINITION, + ENUM_DEFINITION, + ENUM_VALUE_DEFINITION, + INPUT_OBJECT_DEFINITION, } from './kinds'; -import { - makeParser, - peek, - skip, - loc, - any, - many, - expect, - unexpected, - expectKeyword, - advance, - ParseOptions, -} from './parserCore'; + +/** + * Configuration options to control parser behavior + */ +export type ParseOptions = { + /** + * By default, the parser creates AST nodes that know the location + * in the source that they correspond to. This configuration flag + * disables that behavior for performance or testing. + */ + noLocation?: boolean, + + /** + * By default, the parser creates AST nodes that contain a reference + * to the source that they were created from. This configuration flag + * disables that behavior for performance or testing. + */ + noSource?: boolean, +} /** * Given a GraphQL source, parses it into a Document. @@ -114,7 +143,7 @@ export function parseValue( /** * Converts a name lex token into a name parse node. */ -export function parseName(parser): Name { +function parseName(parser): Name { var token = expect(parser, TokenKind.NAME); return { kind: NAME, @@ -125,24 +154,17 @@ export function parseName(parser): Name { // Implements the parsing rules in the Document section. +/** + * Document : Definition+ + */ function parseDocument(parser): Document { var start = parser.token.start; + var definitions = []; do { - if (peek(parser, TokenKind.BRACE_L)) { - definitions.push(parseOperationDefinition(parser)); - } else if (peek(parser, TokenKind.NAME)) { - if (parser.token.value === 'query' || parser.token.value === 'mutation') { - definitions.push(parseOperationDefinition(parser)); - } else if (parser.token.value === 'fragment') { - definitions.push(parseFragmentDefinition(parser)); - } else { - throw unexpected(parser); - } - } else { - throw unexpected(parser); - } + definitions.push(parseDefinition(parser)); } while (!skip(parser, TokenKind.EOF)); + return { kind: DOCUMENT, definitions, @@ -150,9 +172,46 @@ function parseDocument(parser): Document { }; } +/** + * Definition : + * - OperationDefinition + * - FragmentDefinition + * - TypeDefinition + */ +function parseDefinition(parser): Definition { + if (peek(parser, TokenKind.BRACE_L)) { + return parseOperationDefinition(parser); + } + + if (peek(parser, TokenKind.NAME)) { + switch (parser.token.value) { + case 'query': + case 'mutation': return parseOperationDefinition(parser); + + case 'fragment': return parseFragmentDefinition(parser); + + case 'type': + case 'interface': + case 'union': + case 'scalar': + case 'enum': + case 'input': return parseTypeDefinition(parser); + } + } + + throw unexpected(parser); +} + // Implements the parsing rules in the Operations section. +/** + * OperationDefinition : + * - SelectionSet + * - OperationType Name VariableDefinitions? Directives? SelectionSet + * + * OperationType : one of query mutation + */ function parseOperationDefinition(parser): OperationDefinition { var start = parser.token.start; if (peek(parser, TokenKind.BRACE_L)) { @@ -179,6 +238,9 @@ function parseOperationDefinition(parser): OperationDefinition { }; } +/** + * VariableDefinitions : ( VariableDefinition+ ) + */ function parseVariableDefinitions(parser): Array { return peek(parser, TokenKind.PAREN_L) ? many( @@ -190,6 +252,9 @@ function parseVariableDefinitions(parser): Array { []; } +/** + * VariableDefinition : Variable : Type DefaultValue? + */ function parseVariableDefinition(parser): VariableDefinition { var start = parser.token.start; return { @@ -202,6 +267,9 @@ function parseVariableDefinition(parser): VariableDefinition { }; } +/** + * Variable : $ Name + */ function parseVariable(parser): Variable { var start = parser.token.start; expect(parser, TokenKind.DOLLAR); @@ -212,6 +280,9 @@ function parseVariable(parser): Variable { }; } +/** + * SelectionSet : { Selection+ } + */ function parseSelectionSet(parser): SelectionSet { var start = parser.token.start; return { @@ -222,6 +293,12 @@ function parseSelectionSet(parser): SelectionSet { }; } +/** + * Selection : + * - Field + * - FragmentSpread + * - InlineFragment + */ function parseSelection(parser): Selection { return peek(parser, TokenKind.SPREAD) ? parseFragment(parser) : @@ -229,7 +306,9 @@ function parseSelection(parser): Selection { } /** - * Corresponds to both Field and Alias in the spec + * Field : Alias? Name Arguments? Directives? SelectionSet? + * + * Alias : Name : */ function parseField(parser): Field { var start = parser.token.start; @@ -257,12 +336,18 @@ function parseField(parser): Field { }; } +/** + * Arguments : ( Argument+ ) + */ function parseArguments(parser): Array { return peek(parser, TokenKind.PAREN_L) ? many(parser, TokenKind.PAREN_L, parseArgument, TokenKind.PAREN_R) : []; } +/** + * Argument : Name : Value + */ function parseArgument(parser): Argument { var start = parser.token.start; return { @@ -277,7 +362,11 @@ function parseArgument(parser): Argument { // Implements the parsing rules in the Fragments section. /** - * Corresponds to both FragmentSpread and InlineFragment in the spec + * Corresponds to both FragmentSpread and InlineFragment in the spec. + * + * FragmentSpread : ... FragmentName Directives? + * + * InlineFragment : ... on TypeCondition Directives? SelectionSet */ function parseFragment(parser): FragmentSpread | InlineFragment { var start = parser.token.start; @@ -300,13 +389,12 @@ function parseFragment(parser): FragmentSpread | InlineFragment { }; } -function parseFragmentName(parser): Name { - if (parser.token.value === 'on') { - throw unexpected(parser); - } - return parseName(parser); -} - +/** + * FragmentDefinition : + * - fragment FragmentName on TypeCondition Directives? SelectionSet + * + * TypeCondition : NamedType + */ function parseFragmentDefinition(parser): FragmentDefinition { var start = parser.token.start; expectKeyword(parser, 'fragment'); @@ -320,17 +408,34 @@ function parseFragmentDefinition(parser): FragmentDefinition { }; } - -// Implements the parsing rules in the Values section. - -export function parseConstValue(parser): Value { - return parseValueLiteral(parser, true); +/** + * FragmentName : Name but not `on` + */ +function parseFragmentName(parser): Name { + if (parser.token.value === 'on') { + throw unexpected(parser); + } + return parseName(parser); } -function parseValueValue(parser): Value { - return parseValueLiteral(parser, false); -} +// Implements the parsing rules in the Values section. + +/** + * Value[Const] : + * - [~Const] Variable + * - IntValue + * - FloatValue + * - StringValue + * - BooleanValue + * - EnumValue + * - ListValue[?Const] + * - ObjectValue[?Const] + * + * BooleanValue : one of `true` `false` + * + * EnumValue : Name but not `true`, `false` or `null` + */ function parseValueLiteral(parser, isConst: boolean): Value { var token = parser.token; switch (token.kind) { @@ -385,6 +490,19 @@ function parseValueLiteral(parser, isConst: boolean): Value { throw unexpected(parser); } +export function parseConstValue(parser): Value { + return parseValueLiteral(parser, true); +} + +function parseValueValue(parser): Value { + return parseValueLiteral(parser, false); +} + +/** + * ListValue[Const] : + * - [ ] + * - [ Value[?Const]+ ] + */ function parseList(parser, isConst: boolean): ListValue { var start = parser.token.start; var item = isConst ? parseConstValue : parseValueValue; @@ -395,6 +513,11 @@ function parseList(parser, isConst: boolean): ListValue { }; } +/** + * ObjectValue[Const] : + * - { } + * - { ObjectField[?Const]+ } + */ function parseObject(parser, isConst: boolean): ObjectValue { var start = parser.token.start; expect(parser, TokenKind.BRACE_L); @@ -410,6 +533,9 @@ function parseObject(parser, isConst: boolean): ObjectValue { }; } +/** + * ObjectField[Const] : Name : Value[?Const] + */ function parseObjectField( parser, isConst: boolean, @@ -434,8 +560,12 @@ function parseObjectField( }; } + // Implements the parsing rules in the Directives section. +/** + * Directives : Directive+ + */ function parseDirectives(parser): Array { var directives = []; while (peek(parser, TokenKind.AT)) { @@ -444,6 +574,9 @@ function parseDirectives(parser): Array { return directives; } +/** + * Directive : @ Name Arguments? + */ function parseDirective(parser): Directive { var start = parser.token.start; expect(parser, TokenKind.AT); @@ -459,7 +592,10 @@ function parseDirective(parser): Directive { // Implements the parsing rules in the Types section. /** - * Handles the Type: NamedType, ListType, and NonNullType parsing rules. + * Type : + * - NamedType + * - ListType + * - NonNullType */ export function parseType(parser): Type { var start = parser.token.start; @@ -485,6 +621,9 @@ export function parseType(parser): Type { return type; } +/** + * NamedType : Name + */ export function parseNamedType(parser): NamedType { var start = parser.token.start; return { @@ -493,3 +632,399 @@ export function parseNamedType(parser): NamedType { loc: loc(parser, start) }; } + + +// Implements the parsing rules in the Type Definition section. + +/** + * TypeDefinition : + * - ObjectDefinition + * - InterfaceDefinition + * - UnionDefinition + * - ScalarDefinition + * - EnumDefinition + * - InputObjectDefinition + */ +function parseTypeDefinition(parser): TypeDefinition { + if (!peek(parser, TokenKind.NAME)) { + throw unexpected(parser); + } + switch (parser.token.value) { + case 'type': + return parseObjectDefinition(parser); + case 'interface': + return parseInterfaceDefinition(parser); + case 'union': + return parseUnionDefinition(parser); + case 'scalar': + return parseScalarDefinition(parser); + case 'enum': + return parseEnumDefinition(parser); + case 'input': + return parseInputObjectDefinition(parser); + default: + throw unexpected(parser); + } +} + +/** + * ObjectDefinition : type TypeName ImplementsInterfaces? { FieldDefinition+ } + * + * TypeName : Name + */ +function parseObjectDefinition(parser): ObjectDefinition { + var start = parser.token.start; + expectKeyword(parser, 'type'); + var name = parseName(parser); + var interfaces = parseImplementsInterfaces(parser); + var fields = any( + parser, + TokenKind.BRACE_L, + parseFieldDefinition, + TokenKind.BRACE_R + ); + return { + kind: OBJECT_DEFINITION, + name, + interfaces, + fields, + loc: loc(parser, start), + }; +} + +/** + * ImplementsInterfaces : `implements` NamedType+ + */ +function parseImplementsInterfaces(parser): Array { + var types = []; + if (parser.token.value === 'implements') { + advance(parser); + do { + types.push(parseNamedType(parser)); + } while (!peek(parser, TokenKind.BRACE_L)); + } + 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); + return { + kind: FIELD_DEFINITION, + name, + arguments: args, + type, + loc: loc(parser, start), + }; +} + +/** + * ArgumentsDefinition : ( InputValueDefinition+ ) + */ +function parseArgumentDefs(parser): Array { + if (!peek(parser, TokenKind.PAREN_L)) { + return []; + } + return many(parser, TokenKind.PAREN_L, parseInputValueDef, TokenKind.PAREN_R); +} + +/** + * InputValueDefinition : Name : Type DefaultValue? + * + * DefaultValue : = Value[Const] + */ +function parseInputValueDef(parser): InputValueDefinition { + var start = parser.token.start; + var name = parseName(parser); + expect(parser, TokenKind.COLON); + var type = parseType(parser, false); + var defaultValue = null; + if (skip(parser, TokenKind.EQUALS)) { + defaultValue = parseConstValue(parser); + } + return { + kind: INPUT_VALUE_DEFINITION, + name, + type, + defaultValue, + loc: loc(parser, start), + }; +} + +/** + * InterfaceDefinition : interface TypeName { Fields+ } + */ +function parseInterfaceDefinition(parser): InterfaceDefinition { + var start = parser.token.start; + expectKeyword(parser, 'interface'); + var name = parseName(parser); + var fields = any( + parser, + TokenKind.BRACE_L, + parseFieldDefinition, + TokenKind.BRACE_R + ); + return { + kind: INTERFACE_DEFINITION, + name, + 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); + return { + kind: UNION_DEFINITION, + name, + types, + loc: loc(parser, start), + }; +} + +/** + * 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); + return { + kind: SCALAR_DEFINITION, + name, + loc: loc(parser, start), + }; +} + +/** + * EnumDefinition : enum TypeName { EnumValueDefinition+ } + */ +function parseEnumDefinition(parser): EnumDefinition { + var start = parser.token.start; + expectKeyword(parser, 'enum'); + var name = parseName(parser); + var values = many( + parser, + TokenKind.BRACE_L, + parseEnumValueDefinition, + TokenKind.BRACE_R + ); + return { + kind: ENUM_DEFINITION, + name, + values, + loc: loc(parser, start), + }; +} + +/** + * EnumValueDefinition : EnumValue + * + * EnumValue : Name + */ +function parseEnumValueDefinition(parser) : EnumValueDefinition { + var start = parser.token.start; + var name = parseName(parser); + return { + kind: ENUM_VALUE_DEFINITION, + name, + loc: loc(parser, start), + }; +} + +/** + * InputObjectDefinition : input TypeName { InputValueDefinition+ } + */ +function parseInputObjectDefinition(parser): InputObjectDefinition { + var start = parser.token.start; + expectKeyword(parser, 'input'); + var name = parseName(parser); + var fields = any( + parser, + TokenKind.BRACE_L, + parseInputValueDef, + TokenKind.BRACE_R + ); + return { + kind: INPUT_OBJECT_DEFINITION, + name, + fields, + loc: loc(parser, start), + }; +} + + +// Core parsing utility functions + +/** + * Returns the parser object that is used to store state throughout the + * process of parsing. + */ +function makeParser(source: Source, options: ParseOptions) { + var _lexToken = lex(source); + return { + _lexToken, + source, + options, + prevEnd: 0, + token: _lexToken(), + }; +} + +/** + * Returns a location object, used to identify the place in + * the source that created a given parsed object. + */ +function loc(parser, start: number) { + if (parser.options.noLocation) { + return null; + } + if (parser.options.noSource) { + return { start, end: parser.prevEnd }; + } + return { start, end: parser.prevEnd, source: parser.source }; +} + +/** + * Moves the internal parser object to the next lexed token. + */ +function advance(parser): void { + var prevEnd = parser.token.end; + parser.prevEnd = prevEnd; + parser.token = parser._lexToken(prevEnd); +} + +/** + * Determines if the next token is of a given kind + */ +function peek(parser, kind: string): boolean { + return parser.token.kind === kind; +} + +/** + * If the next token is of the given kind, return true after advancing + * the parser. Otherwise, do not change the parser state and return false. + */ +function skip(parser, kind: string): boolean { + var match = parser.token.kind === kind; + if (match) { + advance(parser); + } + return match; +} + +/** + * If the next token is of the given kind, return that token after advancing + * the parser. Otherwise, do not change the parser state and return false. + */ +function expect(parser, kind: string): Token { + var token = parser.token; + if (token.kind === kind) { + advance(parser); + return token; + } + throw syntaxError( + parser.source, + token.start, + `Expected ${getTokenKindDesc(kind)}, found ${getTokenDesc(token)}` + ); +} + +/** + * If the next token is a keyword with the given value, return that token after + * advancing the parser. Otherwise, do not change the parser state and return + * false. + */ +function expectKeyword(parser, value: string): Token { + var token = parser.token; + if (token.kind === TokenKind.NAME && token.value === value) { + advance(parser); + return token; + } + throw syntaxError( + parser.source, + token.start, + `Expected "${value}", found ${getTokenDesc(token)}` + ); +} + +/** + * Helper function for creating an error when an unexpected lexed token + * is encountered. + */ +function unexpected(parser, atToken?: ?Token): Error { + var token = atToken || parser.token; + return syntaxError( + parser.source, + token.start, + `Unexpected ${getTokenDesc(token)}` + ); +} + +/** + * Returns a possibly empty list of parse nodes, determined by + * the parseFn. This list begins with a lex token of openKind + * and ends with a lex token of closeKind. Advances the parser + * to the next lex token after the closing token. + */ +function any( + parser, + openKind: string, + parseFn: (parser: any) => T, + closeKind: string +): Array { + expect(parser, openKind); + var nodes = []; + while (!skip(parser, closeKind)) { + nodes.push(parseFn(parser)); + } + return nodes; +} + +/** + * Returns a non-empty list of parse nodes, determined by + * the parseFn. This list begins with a lex token of openKind + * and ends with a lex token of closeKind. Advances the parser + * to the next lex token after the closing token. + */ +function many( + parser, + openKind: string, + parseFn: (parser: any) => T, + closeKind: string +): Array { + expect(parser, openKind); + var nodes = [ parseFn(parser) ]; + while (!skip(parser, closeKind)) { + nodes.push(parseFn(parser)); + } + return nodes; +} diff --git a/src/language/parserCore.js b/src/language/parserCore.js deleted file mode 100644 index f02ecce41f..0000000000 --- a/src/language/parserCore.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -import { lex, TokenKind, getTokenKindDesc, getTokenDesc } from './lexer'; - -import { Source } from './source'; -import { syntaxError } from '../error'; -import type { Token } from './lexer'; - -/** - * Returns the parser object that is used to store state throughout the - * process of parsing. - */ -export function makeParser(source: Source, options: ParseOptions) { - var _lexToken = lex(source); - return { - _lexToken, - source, - options, - prevEnd: 0, - token: _lexToken(), - }; -} - -/** - * Configuration options to control parser behavior - */ -export type ParseOptions = { - /** - * By default, the parser creates AST nodes that know the location - * in the source that they correspond to. This configuration flag - * disables that behavior for performance or testing. - */ - noLocation?: boolean, - - /** - * By default, the parser creates AST nodes that contain a reference - * to the source that they were created from. This configuration flag - * disables that behavior for performance or testing. - */ - noSource?: boolean, -} - -/** - * Returns a location object, used to identify the place in - * the source that created a given parsed object. - */ -export function loc(parser, start: number) { - if (parser.options.noLocation) { - return null; - } - if (parser.options.noSource) { - return { start, end: parser.prevEnd }; - } - return { start, end: parser.prevEnd, source: parser.source }; -} - -/** - * Moves the internal parser object to the next lexed token. - */ -export function advance(parser): void { - var prevEnd = parser.token.end; - parser.prevEnd = prevEnd; - parser.token = parser._lexToken(prevEnd); -} - -/** - * Determines if the next token is of a given kind - */ -export function peek(parser, kind: string): boolean { - return parser.token.kind === kind; -} - -/** - * If the next token is of the given kind, return true after advancing - * the parser. Otherwise, do not change the parser state and return false. - */ -export function skip(parser, kind: string): boolean { - var match = parser.token.kind === kind; - if (match) { - advance(parser); - } - return match; -} - -/** - * If the next token is of the given kind, return that token after advancing - * the parser. Otherwise, do not change the parser state and return false. - */ -export function expect(parser, kind: string): Token { - var token = parser.token; - if (token.kind === kind) { - advance(parser); - return token; - } - throw syntaxError( - parser.source, - token.start, - `Expected ${getTokenKindDesc(kind)}, found ${getTokenDesc(token)}` - ); -} - -/** - * If the next token is a keyword with the given value, return that token after - * advancing the parser. Otherwise, do not change the parser state and return - * false. - */ -export function expectKeyword(parser, value: string): Token { - var token = parser.token; - if (token.kind === TokenKind.NAME && token.value === value) { - advance(parser); - return token; - } - throw syntaxError( - parser.source, - token.start, - `Expected "${value}", found ${getTokenDesc(token)}` - ); -} - -/** - * Helper export function for creating an error when an unexpected lexed token - * is encountered. - */ -export function unexpected(parser, atToken?: ?Token): Error { - var token = atToken || parser.token; - return syntaxError( - parser.source, - token.start, - `Unexpected ${getTokenDesc(token)}` - ); -} - -/** - * Returns a possibly empty list of parse nodes, determined by - * the parseFn. This list begins with a lex token of openKind - * and ends with a lex token of closeKind. Advances the parser - * to the next lex token after the closing token. - */ -export function any( - parser, - openKind: number, - parseFn: (parser: any) => T, - closeKind: number -): Array { - expect(parser, openKind); - var nodes = []; - while (!skip(parser, closeKind)) { - nodes.push(parseFn(parser)); - } - return nodes; -} - -/** - * Returns a non-empty list of parse nodes, determined by - * the parseFn. This list begins with a lex token of openKind - * and ends with a lex token of closeKind. Advances the parser - * to the next lex token after the closing token. - */ -export function many( - parser, - openKind: number, - parseFn: (parser: any) => T, - closeKind: number -): Array { - expect(parser, openKind); - var nodes = [ parseFn(parser) ]; - while (!skip(parser, closeKind)) { - nodes.push(parseFn(parser)); - } - return nodes; -} diff --git a/src/language/printer.js b/src/language/printer.js index b43c1663c0..073c5918fe 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -17,7 +17,7 @@ export function print(ast) { return visit(ast, { leave: printDocASTReducer }); } -export var printDocASTReducer = { +var printDocASTReducer = { Name: node => node.value, Variable: node => '$' + node.name, @@ -85,13 +85,43 @@ export var printDocASTReducer = { NamedType: ({ name }) => name, ListType: ({ type }) => '[' + type + ']', NonNullType: ({ type }) => type + '!', + + // Type Definitions + + ObjectDefinition: ({ name, interfaces, fields }) => + 'type ' + name + ' ' + + wrap('implements ', join(interfaces, ', '), ' ') + + block(fields), + + FieldDefinition: ({ name, arguments: args, type }) => + name + wrap('(', join(args, ', '), ')') + ': ' + type, + + InputValueDefinition: ({ name, type, defaultValue }) => + name + ': ' + type + wrap(' = ', defaultValue), + + InterfaceDefinition: ({ name, fields }) => + `interface ${name} ${block(fields)}`, + + UnionDefinition: ({ name, types }) => + `union ${name} = ${join(types, ' | ')}`, + + ScalarDefinition: ({ name }) => + `scalar ${name}`, + + EnumDefinition: ({ name, values }) => + `enum ${name} ${block(values)}`, + + EnumValueDefinition: ({ name }) => name, + + InputObjectDefinition: ({ name, fields }) => + `input ${name} ${block(fields)}`, }; /** * Given maybeArray, print an empty string if it is null or empty, otherwise * print all items together separated by separator if provided */ -export function join(maybeArray, separator) { +function join(maybeArray, separator) { return maybeArray ? maybeArray.filter(x => x).join(separator || '') : ''; } @@ -99,7 +129,7 @@ export function join(maybeArray, separator) { * Given maybeArray, print an empty string if it is null or empty, otherwise * print each item on it's own line, wrapped in an indented "{ }" block. */ -export function block(maybeArray) { +function block(maybeArray) { return length(maybeArray) ? indent('{\n' + join(maybeArray, '\n')) + '\n}' : ''; @@ -109,7 +139,7 @@ export function block(maybeArray) { * If maybeString is not null or empty, then wrap with start and end, otherwise * print an empty string. */ -export function wrap(start, maybeString, end) { +function wrap(start, maybeString, end) { return maybeString ? start + maybeString + (end || '') : ''; diff --git a/src/language/schema/README.md b/src/language/schema/README.md deleted file mode 100644 index 964fd3994b..0000000000 --- a/src/language/schema/README.md +++ /dev/null @@ -1,10 +0,0 @@ -GraphQL Schema Language ------------------------ - -The `graphql/language/schema` module is responsible for parsing and operating on -the GraphQL schema definition language. - -```js -import { ... } from 'graphql/language/schema'; // ES6 -var GraphQLSchemaLanguage = require('graphql/language/schema'); // CommonJS -``` diff --git a/src/language/schema/ast.js b/src/language/schema/ast.js deleted file mode 100644 index 156545c137..0000000000 --- a/src/language/schema/ast.js +++ /dev/null @@ -1,95 +0,0 @@ -/* @flow */ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -import type { - Location, - Name, - Value, - Type, - NamedType -} from '../ast'; - -export type SchemaDocument = { - kind: 'SchemaDocument'; - loc?: ?Location; - definitions: Array; -} - -export type SchemaDefinition = - TypeDefinition | - InterfaceDefinition | - UnionDefinition | - ScalarDefinition | - EnumDefinition | - InputObjectDefinition - -export type TypeDefinition = { - kind: 'TypeDefinition'; - loc?: ?Location; - name: Name; - interfaces?: ?Array; - fields: Array; -} - -export type FieldDefinition = { - kind: 'FieldDefinition'; - loc?: ?Location; - name: Name; - arguments: Array; - type: Type; -} - -export type InputValueDefinition = { - kind: 'InputValueDefinition'; - loc?: ?Location; - name: Name; - type: Type; - defaultValue?: ?Value; -} - -export type InterfaceDefinition = { - kind: 'InterfaceDefinition'; - loc?: ?Location; - name: Name; - fields: Array; -} - -export type UnionDefinition = { - kind: 'UnionDefinition'; - loc?: ?Location; - name: Name; - types: Array; -} - -export type ScalarDefinition = { - kind: 'ScalarDefinition'; - loc?: ?Location; - name: Name; -} - -export type EnumDefinition = { - kind: 'EnumDefinition'; - loc?: ?Location; - name: Name; - values: Array; -} - -export type EnumValueDefinition = { - kind: 'EnumValueDefinition'; - loc?: ?Location; - name: Name; -} - -export type InputObjectDefinition = { - kind: 'InputObjectDefinition'; - loc?: ?Location; - name: Name; - fields: Array; -} diff --git a/src/language/schema/index.js b/src/language/schema/index.js deleted file mode 100644 index 84d6b8e272..0000000000 --- a/src/language/schema/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -import * as Kind from './kinds'; -export { Kind }; -export { parseSchemaIntoAST } from './parser'; -export { printSchema } from './printer'; -export { visitSchema } from './visitor'; diff --git a/src/language/schema/kinds.js b/src/language/schema/kinds.js deleted file mode 100644 index 5460322e49..0000000000 --- a/src/language/schema/kinds.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -// Schema - -export const SCHEMA_DOCUMENT = 'SchemaDocument'; -export const TYPE_DEFINITION = 'TypeDefinition'; -export const FIELD_DEFINITION = 'FieldDefinition'; -export const INPUT_VALUE_DEFINITION = 'InputValueDefinition'; -export const INTERFACE_DEFINITION = 'InterfaceDefinition'; -export const UNION_DEFINITION = 'UnionDefinition'; -export const SCALAR_DEFINITION = 'ScalarDefinition'; -export const ENUM_DEFINITION = 'EnumDefinition'; -export const ENUM_VALUE_DEFINITION = 'EnumValueDefinition'; -export const INPUT_OBJECT_DEFINITION = 'InputObjectDefinition'; diff --git a/src/language/schema/parser.js b/src/language/schema/parser.js deleted file mode 100644 index 0c918f9d2c..0000000000 --- a/src/language/schema/parser.js +++ /dev/null @@ -1,334 +0,0 @@ -/* @flow */ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -import { Source } from '../source'; - -import { TokenKind } from '../lexer'; - -import { - ParseOptions, - makeParser, - peek, - skip, - loc, - any, - many, - expect, - unexpected, - expectKeyword, - advance, -} from '../parserCore'; - -import { - parseName, - parseConstValue, - parseType, - parseNamedType, -} from '../parser'; - -import type { NamedType } from '../ast'; - -import type { - SchemaDocument, - SchemaDefinition, - TypeDefinition, - FieldDefinition, - InputValueDefinition, - EnumDefinition, - EnumValueDefinition, - InterfaceDefinition, - UnionDefinition, - InputObjectDefinition, - ScalarDefinition, -} from './ast'; - -import { - SCHEMA_DOCUMENT, - ENUM_DEFINITION, - ENUM_VALUE_DEFINITION, - TYPE_DEFINITION, - INTERFACE_DEFINITION, - FIELD_DEFINITION, - INPUT_VALUE_DEFINITION, - UNION_DEFINITION, - SCALAR_DEFINITION, - INPUT_OBJECT_DEFINITION, -} from './kinds'; - -export function parseSchemaIntoAST( - source: Source | string, - options?: ParseOptions -): SchemaDocument { - var sourceObj = source instanceof Source ? source : new Source(source); - var parser = makeParser(sourceObj, options || {}); - return parseSchemaDocument(parser); -} - -/** - * SchemaDocument : SchemaDefinition+ - */ -function parseSchemaDocument(parser): SchemaDocument { - var start = parser.token.start; - var definitions = []; - do { - definitions.push(parseSchemaDefinition(parser)); - } while (!skip(parser, TokenKind.EOF)); - - return { - kind: SCHEMA_DOCUMENT, - definitions, - loc: loc(parser, start) - }; -} - -/** - * SchemaDefinition : - * - TypeDefinition - * - InterfaceDefinition - * - UnionDefinition - * - ScalarDefinition - * - EnumDefinition - * - InputObjectDefinition - */ -function parseSchemaDefinition(parser): SchemaDefinition { - if (!peek(parser, TokenKind.NAME)) { - throw unexpected(parser); - } - switch (parser.token.value) { - case 'type': - return parseTypeDefinition(parser); - case 'interface': - return parseInterfaceDefinition(parser); - case 'union': - return parseUnionDefinition(parser); - case 'scalar': - return parseScalarDefinition(parser); - case 'enum': - return parseEnumDefinition(parser); - case 'input': - return parseInputObjectDefinition(parser); - default: - throw unexpected(parser); - } -} - -/** - * TypeDefinition : TypeName ImplementsInterfaces? { FieldDefinition+ } - * - * TypeName : Name - */ -function parseTypeDefinition(parser): TypeDefinition { - var start = parser.token.start; - expectKeyword(parser, 'type'); - var name = parseName(parser); - var interfaces = parseImplementsInterfaces(parser); - var fields = any( - parser, - TokenKind.BRACE_L, - parseFieldDefinition, - TokenKind.BRACE_R - ); - return { - kind: TYPE_DEFINITION, - name, - interfaces, - fields, - loc: loc(parser, start), - }; -} - -/** - * ImplementsInterfaces : `implements` NamedType+ - */ -function parseImplementsInterfaces(parser): Array { - var types = []; - if (parser.token.value === 'implements') { - advance(parser); - do { - types.push(parseNamedType(parser)); - } while (!peek(parser, TokenKind.BRACE_L)); - } - 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); - return { - kind: FIELD_DEFINITION, - name, - arguments: args, - type, - loc: loc(parser, start), - }; -} - -/** - * ArgumentsDefinition : ( InputValueDefinition+ ) - */ -function parseArgumentDefs(parser): Array { - if (!peek(parser, TokenKind.PAREN_L)) { - return []; - } - return many(parser, TokenKind.PAREN_L, parseInputValueDef, TokenKind.PAREN_R); -} - -/** - * InputValueDefinition : Name : Value[Const] DefaultValue? - * - * DefaultValue : = Value[Const] - */ -function parseInputValueDef(parser): InputValueDefinition { - var start = parser.token.start; - var name = parseName(parser); - expect(parser, TokenKind.COLON); - var type = parseType(parser, false); - var defaultValue = null; - if (skip(parser, TokenKind.EQUALS)) { - defaultValue = parseConstValue(parser); - } - return { - kind: INPUT_VALUE_DEFINITION, - name, - type, - defaultValue, - loc: loc(parser, start), - }; -} - -/** - * InterfaceDefinition : `interface` TypeName { Fields+ } - */ -function parseInterfaceDefinition(parser): InterfaceDefinition { - var start = parser.token.start; - expectKeyword(parser, 'interface'); - var name = parseName(parser); - var fields = any( - parser, - TokenKind.BRACE_L, - parseFieldDefinition, - TokenKind.BRACE_R - ); - return { - kind: INTERFACE_DEFINITION, - name, - 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); - return { - kind: UNION_DEFINITION, - name, - types, - loc: loc(parser, start), - }; -} - -/** - * 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); - return { - kind: SCALAR_DEFINITION, - name, - loc: loc(parser, start), - }; -} - -/** - * EnumDefinition : `enum` TypeName { EnumValueDefinition+ } - */ -function parseEnumDefinition(parser): EnumDefinition { - var start = parser.token.start; - expectKeyword(parser, 'enum'); - var name = parseName(parser); - var values = many( - parser, - TokenKind.BRACE_L, - parseEnumValueDefinition, - TokenKind.BRACE_R - ); - return { - kind: ENUM_DEFINITION, - name, - values, - loc: loc(parser, start), - }; -} - -/** - * EnumValueDefinition : EnumValue - * - * EnumValue : Name - */ -function parseEnumValueDefinition(parser) : EnumValueDefinition { - var start = parser.token.start; - var name = parseName(parser); - return { - kind: ENUM_VALUE_DEFINITION, - name, - loc: loc(parser, start), - }; -} - -/** - * InputObjectDefinition : `input` TypeName { InputValueDefinition+ } - */ -function parseInputObjectDefinition(parser): InputObjectDefinition { - var start = parser.token.start; - expectKeyword(parser, 'input'); - var name = parseName(parser); - var fields = any( - parser, - TokenKind.BRACE_L, - parseInputValueDef, - TokenKind.BRACE_R - ); - return { - kind: INPUT_OBJECT_DEFINITION, - name, - fields, - loc: loc(parser, start), - }; -} diff --git a/src/language/schema/printer.js b/src/language/schema/printer.js deleted file mode 100644 index 28db8af134..0000000000 --- a/src/language/schema/printer.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -import { visitSchema } from './visitor'; -import { printDocASTReducer, join, block, wrap } from '../printer'; - -/** - * Converts a Schema AST into a string, using one set of reasonable - * formatting rules. - */ -export function printSchema(ast) { - return visitSchema(ast, { leave: printSchemaASTReducer }); -} - -export var printSchemaASTReducer = { - Name: printDocASTReducer.Name, - - // Document - - SchemaDocument: ({ definitions }) => - join(definitions, '\n\n') + '\n', - - TypeDefinition: ({ name, interfaces, fields }) => - 'type ' + name + ' ' + - wrap('implements ', join(interfaces, ', '), ' ') + - block(fields), - - FieldDefinition: ({ name, arguments: args, type }) => - name + wrap('(', join(args, ', '), ')') + ': ' + type, - - InputValueDefinition: ({ name, type, defaultValue }) => - name + ': ' + type + wrap(' = ', defaultValue), - - InterfaceDefinition: ({ name, fields }) => - `interface ${name} ${block(fields)}`, - - UnionDefinition: ({ name, types }) => - `union ${name} = ${join(types, ' | ')}`, - - ScalarDefinition: ({ name }) => - `scalar ${name}`, - - EnumDefinition: ({ name, values }) => - `enum ${name} ${block(values)}`, - - EnumValueDefinition: ({ name }) => name, - - InputObjectDefinition: ({ name, fields }) => - `input ${name} ${block(fields)}`, - - // Value - - IntValue: printDocASTReducer.IntValue, - FloatValue: printDocASTReducer.FloatValue, - StringValue: printDocASTReducer.StringValue, - BooleanValue: printDocASTReducer.BooleanValue, - EnumValue: printDocASTReducer.EnumValue, - ListValue: printDocASTReducer.ListValue, - ObjectValue: printDocASTReducer.ObjectValue, - ObjectField: printDocASTReducer.ObjectField, - - // Type - - NamedType: printDocASTReducer.NamedType, - ListType: printDocASTReducer.ListType, - NonNullType: printDocASTReducer.NonNullType, -}; diff --git a/src/language/schema/visitor.js b/src/language/schema/visitor.js deleted file mode 100644 index e7d24ab821..0000000000 --- a/src/language/schema/visitor.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -import { visit, QueryDocumentKeys } from '../visitor'; - - -export var SchemaKeys = { - Name: QueryDocumentKeys.Name, - - SchemaDocument: [ 'definitions' ], - TypeDefinition: [ 'name', 'interfaces', 'fields' ], - FieldDefinition: [ 'name', 'arguments', 'type' ], - InputValueDefinition: [ 'name', 'type', 'defaultValue' ], - InterfaceDefinition: [ 'name', 'fields' ], - UnionDefinition: [ 'name', 'types' ], - ScalarDefinition: [ 'name' ], - EnumDefinition: [ 'name', 'values' ], - EnumValueDefinition: [ 'name' ], - InputObjectDefinition: [ 'name', 'fields' ], - - IntValue: QueryDocumentKeys.IntValue, - FloatValue: QueryDocumentKeys.FloatValue, - StringValue: QueryDocumentKeys.StringValue, - BooleanValue: QueryDocumentKeys.BooleanValue, - EnumValue: QueryDocumentKeys.EnumValue, - ListValue: QueryDocumentKeys.ListValue, - ObjectValue: QueryDocumentKeys.ObjectValue, - ObjectField: QueryDocumentKeys.ObjectField, - - NamedType: QueryDocumentKeys.NamedType, - ListType: QueryDocumentKeys.ListType, - NonNullType: QueryDocumentKeys.NonNullType, -}; - -export function visitSchema(root, visitor, keys) { - return visit(root, visitor, keys || SchemaKeys); -} diff --git a/src/language/visitor.js b/src/language/visitor.js index a6d18db469..44b40f4fcb 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -37,6 +37,16 @@ export var QueryDocumentKeys = { NamedType: [ 'name' ], ListType: [ 'type' ], NonNullType: [ 'type' ], + + ObjectDefinition: [ 'name', 'interfaces', 'fields' ], + FieldDefinition: [ 'name', 'arguments', 'type' ], + InputValueDefinition: [ 'name', 'type', 'defaultValue' ], + InterfaceDefinition: [ 'name', 'fields' ], + UnionDefinition: [ 'name', 'types' ], + ScalarDefinition: [ 'name' ], + EnumDefinition: [ 'name', 'values' ], + EnumValueDefinition: [ 'name' ], + InputObjectDefinition: [ 'name', 'fields' ], }; export const BREAK = {}; diff --git a/src/tools/print-schema/src/index.js b/src/tools/print-schema/src/index.js index ead111d23c..58cd001419 100644 --- a/src/tools/print-schema/src/index.js +++ b/src/tools/print-schema/src/index.js @@ -8,8 +8,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -import { parseSchemaIntoAST } - from '../../../language/schema/'; +import { parse } + from '../../../language/'; import { buildASTSchema, introspectionQuery } from '../../../utilities/'; import { graphql } @@ -43,7 +43,7 @@ export async function executeTool() { } var body = await fs.readFileAsync(argDict.file, 'utf8'); - var ast = parseSchemaIntoAST(body); + var ast = parse(body); var astSchema = buildASTSchema(ast, argDict.query, argDict.mutation); var result = await graphql(astSchema, introspectionQuery); var out = await JSON.stringify(result, null, 2); diff --git a/src/utilities/__tests__/buildASTSchema.js b/src/utilities/__tests__/buildASTSchema.js index 2ebe58d99c..14786d10cf 100644 --- a/src/utilities/__tests__/buildASTSchema.js +++ b/src/utilities/__tests__/buildASTSchema.js @@ -9,7 +9,7 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; -import { parseSchemaIntoAST } from '../../language/schema/parser'; +import { parse } from '../../language'; import { printSchema } from '../schemaPrinter'; import { buildASTSchema } from '../buildASTSchema'; @@ -21,7 +21,7 @@ import { buildASTSchema } from '../buildASTSchema'; * printing that GraphQL into the DSL */ function cycleOutput(body, queryType, mutationType) { - var ast = parseSchemaIntoAST(body); + var ast = parse(body); var schema = buildASTSchema(ast, queryType, mutationType); return '\n' + printSchema(schema); } @@ -296,7 +296,7 @@ type Hello { bar: Bar } `; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); expect(() => buildASTSchema(doc, 'Hello')) .to.throw('Type Bar not found in document'); }); @@ -305,7 +305,7 @@ type Hello { var body = ` type Hello implements Bar { } `; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); expect(() => buildASTSchema(doc, 'Hello')) .to.throw('Type Bar not found in document'); }); @@ -315,7 +315,7 @@ type Hello implements Bar { } union TestUnion = Bar type Hello { testUnion: TestUnion } `; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); expect(() => buildASTSchema(doc, 'Hello')) .to.throw('Type Bar not found in document'); }); @@ -326,7 +326,7 @@ type Hello { str: String } `; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); expect(() => buildASTSchema(doc, 'Wat')) .to.throw('Specified query type Wat not found in document'); }); @@ -337,8 +337,23 @@ type Hello { str: String } `; - var doc = parseSchemaIntoAST(body); + var doc = parse(body); expect(() => buildASTSchema(doc, 'Hello', 'Wat')) .to.throw('Specified mutation type Wat not found in document'); }); + + it('Rejects query names', () => { + var body = `query Foo { field }`; + var doc = parse(body); + expect(() => buildASTSchema(doc, 'Foo')) + .to.throw('Specified query type Foo not found in document.'); + }); + + it('Rejects fragment names', () => { + var body = `fragment Foo on Type { field }`; + var doc = parse(body); + expect(() => buildASTSchema(doc, 'Foo')) + .to.throw('Specified query type Foo not found in document.'); + }); + }); diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 295507799d..4d4c85098d 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -18,24 +18,24 @@ import { } from '../language/kinds'; import { - TYPE_DEFINITION, + OBJECT_DEFINITION, INTERFACE_DEFINITION, ENUM_DEFINITION, UNION_DEFINITION, SCALAR_DEFINITION, INPUT_OBJECT_DEFINITION, -} from '../language/schema/kinds'; +} from '../language/kinds'; import { - SchemaDocument, - EnumDefinition, + Document, + ObjectDefinition, + InputValueDefinition, InterfaceDefinition, UnionDefinition, - TypeDefinition, - InputValueDefinition, ScalarDefinition, + EnumDefinition, InputObjectDefinition, -} from '../language/schema/ast'; +} from '../language/ast'; import { GraphQLSchema, @@ -56,7 +56,7 @@ import { type CompositeDefinition = - TypeDefinition | + ObjectDefinition | InterfaceDefinition | UnionDefinition; @@ -86,7 +86,7 @@ function getInnerTypeName(typeAST) { * since they have no resolve methods. */ export function buildASTSchema( - ast: SchemaDocument, + ast: Document, queryTypeName: string, mutationTypeName: ?string ): GraphQLSchema { @@ -98,7 +98,18 @@ export function buildASTSchema( throw new Error('must pass in query type'); } - var astMap = keyMap(ast.definitions, d => d.name.value); + var typeDefs = ast.definitions.filter(d => { + switch (d.kind) { + case OBJECT_DEFINITION: + case INTERFACE_DEFINITION: + case ENUM_DEFINITION: + case UNION_DEFINITION: + case SCALAR_DEFINITION: + case INPUT_OBJECT_DEFINITION: return true; + } + }); + + var astMap = keyMap(typeDefs, d => d.name.value); if (isNullish(astMap[queryTypeName])) { throw new Error('Specified query type ' + queryTypeName + @@ -167,7 +178,7 @@ export function buildASTSchema( throw new Error('def must be defined'); } switch (def.kind) { - case TYPE_DEFINITION: + case OBJECT_DEFINITION: return makeTypeDef(def); case INTERFACE_DEFINITION: return makeInterfaceDef(def); @@ -184,7 +195,7 @@ export function buildASTSchema( } } - function makeTypeDef(def: TypeDefinition) { + function makeTypeDef(def: ObjectDefinition) { var typeName = def.name.value; var config = { name: typeName, @@ -205,7 +216,7 @@ export function buildASTSchema( ); } - function makeImplementedInterfaces(def: TypeDefinition) { + function makeImplementedInterfaces(def: ObjectDefinition) { return def.interfaces.map(inter => produceTypeDef(inter)); }