From b5dfb4c7cff2f1d5de65dde3beebf24d8c2b5391 Mon Sep 17 00:00:00 2001 From: Tom Scott <113026+tubbo@users.noreply.github.com> Date: Sun, 20 Jun 2021 06:54:49 -0400 Subject: [PATCH] Backport instanceOf Error Check Improvements (#3186) Co-authored-by: Ivan Goncharov --- src/jsutils/__tests__/instanceOf-test.js | 67 ++++++++++++++++++++++-- src/jsutils/instanceOf.js | 17 ++++-- 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/jsutils/__tests__/instanceOf-test.js b/src/jsutils/__tests__/instanceOf-test.js index 17a8d4e46d..01098f0ca9 100644 --- a/src/jsutils/__tests__/instanceOf-test.js +++ b/src/jsutils/__tests__/instanceOf-test.js @@ -4,19 +4,80 @@ 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 { + // $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