From fdf981cae260ca8788275a736933701cb210142f Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Tue, 6 Jun 2017 11:48:44 +0300 Subject: [PATCH 1/4] Switch Enum tests to not depend on JSON representation If we add additional fields (e.g. astNode) into values of enum these test will be broken. --- .../__tests__/buildASTSchema-test.js | 35 +++++++------------ src/utilities/__tests__/extendSchema-test.js | 14 +++----- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index 1fd8c71e18..0bdce53f4a 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -540,29 +540,18 @@ describe('Schema Builder', () => { const ast = parse(body); const schema = buildASTSchema(ast); - expect(schema.getType('MyEnum').getValues()).to.deep.equal([ - { - name: 'VALUE', - description: '', - isDeprecated: false, - deprecationReason: undefined, - value: 'VALUE' - }, - { - name: 'OLD_VALUE', - description: '', - isDeprecated: true, - deprecationReason: 'No longer supported', - value: 'OLD_VALUE' - }, - { - name: 'OTHER_VALUE', - description: '', - isDeprecated: true, - deprecationReason: 'Terrible reasons', - value: 'OTHER_VALUE' - } - ]); + const myEnum = schema.getType('MyEnum'); + + const value = myEnum.getValue('VALUE'); + expect(value.isDeprecated).to.equal(false); + + const oldValue = myEnum.getValue('OLD_VALUE'); + expect(oldValue.isDeprecated).to.equal(true); + expect(oldValue.deprecationReason).to.equal('No longer supported'); + + const otherValue = myEnum.getValue('OTHER_VALUE'); + expect(otherValue.isDeprecated).to.equal(true); + expect(otherValue.deprecationReason).to.equal('Terrible reasons'); const rootFields = schema.getType('Query').getFields(); expect(rootFields.field1.isDeprecated).to.equal(true); diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index 0aa3d5deb6..c997772759 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -215,16 +215,10 @@ describe('extendSchema', () => { expect(deprecatedFieldDef.deprecationReason).to.equal('not used anymore'); const deprecatedEnumDef = extendedSchema - .getType('EnumWithDeprecatedValue'); - expect(deprecatedEnumDef.getValues()).to.deep.equal([ - { - name: 'DEPRECATED', - description: '', - isDeprecated: true, - deprecationReason: 'do not use', - value: 'DEPRECATED' - } - ]); + .getType('EnumWithDeprecatedValue') + .getValue('DEPRECATED'); + expect(deprecatedEnumDef.isDeprecated).to.equal(true); + expect(deprecatedEnumDef.deprecationReason).to.equal('do not use'); }); it('extends objects with deprecated fields', () => { From 4540144d6f65deb58d9005dafb5a901361797e8f Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Tue, 6 Jun 2017 12:14:16 +0300 Subject: [PATCH 2/4] Add 'astNode' & 'extensionASTNodes' fields to type definitions --- src/type/__tests__/definition-test.js | 5 ++- src/type/definition.js | 45 ++++++++++++++++++- src/type/directives.js | 8 +++- src/type/schema.js | 4 ++ .../__tests__/buildClientSchema-test.js | 15 ++++--- 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/type/__tests__/definition-test.js b/src/type/__tests__/definition-test.js index 1eff90137a..9a256874e8 100644 --- a/src/type/__tests__/definition-test.js +++ b/src/type/__tests__/definition-test.js @@ -180,7 +180,8 @@ describe('Type System: Example', () => { description: undefined, isDeprecated: true, deprecationReason: 'Just because', - value: 'foo' + value: 'foo', + astNode: undefined, }); }); @@ -200,6 +201,7 @@ describe('Type System: Example', () => { isDeprecated: false, deprecationReason: undefined, value: null, + astNode: undefined, }, { name: 'UNDEFINED', @@ -207,6 +209,7 @@ describe('Type System: Example', () => { isDeprecated: false, deprecationReason: undefined, value: undefined, + astNode: undefined, }, ]); }); diff --git a/src/type/definition.js b/src/type/definition.js index eb074ccde8..17c3c16e87 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -13,6 +13,16 @@ import isNullish from '../jsutils/isNullish'; import * as Kind from '../language/kinds'; import { assertValidName } from '../utilities/assertValidName'; import type { + ScalarTypeDefinitionNode, + ObjectTypeDefinitionNode, + FieldDefinitionNode, + InputValueDefinitionNode, + InterfaceTypeDefinitionNode, + UnionTypeDefinitionNode, + EnumTypeDefinitionNode, + EnumValueDefinitionNode, + InputObjectTypeDefinitionNode, + TypeExtensionDefinitionNode, OperationDefinitionNode, FieldNode, FragmentDefinitionNode, @@ -294,6 +304,7 @@ function resolveThunk(thunk: Thunk): T { export class GraphQLScalarType { name: string; description: ?string; + astNode: ?ScalarTypeDefinitionNode; _scalarConfig: GraphQLScalarTypeConfig<*, *>; @@ -301,6 +312,7 @@ export class GraphQLScalarType { assertValidName(config.name); this.name = config.name; this.description = config.description; + this.astNode = config.astNode; invariant( typeof config.serialize === 'function', `${this.name} must provide "serialize" function. If this custom Scalar ` + @@ -364,6 +376,7 @@ GraphQLScalarType.prototype.toJSON = export type GraphQLScalarTypeConfig = { name: string; description?: ?string; + astNode?: ?ScalarTypeDefinitionNode; serialize: (value: mixed) => ?TExternal; parseValue?: (value: mixed) => ?TInternal; parseLiteral?: (valueNode: ValueNode) => ?TInternal; @@ -411,6 +424,8 @@ export type GraphQLScalarTypeConfig = { export class GraphQLObjectType { name: string; description: ?string; + astNode: ?ObjectTypeDefinitionNode; + extensionASTNodes: Array; isTypeOf: ?GraphQLIsTypeOfFn<*, *>; _typeConfig: GraphQLObjectTypeConfig<*, *>; @@ -421,6 +436,8 @@ export class GraphQLObjectType { assertValidName(config.name, config.isIntrospection); this.name = config.name; this.description = config.description; + this.astNode = config.astNode; + this.extensionASTNodes = config.extensionASTNodes || []; if (config.isTypeOf) { invariant( typeof config.isTypeOf === 'function', @@ -562,7 +579,8 @@ function defineFieldMap( name: argName, description: arg.description === undefined ? null : arg.description, type: arg.type, - defaultValue: arg.defaultValue + defaultValue: arg.defaultValue, + astNode: arg.astNode, }; }); } @@ -587,6 +605,8 @@ export type GraphQLObjectTypeConfig = { isTypeOf?: ?GraphQLIsTypeOfFn; description?: ?string; isIntrospection?: boolean; + astNode?: ?ObjectTypeDefinitionNode; + extensionASTNodes?: ?Array; }; export type GraphQLTypeResolver = ( @@ -630,6 +650,7 @@ export type GraphQLFieldConfig = { subscribe?: GraphQLFieldResolver; deprecationReason?: ?string; description?: ?string; + astNode?: ?FieldDefinitionNode; }; export type GraphQLFieldConfigArgumentMap = { @@ -640,6 +661,7 @@ export type GraphQLArgumentConfig = { type: GraphQLInputType; defaultValue?: mixed; description?: ?string; + astNode?: ?InputValueDefinitionNode; }; export type GraphQLFieldConfigMap = { @@ -655,6 +677,7 @@ export type GraphQLField = { subscribe?: GraphQLFieldResolver; isDeprecated?: boolean; deprecationReason?: ?string; + astNode?: ?FieldDefinitionNode; }; export type GraphQLArgument = { @@ -662,6 +685,7 @@ export type GraphQLArgument = { type: GraphQLInputType; defaultValue?: mixed; description?: ?string; + astNode?: ?InputValueDefinitionNode; }; export type GraphQLFieldMap = { @@ -691,6 +715,7 @@ export type GraphQLFieldMap = { export class GraphQLInterfaceType { name: string; description: ?string; + astNode: ?InterfaceTypeDefinitionNode; resolveType: ?GraphQLTypeResolver<*, *>; _typeConfig: GraphQLInterfaceTypeConfig<*, *>; @@ -700,6 +725,7 @@ export class GraphQLInterfaceType { assertValidName(config.name); this.name = config.name; this.description = config.description; + this.astNode = config.astNode; if (config.resolveType) { invariant( typeof config.resolveType === 'function', @@ -737,7 +763,8 @@ export type GraphQLInterfaceTypeConfig = { * Object type. */ resolveType?: ?GraphQLTypeResolver, - description?: ?string + description?: ?string, + astNode?: ?InterfaceTypeDefinitionNode, }; @@ -768,6 +795,7 @@ export type GraphQLInterfaceTypeConfig = { export class GraphQLUnionType { name: string; description: ?string; + astNode: ?UnionTypeDefinitionNode; resolveType: ?GraphQLTypeResolver<*, *>; _typeConfig: GraphQLUnionTypeConfig<*, *>; @@ -778,6 +806,7 @@ export class GraphQLUnionType { assertValidName(config.name); this.name = config.name; this.description = config.description; + this.astNode = config.astNode; if (config.resolveType) { invariant( typeof config.resolveType === 'function', @@ -854,6 +883,7 @@ export type GraphQLUnionTypeConfig = { */ resolveType?: ?GraphQLTypeResolver; description?: ?string; + astNode?: ?UnionTypeDefinitionNode; }; @@ -882,6 +912,7 @@ export type GraphQLUnionTypeConfig = { export class GraphQLEnumType/* */ { name: string; description: ?string; + astNode: ?EnumTypeDefinitionNode; _enumConfig: GraphQLEnumTypeConfig/* */; _values: Array */>; @@ -892,6 +923,7 @@ export class GraphQLEnumType/* */ { this.name = config.name; assertValidName(config.name, config.isIntrospection); this.description = config.description; + this.astNode = config.astNode; this._values = defineEnumValues(this, config.values); this._enumConfig = config; } @@ -1008,6 +1040,7 @@ function defineEnumValues( description: value.description, isDeprecated: Boolean(value.deprecationReason), deprecationReason: value.deprecationReason, + astNode: value.astNode, value: value.hasOwnProperty('value') ? value.value : valueName, }; }); @@ -1017,6 +1050,7 @@ export type GraphQLEnumTypeConfig/* */ = { name: string; values: GraphQLEnumValueConfigMap/* */; description?: ?string; + astNode?: ?EnumTypeDefinitionNode; isIntrospection?: boolean; }; @@ -1028,6 +1062,7 @@ export type GraphQLEnumValueConfig/* */ = { value?: any/* T */; deprecationReason?: ?string; description?: ?string; + astNode?: ?EnumValueDefinitionNode; }; export type GraphQLEnumValue/* */ = { @@ -1035,6 +1070,7 @@ export type GraphQLEnumValue/* */ = { description: ?string; isDeprecated?: boolean; deprecationReason: ?string; + astNode?: ?EnumValueDefinitionNode; value: any/* T */; }; @@ -1063,6 +1099,7 @@ export type GraphQLEnumValue/* */ = { export class GraphQLInputObjectType { name: string; description: ?string; + astNode: ?InputObjectTypeDefinitionNode; _typeConfig: GraphQLInputObjectTypeConfig; _fields: GraphQLInputFieldMap; @@ -1071,6 +1108,7 @@ export class GraphQLInputObjectType { assertValidName(config.name); this.name = config.name; this.description = config.description; + this.astNode = config.astNode; this._typeConfig = config; } @@ -1130,12 +1168,14 @@ export type GraphQLInputObjectTypeConfig = { name: string; fields: Thunk; description?: ?string; + astNode?: ?InputObjectTypeDefinitionNode; }; export type GraphQLInputFieldConfig = { type: GraphQLInputType; defaultValue?: mixed; description?: ?string; + astNode?: ?InputValueDefinitionNode; }; export type GraphQLInputFieldConfigMap = { @@ -1147,6 +1187,7 @@ export type GraphQLInputField = { type: GraphQLInputType; defaultValue?: mixed; description?: ?string; + astNode?: ?InputValueDefinitionNode; }; export type GraphQLInputFieldMap = { diff --git a/src/type/directives.js b/src/type/directives.js index bc2feff27c..ad0298bced 100644 --- a/src/type/directives.js +++ b/src/type/directives.js @@ -16,6 +16,7 @@ import type { import { GraphQLString, GraphQLBoolean } from './scalars'; import invariant from '../jsutils/invariant'; import { assertValidName } from '../utilities/assertValidName'; +import type { DirectiveDefinitionNode } from '../language/ast'; export const DirectiveLocation = { @@ -52,6 +53,7 @@ export class GraphQLDirective { description: ?string; locations: Array; args: Array; + astNode: ?DirectiveDefinitionNode; constructor(config: GraphQLDirectiveConfig): void { invariant(config.name, 'Directive must be named.'); @@ -63,6 +65,7 @@ export class GraphQLDirective { this.name = config.name; this.description = config.description; this.locations = config.locations; + this.astNode = config.astNode; const args = config.args; if (!args) { @@ -84,10 +87,12 @@ export class GraphQLDirective { name: argName, description: arg.description === undefined ? null : arg.description, type: arg.type, - defaultValue: arg.defaultValue + defaultValue: arg.defaultValue, + astNode: arg.astNode, }; }); } + } } @@ -96,6 +101,7 @@ type GraphQLDirectiveConfig = { description?: ?string; locations: Array; args?: ?GraphQLFieldConfigArgumentMap; + astNode?: ?DirectiveDefinitionNode; }; /** diff --git a/src/type/schema.js b/src/type/schema.js index 45c5cb5988..92fb236538 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -21,6 +21,7 @@ import type { GraphQLNamedType, GraphQLAbstractType } from './definition'; +import type { SchemaDefinitionNode } from '../language/ast'; import { GraphQLDirective, specifiedDirectives } from './directives'; import { __Schema } from './introspection'; import find from '../jsutils/find'; @@ -55,6 +56,7 @@ import { isEqualType, isTypeSubTypeOf } from '../utilities/typeComparators'; * */ export class GraphQLSchema { + astNode: ?SchemaDefinitionNode; _queryType: GraphQLObjectType; _mutationType: ?GraphQLObjectType; _subscriptionType: ?GraphQLObjectType; @@ -107,6 +109,7 @@ export class GraphQLSchema { ); // Provide specified directives (e.g. @include and @skip) by default. this._directives = config.directives || specifiedDirectives; + this.astNode = config.astNode || null; // Build type map now to detect any errors within this schema. let initialTypes: Array = [ @@ -227,6 +230,7 @@ type GraphQLSchemaConfig = { subscription?: ?GraphQLObjectType; types?: ?Array; directives?: ?Array; + astNode?: ?SchemaDefinitionNode; }; function typeMapReducer(map: TypeMap, type: ?GraphQLType): TypeMap { diff --git a/src/utilities/__tests__/buildClientSchema-test.js b/src/utilities/__tests__/buildClientSchema-test.js index e1ca2e62da..49919f8599 100644 --- a/src/utilities/__tests__/buildClientSchema-test.js +++ b/src/utilities/__tests__/buildClientSchema-test.js @@ -404,27 +404,32 @@ describe('Type System: build schema from introspection', () => { value: 'VEGETABLES', description: 'Foods that are vegetables.', isDeprecated: false, - deprecationReason: null, }, + deprecationReason: null, + astNode: undefined, }, { name: 'FRUITS', value: 'FRUITS', description: 'Foods that are fruits.', isDeprecated: false, - deprecationReason: null, }, + deprecationReason: null, + astNode: undefined, }, { name: 'OILS', value: 'OILS', description: 'Foods that are oils.', isDeprecated: false, - deprecationReason: null, }, + deprecationReason: null, + astNode: undefined, }, { name: 'DAIRY', value: 'DAIRY', description: 'Foods that are dairy.', isDeprecated: false, - deprecationReason: null, }, + deprecationReason: null, + astNode: undefined, }, { name: 'MEAT', value: 'MEAT', description: 'Foods that are meat.', isDeprecated: false, - deprecationReason: null, }, + deprecationReason: null, + astNode: undefined, }, ]); }); From 8665b98c0041775659e42d41a36cef2f8d183ca4 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Tue, 6 Jun 2017 12:16:38 +0300 Subject: [PATCH 3/4] Make 'buildASTSchema' assign 'astNode' to types --- .../__tests__/buildASTSchema-test.js | 74 ++++++++++++++++++- src/utilities/buildASTSchema.js | 17 ++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index 0bdce53f4a..607a2a8c29 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -9,7 +9,7 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; -import { parse } from '../../language'; +import { parse, print } from '../../language'; import { printSchema } from '../schemaPrinter'; import { buildASTSchema, buildSchema } from '../buildASTSchema'; import dedent from '../../jsutils/dedent'; @@ -560,6 +560,78 @@ describe('Schema Builder', () => { expect(rootFields.field2.isDeprecated).to.equal(true); expect(rootFields.field2.deprecationReason).to.equal('Because I said so'); }); + + it('Correctly assign AST nodes', () => { + const schema = buildSchema(` + schema { + query: Query + } + + type Query { + testField(testArg: TestInput): TestUnion + } + + input TestInput { + testInputField: TestEnum + } + + enum TestEnum { + TEST_VALUE + } + + union TestUnion = TestType + + interface TestInterface { + interfaceField: String + } + + type TestType implements TestInterface { + interfaceField: String + } + + directive @test(arg: Int) on FIELD + `); + const query = schema.getType('Query'); + const testInput = schema.getType('TestInput'); + const testEnum = schema.getType('TestEnum'); + const testUnion = schema.getType('TestUnion'); + const testInterface = schema.getType('TestInterface'); + const testType = schema.getType('TestType'); + const testDirective = schema.getDirective('test'); + + const restoredIDL = printSchema(buildSchema( + print(schema.astNode) + '\n' + + print(query.astNode) + '\n' + + print(testInput.astNode) + '\n' + + print(testEnum.astNode) + '\n' + + print(testUnion.astNode) + '\n' + + print(testInterface.astNode) + '\n' + + print(testType.astNode) + '\n' + + print(testDirective.astNode) + )); + expect(restoredIDL).to.be.equal(printSchema(schema)); + + const testField = query.getFields().testField; + expect(print(testField.astNode)).to.equal( + 'testField(testArg: TestInput): TestUnion' + ); + expect(print(testField.args[0].astNode)).to.equal( + 'testArg: TestInput' + ); + expect(print(testInput.getFields().testInputField.astNode)).to.equal( + 'testInputField: TestEnum' + ); + expect(print(testEnum.getValue('TEST_VALUE').astNode)).to.equal( + 'TEST_VALUE' + ); + expect(print(testInterface.getFields().interfaceField.astNode)).to.equal( + 'interfaceField: String' + ); + expect(print(testType.getFields().interfaceField.astNode)).to.equal( + 'interfaceField: String' + ); + expect(print(testDirective.args[0].astNode)).to.equal('arg: Int'); + }); }); describe('Failures', () => { diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 511dca40c9..f3d0e6dac2 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -263,6 +263,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { null, types, directives, + astNode: schemaDef, }); function getDirective( @@ -275,6 +276,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { node => ((node.value: any): DirectiveLocationEnum) ), args: directiveNode.arguments && makeInputValues(directiveNode.arguments), + astNode: directiveNode, }); } @@ -359,6 +361,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { description: getDescription(def), fields: () => makeFieldDefMap(def), interfaces: () => makeImplementedInterfaces(def), + astNode: def, }); } @@ -372,7 +375,8 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { type: produceOutputType(field.type), description: getDescription(field), args: makeInputValues(field.arguments), - deprecationReason: getDeprecationReason(field) + deprecationReason: getDeprecationReason(field), + astNode: field, }) ); } @@ -391,7 +395,8 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { return { type, description: getDescription(value), - defaultValue: valueFromAST(value.defaultValue, type) + defaultValue: valueFromAST(value.defaultValue, type), + astNode: value, }; } ); @@ -403,6 +408,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { name: typeName, description: getDescription(def), fields: () => makeFieldDefMap(def), + astNode: def, resolveType: cannotExecuteSchema, }); } @@ -416,9 +422,11 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { enumValue => enumValue.name.value, enumValue => ({ description: getDescription(enumValue), - deprecationReason: getDeprecationReason(enumValue) + deprecationReason: getDeprecationReason(enumValue), + astNode: enumValue, }) ), + astNode: def, }); return enumType; @@ -430,6 +438,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { description: getDescription(def), types: def.types.map(t => produceObjectType(t)), resolveType: cannotExecuteSchema, + astNode: def, }); } @@ -437,6 +446,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { return new GraphQLScalarType({ name: def.name.value, description: getDescription(def), + astNode: def, serialize: () => null, // Note: validation calls the parse functions to determine if a // literal value is correct. Returning null would cause use of custom @@ -452,6 +462,7 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { name: def.name.value, description: getDescription(def), fields: () => makeInputValues(def.fields), + astNode: def, }); } } From a75020a0acaa6ce4d4c2856c2e7f0c9a8f63d310 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Tue, 6 Jun 2017 13:33:17 +0300 Subject: [PATCH 4/4] Make 'extendSchema' assign 'astNode' & 'extensionASTNodes' --- src/utilities/__tests__/extendSchema-test.js | 93 +++++++++++++++++++- src/utilities/extendSchema.js | 27 +++++- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index c997772759..80116718ad 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -10,9 +10,10 @@ import { describe, it } from 'mocha'; import { expect } from 'chai'; import dedent from '../../jsutils/dedent'; +import { buildSchema } from '../buildASTSchema'; import { extendSchema } from '../extendSchema'; import { execute } from '../../execution'; -import { parse } from '../../language'; +import { parse, print } from '../../language'; import { printSchema } from '../schemaPrinter'; import { GraphQLSchema, @@ -196,6 +197,96 @@ describe('extendSchema', () => { `); }); + it('correctly assign AST nodes to new and extended types', () => { + const schemaFromIDL = buildSchema(` + type Query { + dummyField: String + } + `); + const extensionAst = parse(` + extend type Query { + newField(testArg: TestInput): TestEnum + } + + enum TestEnum { + TEST_VALUE + } + + input TestInput { + testInputField: TestEnum + } + `); + const extendedSchema = extendSchema(schemaFromIDL, extensionAst); + const secondExtensionAst = parse(` + extend type Query { + oneMoreNewField: TestUnion + } + + union TestUnion = TestType + + interface TestInterface { + interfaceField: String + } + + type TestType implements TestInterface { + interfaceField: String + } + + directive @test(arg: Int) on FIELD + `); + const extendedTwiceSchema = extendSchema(extendedSchema, + secondExtensionAst); + + const query = extendedTwiceSchema.getType('Query'); + const testInput = extendedTwiceSchema.getType('TestInput'); + const testEnum = extendedTwiceSchema.getType('TestEnum'); + const testUnion = extendedTwiceSchema.getType('TestUnion'); + const testInterface = extendedTwiceSchema.getType('TestInterface'); + const testType = extendedTwiceSchema.getType('TestType'); + const testDirective = extendedTwiceSchema.getDirective('test'); + + expect(query.extensionASTNodes).to.have.lengthOf(2); + expect(testType.extensionASTNodes).to.have.lengthOf(0); + + const restoredExtensionAST = parse( + print(query.extensionASTNodes[0]) + '\n' + + print(query.extensionASTNodes[1]) + '\n' + + print(testInput.astNode) + '\n' + + print(testEnum.astNode) + '\n' + + print(testUnion.astNode) + '\n' + + print(testInterface.astNode) + '\n' + + print(testType.astNode) + '\n' + + print(testDirective.astNode) + ); + expect( + printSchema(extendSchema(schemaFromIDL, restoredExtensionAST)) + ).to.be.equal(printSchema(extendedTwiceSchema)); + + const newField = query.getFields().newField; + expect(print(newField.astNode)).to.equal( + 'newField(testArg: TestInput): TestEnum' + ); + expect(print(newField.args[0].astNode)).to.equal( + 'testArg: TestInput' + ); + expect(print(query.getFields().oneMoreNewField.astNode)).to.equal( + 'oneMoreNewField: TestUnion' + ); + expect(print(testInput.getFields().testInputField.astNode)).to.equal( + 'testInputField: TestEnum' + ); + expect(print(testEnum.getValue('TEST_VALUE').astNode)).to.equal( + 'TEST_VALUE' + ); + expect(print(testInterface.getFields().interfaceField.astNode)).to.equal( + 'interfaceField: String' + ); + expect(print(testType.getFields().interfaceField.astNode)).to.equal( + 'interfaceField: String' + ); + expect(print(testDirective.args[0].astNode)).to.equal('arg: Int'); + }); + it('builds types with deprecated fields/values', () => { const ast = parse(` type TypeWithDeprecatedField { diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index c9fe0d956e..0c3b76c9a4 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -240,6 +240,7 @@ export function extendSchema( subscription: subscriptionType, types, directives: getMergedDirectives(), + astNode: schema.astNode, }); // Below are functions used for producing this schema that have closed over @@ -332,11 +333,19 @@ export function extendSchema( } function extendObjectType(type: GraphQLObjectType): GraphQLObjectType { + const name = type.name; + let extensionASTNodes = type.extensionASTNodes; + if (typeExtensionsMap[name]) { + extensionASTNodes = extensionASTNodes.concat(typeExtensionsMap[name]); + } + return new GraphQLObjectType({ - name: type.name, + name, description: type.description, interfaces: () => extendImplementedInterfaces(type), fields: () => extendFieldMap(type), + astNode: type.astNode, + extensionASTNodes, isTypeOf: type.isTypeOf, }); } @@ -348,6 +357,7 @@ export function extendSchema( name: type.name, description: type.description, fields: () => extendFieldMap(type), + astNode: type.astNode, resolveType: type.resolveType, }); } @@ -357,6 +367,7 @@ export function extendSchema( name: type.name, description: type.description, types: type.getTypes().map(getTypeFromDef), + astNode: type.astNode, resolveType: type.resolveType, }); } @@ -397,6 +408,7 @@ export function extendSchema( deprecationReason: field.deprecationReason, type: extendFieldType(field.type), args: keyMap(field.args, arg => arg.name), + astNode: field.astNode, resolve: field.resolve, }; }); @@ -419,6 +431,7 @@ export function extendSchema( type: buildOutputFieldType(field.type), args: buildInputValues(field.arguments), deprecationReason: getDeprecationReason(field), + astNode: field, }; }); }); @@ -456,6 +469,7 @@ export function extendSchema( description: getDescription(typeNode), interfaces: () => buildImplementedInterfaces(typeNode), fields: () => buildFieldMap(typeNode), + astNode: typeNode, }); } @@ -464,6 +478,7 @@ export function extendSchema( name: typeNode.name.value, description: getDescription(typeNode), fields: () => buildFieldMap(typeNode), + astNode: typeNode, resolveType: cannotExecuteExtendedSchema, }); } @@ -473,6 +488,7 @@ export function extendSchema( name: typeNode.name.value, description: getDescription(typeNode), types: typeNode.types.map(getObjectTypeFromAST), + astNode: typeNode, resolveType: cannotExecuteExtendedSchema, }); } @@ -481,6 +497,7 @@ export function extendSchema( return new GraphQLScalarType({ name: typeNode.name.value, description: getDescription(typeNode), + astNode: typeNode, serialize: id => id, // Note: validation calls the parse functions to determine if a // literal value is correct. Returning null would cause use of custom @@ -501,8 +518,10 @@ export function extendSchema( enumValue => ({ description: getDescription(enumValue), deprecationReason: getDeprecationReason(enumValue), + astNode: enumValue, }), ), + astNode: typeNode, }); } @@ -511,6 +530,7 @@ export function extendSchema( name: typeNode.name.value, description: getDescription(typeNode), fields: () => buildInputValues(typeNode.fields), + astNode: typeNode, }); } @@ -524,6 +544,7 @@ export function extendSchema( ), args: directiveNode.arguments && buildInputValues(directiveNode.arguments), + astNode: directiveNode, }); } @@ -541,6 +562,7 @@ export function extendSchema( description: getDescription(field), args: buildInputValues(field.arguments), deprecationReason: getDeprecationReason(field), + astNode: field, }) ); } @@ -554,7 +576,8 @@ export function extendSchema( return { type, description: getDescription(value), - defaultValue: valueFromAST(value.defaultValue, type) + defaultValue: valueFromAST(value.defaultValue, type), + astNode: value, }; } );