Skip to content

Instantly share code, notes, and snippets.

@Hypercubed
Last active November 13, 2017 02:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hypercubed/db7caffce22bd0a874fe8eb254f06dc0 to your computer and use it in GitHub Desktop.
Save Hypercubed/db7caffce22bd0a874fe8eb254f06dc0 to your computer and use it in GitHub Desktop.
node_modules/
*.js
// Define a class for use later
export class A {
}
import { typed, override, addType } from './typed';
import { A } from './A';
addType({ // this is just an example of a defined type
name: 'A',
test: function (x: any) {
return x instanceof A;
}
});
@typed // need this here to actually attach the typed functions
class TypedFunctions {
// some signitures work without any additional annotation
@override('method')
methodOne(x: string) {
console.log('Received a string');
}
// theroetically all primitives and classes
// note that the methoid name iteself doesn't matter
@override('method')
methodTwo_Number(x: number) {
console.log('Received a number');
}
// abstract types do not work
// you will need to duplicate the type in the decorator
@override('method', ['A | Date'])
methodFour(x: A | Date) {
console.log('Received an instance of A or a Date');
}
// The method you are overriding should have the most generic type signature
// Note here we are overriding the method itself, otherwise it will not be part of the typed function
// Because of how TS treats any, `any` types need to be explicit, otherwise the type is `Obejct` :(
@override('method', ['any'])
method(x: any) {
console.log('Received something else');
}
}
const typedFunctions = new TypedFunctions();
// now the `method` method is a typed-function
typedFunctions.method('Hello');
typedFunctions.method(123);
typedFunctions.method(new Date());
import { typed, override } from './typed';
@typed
class MathFunctions {
// Add
add(x: string, y: string): string;
add(x: number, y: number): number;
add(x: boolean, y: boolean): boolean;
@override('add', ['number', 'number'])
@override('add', ['string', 'string'])
add(x: any, y: any): any {
return x + y;
}
@override('add')
or(x: boolean, y: boolean): boolean {
return x || y;
}
// Subtract
sub(x: boolean, y: boolean): boolean;
sub(x: number, y: number): boolean;
@override('sub', ['number', 'number'])
sub(x: any, y: any): any {
return x - y;
}
@override('sub')
xor(x: boolean, y: boolean): boolean {
return (x || y) && !(x && y);
}
// Multiply
mul(x: string, y: number): string;
mul(x: number, y: number): number;
mul(x: boolean, y: boolean): boolean;
@override('mul', ['number', 'number'])
mul(x: any, y: any): any {
return x * y;
}
@override('mul')
private mulStrings(x: string, y: number): string {
return x.repeat(y);
}
@override('mul')
and(x: boolean, y: boolean): boolean {
return x && y;
}
// Divide
div(x: boolean, y: boolean): boolean;
div(x: number, y: number): number;
@override('div', ['number', 'number'])
div(x: any, y: any): any {
return x / y;
}
@override('div')
nand(x: boolean, y: boolean): boolean {
return !(x && y);
}
}
const mathFns = MathFunctions.prototype;
console.log(mathFns.add(1, 2));
console.log(mathFns.add('A', 'B'));
console.log(mathFns.add(true, false));
// console.log(mathFns.add(true, '2'));
// console.log(mathFns.add('1', 2));
console.log(mathFns.sub(1, 2));
console.log(mathFns.sub(true, false));
// console.log(mathFns.sub('1', '2'));
// console.log(mathFns.sub(true, '2'));
// console.log(mathFns.sub('1', 2));
console.log(mathFns.mul(1, 2));
console.log(mathFns.mul('A', 2));
console.log(mathFns.mul(true, false));
// console.log(mathFns.mul(true, '2'));
// console.log(mathFns.mul('1', '2'));
console.log(mathFns.div(1, 2));
console.log(mathFns.div(true, false));
// console.log(mathFns.div('1', 2));
// console.log(mathFns.div(true, '2'));
// console.log(mathFns.div('1', '2'));
import autobind from 'autobind-decorator';
import { typed, override, addType } from './typed';
class Person {
constructor(public firstname: string, public lastname: string) {
}
}
addType({
name: 'Person',
test: function (x: any) {
return x instanceof Person;
}
});
@typed
class Hello {
constructor(private world: string) {
}
hello(x?: Person | string) {
}
@override('hello')
helloPerson(person: Person) {
return(`Hello ${this.world} ${person.firstname} ${person.lastname}`);
}
@override('hello')
helloWorld() {
return(`Hello ${this.world}`);
}
@override('hello')
helloName(name: string) {
return(`Hello ${this.world} ${name}`);
}
}
const hello = new Hello('Earthling');
console.log(hello.hello());
console.log(hello.hello.call({ world: 'Martian' }));
console.log(hello.hello('Mike'));
console.log(hello.hello(new Person('Mike', 'Dee')));
const helloFn = hello.hello;
console.log(helloFn.call({ world: 'Martian' }));
console.log(helloFn.apply({ world: 'Martian' }));
console.log(helloFn.bind({ world: 'Martian' })());
console.log(helloFn.call({ world: 'Venitian' }, 'Mike'));
console.log(helloFn.apply({ world: 'Venitian' }, ['Mike']));
console.log(helloFn.bind({ world: 'Venitian' })('Mike'));
declare module 'typed-function' {
export var create: any;
}
/// <reference path="index.d.ts"/>
import './example1';
import './example2';
import './example3';
{
"name": "typescript-typed-test",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"autobind-decorator": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.1.0.tgz",
"integrity": "sha512-bgyxeRi1R2Q8kWpHsb1c+lXCulbIAHsyZRddaS+agAUX3hFUVZMociwvRgeZi1zWvfqEEjybSv4zxWvFV8ydQQ=="
},
"reflect-metadata": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz",
"integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo="
},
"typed-function": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-0.10.5.tgz",
"integrity": "sha1-Lg8Yq9BlIZ+raUpEamXG0ZgYMsA="
},
"typescript": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.1.tgz",
"integrity": "sha1-7znN6ierrAtQAkLWcmq5DgyEZjE=",
"dev": true
}
}
}
{
"name": "typescript-typed-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^2.6.1"
},
"dependencies": {
"autobind-decorator": "^2.1.0",
"reflect-metadata": "^0.1.10",
"typed-function": "^0.10.5"
}
}
{
"compilerOptions": {
/* Basic Options */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */
// "lib": [], /* Specify library files to be included in the compilation: */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */
}
}
/// <reference path="index.d.ts"/>
import 'reflect-metadata';
import * as Typed from 'typed-function';
export const typedApi = Typed.create();
export const addType = typedApi.addType.bind(typedApi);
// decorator methods
export function typed<T extends { new (...args: any[]): {} }>(constructor: T) {
const target = constructor.prototype;
if (Reflect.hasMetadata('design:signatures', target)) {
const signatures = Reflect.getMetadata('design:signatures', target) || {};
// for each method
for (const key in signatures) {
const getMethod = typedApi(key, signatures[key]);
const fn = function(this: any, ...args: any[]) {
return getMethod(...args).apply(this, args);
};
target[key] = function(...args: any[]) {
return getMethod(...args).apply(this, args);
};
}
}
return constructor;
}
export function override(name: string, paramtypes?: string[]) {
return function override<T>(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>
) {
if (typeof Reflect === 'object') {
paramtypes =
<string[]>paramtypes ||
Reflect.getMetadata('design:paramtypes', target, propertyKey);
const typesKey = paramtypes.map(normalizeName).join(',');
const method = target[propertyKey];
if (typeof method === 'function') {
const signatures =
Reflect.getMetadata('design:signatures', target) || {};
signatures[name] = signatures[name] || {};
signatures[name][typesKey] = () => method;
Reflect.defineMetadata('design:signatures', signatures, target);
}
}
};
function normalizeName(x: any) {
x = typeof x === 'string' ? x : x.name;
if (x === 'String') {
return 'string';
}
if (x === 'Number') {
return 'number';
}
if (x === 'Boolean') {
return 'boolean';
}
return x;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment