Skip to content

Add support for Symbol.toStringTag #1297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/jsutils/applyToStringTag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

/**
* The `applyToStringTag()` function checks first to see if the runtime
* supports the `Symbol` class and then if the `Symbol.toStringTag` constant
* is defined as a `Symbol` instance. If both conditions are met, the
* Symbol.toStringTag property is defined as a getter that returns the
* supplied class constructor's name.
*
* @method applyToStringTag
*
* @param {Class<*>} classObject a class such as Object, String, Number but
* typically one of your own creation through the class keyword; `class A {}`,
* for example.
*/
export function applyToStringTag(classObject: Class<*>): void {
const symbolType: string = typeof Symbol;
const toStringTagType: string = typeof Symbol.toStringTag;

if (symbolType === 'function' && toStringTagType === 'symbol') {
Object.defineProperty(classObject.prototype, Symbol.toStringTag, {
get() {
return this.constructor.name;
},
});
}
}

/** Support both default export and named `applyToStringTag` export */
export default applyToStringTag;
4 changes: 4 additions & 0 deletions src/language/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import invariant from '../jsutils/invariant';
import applyToStringTag from '../jsutils/applyToStringTag';

type Location = {
line: number,
Expand Down Expand Up @@ -41,3 +42,6 @@ export class Source {
);
}
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(Source);
109 changes: 109 additions & 0 deletions src/type/__tests__/toStringTag-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import {
GraphQLDirective,
GraphQLEnumType,
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLObjectType,
GraphQLScalarType,
GraphQLSchema,
GraphQLUnionType,
Source,
} from '../../';

function typeOf(object) {
return /(\b\w+\b)\]/.exec(Object.prototype.toString.call(object))[1];
}

describe('Check to see if Symbol.toStringTag is defined on types', () => {
const s = Symbol.toStringTag;
const hasSymbol = o => Object.getOwnPropertySymbols(o).includes(s);

it('GraphQLDirective should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLDirective.prototype)).to.equal(true);
});

it('GraphQLEnumType should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLEnumType.prototype)).to.equal(true);
});

it('GraphQLInputObjectType should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLInputObjectType.prototype)).to.equal(true);
});

it('GraphQLInterfaceType should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLInterfaceType.prototype)).to.equal(true);
});

it('GraphQLObjectType should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLObjectType.prototype)).to.equal(true);
});

it('GraphQLScalarType should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLScalarType.prototype)).to.equal(true);
});

it('GraphQLSchema should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLSchema.prototype)).to.equal(true);
});

it('GraphQLUnionType should have Symbol.toStringTag', () => {
expect(hasSymbol(GraphQLUnionType.prototype)).to.equal(true);
});

it('Source should have Symbol.toStringTag', () => {
expect(hasSymbol(Source.prototype)).to.equal(true);
});
});

describe('Check to see if Symbol.toStringTag tests on instances', () => {
// variables _interface and _enum have preceding underscores due to being
// reserved keywords in JavaScript

const schema = Object.create(GraphQLSchema.prototype);
const scalar = Object.create(GraphQLScalarType.prototype);
const object = Object.create(GraphQLObjectType.prototype);
const _interface = Object.create(GraphQLInterfaceType.prototype);
const union = Object.create(GraphQLUnionType.prototype);
const _enum = Object.create(GraphQLEnumType.prototype);
const inputType = Object.create(GraphQLInputObjectType.prototype);
const directive = Object.create(GraphQLDirective.prototype);
const source = Object.create(Source.prototype);

it('should return the class name for GraphQLSchema instance', () => {
expect(typeOf(schema)).to.equal(GraphQLSchema.name);
});

it('should return the class name for GraphQLScalarType instance', () => {
expect(typeOf(scalar)).to.equal(GraphQLScalarType.name);
});

it('should return the class name for GraphQLObjectType instance', () => {
expect(typeOf(object)).to.equal(GraphQLObjectType.name);
});

it('should return the class name for GraphQLInterfaceType instance', () => {
expect(typeOf(_interface)).to.equal(GraphQLInterfaceType.name);
});

it('should return the class name for GraphQLUnionType instance', () => {
expect(typeOf(union)).to.equal(GraphQLUnionType.name);
});

it('should return the class name for GraphQLEnumType instance', () => {
expect(typeOf(_enum)).to.equal(GraphQLEnumType.name);
});

it('should return the class name for GraphQLInputObjectType instance', () => {
expect(typeOf(inputType)).to.equal(GraphQLInputObjectType.name);
});

it('should return the class name for GraphQLDirective instance', () => {
expect(typeOf(directive)).to.equal(GraphQLDirective.name);
});

it('should return the class name for Source instance', () => {
expect(typeOf(source)).to.equal(Source.name);
});
});
19 changes: 19 additions & 0 deletions src/type/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @flow strict
*/

import applyToStringTag from '../jsutils/applyToStringTag';
import instanceOf from '../jsutils/instanceOf';
import invariant from '../jsutils/invariant';
import isInvalid from '../jsutils/isInvalid';
Expand Down Expand Up @@ -586,6 +587,9 @@ export class GraphQLScalarType {
inspect: () => string;
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLScalarType);

// Also provide toJSON and inspect aliases for toString.
GraphQLScalarType.prototype.toJSON = GraphQLScalarType.prototype.inspect =
GraphQLScalarType.prototype.toString;
Expand Down Expand Up @@ -688,6 +692,9 @@ export class GraphQLObjectType {
inspect: () => string;
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLObjectType);

// Also provide toJSON and inspect aliases for toString.
GraphQLObjectType.prototype.toJSON = GraphQLObjectType.prototype.inspect =
GraphQLObjectType.prototype.toString;
Expand Down Expand Up @@ -937,6 +944,9 @@ export class GraphQLInterfaceType {
inspect: () => string;
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLInterfaceType);

// Also provide toJSON and inspect aliases for toString.
GraphQLInterfaceType.prototype.toJSON = GraphQLInterfaceType.prototype.inspect =
GraphQLInterfaceType.prototype.toString;
Expand Down Expand Up @@ -1016,6 +1026,9 @@ export class GraphQLUnionType {
inspect: () => string;
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLUnionType);

// Also provide toJSON and inspect aliases for toString.
GraphQLUnionType.prototype.toJSON = GraphQLUnionType.prototype.inspect =
GraphQLUnionType.prototype.toString;
Expand Down Expand Up @@ -1131,6 +1144,9 @@ export class GraphQLEnumType /* <T> */ {
inspect: () => string;
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLEnumType);

// Also provide toJSON and inspect aliases for toString.
GraphQLEnumType.prototype.toJSON = GraphQLEnumType.prototype.inspect =
GraphQLEnumType.prototype.toString;
Expand Down Expand Up @@ -1264,6 +1280,9 @@ export class GraphQLInputObjectType {
inspect: () => string;
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLInputObjectType);

// Also provide toJSON and inspect aliases for toString.
GraphQLInputObjectType.prototype.toJSON =
GraphQLInputObjectType.prototype.toString;
Expand Down
4 changes: 4 additions & 0 deletions src/type/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
} from './definition';
import { GraphQLNonNull } from './definition';
import { GraphQLString, GraphQLBoolean } from './scalars';
import applyToStringTag from '../jsutils/applyToStringTag';
import instanceOf from '../jsutils/instanceOf';
import invariant from '../jsutils/invariant';
import type { DirectiveDefinitionNode } from '../language/ast';
Expand Down Expand Up @@ -76,6 +77,9 @@ export class GraphQLDirective {
}
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLDirective);

export type GraphQLDirectiveConfig = {
name: string,
description?: ?string,
Expand Down
4 changes: 4 additions & 0 deletions src/type/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from './directives';
import type { GraphQLError } from '../error/GraphQLError';
import { __Schema } from './introspection';
import applyToStringTag from '../jsutils/applyToStringTag';
import find from '../jsutils/find';
import instanceOf from '../jsutils/instanceOf';
import invariant from '../jsutils/invariant';
Expand Down Expand Up @@ -230,6 +231,9 @@ export class GraphQLSchema {
}
}

// Conditionally apply `[Symbol.toStringTag]` if `Symbol`s are supported
applyToStringTag(GraphQLSchema);

type TypeMap = ObjMap<GraphQLNamedType>;

export type GraphQLSchemaValidationOptions = {|
Expand Down