diff --git a/src/jsutils/__tests__/instanceOf-test.js b/src/jsutils/__tests__/instanceOf-test.js index 17a8d4e46d0..5855303d86f 100644 --- a/src/jsutils/__tests__/instanceOf-test.js +++ b/src/jsutils/__tests__/instanceOf-test.js @@ -4,19 +4,45 @@ import { describe, it } from 'mocha'; import instanceOf from '../instanceOf'; describe('instanceOf', () => { + it('allows instances to have share the same constructor name', () => { + function getMinifiedClass(tag: string) { + class SomeNameAfterMinification { + // $FlowFixMe[unsupported-syntax] + get [Symbol.toStringTag]() { + return tag; + } + } + return SomeNameAfterMinification; + } + + const Foo = getMinifiedClass('Foo'); + const Bar = getMinifiedClass('Bar'); + expect(instanceOf(new Foo(), Bar)).to.equal(false); + expect(instanceOf(new Bar(), Foo)).to.equal(false); + + const DuplicateOfFoo = getMinifiedClass('Foo'); + expect(() => instanceOf(new DuplicateOfFoo(), Foo)).to.throw(); + expect(() => instanceOf(new Foo(), DuplicateOfFoo)).to.throw(); + }); + it('fails with descriptive error message', () => { function getFoo() { - class Foo {} + class Foo { + // $FlowFixMe[unsupported-syntax] + get [Symbol.toStringTag]() { + return 'Foo'; + } + } return Foo; } const Foo1 = getFoo(); const Foo2 = getFoo(); expect(() => instanceOf(new Foo1(), Foo2)).to.throw( - /^Cannot use Foo "\[object Object\]" from another module or realm./m, + /^Cannot use Foo "{}" from another module or realm./m, ); expect(() => instanceOf(new Foo2(), Foo1)).to.throw( - /^Cannot use Foo "\[object Object\]" from another module or realm./m, + /^Cannot use Foo "{}" from another module or realm./m, ); }); }); diff --git a/src/jsutils/instanceOf.js b/src/jsutils/instanceOf.js index e55cd13f730..5351b24306c 100644 --- a/src/jsutils/instanceOf.js +++ b/src/jsutils/instanceOf.js @@ -1,3 +1,5 @@ +import inspect from './inspect'; + /** * A replacement for instanceof which includes an error warning when multi-realm * constructors are detected. @@ -20,12 +22,18 @@ export default process.env.NODE_ENV === 'production' if (value instanceof constructor) { return true; } - if (value) { - const valueClass = value.constructor; - const className = constructor.name; - if (className && valueClass && valueClass.name === className) { + if (typeof value === 'object' && value !== null) { + const className = constructor.prototype[Symbol.toStringTag]; + const valueClassName = + // We still need to support constructor's name to detect conflicts with older versions of this library. + Symbol.toStringTag in value + ? // @ts-expect-error TS bug see, https://github.com/microsoft/TypeScript/issues/38009 + value[Symbol.toStringTag] + : value.constructor?.name; + if (className === valueClassName) { + const stringifiedValue = inspect(value); throw new Error( - `Cannot use ${className} "${value}" from another module or realm. + `Cannot use ${className} "${stringifiedValue}" from another module or realm. Ensure that there is only one instance of "graphql" in the node_modules directory. If different versions of "graphql" are the dependencies of other