From 4fdaadebafd0c903e7863ca7612afd7c8e434ff5 Mon Sep 17 00:00:00 2001 From: Tom Scott Date: Thu, 17 Jun 2021 10:07:59 -0400 Subject: [PATCH] Backport instanceOf Error Check Improvements This backports #3172 to the `15.x.x` branch so that users of GraphQL v15.x can take advantage of it. GraphQL v16.x has some breaking changes and is not possible to run when you have other libraries depending on the behavior of v15, such as GraphQL Code Generator. --- src/jsutils/__tests__/instanceOf-test.js | 32 +++++++++++++++++++++--- src/jsutils/instanceOf.js | 18 +++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) 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