diff --git a/src/language/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql index 016707ddcd..f18b09968b 100644 --- a/src/language/__tests__/schema-kitchen-sink.graphql +++ b/src/language/__tests__/schema-kitchen-sink.graphql @@ -26,6 +26,14 @@ type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } +type UndefinedType + +extend type Foo { + seven(argument: [String]): Type +} + +extend type Foo @onType + interface Bar { one: Type four(argument: String = "string"): String @@ -35,16 +43,32 @@ interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } +interface UndefinedInterface + +extend interface Bar { + two(argument: InputType!): Type +} + +extend interface Bar @onInterface + union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B union AnnotatedUnionTwo @onUnion = | A | B +union UndefinedUnion + +extend union Feed = Photo | Video + +extend union Feed @onUnion + scalar CustomScalar scalar AnnotatedScalar @onScalar +extend scalar CustomScalar @onScalar + enum Site { DESKTOP MOBILE @@ -55,20 +79,30 @@ enum AnnotatedEnum @onEnum { OTHER_VALUE } +enum UndefinedEnum + +extend enum Site { + VR +} + +extend enum Site @onEnum + input InputType { key: String! answer: Int = 42 } -input AnnotatedInput @onInputObjectType { +input AnnotatedInput @onInputObject { annotatedField: Type @onField } -extend type Foo { - seven(argument: [String]): Type +input UndefinedInput + +extend input InputType { + other: Float = 1.23e4 } -extend type Foo @onType +extend input InputType @onInputObject directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/src/language/__tests__/schema-printer-test.js b/src/language/__tests__/schema-printer-test.js index ac35558844..d2eb4f7de4 100644 --- a/src/language/__tests__/schema-printer-test.js +++ b/src/language/__tests__/schema-printer-test.js @@ -70,6 +70,14 @@ type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } +type UndefinedType + +extend type Foo { + seven(argument: [String]): Type +} + +extend type Foo @onType + interface Bar { one: Type four(argument: String = "string"): String @@ -79,16 +87,32 @@ interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } +interface UndefinedInterface + +extend interface Bar { + two(argument: InputType!): Type +} + +extend interface Bar @onInterface + union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B union AnnotatedUnionTwo @onUnion = A | B +union UndefinedUnion + +extend union Feed = Photo | Video + +extend union Feed @onUnion + scalar CustomScalar scalar AnnotatedScalar @onScalar +extend scalar CustomScalar @onScalar + enum Site { DESKTOP MOBILE @@ -99,20 +123,30 @@ enum AnnotatedEnum @onEnum { OTHER_VALUE } +enum UndefinedEnum + +extend enum Site { + VR +} + +extend enum Site @onEnum + input InputType { key: String! answer: Int = 42 } -input AnnotatedInput @onInputObjectType { +input AnnotatedInput @onInputObject { annotatedField: Type @onField } -extend type Foo { - seven(argument: [String]): Type +input UndefinedInput + +extend input InputType { + other: Float = 1.23e4 } -extend type Foo @onType +extend input InputType @onInputObject directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/src/language/ast.js b/src/language/ast.js index f23ec740e0..22097b494f 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -151,7 +151,12 @@ export type ASTNode = | EnumTypeDefinitionNode | EnumValueDefinitionNode | InputObjectTypeDefinitionNode + | ScalarTypeExtensionNode | ObjectTypeExtensionNode + | InterfaceTypeExtensionNode + | UnionTypeExtensionNode + | EnumTypeExtensionNode + | InputObjectTypeExtensionNode | DirectiveDefinitionNode; // Name @@ -398,7 +403,7 @@ export type ObjectTypeDefinitionNode = { +name: NameNode, +interfaces?: Array, +directives?: Array, - +fields: Array, + +fields?: Array, }; export type FieldDefinitionNode = { @@ -427,7 +432,7 @@ export type InterfaceTypeDefinitionNode = { +description?: StringValueNode, +name: NameNode, +directives?: Array, - +fields: Array, + +fields?: Array, }; export type UnionTypeDefinitionNode = { @@ -436,7 +441,7 @@ export type UnionTypeDefinitionNode = { +description?: StringValueNode, +name: NameNode, +directives?: Array, - +types: Array, + +types?: Array, }; export type EnumTypeDefinitionNode = { @@ -445,7 +450,7 @@ export type EnumTypeDefinitionNode = { +description?: StringValueNode, +name: NameNode, +directives?: Array, - +values: Array, + +values?: Array, }; export type EnumValueDefinitionNode = { @@ -462,12 +467,25 @@ export type InputObjectTypeDefinitionNode = { +description?: StringValueNode, +name: NameNode, +directives?: Array, - +fields: Array, + +fields?: Array, }; // Type Extensions -export type TypeExtensionNode = ObjectTypeExtensionNode; +export type TypeExtensionNode = + | ScalarTypeExtensionNode + | ObjectTypeExtensionNode + | InterfaceTypeExtensionNode + | UnionTypeExtensionNode + | EnumTypeExtensionNode + | InputObjectTypeExtensionNode; + +export type ScalarTypeExtensionNode = { + kind: 'ScalarTypeExtension', + loc?: Location, + name: NameNode, + directives?: ?Array, +}; export type ObjectTypeExtensionNode = { +kind: 'ObjectTypeExtension', @@ -478,6 +496,38 @@ export type ObjectTypeExtensionNode = { +fields?: Array, }; +export type InterfaceTypeExtensionNode = { + kind: 'InterfaceTypeExtension', + loc?: Location, + name: NameNode, + directives?: ?Array, + fields?: ?Array, +}; + +export type UnionTypeExtensionNode = { + kind: 'UnionTypeExtension', + loc?: Location, + name: NameNode, + directives?: ?Array, + types?: ?Array, +}; + +export type EnumTypeExtensionNode = { + kind: 'EnumTypeExtension', + loc?: Location, + name: NameNode, + directives?: ?Array, + values?: ?Array, +}; + +export type InputObjectTypeExtensionNode = { + kind: 'InputObjectTypeExtension', + loc?: Location, + name: NameNode, + directives?: ?Array, + fields?: ?Array, +}; + // Directive Definitions export type DirectiveDefinitionNode = { diff --git a/src/language/kinds.js b/src/language/kinds.js index 2157b76586..f36f945afe 100644 --- a/src/language/kinds.js +++ b/src/language/kinds.js @@ -68,7 +68,12 @@ export const INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition'; // Type Extensions +export const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension'; export const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension'; +export const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension'; +export const UNION_TYPE_EXTENSION = 'UnionTypeExtension'; +export const ENUM_TYPE_EXTENSION = 'EnumTypeExtension'; +export const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension'; // Directive Definitions diff --git a/src/language/parser.js b/src/language/parser.js index f7d9f221ba..6600180234 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -52,7 +52,12 @@ import type { EnumValueDefinitionNode, InputObjectTypeDefinitionNode, TypeExtensionNode, + ScalarTypeExtensionNode, ObjectTypeExtensionNode, + InterfaceTypeExtensionNode, + UnionTypeExtensionNode, + EnumTypeExtensionNode, + InputObjectTypeExtensionNode, DirectiveDefinitionNode, } from './ast'; @@ -92,7 +97,12 @@ import { ENUM_TYPE_DEFINITION, ENUM_VALUE_DEFINITION, INPUT_OBJECT_TYPE_DEFINITION, + SCALAR_TYPE_EXTENSION, OBJECT_TYPE_EXTENSION, + INTERFACE_TYPE_EXTENSION, + UNION_TYPE_EXTENSION, + ENUM_TYPE_EXTENSION, + INPUT_OBJECT_TYPE_EXTENSION, DIRECTIVE_DEFINITION, } from './kinds'; import { DirectiveLocation } from './directiveLocation'; @@ -832,7 +842,7 @@ function parseScalarTypeDefinition(lexer: Lexer<*>): ScalarTypeDefinitionNode { /** * ObjectTypeDefinition : * Description? - * type Name ImplementsInterfaces? Directives[Const]? FieldDefinitions + * type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition? */ function parseObjectTypeDefinition(lexer: Lexer<*>): ObjectTypeDefinitionNode { const start = lexer.token; @@ -841,7 +851,7 @@ function parseObjectTypeDefinition(lexer: Lexer<*>): ObjectTypeDefinitionNode { const name = parseName(lexer); const interfaces = parseImplementsInterfaces(lexer); const directives = parseDirectives(lexer, true); - const fields = parseFieldDefinitions(lexer); + const fields = parseFieldsDefinition(lexer); return { kind: OBJECT_TYPE_DEFINITION, description, @@ -868,15 +878,12 @@ function parseImplementsInterfaces(lexer: Lexer<*>): Array { } /** - * FieldDefinitions : { FieldDefinition+ } + * FieldsDefinition : { FieldDefinition+ } */ -function parseFieldDefinitions(lexer: Lexer<*>): Array { - return many( - lexer, - TokenKind.BRACE_L, - parseFieldDefinition, - TokenKind.BRACE_R, - ); +function parseFieldsDefinition(lexer: Lexer<*>): Array { + return peek(lexer, TokenKind.BRACE_L) + ? many(lexer, TokenKind.BRACE_L, parseFieldDefinition, TokenKind.BRACE_R) + : []; } /** @@ -940,7 +947,7 @@ function parseInputValueDef(lexer: Lexer<*>): InputValueDefinitionNode { /** * InterfaceTypeDefinition : - * - Description? interface Name Directives[Const]? FieldDefinitions + * - Description? interface Name Directives[Const]? FieldsDefinition? */ function parseInterfaceTypeDefinition( lexer: Lexer<*>, @@ -950,7 +957,7 @@ function parseInterfaceTypeDefinition( expectKeyword(lexer, 'interface'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); - const fields = parseFieldDefinitions(lexer); + const fields = parseFieldsDefinition(lexer); return { kind: INTERFACE_TYPE_DEFINITION, description, @@ -963,7 +970,7 @@ function parseInterfaceTypeDefinition( /** * UnionTypeDefinition : - * - Description? union Name Directives[Const]? = UnionMembers + * - Description? union Name Directives[Const]? MemberTypesDefinition? */ function parseUnionTypeDefinition(lexer: Lexer<*>): UnionTypeDefinitionNode { const start = lexer.token; @@ -971,8 +978,7 @@ function parseUnionTypeDefinition(lexer: Lexer<*>): UnionTypeDefinitionNode { expectKeyword(lexer, 'union'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); - expect(lexer, TokenKind.EQUALS); - const types = parseUnionMembers(lexer); + const types = parseMemberTypesDefinition(lexer); return { kind: UNION_TYPE_DEFINITION, description, @@ -984,23 +990,27 @@ function parseUnionTypeDefinition(lexer: Lexer<*>): UnionTypeDefinitionNode { } /** - * UnionMembers : + * MemberTypesDefinition : = MemberTypes + * + * MemberTypes : * - `|`? NamedType - * - UnionMembers | NamedType + * - MemberTypes | NamedType */ -function parseUnionMembers(lexer: Lexer<*>): Array { - // Optional leading pipe - skip(lexer, TokenKind.PIPE); - const members = []; - do { - members.push(parseNamedType(lexer)); - } while (skip(lexer, TokenKind.PIPE)); - return members; +function parseMemberTypesDefinition(lexer: Lexer<*>): Array { + const types = []; + if (skip(lexer, TokenKind.EQUALS)) { + // Optional leading pipe + skip(lexer, TokenKind.PIPE); + do { + types.push(parseNamedType(lexer)); + } while (skip(lexer, TokenKind.PIPE)); + } + return types; } /** * EnumTypeDefinition : - * - Description? enum Name Directives[Const]? { EnumValueDefinition+ } + * - Description? enum Name Directives[Const]? EnumValuesDefinition? */ function parseEnumTypeDefinition(lexer: Lexer<*>): EnumTypeDefinitionNode { const start = lexer.token; @@ -1008,12 +1018,7 @@ function parseEnumTypeDefinition(lexer: Lexer<*>): EnumTypeDefinitionNode { expectKeyword(lexer, 'enum'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); - const values = many( - lexer, - TokenKind.BRACE_L, - parseEnumValueDefinition, - TokenKind.BRACE_R, - ); + const values = parseEnumValuesDefinition(lexer); return { kind: ENUM_TYPE_DEFINITION, description, @@ -1024,6 +1029,22 @@ function parseEnumTypeDefinition(lexer: Lexer<*>): EnumTypeDefinitionNode { }; } +/** + * EnumValuesDefinition : { EnumValueDefinition+ } + */ +function parseEnumValuesDefinition( + lexer: Lexer<*>, +): Array { + return peek(lexer, TokenKind.BRACE_L) + ? many( + lexer, + TokenKind.BRACE_L, + parseEnumValueDefinition, + TokenKind.BRACE_R, + ) + : []; +} + /** * EnumValueDefinition : Description? EnumValue Directives[Const]? * @@ -1045,7 +1066,7 @@ function parseEnumValueDefinition(lexer: Lexer<*>): EnumValueDefinitionNode { /** * InputObjectTypeDefinition : - * - Description? input Name Directives[Const]? { InputValueDefinition+ } + * - Description? input Name Directives[Const]? InputFieldsDefinition? */ function parseInputObjectTypeDefinition( lexer: Lexer<*>, @@ -1055,12 +1076,7 @@ function parseInputObjectTypeDefinition( expectKeyword(lexer, 'input'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); - const fields = many( - lexer, - TokenKind.BRACE_L, - parseInputValueDef, - TokenKind.BRACE_R, - ); + const fields = parseInputFieldsDefinition(lexer); return { kind: INPUT_OBJECT_TYPE_DEFINITION, description, @@ -1071,26 +1087,73 @@ function parseInputObjectTypeDefinition( }; } +/** + * InputFieldsDefinition : { InputValueDefinition+ } + */ +function parseInputFieldsDefinition( + lexer: Lexer<*>, +): Array { + return peek(lexer, TokenKind.BRACE_L) + ? many(lexer, TokenKind.BRACE_L, parseInputValueDef, TokenKind.BRACE_R) + : []; +} + /** * TypeExtension : + * - ScalarTypeExtension * - ObjectTypeExtension + * - InterfaceTypeExtension + * - UnionTypeExtension + * - EnumTypeExtension + * - InputObjectTypeDefinition */ function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode { const keywordToken = lexer.lookahead(); if (keywordToken.kind === TokenKind.NAME) { switch (keywordToken.value) { + case 'scalar': + return parseScalarTypeExtension(lexer); case 'type': return parseObjectTypeExtension(lexer); + case 'interface': + return parseInterfaceTypeExtension(lexer); + case 'union': + return parseUnionTypeExtension(lexer); + case 'enum': + return parseEnumTypeExtension(lexer); + case 'input': + return parseInputObjectTypeExtension(lexer); } } throw unexpected(lexer, keywordToken); } +/** + * ScalarTypeExtension : + * - extend scalar Name Directives[Const] + */ +function parseScalarTypeExtension(lexer: Lexer<*>): ScalarTypeExtensionNode { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + expectKeyword(lexer, 'scalar'); + const name = parseName(lexer); + const directives = parseDirectives(lexer, true); + if (directives.length === 0) { + throw unexpected(lexer); + } + return { + kind: SCALAR_TYPE_EXTENSION, + name, + directives, + loc: loc(lexer, start), + }; +} + /** * ObjectTypeExtension : - * - extend type Name ImplementsInterfaces? Directives[Const]? FieldDefinitions + * - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition * - extend type Name ImplementsInterfaces? Directives[Const] * - extend type Name ImplementsInterfaces */ @@ -1101,9 +1164,7 @@ function parseObjectTypeExtension(lexer: Lexer<*>): ObjectTypeExtensionNode { const name = parseName(lexer); const interfaces = parseImplementsInterfaces(lexer); const directives = parseDirectives(lexer, true); - const fields = peek(lexer, TokenKind.BRACE_L) - ? parseFieldDefinitions(lexer) - : []; + const fields = parseFieldsDefinition(lexer); if ( interfaces.length === 0 && directives.length === 0 && @@ -1121,6 +1182,106 @@ function parseObjectTypeExtension(lexer: Lexer<*>): ObjectTypeExtensionNode { }; } +/** + * InterfaceTypeExtension : + * - extend interface Name Directives[Const]? FieldsDefinition + * - extend interface Name Directives[Const] + */ +function parseInterfaceTypeExtension( + lexer: Lexer<*>, +): InterfaceTypeExtensionNode { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + expectKeyword(lexer, 'interface'); + const name = parseName(lexer); + const directives = parseDirectives(lexer, true); + const fields = parseFieldsDefinition(lexer); + if (directives.length === 0 && fields.length === 0) { + throw unexpected(lexer); + } + return { + kind: INTERFACE_TYPE_EXTENSION, + name, + directives, + fields, + loc: loc(lexer, start), + }; +} + +/** + * UnionTypeExtension : + * - extend union Name Directives[Const]? MemberTypesDefinition + * - extend union Name Directives[Const] + */ +function parseUnionTypeExtension(lexer: Lexer<*>): UnionTypeExtensionNode { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + expectKeyword(lexer, 'union'); + const name = parseName(lexer); + const directives = parseDirectives(lexer, true); + const types = parseMemberTypesDefinition(lexer); + if (directives.length === 0 && types.length === 0) { + throw unexpected(lexer); + } + return { + kind: UNION_TYPE_EXTENSION, + name, + directives, + types, + loc: loc(lexer, start), + }; +} + +/** + * EnumTypeExtension : + * - extend enum Name Directives[Const]? EnumValuesDefinition + * - extend enum Name Directives[Const] + */ +function parseEnumTypeExtension(lexer: Lexer<*>): EnumTypeExtensionNode { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + expectKeyword(lexer, 'enum'); + const name = parseName(lexer); + const directives = parseDirectives(lexer, true); + const values = parseEnumValuesDefinition(lexer); + if (directives.length === 0 && values.length === 0) { + throw unexpected(lexer); + } + return { + kind: ENUM_TYPE_EXTENSION, + name, + directives, + values, + loc: loc(lexer, start), + }; +} + +/** + * InputObjectTypeExtension : + * - extend input Name Directives[Const]? InputFieldsDefinition + * - extend input Name Directives[Const] + */ +function parseInputObjectTypeExtension( + lexer: Lexer<*>, +): InputObjectTypeExtensionNode { + const start = lexer.token; + expectKeyword(lexer, 'extend'); + expectKeyword(lexer, 'input'); + const name = parseName(lexer); + const directives = parseDirectives(lexer, true); + const fields = parseInputFieldsDefinition(lexer); + if (directives.length === 0 && fields.length === 0) { + throw unexpected(lexer); + } + return { + kind: INPUT_OBJECT_TYPE_EXTENSION, + name, + directives, + fields, + loc: loc(lexer, start), + }; +} + /** * DirectiveDefinition : * - Description? directive @ Name ArgumentsDefinition? on DirectiveLocations diff --git a/src/language/printer.js b/src/language/printer.js index 4ef6ea7177..5497745006 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -177,7 +177,12 @@ const printDocASTReducer = { [ description, join( - ['union', name, join(directives, ' '), '= ' + join(types, ' | ')], + [ + 'union', + name, + join(directives, ' '), + types && types.length !== 0 ? '= ' + join(types, ' | ') : '', + ], ' ', ), ], @@ -205,6 +210,9 @@ const printDocASTReducer = { '\n', ), + ScalarTypeExtension: ({ name, directives }) => + join(['extend scalar', name, join(directives, ' ')], ' '), + ObjectTypeExtension: ({ name, interfaces, directives, fields }) => join( [ @@ -217,6 +225,26 @@ const printDocASTReducer = { ' ', ), + InterfaceTypeExtension: ({ name, directives, fields }) => + join(['extend interface', name, join(directives, ' '), block(fields)], ' '), + + UnionTypeExtension: ({ name, directives, types }) => + join( + [ + 'extend union', + name, + join(directives, ' '), + types && types.length !== 0 ? '= ' + join(types, ' | ') : '', + ], + ' ', + ), + + EnumTypeExtension: ({ name, directives, values }) => + join(['extend enum', name, join(directives, ' '), block(values)], ' '), + + InputObjectTypeExtension: ({ name, directives, fields }) => + join(['extend input', name, join(directives, ' '), block(fields)], ' '), + DirectiveDefinition: ({ description, name, arguments: args, locations }) => join( [ diff --git a/src/language/visitor.js b/src/language/visitor.js index 14a0615d9e..f47ae59b7a 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -66,7 +66,12 @@ export const QueryDocumentKeys = { EnumValueDefinition: ['description', 'name', 'directives'], InputObjectTypeDefinition: ['description', 'name', 'directives', 'fields'], + ScalarTypeExtension: ['name', 'directives'], ObjectTypeExtension: ['name', 'interfaces', 'directives', 'fields'], + InterfaceTypeExtension: ['name', 'directives', 'fields'], + UnionTypeExtension: ['name', 'directives', 'types'], + EnumTypeExtension: ['name', 'directives', 'values'], + InputObjectTypeExtension: ['name', 'directives', 'fields'], DirectiveDefinition: ['description', 'name', 'arguments', 'locations'], }; diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 80b8af2fbb..aaedb3e318 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -362,11 +362,13 @@ export class ASTDefinitionBuilder { _makeFieldDefMap( def: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode, ) { - return keyValMap( - def.fields, - field => field.name.value, - field => this.buildField(field), - ); + return def.fields + ? keyValMap( + def.fields, + field => field.name.value, + field => this.buildField(field), + ) + : {}; } _makeImplementedInterfaces(def: ObjectTypeDefinitionNode) { @@ -405,15 +407,17 @@ export class ASTDefinitionBuilder { return new GraphQLEnumType({ name: def.name.value, description: getDescription(def, this._options), - values: keyValMap( - def.values, - enumValue => enumValue.name.value, - enumValue => ({ - description: getDescription(enumValue, this._options), - deprecationReason: getDeprecationReason(enumValue), - astNode: enumValue, - }), - ), + values: def.values + ? keyValMap( + def.values, + enumValue => enumValue.name.value, + enumValue => ({ + description: getDescription(enumValue, this._options), + deprecationReason: getDeprecationReason(enumValue), + astNode: enumValue, + }), + ) + : {}, astNode: def, }); } @@ -422,7 +426,7 @@ export class ASTDefinitionBuilder { return new GraphQLUnionType({ name: def.name.value, description: getDescription(def, this._options), - types: def.types.map(t => this.buildObjectType(t)), + types: def.types ? def.types.map(t => this.buildObjectType(t)) : [], astNode: def, }); } @@ -440,7 +444,7 @@ export class ASTDefinitionBuilder { return new GraphQLInputObjectType({ name: def.name.value, description: getDescription(def, this._options), - fields: () => this._makeInputValues(def.fields), + fields: () => (def.fields ? this._makeInputValues(def.fields) : {}), astNode: def, }); } diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index bf2c25bd76..bbaa5cb836 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -131,6 +131,14 @@ export function extendSchema( } directiveDefinitions.push(def); break; + case Kind.SCALAR_TYPE_EXTENSION: + case Kind.INTERFACE_TYPE_EXTENSION: + case Kind.UNION_TYPE_EXTENSION: + case Kind.ENUM_TYPE_EXTENSION: + case Kind.INPUT_OBJECT_TYPE_EXTENSION: + throw new Error( + `The ${def.kind} kind is not yet supported by extendSchema().`, + ); } } diff --git a/src/validation/__tests__/KnownDirectives-test.js b/src/validation/__tests__/KnownDirectives-test.js index e0a723ec32..c33a7ed680 100644 --- a/src/validation/__tests__/KnownDirectives-test.js +++ b/src/validation/__tests__/KnownDirectives-test.js @@ -156,20 +156,30 @@ describe('Validate: Known directives', () => { scalar MyScalar @onScalar + extend scalar MyScalar @onScalar + interface MyInterface @onInterface { myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition } + extend interface MyInterface @onInterface + union MyUnion @onUnion = MyObj | Other + extend union MyUnion @onUnion + enum MyEnum @onEnum { MY_VALUE @onEnumValue } + extend enum MyEnum @onEnum + input MyInput @onInputObject { myField: Int @onInputFieldDefinition } + extend input MyInput @onInputObject + schema @onSchema { query: MyQuery } diff --git a/src/validation/rules/KnownDirectives.js b/src/validation/rules/KnownDirectives.js index 4802715e12..1422c138d2 100644 --- a/src/validation/rules/KnownDirectives.js +++ b/src/validation/rules/KnownDirectives.js @@ -87,6 +87,7 @@ function getDirectiveLocationForASTPath(ancestors) { case Kind.SCHEMA_DEFINITION: return DirectiveLocation.SCHEMA; case Kind.SCALAR_TYPE_DEFINITION: + case Kind.SCALAR_TYPE_EXTENSION: return DirectiveLocation.SCALAR; case Kind.OBJECT_TYPE_DEFINITION: case Kind.OBJECT_TYPE_EXTENSION: @@ -94,14 +95,18 @@ function getDirectiveLocationForASTPath(ancestors) { case Kind.FIELD_DEFINITION: return DirectiveLocation.FIELD_DEFINITION; case Kind.INTERFACE_TYPE_DEFINITION: + case Kind.INTERFACE_TYPE_EXTENSION: return DirectiveLocation.INTERFACE; case Kind.UNION_TYPE_DEFINITION: + case Kind.UNION_TYPE_EXTENSION: return DirectiveLocation.UNION; case Kind.ENUM_TYPE_DEFINITION: + case Kind.ENUM_TYPE_EXTENSION: return DirectiveLocation.ENUM; case Kind.ENUM_VALUE_DEFINITION: return DirectiveLocation.ENUM_VALUE; case Kind.INPUT_OBJECT_TYPE_DEFINITION: + case Kind.INPUT_OBJECT_TYPE_EXTENSION: return DirectiveLocation.INPUT_OBJECT; case Kind.INPUT_VALUE_DEFINITION: const parentNode = ancestors[ancestors.length - 3];