Skip to content

Commit 44c8adc

Browse files
nyteshadeBrielle Harrison
authored and
Brielle Harrison
committed
Add support for Symbol.toStringTag
**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 0a30b62 commit 44c8adc

File tree

6 files changed

+177
-0
lines changed

6 files changed

+177
-0
lines changed

src/jsutils/applyToStringTag.js

Lines changed: 37 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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);
Lines changed: 109 additions & 0 deletions
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

Lines changed: 19 additions & 0 deletions
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 invariant from '../jsutils/invariant';
1213
import isInvalid from '../jsutils/isInvalid';
@@ -586,6 +587,9 @@ export class GraphQLScalarType {
586587
inspect: () => string;
587588
}
588589

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

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

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

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

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

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

src/type/directives.js

Lines changed: 4 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from './directives';
3333
import type { GraphQLError } from '../error/GraphQLError';
3434
import { __Schema } from './introspection';
35+
import applyToStringTag from '../jsutils/applyToStringTag';
3536
import find from '../jsutils/find';
3637
import instanceOf from '../jsutils/instanceOf';
3738
import invariant from '../jsutils/invariant';
@@ -230,6 +231,9 @@ export class GraphQLSchema {
230231
}
231232
}
232233

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

235239
export type GraphQLSchemaValidationOptions = {|

0 commit comments

Comments
 (0)