diff --git a/src/type/definition.d.ts b/src/type/definition.d.ts index 2f085c9c8a..42fb596024 100644 --- a/src/type/definition.d.ts +++ b/src/type/definition.d.ts @@ -306,7 +306,7 @@ export class GraphQLScalarType { parseLiteral: GraphQLScalarLiteralParser; extensions: Maybe>; astNode: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly>); @@ -409,7 +409,7 @@ export class GraphQLObjectType { isTypeOf: Maybe>; extensions: Maybe>>; astNode: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly>); @@ -616,7 +616,7 @@ export class GraphQLInterfaceType { resolveType: Maybe>; extensions: Maybe>; astNode?: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly>); getFields(): GraphQLFieldMap; @@ -692,7 +692,7 @@ export class GraphQLUnionType { resolveType: Maybe>; extensions: Maybe>; astNode: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly>); getTypes(): Array; @@ -762,7 +762,7 @@ export class GraphQLEnumType { description: Maybe; extensions: Maybe>; astNode: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly); getValues(): Array; @@ -865,7 +865,7 @@ export class GraphQLInputObjectType { description: Maybe; extensions: Maybe>; astNode: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly); getFields(): GraphQLInputFieldMap; diff --git a/src/type/definition.js b/src/type/definition.js index fa37748347..04c5f05ef3 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -522,10 +522,6 @@ function resolveObjMapThunk(thunk: ThunkObjMap): ObjMap { return typeof thunk === 'function' ? thunk() : thunk; } -function undefineIfEmpty(arr: ?$ReadOnlyArray): ?$ReadOnlyArray { - return arr && arr.length > 0 ? arr : undefined; -} - /** * Scalar Type Definition * @@ -559,7 +555,7 @@ export class GraphQLScalarType { parseLiteral: GraphQLScalarLiteralParser; extensions: ?ReadOnlyObjMap; astNode: ?ScalarTypeDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; constructor(config: $ReadOnly>) { const parseValue = config.parseValue ?? identityFunc; @@ -573,7 +569,7 @@ export class GraphQLScalarType { ((node, variables) => parseValue(valueFromASTUntyped(node, variables))); this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this.extensionASTNodes = config.extensionASTNodes ?? []; devAssert(typeof config.name === 'string', 'Must provide name.'); @@ -608,7 +604,7 @@ export class GraphQLScalarType { parseLiteral: this.parseLiteral, extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes ?? [], + extensionASTNodes: this.extensionASTNodes, }; } @@ -706,7 +702,7 @@ export class GraphQLObjectType { isTypeOf: ?GraphQLIsTypeOfFn; extensions: ?ReadOnlyObjMap; astNode: ?ObjectTypeDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; _fields: ThunkObjMap>; _interfaces: ThunkArray; @@ -717,7 +713,7 @@ export class GraphQLObjectType { this.isTypeOf = config.isTypeOf; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this.extensionASTNodes = config.extensionASTNodes ?? []; this._fields = defineFieldMap.bind(undefined, config); this._interfaces = defineInterfaces.bind(undefined, config); @@ -752,7 +748,7 @@ export class GraphQLObjectType { isTypeOf: this.isTypeOf, extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes || [], + extensionASTNodes: this.extensionASTNodes, }; } @@ -1023,7 +1019,7 @@ export class GraphQLInterfaceType { resolveType: ?GraphQLTypeResolver; extensions: ?ReadOnlyObjMap; astNode: ?InterfaceTypeDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; _fields: ThunkObjMap>; _interfaces: ThunkArray; @@ -1034,7 +1030,7 @@ export class GraphQLInterfaceType { this.resolveType = config.resolveType; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this.extensionASTNodes = config.extensionASTNodes ?? []; this._fields = defineFieldMap.bind(undefined, config); this._interfaces = defineInterfaces.bind(undefined, config); @@ -1069,7 +1065,7 @@ export class GraphQLInterfaceType { resolveType: this.resolveType, extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes ?? [], + extensionASTNodes: this.extensionASTNodes, }; } @@ -1140,7 +1136,7 @@ export class GraphQLUnionType { resolveType: ?GraphQLTypeResolver; extensions: ?ReadOnlyObjMap; astNode: ?UnionTypeDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; _types: ThunkArray; @@ -1150,7 +1146,7 @@ export class GraphQLUnionType { this.resolveType = config.resolveType; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this.extensionASTNodes = config.extensionASTNodes ?? []; this._types = defineTypes.bind(undefined, config); devAssert(typeof config.name === 'string', 'Must provide name.'); @@ -1176,7 +1172,7 @@ export class GraphQLUnionType { resolveType: this.resolveType, extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes ?? [], + extensionASTNodes: this.extensionASTNodes, }; } @@ -1253,7 +1249,7 @@ export class GraphQLEnumType /* */ { description: ?string; extensions: ?ReadOnlyObjMap; astNode: ?EnumTypeDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; _values: Array */>; _valueLookup: Map; @@ -1264,7 +1260,7 @@ export class GraphQLEnumType /* */ { this.description = config.description; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this.extensionASTNodes = config.extensionASTNodes ?? []; this._values = defineEnumValues(this.name, config.values); this._valueLookup = new Map( @@ -1354,7 +1350,7 @@ export class GraphQLEnumType /* */ { values, extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes ?? [], + extensionASTNodes: this.extensionASTNodes, }; } @@ -1466,7 +1462,7 @@ export class GraphQLInputObjectType { description: ?string; extensions: ?ReadOnlyObjMap; astNode: ?InputObjectTypeDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; _fields: ThunkObjMap; @@ -1475,7 +1471,7 @@ export class GraphQLInputObjectType { this.description = config.description; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this.extensionASTNodes = config.extensionASTNodes ?? []; this._fields = defineInputFieldMap.bind(undefined, config); devAssert(typeof config.name === 'string', 'Must provide name.'); @@ -1503,7 +1499,7 @@ export class GraphQLInputObjectType { fields, extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes ?? [], + extensionASTNodes: this.extensionASTNodes, }; } diff --git a/src/type/schema.d.ts b/src/type/schema.d.ts index c4c366f737..9e751faff0 100644 --- a/src/type/schema.d.ts +++ b/src/type/schema.d.ts @@ -62,7 +62,7 @@ export class GraphQLSchema { description: Maybe; extensions: Maybe>; astNode: Maybe; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; constructor(config: Readonly); getQueryType(): Maybe; @@ -136,6 +136,6 @@ export interface GraphQLSchemaNormalizedConfig extends GraphQLSchemaConfig { types: Array; directives: Array; extensions: Maybe>; - extensionASTNodes: Maybe>; + extensionASTNodes: ReadonlyArray; assumeValid: boolean; } diff --git a/src/type/schema.js b/src/type/schema.js index 5b2e834865..be67ffd270 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -121,7 +121,7 @@ export class GraphQLSchema { description: ?string; extensions: ?ReadOnlyObjMap; astNode: ?SchemaDefinitionNode; - extensionASTNodes: ?$ReadOnlyArray; + extensionASTNodes: $ReadOnlyArray; _queryType: ?GraphQLObjectType; _mutationType: ?GraphQLObjectType; @@ -157,7 +157,7 @@ export class GraphQLSchema { this.description = config.description; this.extensions = config.extensions && toObjMap(config.extensions); this.astNode = config.astNode; - this.extensionASTNodes = config.extensionASTNodes; + this.extensionASTNodes = config.extensionASTNodes ?? []; this._queryType = config.query; this._mutationType = config.mutation; @@ -337,7 +337,7 @@ export class GraphQLSchema { directives: this.getDirectives().slice(), extensions: this.extensions, astNode: this.astNode, - extensionASTNodes: this.extensionASTNodes ?? [], + extensionASTNodes: this.extensionASTNodes, assumeValid: this.__validationErrors !== undefined, }; } diff --git a/src/type/validate.js b/src/type/validate.js index 4d39256ac4..bc0763a0f7 100644 --- a/src/type/validate.js +++ b/src/type/validate.js @@ -261,10 +261,10 @@ function validateFields( // Objects and Interfaces both must define one or more fields. if (fields.length === 0) { - context.reportError( - `Type ${type.name} must define one or more fields.`, - [type.astNode].concat(type.extensionASTNodes), - ); + context.reportError(`Type ${type.name} must define one or more fields.`, [ + type.astNode, + ...type.extensionASTNodes, + ]); } for (const field of fields) { @@ -364,7 +364,7 @@ function validateTypeImplementsInterface( if (!typeField) { context.reportError( `Interface field ${iface.name}.${fieldName} expected but ${type.name} does not provide it.`, - [ifaceField.astNode, type.astNode].concat(type.extensionASTNodes), + [ifaceField.astNode, type.astNode, ...type.extensionASTNodes], ); continue; } @@ -464,7 +464,7 @@ function validateUnionMembers( if (memberTypes.length === 0) { context.reportError( `Union type ${union.name} must define one or more member types.`, - [union.astNode].concat(union.extensionASTNodes), + [union.astNode, ...union.extensionASTNodes], ); } @@ -497,7 +497,7 @@ function validateEnumValues( if (enumValues.length === 0) { context.reportError( `Enum type ${enumType.name} must define one or more values.`, - [enumType.astNode].concat(enumType.extensionASTNodes), + [enumType.astNode, ...enumType.extensionASTNodes], ); } @@ -524,7 +524,7 @@ function validateInputFields( if (fields.length === 0) { context.reportError( `Input Object type ${inputObj.name} must define one or more fields.`, - [inputObj.astNode].concat(inputObj.extensionASTNodes), + [inputObj.astNode, ...inputObj.extensionASTNodes], ); } @@ -611,10 +611,13 @@ function getAllImplementsInterfaceNodes( type: GraphQLObjectType | GraphQLInterfaceType, iface: GraphQLInterfaceType, ): $ReadOnlyArray { + const { astNode, extensionASTNodes } = type; + const nodes = + astNode != null ? [astNode, ...extensionASTNodes] : extensionASTNodes; + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') - return [type.astNode] - .concat(type.extensionASTNodes) - .flatMap((typeNode) => typeNode?.interfaces ?? []) + return nodes + .flatMap((typeNode) => typeNode.interfaces ?? []) .filter((ifaceNode) => ifaceNode.name.value === iface.name); } @@ -622,10 +625,13 @@ function getUnionMemberTypeNodes( union: GraphQLUnionType, typeName: string, ): ?$ReadOnlyArray { + const { astNode, extensionASTNodes } = union; + const nodes = + astNode != null ? [astNode, ...extensionASTNodes] : extensionASTNodes; + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') - return [union.astNode] - .concat(union.extensionASTNodes) - .flatMap((unionNode) => unionNode?.types ?? []) + return nodes + .flatMap((unionNode) => unionNode.types ?? []) .filter((typeNode) => typeNode.name.value === typeName); } diff --git a/src/utilities/__tests__/buildASTSchema-test.js b/src/utilities/__tests__/buildASTSchema-test.js index b6369be924..1fa28dc073 100644 --- a/src/utilities/__tests__/buildASTSchema-test.js +++ b/src/utilities/__tests__/buildASTSchema-test.js @@ -58,7 +58,7 @@ function printASTNode(obj: ?{ +astNode: ?ASTNode, ... }): string { } function printAllASTNodes(obj: GraphQLNamedType): string { - invariant(obj.astNode != null && obj.extensionASTNodes != null); + invariant(obj.astNode != null); return print({ kind: Kind.DOCUMENT, definitions: [obj.astNode, ...obj.extensionASTNodes], diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index 1c7c920cd1..1d8470cd7a 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -38,8 +38,7 @@ import { extendSchema } from '../extendSchema'; import { buildSchema } from '../buildASTSchema'; function printExtensionNodes(obj: ?GraphQLNamedType | GraphQLSchema): string { - // istanbul ignore next (FIXME) - invariant(obj?.extensionASTNodes != null); + invariant(obj != null); return print({ kind: Kind.DOCUMENT, definitions: obj.extensionASTNodes, @@ -442,18 +441,11 @@ describe('extendSchema', () => { extendedTwiceSchema.getDirective('test'), ); - expect(testType).to.include({ extensionASTNodes: undefined }); - expect(testEnum).to.include({ extensionASTNodes: undefined }); - expect(testUnion).to.include({ extensionASTNodes: undefined }); - expect(testInput).to.include({ extensionASTNodes: undefined }); - expect(testInterface).to.include({ extensionASTNodes: undefined }); - - invariant(query.extensionASTNodes); - invariant(someScalar.extensionASTNodes); - invariant(someEnum.extensionASTNodes); - invariant(someUnion.extensionASTNodes); - invariant(someInput.extensionASTNodes); - invariant(someInterface.extensionASTNodes); + expect(testType.extensionASTNodes).to.deep.equal([]); + expect(testEnum.extensionASTNodes).to.deep.equal([]); + expect(testUnion.extensionASTNodes).to.deep.equal([]); + expect(testInput.extensionASTNodes).to.deep.equal([]); + expect(testInterface.extensionASTNodes).to.deep.equal([]); expect([ testInput.astNode,