Skip to content

Commit 58122ef

Browse files
Avoid relying on constructor.name for instanceOf error check. (#3172)
Fixes #2894
1 parent 81ca778 commit 58122ef

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

src/jsutils/__tests__/instanceOf-test.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,43 @@ import { describe, it } from 'mocha';
44
import { instanceOf } from '../instanceOf';
55

66
describe('instanceOf', () => {
7+
it('allows instances to have share the same constructor name', () => {
8+
function getMinifiedClass(tag: string) {
9+
class SomeNameAfterMinification {
10+
get [Symbol.toStringTag]() {
11+
return tag;
12+
}
13+
}
14+
return SomeNameAfterMinification;
15+
}
16+
17+
const Foo = getMinifiedClass('Foo');
18+
const Bar = getMinifiedClass('Bar');
19+
expect(instanceOf(new Foo(), Bar)).to.equal(false);
20+
expect(instanceOf(new Bar(), Foo)).to.equal(false);
21+
22+
const DuplicateOfFoo = getMinifiedClass('Foo');
23+
expect(() => instanceOf(new DuplicateOfFoo(), Foo)).to.throw();
24+
expect(() => instanceOf(new Foo(), DuplicateOfFoo)).to.throw();
25+
});
26+
727
it('fails with descriptive error message', () => {
828
function getFoo() {
9-
class Foo {}
29+
class Foo {
30+
get [Symbol.toStringTag]() {
31+
return 'Foo';
32+
}
33+
}
1034
return Foo;
1135
}
1236
const Foo1 = getFoo();
1337
const Foo2 = getFoo();
1438

1539
expect(() => instanceOf(new Foo1(), Foo2)).to.throw(
16-
/^Cannot use Foo "\[object Object\]" from another module or realm./m,
40+
/^Cannot use Foo "{}" from another module or realm./m,
1741
);
1842
expect(() => instanceOf(new Foo2(), Foo1)).to.throw(
19-
/^Cannot use Foo "\[object Object\]" from another module or realm./m,
43+
/^Cannot use Foo "{}" from another module or realm./m,
2044
);
2145
});
2246
});

src/jsutils/instanceOf.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { inspect } from './inspect';
2+
13
/**
24
* A replacement for instanceof which includes an error warning when multi-realm
35
* constructors are detected.
@@ -10,16 +12,23 @@ export const instanceOf: (value: unknown, constructor: Constructor) => boolean =
1012
function instanceOf(value: unknown, constructor: Constructor): boolean {
1113
return value instanceof constructor;
1214
}
13-
: function instanceOf(value: any, constructor: Constructor): boolean {
15+
: function instanceOf(value: unknown, constructor: Constructor): boolean {
1416
if (value instanceof constructor) {
1517
return true;
1618
}
17-
if (value) {
18-
const valueClass = value.constructor;
19-
const className = constructor.name;
20-
if (className && valueClass && valueClass.name === className) {
19+
if (typeof value === 'object' && value !== null) {
20+
// Prefer Symbol.toStringTag since it is immune to minification.
21+
const className = constructor.prototype[Symbol.toStringTag];
22+
const valueClassName =
23+
// We still need to support constructor's name to detect conflicts with older versions of this library.
24+
Symbol.toStringTag in value
25+
? // @ts-expect-error TS bug see, https://github.com/microsoft/TypeScript/issues/38009
26+
value[Symbol.toStringTag]
27+
: value.constructor?.name;
28+
if (className === valueClassName) {
29+
const stringifiedValue = inspect(value);
2130
throw new Error(
22-
`Cannot use ${className} "${value}" from another module or realm.
31+
`Cannot use ${className} "${stringifiedValue}" from another module or realm.
2332
2433
Ensure that there is only one instance of "graphql" in the node_modules
2534
directory. If different versions of "graphql" are the dependencies of other
@@ -38,5 +47,7 @@ spurious results.`,
3847
};
3948

4049
interface Constructor extends Function {
41-
name: string;
50+
prototype: {
51+
[Symbol.toStringTag]: string;
52+
};
4253
}

0 commit comments

Comments
 (0)