Skip to content

Commit e79faaa

Browse files
nyteshademjmahone
authored andcommitted
Add support for Symbol.toStringTag (#1297)
**Changes** * Add static and instance property getters for Symbol.toStringTag for each exported class. **Purpose** Being able to compare class and class instances via internal class type as per the definition and usage of Symbol.toStringTag allows other libraries to validate types during runtime in this manner. It also prevents them from having to patch the live values in their own codebases. **Contrived Example** With no modules and vanilla JavaScript we should be able to do something like the following. ```javascript let type = new GraphQLObjectType({name: 'Sample'}); if (({}).toString.call(type) === '[object GraphQLObjectType]') { // we have the right type of class } ``` However, with libraries such as `type-detect` or `ne-types` the code can look far cleaner. ```javascript // type-detect let type = require('type-detect') let obj = new GraphQLObjectType({name:'Example'}) assert(type(obj) === GraphQLObjectType.name) // ne-types let { typeOf } = require('ne-types') let obj = new GraphQLObjectType({name:'Example'}) assert(typeOf(obj) === GraphQLObjectType.name) ``` There are a lot of libraries out there, despite doing nearly the same thing in all cases, that support the usage of `Symbol.toStringTag` and by adding support for that in the base GraphQL classes, all of these libraries can be used with GraphQL.
1 parent 10e80d0 commit e79faaa

File tree

6 files changed

+177
-0
lines changed

6 files changed

+177
-0
lines changed

src/jsutils/applyToStringTag.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
*/
9+
10+
/**
11+
* The `applyToStringTag()` function checks first to see if the runtime
12+
* supports the `Symbol` class and then if the `Symbol.toStringTag` constant
13+
* is defined as a `Symbol` instance. If both conditions are met, the
14+
* Symbol.toStringTag property is defined as a getter that returns the
15+
* supplied class constructor's name.
16+
*
17+
* @method applyToStringTag
18+
*
19+
* @param {Class<*>} classObject a class such as Object, String, Number but
20+
* typically one of your own creation through the class keyword; `class A {}`,
21+
* for example.
22+
*/
23+
export function applyToStringTag(classObject: Class<*>): void {
24+
const symbolType: string = typeof Symbol;
25+
const toStringTagType: string = typeof Symbol.toStringTag;
26+
27+
if (symbolType === 'function' && toStringTagType === 'symbol') {
28+
Object.defineProperty(classObject.prototype, Symbol.toStringTag, {
29+
get() {
30+
return this.constructor.name;
31+
},
32+
});
33+
}
34+
}
35+
36+
/** Support both default export and named `applyToStringTag` export */
37+
export default applyToStringTag;

src/language/source.js

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import invariant from '../jsutils/invariant';
11+
import applyToStringTag from '../jsutils/applyToStringTag';
1112

1213
type Location = {
1314
line: number,
@@ -41,3 +42,6 @@ export class Source {
4142
);
4243
}
4344
}
45+
46+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
47+
applyToStringTag(Source);
+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { describe, it } from 'mocha';
2+
import { expect } from 'chai';
3+
import {
4+
GraphQLDirective,
5+
GraphQLEnumType,
6+
GraphQLInputObjectType,
7+
GraphQLInterfaceType,
8+
GraphQLObjectType,
9+
GraphQLScalarType,
10+
GraphQLSchema,
11+
GraphQLUnionType,
12+
Source,
13+
} from '../../';
14+
15+
function typeOf(object) {
16+
return /(\b\w+\b)\]/.exec(Object.prototype.toString.call(object))[1];
17+
}
18+
19+
describe('Check to see if Symbol.toStringTag is defined on types', () => {
20+
const s = Symbol.toStringTag;
21+
const hasSymbol = o => Object.getOwnPropertySymbols(o).includes(s);
22+
23+
it('GraphQLDirective should have Symbol.toStringTag', () => {
24+
expect(hasSymbol(GraphQLDirective.prototype)).to.equal(true);
25+
});
26+
27+
it('GraphQLEnumType should have Symbol.toStringTag', () => {
28+
expect(hasSymbol(GraphQLEnumType.prototype)).to.equal(true);
29+
});
30+
31+
it('GraphQLInputObjectType should have Symbol.toStringTag', () => {
32+
expect(hasSymbol(GraphQLInputObjectType.prototype)).to.equal(true);
33+
});
34+
35+
it('GraphQLInterfaceType should have Symbol.toStringTag', () => {
36+
expect(hasSymbol(GraphQLInterfaceType.prototype)).to.equal(true);
37+
});
38+
39+
it('GraphQLObjectType should have Symbol.toStringTag', () => {
40+
expect(hasSymbol(GraphQLObjectType.prototype)).to.equal(true);
41+
});
42+
43+
it('GraphQLScalarType should have Symbol.toStringTag', () => {
44+
expect(hasSymbol(GraphQLScalarType.prototype)).to.equal(true);
45+
});
46+
47+
it('GraphQLSchema should have Symbol.toStringTag', () => {
48+
expect(hasSymbol(GraphQLSchema.prototype)).to.equal(true);
49+
});
50+
51+
it('GraphQLUnionType should have Symbol.toStringTag', () => {
52+
expect(hasSymbol(GraphQLUnionType.prototype)).to.equal(true);
53+
});
54+
55+
it('Source should have Symbol.toStringTag', () => {
56+
expect(hasSymbol(Source.prototype)).to.equal(true);
57+
});
58+
});
59+
60+
describe('Check to see if Symbol.toStringTag tests on instances', () => {
61+
// variables _interface and _enum have preceding underscores due to being
62+
// reserved keywords in JavaScript
63+
64+
const schema = Object.create(GraphQLSchema.prototype);
65+
const scalar = Object.create(GraphQLScalarType.prototype);
66+
const object = Object.create(GraphQLObjectType.prototype);
67+
const _interface = Object.create(GraphQLInterfaceType.prototype);
68+
const union = Object.create(GraphQLUnionType.prototype);
69+
const _enum = Object.create(GraphQLEnumType.prototype);
70+
const inputType = Object.create(GraphQLInputObjectType.prototype);
71+
const directive = Object.create(GraphQLDirective.prototype);
72+
const source = Object.create(Source.prototype);
73+
74+
it('should return the class name for GraphQLSchema instance', () => {
75+
expect(typeOf(schema)).to.equal(GraphQLSchema.name);
76+
});
77+
78+
it('should return the class name for GraphQLScalarType instance', () => {
79+
expect(typeOf(scalar)).to.equal(GraphQLScalarType.name);
80+
});
81+
82+
it('should return the class name for GraphQLObjectType instance', () => {
83+
expect(typeOf(object)).to.equal(GraphQLObjectType.name);
84+
});
85+
86+
it('should return the class name for GraphQLInterfaceType instance', () => {
87+
expect(typeOf(_interface)).to.equal(GraphQLInterfaceType.name);
88+
});
89+
90+
it('should return the class name for GraphQLUnionType instance', () => {
91+
expect(typeOf(union)).to.equal(GraphQLUnionType.name);
92+
});
93+
94+
it('should return the class name for GraphQLEnumType instance', () => {
95+
expect(typeOf(_enum)).to.equal(GraphQLEnumType.name);
96+
});
97+
98+
it('should return the class name for GraphQLInputObjectType instance', () => {
99+
expect(typeOf(inputType)).to.equal(GraphQLInputObjectType.name);
100+
});
101+
102+
it('should return the class name for GraphQLDirective instance', () => {
103+
expect(typeOf(directive)).to.equal(GraphQLDirective.name);
104+
});
105+
106+
it('should return the class name for Source instance', () => {
107+
expect(typeOf(source)).to.equal(Source.name);
108+
});
109+
});

