Skip to content

Commit c9f968b

Browse files
Motivation: looked into this it as part of #3687 (#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 7f5fe4d commit c9f968b

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

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

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

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

@@ -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)