Skip to content

Commit 11e87c7

Browse files
JoviDeCroockn1ru4l
authored andcommitted
Support symbol properties on extension objects (graphql#4234)
Supersedes graphql#3511 This adds support for `Symbol` property keys on the extension property of AST nodes. Had to tweak `toObjMap` here to support symbol keys by adding `getOwnPropertySymbols` which is the only way to enumerate over them. --------- Co-authored-by: n1ru4l <laurinquast@googlemail.com>
1 parent d4f5534 commit 11e87c7

6 files changed

Lines changed: 84 additions & 28 deletions

File tree

src/jsutils/ObjMap.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ export interface ReadOnlyObjMap<T> {
88
readonly [key: string]: T;
99
}
1010

11+
export interface ReadOnlyObjMapWithSymbol<T> {
12+
readonly [key: string | symbol]: T;
13+
}
14+
1115
export type ReadOnlyObjMapLike<T> =
1216
| ReadOnlyObjMap<T>
1317
| { readonly [key: string]: T };
18+
19+
export type ReadOnlyObjMapSymbolLike<T> =
20+
| ReadOnlyObjMapWithSymbol<T>
21+
| { readonly [key: string | symbol]: T };

src/jsutils/toObjMap.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { Maybe } from './Maybe.js';
2-
import type { ReadOnlyObjMap, ReadOnlyObjMapLike } from './ObjMap.js';
2+
import type {
3+
ReadOnlyObjMap,
4+
ReadOnlyObjMapLike,
5+
ReadOnlyObjMapSymbolLike,
6+
ReadOnlyObjMapWithSymbol,
7+
} from './ObjMap.js';
38

49
export function toObjMap<T>(
510
obj: Maybe<ReadOnlyObjMapLike<T>>,
@@ -16,5 +21,29 @@ export function toObjMap<T>(
1621
for (const [key, value] of Object.entries(obj)) {
1722
map[key] = value;
1823
}
24+
25+
return map;
26+
}
27+
28+
export function toObjMapWithSymbols<T>(
29+
obj: Maybe<ReadOnlyObjMapSymbolLike<T>>,
30+
): ReadOnlyObjMapWithSymbol<T> {
31+
if (obj == null) {
32+
return Object.create(null);
33+
}
34+
35+
if (Object.getPrototypeOf(obj) === null) {
36+
return obj;
37+
}
38+
39+
const map = Object.create(null);
40+
for (const [key, value] of Object.entries(obj)) {
41+
map[key] = value;
42+
}
43+
44+
for (const key of Object.getOwnPropertySymbols(obj)) {
45+
map[key] = obj[key];
46+
}
47+
1948
return map;
2049
}

src/type/__tests__/definition-test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,25 @@ describe('Type System: Scalars', () => {
8686
expect(someScalar.toConfig()).to.deep.equal(someScalarConfig);
8787
});
8888

89+
it('supports symbol extensions', () => {
90+
const test = Symbol.for('test');
91+
const someScalarConfig: GraphQLScalarTypeConfig<unknown, unknown> = {
92+
name: 'SomeScalar',
93+
description: 'SomeScalar description.',
94+
specifiedByURL: 'https://example.com/foo_spec',
95+
serialize: passThroughFunc,
96+
parseValue: passThroughFunc,
97+
parseLiteral: passThroughFunc,
98+
coerceInputLiteral: passThroughFunc,
99+
valueToLiteral: passThroughFunc,
100+
extensions: { [test]: 'extension' },
101+
astNode: dummyAny,
102+
extensionASTNodes: [dummyAny],
103+
};
104+
const someScalar = new GraphQLScalarType(someScalarConfig);
105+
expect(someScalar.toConfig()).to.deep.equal(someScalarConfig);
106+
});
107+
89108
it('provides default methods if omitted', () => {
90109
const scalar = new GraphQLScalarType({ name: 'Foo' });
91110

src/type/definition.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { ObjMap } from '../jsutils/ObjMap.js';
1111
import type { Path } from '../jsutils/Path.js';
1212
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.js';
1313
import { suggestionList } from '../jsutils/suggestionList.js';
14-
import { toObjMap } from '../jsutils/toObjMap.js';
14+
import { toObjMapWithSymbols } from '../jsutils/toObjMap.js';
1515

1616
import { GraphQLError } from '../error/GraphQLError.js';
1717

@@ -511,7 +511,7 @@ export function resolveObjMapThunk<T>(thunk: ThunkObjMap<T>): ObjMap<T> {
511511
* an object which can contain all the values you need.
512512
*/
513513
export interface GraphQLScalarTypeExtensions {
514-
[attributeName: string]: unknown;
514+
[attributeName: string | symbol]: unknown;
515515
}
516516

517517
/**
@@ -610,7 +610,7 @@ export class GraphQLScalarType<TInternal = unknown, TExternal = TInternal> {
610610
((node, variables) => parseValue(valueFromASTUntyped(node, variables)));
611611
this.coerceInputLiteral = config.coerceInputLiteral;
612612
this.valueToLiteral = config.valueToLiteral;
613-
this.extensions = toObjMap(config.extensions);
613+
this.extensions = toObjMapWithSymbols(config.extensions);
614614
this.astNode = config.astNode;
615615
this.extensionASTNodes = config.extensionASTNodes ?? [];
616616

@@ -725,7 +725,7 @@ interface GraphQLScalarTypeNormalizedConfig<TInternal, TExternal>
725725
* you may find them useful.
726726
*/
727727
export interface GraphQLObjectTypeExtensions<_TSource = any, _TContext = any> {
728-
[attributeName: string]: unknown;
728+
[attributeName: string | symbol]: unknown;
729729
}
730730

731731
/**
@@ -783,7 +783,7 @@ export class GraphQLObjectType<TSource = any, TContext = any> {
783783
this.name = assertName(config.name);
784784
this.description = config.description;
785785
this.isTypeOf = config.isTypeOf;
786-
this.extensions = toObjMap(config.extensions);
786+
this.extensions = toObjMapWithSymbols(config.extensions);
787787
this.astNode = config.astNode;
788788
this.extensionASTNodes = config.extensionASTNodes ?? [];
789789
this._fields = (defineFieldMap<TSource, TContext>).bind(
@@ -854,7 +854,7 @@ function defineFieldMap<TSource, TContext>(
854854
resolve: fieldConfig.resolve,
855855
subscribe: fieldConfig.subscribe,
856856
deprecationReason: fieldConfig.deprecationReason,
857-
extensions: toObjMap(fieldConfig.extensions),
857+
extensions: toObjMapWithSymbols(fieldConfig.extensions),
858858
astNode: fieldConfig.astNode,
859859
};
860860
});
@@ -869,7 +869,7 @@ export function defineArguments(
869869
type: argConfig.type,
870870
defaultValue: defineDefaultValue(argName, argConfig),
871871
deprecationReason: argConfig.deprecationReason,
872-
extensions: toObjMap(argConfig.extensions),
872+
extensions: toObjMapWithSymbols(argConfig.extensions),
873873
astNode: argConfig.astNode,
874874
}));
875875
}
@@ -980,7 +980,7 @@ export interface GraphQLResolveInfo {
980980
* you may find them useful.
981981
*/
982982
export interface GraphQLFieldExtensions<_TSource, _TContext, _TArgs = any> {
983-
[attributeName: string]: unknown;
983+
[attributeName: string | symbol]: unknown;
984984
}
985985

986986
export interface GraphQLFieldConfig<TSource, TContext, TArgs = any> {
@@ -1008,7 +1008,7 @@ export type GraphQLFieldConfigArgumentMap = ObjMap<GraphQLArgumentConfig>;
10081008
* an object which can contain all the values you need.
10091009
*/
10101010
export interface GraphQLArgumentExtensions {
1011-
[attributeName: string]: unknown;
1011+
[attributeName: string | symbol]: unknown;
10121012
}
10131013

10141014
export interface GraphQLArgumentConfig {
@@ -1085,7 +1085,7 @@ export function defineDefaultValue(
10851085
* an object which can contain all the values you need.
10861086
*/
10871087
export interface GraphQLInterfaceTypeExtensions {
1088-
[attributeName: string]: unknown;
1088+
[attributeName: string | symbol]: unknown;
10891089
}
10901090

10911091
/**
@@ -1122,7 +1122,7 @@ export class GraphQLInterfaceType<TSource = any, TContext = any> {
11221122
this.name = assertName(config.name);
11231123
this.description = config.description;
11241124
this.resolveType = config.resolveType;
1125-
this.extensions = toObjMap(config.extensions);
1125+
this.extensions = toObjMapWithSymbols(config.extensions);
11261126
this.astNode = config.astNode;
11271127
this.extensionASTNodes = config.extensionASTNodes ?? [];
11281128
this._fields = (defineFieldMap<TSource, TContext>).bind(
@@ -1206,7 +1206,7 @@ interface GraphQLInterfaceTypeNormalizedConfig<TSource, TContext>
12061206
* an object which can contain all the values you need.
12071207
*/
12081208
export interface GraphQLUnionTypeExtensions {
1209-
[attributeName: string]: unknown;
1209+
[attributeName: string | symbol]: unknown;
12101210
}
12111211

12121212
/**
@@ -1247,7 +1247,7 @@ export class GraphQLUnionType {
12471247
this.name = assertName(config.name);
12481248
this.description = config.description;
12491249
this.resolveType = config.resolveType;
1250-
this.extensions = toObjMap(config.extensions);
1250+
this.extensions = toObjMapWithSymbols(config.extensions);
12511251
this.astNode = config.astNode;
12521252
this.extensionASTNodes = config.extensionASTNodes ?? [];
12531253

@@ -1324,7 +1324,7 @@ interface GraphQLUnionTypeNormalizedConfig
13241324
* an object which can contain all the values you need.
13251325
*/
13261326
export interface GraphQLEnumTypeExtensions {
1327-
[attributeName: string]: unknown;
1327+
[attributeName: string | symbol]: unknown;
13281328
}
13291329

13301330
/**
@@ -1367,7 +1367,7 @@ export class GraphQLEnumType /* <T> */ {
13671367
constructor(config: Readonly<GraphQLEnumTypeConfig /* <T> */>) {
13681368
this.name = assertName(config.name);
13691369
this.description = config.description;
1370-
this.extensions = toObjMap(config.extensions);
1370+
this.extensions = toObjMapWithSymbols(config.extensions);
13711371
this.astNode = config.astNode;
13721372
this.extensionASTNodes = config.extensionASTNodes ?? [];
13731373

@@ -1528,7 +1528,7 @@ function defineEnumValues(
15281528
description: valueConfig.description,
15291529
value: valueConfig.value !== undefined ? valueConfig.value : valueName,
15301530
deprecationReason: valueConfig.deprecationReason,
1531-
extensions: toObjMap(valueConfig.extensions),
1531+
extensions: toObjMapWithSymbols(valueConfig.extensions),
15321532
astNode: valueConfig.astNode,
15331533
}));
15341534
}
@@ -1561,7 +1561,7 @@ export type GraphQLEnumValueConfigMap /* <T> */ =
15611561
* an object which can contain all the values you need.
15621562
*/
15631563
export interface GraphQLEnumValueExtensions {
1564-
[attributeName: string]: unknown;
1564+
[attributeName: string | symbol]: unknown;
15651565
}
15661566

15671567
export interface GraphQLEnumValueConfig {
@@ -1591,7 +1591,7 @@ export interface GraphQLEnumValue {
15911591
* an object which can contain all the values you need.
15921592
*/
15931593
export interface GraphQLInputObjectTypeExtensions {
1594-
[attributeName: string]: unknown;
1594+
[attributeName: string | symbol]: unknown;
15951595
}
15961596

15971597
/**
@@ -1628,7 +1628,7 @@ export class GraphQLInputObjectType {
16281628
constructor(config: Readonly<GraphQLInputObjectTypeConfig>) {
16291629
this.name = assertName(config.name);
16301630
this.description = config.description;
1631-
this.extensions = toObjMap(config.extensions);
1631+
this.extensions = toObjMapWithSymbols(config.extensions);
16321632
this.astNode = config.astNode;
16331633
this.extensionASTNodes = config.extensionASTNodes ?? [];
16341634
this.isOneOf = config.isOneOf ?? false;
@@ -1688,7 +1688,7 @@ function defineInputFieldMap(
16881688
type: fieldConfig.type,
16891689
defaultValue: defineDefaultValue(fieldName, fieldConfig),
16901690
deprecationReason: fieldConfig.deprecationReason,
1691-
extensions: toObjMap(fieldConfig.extensions),
1691+
extensions: toObjMapWithSymbols(fieldConfig.extensions),
16921692
astNode: fieldConfig.astNode,
16931693
}));
16941694
}
@@ -1720,7 +1720,7 @@ interface GraphQLInputObjectTypeNormalizedConfig
17201720
* an object which can contain all the values you need.
17211721
*/
17221722
export interface GraphQLInputFieldExtensions {
1723-
[attributeName: string]: unknown;
1723+
[attributeName: string | symbol]: unknown;
17241724
}
17251725

17261726
export interface GraphQLInputFieldConfig {

src/type/directives.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { inspect } from '../jsutils/inspect.js';
22
import { instanceOf } from '../jsutils/instanceOf.js';
33
import type { Maybe } from '../jsutils/Maybe.js';
4-
import { toObjMap } from '../jsutils/toObjMap.js';
4+
import { toObjMapWithSymbols } from '../jsutils/toObjMap.js';
55

66
import type { DirectiveDefinitionNode } from '../language/ast.js';
77
import { DirectiveLocation } from '../language/directiveLocation.js';
@@ -44,7 +44,7 @@ export function assertDirective(directive: unknown): GraphQLDirective {
4444
* an object which can contain all the values you need.
4545
*/
4646
export interface GraphQLDirectiveExtensions {
47-
[attributeName: string]: unknown;
47+
[attributeName: string | symbol]: unknown;
4848
}
4949

5050
/**
@@ -65,7 +65,7 @@ export class GraphQLDirective {
6565
this.description = config.description;
6666
this.locations = config.locations;
6767
this.isRepeatable = config.isRepeatable ?? false;
68-
this.extensions = toObjMap(config.extensions);
68+
this.extensions = toObjMapWithSymbols(config.extensions);
6969
this.astNode = config.astNode;
7070

7171
const args = config.args ?? {};

src/type/schema.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { inspect } from '../jsutils/inspect.js';
22
import { instanceOf } from '../jsutils/instanceOf.js';
33
import type { Maybe } from '../jsutils/Maybe.js';
44
import type { ObjMap } from '../jsutils/ObjMap.js';
5-
import { toObjMap } from '../jsutils/toObjMap.js';
5+
import { toObjMapWithSymbols } from '../jsutils/toObjMap.js';
66

77
import type { GraphQLError } from '../error/GraphQLError.js';
88

@@ -61,7 +61,7 @@ export function assertSchema(schema: unknown): GraphQLSchema {
6161
* an object which can contain all the values you need.
6262
*/
6363
export interface GraphQLSchemaExtensions {
64-
[attributeName: string]: unknown;
64+
[attributeName: string | symbol]: unknown;
6565
}
6666

6767
/**
@@ -162,7 +162,7 @@ export class GraphQLSchema {
162162
this.__validationErrors = config.assumeValid === true ? [] : undefined;
163163

164164
this.description = config.description;
165-
this.extensions = toObjMap(config.extensions);
165+
this.extensions = toObjMapWithSymbols(config.extensions);
166166
this.astNode = config.astNode;
167167
this.extensionASTNodes = config.extensionASTNodes ?? [];
168168

0 commit comments

Comments
 (0)