src/type/definition.js

+19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @flow strict
88
*/
99

10+
import applyToStringTag from '../jsutils/applyToStringTag';
1011
import instanceOf from '../jsutils/instanceOf';
1112
import inspect from '../jsutils/inspect';
1213
import invariant from '../jsutils/invariant';
@@ -587,6 +588,9 @@ export class GraphQLScalarType {
587588
inspect: () => string;
588589
}
589590

591+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
592+
applyToStringTag(GraphQLScalarType);
593+
590594
// Also provide toJSON and inspect aliases for toString.
591595
GraphQLScalarType.prototype.toJSON = GraphQLScalarType.prototype.inspect =
592596
GraphQLScalarType.prototype.toString;
@@ -689,6 +693,9 @@ export class GraphQLObjectType {
689693
inspect: () => string;
690694
}
691695

696+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
697+
applyToStringTag(GraphQLObjectType);
698+
692699
// Also provide toJSON and inspect aliases for toString.
693700
GraphQLObjectType.prototype.toJSON = GraphQLObjectType.prototype.inspect =
694701
GraphQLObjectType.prototype.toString;
@@ -938,6 +945,9 @@ export class GraphQLInterfaceType {
938945
inspect: () => string;
939946
}
940947

948+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
949+
applyToStringTag(GraphQLInterfaceType);
950+
941951
// Also provide toJSON and inspect aliases for toString.
942952
GraphQLInterfaceType.prototype.toJSON = GraphQLInterfaceType.prototype.inspect =
943953
GraphQLInterfaceType.prototype.toString;
@@ -1017,6 +1027,9 @@ export class GraphQLUnionType {
10171027
inspect: () => string;
10181028
}
10191029

