Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions src/jsutils/__tests__/instanceOf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { instanceOf } from '../instanceOf.js';
describe('instanceOf', () => {
it('do not throw on values without prototype', () => {
class Foo {
readonly __isFoo = true as const;
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);
expect(instanceOf(undefined, true, Foo)).to.equal(false);
expect(instanceOf(undefined, null, Foo)).to.equal(false);
expect(instanceOf(undefined, Object.create(null), Foo)).to.equal(false);
});

it('detect name clashes with older versions of this lib', () => {
Expand All @@ -24,6 +25,7 @@ describe('instanceOf', () => {

function newVersion() {
class Foo {
readonly __isFoo = true as const;
get [Symbol.toStringTag]() {
return 'Foo';
}
Expand All @@ -33,13 +35,17 @@ describe('instanceOf', () => {

const NewClass = newVersion();
const OldClass = oldVersion();
expect(instanceOf(new NewClass(), NewClass)).to.equal(true);
expect(() => instanceOf(new OldClass(), NewClass)).to.throw();
const newInstance = new NewClass();
expect(instanceOf(newInstance.__isFoo, newInstance, NewClass)).to.equal(
true,
);
expect(() => instanceOf(undefined, new OldClass(), NewClass)).to.throw();
});

it('allows instances to have share the same constructor name', () => {
function getMinifiedClass(tag: string) {
class SomeNameAfterMinification {
readonly [tag] = true as const;
get [Symbol.toStringTag]() {
return tag;
}
Expand All @@ -49,17 +55,25 @@ describe('instanceOf', () => {

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 fooInstance = new Foo();
const barInstance = new Bar();
expect(instanceOf(fooInstance.foo, fooInstance, Bar)).to.equal(false);
expect(instanceOf(barInstance.bar, barInstance, Foo)).to.equal(false);

const DuplicateOfFoo = getMinifiedClass('Foo');
expect(() => instanceOf(new DuplicateOfFoo(), Foo)).to.throw();
expect(() => instanceOf(new Foo(), DuplicateOfFoo)).to.throw();
const duplicateOfFooInstance = new DuplicateOfFoo();
expect(() =>
instanceOf(duplicateOfFooInstance.foo, new DuplicateOfFoo(), Foo),
).to.throw();
expect(() =>
instanceOf(fooInstance.foo, fooInstance, DuplicateOfFoo),
).to.throw();
});

it('fails with descriptive error message', () => {
function getFoo() {
class Foo {
readonly __isFoo = true as const;
get [Symbol.toStringTag]() {
return 'Foo';
}
Expand All @@ -69,11 +83,14 @@ describe('instanceOf', () => {
const Foo1 = getFoo();
const Foo2 = getFoo();

expect(() => instanceOf(new Foo1(), Foo2)).to.throw(
/^Cannot use Foo "{}" from another module or realm./m,
const foo1Instance = new Foo1();
const foo2Instance = new Foo2();

expect(() => instanceOf(foo1Instance.__isFoo, foo1Instance, Foo2)).to.throw(
/^Cannot use Foo "{ __isFoo: true }" from another module or realm./m,
);
expect(() => instanceOf(new Foo2(), Foo1)).to.throw(
/^Cannot use Foo "{}" from another module or realm./m,
expect(() => instanceOf(foo2Instance.__isFoo, foo2Instance, Foo1)).to.throw(
/^Cannot use Foo "{ __isFoo: true }" from another module or realm./m,
);
});
});
21 changes: 16 additions & 5 deletions src/jsutils/instanceOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@ const isProduction =
/**
* A replacement for instanceof which includes an error warning when multi-realm
* constructors are detected.
*
* In production, it simply uses the provided type brand.
*
* See: https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production
* See: https://webpack.js.org/guides/production/
*/
export const instanceOf: (value: unknown, constructor: Constructor) => boolean =
/* c8 ignore next 6 */
export const instanceOf: (
typeBrand: true | undefined,
value: unknown,
constructor: Constructor,
) => boolean =
/* c8 ignore next 9 */
// FIXME: https://github.com/graphql/graphql-js/issues/2317
isProduction
? function instanceOf(value: unknown, constructor: Constructor): boolean {
return value instanceof constructor;
? function instanceOf(typeBrand: true | undefined): boolean {
return typeBrand === true;
}
: function instanceOf(value: unknown, constructor: Constructor): boolean {
: function instanceOf(
_typeBrand: true | undefined,
value: unknown,
constructor: Constructor,
): boolean {
if (value instanceof constructor) {
return true;
}
Expand Down
4 changes: 3 additions & 1 deletion src/language/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface Location {
* The `line` and `column` properties in `locationOffset` are 1-indexed.
*/
export class Source {
readonly __isSource = true as const;

body: string;
name: string;
locationOffset: Location;
Expand Down Expand Up @@ -47,5 +49,5 @@ export class Source {
* @internal
*/
export function isSource(source: unknown): source is Source {
return instanceOf(source, Source);
return instanceOf((source as any)?.__isSource, source, Source);
}
Loading
Loading