-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
**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.
- Loading branch information
Showing
8 changed files
with
8,648 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
import { expect } from 'chai'; | ||
import { describe, it } from 'mocha'; | ||
import { hasSymbolSupport, applyToStringTag } from '../symbolSupport'; | ||
|
||
describe('symbolSupportTests', () => { | ||
// NOTE Symbol appeared in nodejs in 0.12.18 but was largely unusable in | ||
// that format. Symbol.toStringTag showed up in node 6.4.0, but was available | ||
// behind a flag as early as 4.9.1. | ||
const [, major, minor, patch] = /^v(\d+)\.?(\d+)?\.?(\d+)?/.exec( | ||
process.version, | ||
); | ||
|
||
it('should have Symbol in scope if the version >= 4.9.1', () => { | ||
expect(major >= 4).to.equal(true); | ||
|
||
if (major === 4) { | ||
expect(minor >= 9).to.equal(true); | ||
} | ||
|
||
if (minor === 9) { | ||
expect(patch >= 1).to.equal(true); | ||
} | ||
|
||
expect(hasSymbolSupport()).to.equal(true); | ||
}); | ||
|
||
it('should have Symbol in scope if the version >= 6.4.0', () => { | ||
expect(major >= 6).to.equal(true); | ||
|
||
if (major === 6) { | ||
expect(minor >= 4).to.equal(true); | ||
} | ||
|
||
if (minor === 4) { | ||
expect(patch >= 0).to.equal(true); | ||
} | ||
|
||
expect(typeof Symbol !== 'undefined').to.equal(true); | ||
expect(hasSymbolSupport('toStringTag')).to.equal(true); | ||
}); | ||
|
||
it('should be able to apply toStringTag to a class', () => { | ||
class A {} | ||
applyToStringTag(A); | ||
|
||
const a = new A(); | ||
|
||
expect(Object.prototype.toString.call(a)).to.equal('[object A]'); | ||
expect(Object.prototype.toString.call(A)).not.to.equal('[object A]'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
/** | ||
* A function that can either simply determine if `Symbol`s are allowed as | ||
* well as, optionally, determine whether or not a property of symbol | ||
* exists. | ||
* | ||
* If the name of a specific `Symbol` property is supplied, the resulting | ||
* value will only be true if property is mapped to an instance of `Symbol` | ||
* such as `.toStringTag`, `.iterator`, `.species`, `.isConcatSpreadable`, | ||
* etc... | ||
* | ||
* @method hasSymbolSupport | ||
* | ||
* @param {string} specificSymbol an optional name of a property on the | ||
* `Symbol` class itself that statically refers to a predefined `Symbol`. If | ||
* properly specified and the value is set, then true will be returned. | ||
* @return {bool} true if `Symbol` is both defined and a function. Optionally | ||
* true if the `Symbol` class is true, a predefined symbol name such as | ||
* `toStringTag` is set on the `Symbol` class and it maps to an instance of | ||
* `Symbol`. False in all other cases | ||
*/ | ||
export function hasSymbolSupport(specificSymbol?: string): boolean { | ||
const hasSymbols: boolean = typeof Symbol === 'function'; | ||
|
||
if (!hasSymbols) { | ||
return false; | ||
} | ||
|
||
if (specificSymbol) { | ||
// NOTE: The capture of type and string comparison over a few lines is | ||
// necessary to appease the lint and flowtype gods. | ||
// | ||
// ((typeof Symbol[specificSymbol]): string) !== 'symbol' makes lint angry | ||
// and typeof Symbol[specificSymbol] !== 'symbol' makes flow angry | ||
// | ||
// The former thinks everything is too verbose the later thinks I am | ||
// comparing Symbol instance rather than the string resulting from a call | ||
// to typeof. | ||
// | ||
// Le sigh.... | ||
const type: string = typeof Symbol[specificSymbol]; | ||
|
||
if (type !== 'symbol') { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* 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 { | ||
if (hasSymbolSupport('toStringTag')) { | ||
Object.defineProperty(classObject.prototype, Symbol.toStringTag, { | ||
get() { | ||
return this.constructor.name; | ||
}, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters