node_modules/ | |
*.js |
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; | |
} |
{ | |
"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