From 7b724c7184570342a813160594d63ce106f6e875 Mon Sep 17 00:00:00 2001 From: Tom Scott Date: Thu, 17 Jun 2021 10:07:59 -0400 Subject: [PATCH 1/2] 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 | 17 +++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/jsutils/__tests__/instanceOf-test.js b/src/jsutils/__tests__/instanceOf-test.js index 17a8d4e46d..5855303d86 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 e55cd13f73..1b634ec5ce 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,17 @@ 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 + ? 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 From 4a45a9b0ede7a28b8e07e0b9673a44310a660444 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Sun, 20 Jun 2021 13:44:42 +0300 Subject: [PATCH 2/2] Backport instanceOf: add additional tests --- src/jsutils/__tests__/instanceOf-test.js | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/jsutils/__tests__/instanceOf-test.js b/src/jsutils/__tests__/instanceOf-test.js index 5855303d86..01098f0ca9 100644 --- a/src/jsutils/__tests__/instanceOf-test.js +++ b/src/jsutils/__tests__/instanceOf-test.js @@ -4,6 +4,41 @@ import { describe, it } from 'mocha'; import instanceOf from '../instanceOf'; describe('instanceOf', () => { + it('do not throw on values without prototype', () => { + class Foo { + // $FlowFixMe[unsupported-syntax] + get [Symbol.toStringTag]() { + return 'Foo'; + } + } + + expect(instanceOf(true, Foo)).to.equal(false); + expect(instanceOf(null, Foo)).to.equal(false); + expect(instanceOf(Object.create(null), Foo)).to.equal(false); + }); + + it('detect name clashes with older versions of this lib', () => { + function oldVersion() { + class Foo {} + return Foo; + } + + function newVersion() { + class Foo { + // $FlowFixMe[unsupported-syntax] + get [Symbol.toStringTag]() { + return 'Foo'; + } + } + return Foo; + } + + const NewClass = newVersion(); + const OldClass = oldVersion(); + expect(instanceOf(new NewClass(), NewClass)).to.equal(true); + expect(() => instanceOf(new OldClass(), NewClass)).to.throw(); + }); + it('allows instances to have share the same constructor name', () => { function getMinifiedClass(tag: string) { class SomeNameAfterMinification {