Skip to content

Instantly share code, notes, and snippets.

@infloop
Last active August 16, 2018 13:30
Show Gist options
  • Save infloop/109a89e4980055ffffc30d0563eb3e68 to your computer and use it in GitHub Desktop.
Save infloop/109a89e4980055ffffc30d0563eb3e68 to your computer and use it in GitHub Desktop.
instanceOf Typescript
import {isImplements} from '../index';
export type InterfaceTag<T> = ({ new (...args: any[]): T, readonly id: symbol });
export function createInterface<I>(name: string): InterfaceTag<I>{
let id = Symbol.for(name);
let interfaceTag = (class {
private static readonly identifier: symbol = id;
static [Symbol.hasInstance](instance) {
return isImplements(instance, this);
};
public static get id(): symbol {
return this.identifier;
}
});
Object.freeze(interfaceTag);
return (<InterfaceTag<I>>(<any> interfaceTag));
}
import * as chai from 'chai';
import 'reflect-metadata';
import {implement, createInterface} from '../';
const expect = chai.expect;
describe(`Interface '@implement' decorator`, async () => {
it('should work', async () => {
const IFoo = createInterface<IFoo>('IFoo');
const IBar = createInterface<IBar>('IBar');
interface IFoo {
helloFoo(): string;
}
interface IBar {
helloBar(): string;
}
@implement(IFoo)
class Foo implements IFoo {
helloFoo(): string {
return 'helloFoo';
}
}
// TS warnings will also work without implements keyword
@implement(IBar)
class Bar {
helloBar(): string {
return 'helloBar';
}
}
const foo = new Foo();
const bar = new Bar();
const baz = {};
// magic!
expect(bar instanceof IFoo).to.be.equal(false);
expect(foo instanceof IFoo).to.be.equal(true);
if (bar instanceof IFoo) {
// TS type checking also works
bar.helloFoo();
expect.fail('bar is not instance of IFoo');
}
if (baz instanceof IFoo) {
// TS type checking also works
baz.helloFoo();
expect.fail('baz is not instance of IFoo');
}
});
});
import {TAG} from '../constants';
import {InterfaceTag} from '../functions/createInterface';
export function implement<T>(interfaceTag: InterfaceTag<T>) {
return function (target: {new (...args: any[]): T } ): any {
let tags: symbol[] = [];
if (interfaceTag instanceof Function) {
tags.push(interfaceTag.id);
} else {
throw new Error('interfaceTag must be a Function');
}
if (Reflect.hasMetadata(TAG, target)) {
let existingTags = Reflect.getMetadata(TAG, target);
tags.push(...existingTags);
}
Reflect.defineMetadata(TAG, tags, target);
}
}
import {TAG} from '../constants';
import {InterfaceTag} from './createInterface';
export function isImplements<T>(target: T | any, interfaceTag: InterfaceTag<any>): target is T {
if (typeof target === 'undefined') {
return false;
}
if (Reflect.hasMetadata(TAG, target.constructor)) {
let tags: symbol[] = Reflect.getMetadata(TAG, target.constructor);
let tag: symbol;
if (interfaceTag instanceof Function) {
tag = interfaceTag.id;
} else {
throw new Error('interfaceTag must be a Function');
}
return tags.indexOf(tag) >= 0;
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment