Skip to content

Commit

Permalink
Backport instanceOf Error Check Improvements (#3186)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Goncharov <ivan.goncharov.ua@gmail.com>
  • Loading branch information
tubbo and IvanGoncharov committed Jun 20, 2021
1 parent 1611bbb commit b5dfb4c
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 8 deletions.
67 changes: 64 additions & 3 deletions src/jsutils/__tests__/instanceOf-test.js
Expand Up @@ -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,
);
});
});
17 changes: 12 additions & 5 deletions 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.
Expand All @@ -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
Expand Down

0 comments on commit b5dfb4c

Please sign in to comment.