1030+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
1031+
applyToStringTag(GraphQLUnionType);
1032+
10201033
// Also provide toJSON and inspect aliases for toString.
10211034
GraphQLUnionType.prototype.toJSON = GraphQLUnionType.prototype.inspect =
10221035
GraphQLUnionType.prototype.toString;
@@ -1132,6 +1145,9 @@ export class GraphQLEnumType /* <T> */ {
11321145
inspect: () => string;
11331146
}
11341147

1148+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
1149+
applyToStringTag(GraphQLEnumType);
1150+
11351151
// Also provide toJSON and inspect aliases for toString.
11361152
GraphQLEnumType.prototype.toJSON = GraphQLEnumType.prototype.inspect =
11371153
GraphQLEnumType.prototype.toString;
@@ -1265,6 +1281,9 @@ export class GraphQLInputObjectType {
12651281
inspect: () => string;
12661282
}
12671283

1284+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
1285+
applyToStringTag(GraphQLInputObjectType);
1286+
12681287
// Also provide toJSON and inspect aliases for toString.
12691288
GraphQLInputObjectType.prototype.toJSON =
12701289
GraphQLInputObjectType.prototype.toString;

src/type/directives.js

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from './definition';
1414
import { GraphQLNonNull } from './definition';
1515
import { GraphQLString, GraphQLBoolean } from './scalars';
16+
import applyToStringTag from '../jsutils/applyToStringTag';
1617
import instanceOf from '../jsutils/instanceOf';
1718
import invariant from '../jsutils/invariant';
1819
import type { DirectiveDefinitionNode } from '../language/ast';
@@ -76,6 +77,9 @@ export class GraphQLDirective {
7677
}
7778
}
7879

80+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
81+
applyToStringTag(GraphQLDirective);
82+
7983
export type GraphQLDirectiveConfig = {
8084
name: string,
8185
description?: ?string,

src/type/schema.js

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import type { GraphQLError } from '../error/GraphQLError';
3434
import inspect from '../jsutils/inspect';
3535
import { __Schema } from './introspection';
36+
import applyToStringTag from '../jsutils/applyToStringTag';
3637
import find from '../jsutils/find';
3738
import instanceOf from '../jsutils/instanceOf';
3839
import invariant from '../jsutils/invariant';
@@ -231,6 +232,9 @@ export class GraphQLSchema {
231232
}
232233
}
233234

235+
// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
236+
applyToStringTag(GraphQLSchema);
237+
234238
type TypeMap = ObjMap<GraphQLNamedType>;
235239

236240
export type GraphQLSchemaValidationOptions = {|

0 commit comments

Comments
 (0)