Skip to content

Commit 098ba10

Browse files
IvanGoncharovyaacovCR
authored andcommitted
Motivation: looked into this it as part of graphql#3687 (graphql#3690)
Explanation: `Object.create(null)` returns value of `any` type. So bellow construct is not reported by TS even in "strict" mode: ```ts const foo = Object.create(null); ``` Fixing this issue in `extendSchema` requires adding more code since we can't put all extensions nodes into one collection without loosing typesafety.
1 parent 1a95fb1 commit 098ba10

File tree

1 file changed

+77
-33
lines changed

1 file changed

+77
-33
lines changed

src/utilities/extendSchema.ts

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AccumulatorMap } from '../jsutils/AccumulatorMap';
12
import { inspect } from '../jsutils/inspect';
23
import { invariant } from '../jsutils/invariant';
34
import { keyMap } from '../jsutils/keyMap';
@@ -29,10 +30,6 @@ import type {
2930
UnionTypeExtensionNode,
3031
} from '../language/ast';
3132
import { Kind } from '../language/kinds';
32-
import {
33-
isTypeDefinitionNode,
34-
isTypeExtensionNode,
35-
} from '../language/predicates';
3633

3734
import type {
3835
GraphQLArgumentConfig,
@@ -132,7 +129,25 @@ export function extendSchemaImpl(
132129
): GraphQLSchemaNormalizedConfig {
133130
// Collect the type definitions and extensions found in the document.
134131
const typeDefs: Array<TypeDefinitionNode> = [];
135-
const typeExtensionsMap = Object.create(null);
132+
133+
const scalarExtensions = new AccumulatorMap<
134+
string,
135+
ScalarTypeExtensionNode
136+
>();
137+
const objectExtensions = new AccumulatorMap<
138+
string,
139+
ObjectTypeExtensionNode
140+
>();
141+
const interfaceExtensions = new AccumulatorMap<
142+
string,
143+
InterfaceTypeExtensionNode
144+
>();
145+
const unionExtensions = new AccumulatorMap<string, UnionTypeExtensionNode>();
146+
const enumExtensions = new AccumulatorMap<string, EnumTypeExtensionNode>();
147+
const inputObjectExtensions = new AccumulatorMap<
148+
string,
149+
InputObjectTypeExtensionNode
150+
>();
136151

137152
// New directives and types are separate because a directives and types can
138153
// have the same name. For example, a type named "skip".
@@ -142,33 +157,57 @@ export function extendSchemaImpl(
142157
// Schema extensions are collected which may add additional operation types.
143158
const schemaExtensions: Array<SchemaExtensionNode> = [];
144159

160+
let isSchemaChanged = false;
145161
for (const def of documentAST.definitions) {
146-
if (def.kind === Kind.SCHEMA_DEFINITION) {
147-
schemaDef = def;
148-
} else if (def.kind === Kind.SCHEMA_EXTENSION) {
149-
schemaExtensions.push(def);
150-
} else if (isTypeDefinitionNode(def)) {
151-
typeDefs.push(def);
152-
} else if (isTypeExtensionNode(def)) {
153-
const extendedTypeName = def.name.value;
154-
const existingTypeExtensions = typeExtensionsMap[extendedTypeName];
155-
typeExtensionsMap[extendedTypeName] = existingTypeExtensions
156-
? existingTypeExtensions.concat([def])
157-
: [def];
158-
} else if (def.kind === Kind.DIRECTIVE_DEFINITION) {
159-
directiveDefs.push(def);
162+
switch (def.kind) {
163+
case Kind.SCHEMA_DEFINITION:
164+
schemaDef = def;
165+
break;
166+
case Kind.SCHEMA_EXTENSION:
167+
schemaExtensions.push(def);
168+
break;
169+
case Kind.DIRECTIVE_DEFINITION:
170+
directiveDefs.push(def);
171+
break;
172+
173+
// Type Definitions
174+
case Kind.SCALAR_TYPE_DEFINITION:
175+
case Kind.OBJECT_TYPE_DEFINITION:
176+
case Kind.INTERFACE_TYPE_DEFINITION:
177+
case Kind.UNION_TYPE_DEFINITION:
178+
case Kind.ENUM_TYPE_DEFINITION:
179+
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
180+
typeDefs.push(def);
181+
break;
182+
183+
// Type System Extensions
184+
case Kind.SCALAR_TYPE_EXTENSION:
185+
scalarExtensions.add(def.name.value, def);
186+
break;
187+
case Kind.OBJECT_TYPE_EXTENSION:
188+
objectExtensions.add(def.name.value, def);
189+
break;
190+
case Kind.INTERFACE_TYPE_EXTENSION:
191+
interfaceExtensions.add(def.name.value, def);
192+
break;
193+
case Kind.UNION_TYPE_EXTENSION:
194+
unionExtensions.add(def.name.value, def);
195+
break;
196+
case Kind.ENUM_TYPE_EXTENSION:
197+
enumExtensions.add(def.name.value, def);
198+
break;
199+
case Kind.INPUT_OBJECT_TYPE_EXTENSION:
200+
inputObjectExtensions.add(def.name.value, def);
201+
break;
202+
default:
203+
continue;
160204
}
205+
isSchemaChanged = true;
161206
}
162207

163208
// If this document contains no new types, extensions, or directives then
164209
// return the same unmodified GraphQLSchema instance.
165-
if (
166-
Object.keys(typeExtensionsMap).length === 0 &&
167-
typeDefs.length === 0 &&
168-
directiveDefs.length === 0 &&
169-
schemaExtensions.length === 0 &&
170-
schemaDef == null
171-
) {
210+
if (!isSchemaChanged) {
172211
return schemaConfig;
173212
}
174213

@@ -275,7 +314,7 @@ export function extendSchemaImpl(
275314
type: GraphQLInputObjectType,
276315
): GraphQLInputObjectType {
277316
const config = type.toConfig();
278-
const extensions = typeExtensionsMap[config.name] ?? [];
317+
const extensions = inputObjectExtensions.get(config.name) ?? [];
279318

280319
return new GraphQLInputObjectType({
281320
...config,
@@ -292,7 +331,7 @@ export function extendSchemaImpl(
292331

293332
function extendEnumType(type: GraphQLEnumType): GraphQLEnumType {
294333
const config = type.toConfig();
295-
const extensions = typeExtensionsMap[type.name] ?? [];
334+
const extensions = enumExtensions.get(type.name) ?? [];
296335

297336
return new GraphQLEnumType({
298337
...config,
@@ -306,7 +345,7 @@ export function extendSchemaImpl(
306345

307346
function extendScalarType(type: GraphQLScalarType): GraphQLScalarType {
308347
const config = type.toConfig();
309-
const extensions = typeExtensionsMap[config.name] ?? [];
348+
const extensions = scalarExtensions.get(config.name) ?? [];
310349

311350
let specifiedByURL = config.specifiedByURL;
312351
for (const extensionNode of extensions) {
@@ -322,7 +361,7 @@ export function extendSchemaImpl(
322361

323362
function extendObjectType(type: GraphQLObjectType): GraphQLObjectType {
324363
const config = type.toConfig();
325-
const extensions = typeExtensionsMap[config.name] ?? [];
364+
const extensions = objectExtensions.get(config.name) ?? [];
326365

327366
return new GraphQLObjectType({
328367
...config,
@@ -342,7 +381,7 @@ export function extendSchemaImpl(
342381
type: GraphQLInterfaceType,
343382
): GraphQLInterfaceType {
344383
const config = type.toConfig();
345-
const extensions = typeExtensionsMap[config.name] ?? [];
384+
const extensions = interfaceExtensions.get(config.name) ?? [];
346385

347386
return new GraphQLInterfaceType({
348387
...config,
@@ -360,7 +399,7 @@ export function extendSchemaImpl(
360399

361400
function extendUnionType(type: GraphQLUnionType): GraphQLUnionType {
362401
const config = type.toConfig();
363-
const extensions = typeExtensionsMap[config.name] ?? [];
402+
const extensions = unionExtensions.get(config.name) ?? [];
364403

365404
return new GraphQLUnionType({
366405
...config,
@@ -579,10 +618,10 @@ export function extendSchemaImpl(
579618

580619
function buildType(astNode: TypeDefinitionNode): GraphQLNamedType {
581620
const name = astNode.name.value;
582-
const extensionASTNodes = typeExtensionsMap[name] ?? [];
583621

584622
switch (astNode.kind) {
585623
case Kind.OBJECT_TYPE_DEFINITION: {
624+
const extensionASTNodes = objectExtensions.get(name) ?? [];
586625
const allNodes = [astNode, ...extensionASTNodes];
587626

588627
return new GraphQLObjectType({
@@ -595,6 +634,7 @@ export function extendSchemaImpl(
595634
});
596635
}
597636
case Kind.INTERFACE_TYPE_DEFINITION: {
637+
const extensionASTNodes = interfaceExtensions.get(name) ?? [];
598638
const allNodes = [astNode, ...extensionASTNodes];
599639

600640
return new GraphQLInterfaceType({
@@ -607,6 +647,7 @@ export function extendSchemaImpl(
607647
});
608648
}
609649
case Kind.ENUM_TYPE_DEFINITION: {
650+
const extensionASTNodes = enumExtensions.get(name) ?? [];
610651
const allNodes = [astNode, ...extensionASTNodes];
611652

612653
return new GraphQLEnumType({
@@ -618,6 +659,7 @@ export function extendSchemaImpl(
618659
});
619660
}
620661
case Kind.UNION_TYPE_DEFINITION: {
662+
const extensionASTNodes = unionExtensions.get(name) ?? [];
621663
const allNodes = [astNode, ...extensionASTNodes];
622664

623665
return new GraphQLUnionType({
@@ -629,6 +671,7 @@ export function extendSchemaImpl(
629671
});
630672
}
631673
case Kind.SCALAR_TYPE_DEFINITION: {
674+
const extensionASTNodes = scalarExtensions.get(name) ?? [];
632675
return new GraphQLScalarType({
633676
name,
634677
description: astNode.description?.value,
@@ -638,6 +681,7 @@ export function extendSchemaImpl(
638681
});
639682
}
640683
case Kind.INPUT_OBJECT_TYPE_DEFINITION: {
684+
const extensionASTNodes = inputObjectExtensions.get(name) ?? [];
641685
const allNodes = [astNode, ...extensionASTNodes];
642686

643687
return new GraphQLInputObjectType({

0 commit comments

Comments
 (0)