-
-
Save larrynung/38fee67c79c09db5fec580bb7f8ee7a8 to your computer and use it in GitHub Desktop.
TypeScript contract-based decorators
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) => | |
TypedPropertyDescriptor | void; | |
type AssertFn = (...args: Array<any>) => void; | |
class TSContract { | |
private static isCustomContractInterruptionCb: boolean = false; | |
private static contractInterruptionCb(message: string): void { | |
console.warn(message); | |
} | |
static setupContractInterruptionCb(cb: (message: string) => void): void { | |
if (TSContract.isCustomContractInterruptionCb) { | |
console.warn('Custom contract interruption callback already setted, break'); | |
return; | |
} | |
TSContract.contractInterruptionCb = cb; | |
TSContract.isCustomContractInterruptionCb = true; | |
} | |
static assert(expression: boolean, errorMessage: string): void { | |
if (!expression) { | |
TSContract.contractInterruptionCb(errorMessage); | |
} | |
} | |
static In(assertFn: AssertFn): MethodDecorator { | |
return function(target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor { | |
const originalValue = descriptor.value; | |
function newValue(...args) { | |
assertFn(...args); | |
return originalValue.apply(this, args); | |
} | |
descriptor.value = newValue; | |
return descriptor; | |
}; | |
} | |
static Out(assertFn: AssertFn): MethodDecorator { | |
return function(target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor { | |
const originalValue = descriptor.value; | |
function newValue(...args) { | |
result = originalValue.apply(this, args); | |
assertFn(result); | |
return result; | |
} | |
descriptor.value = newValue; | |
return descriptor; | |
}; | |
} | |
} | |
class ContractTest { | |
@TSContract.In(a => { | |
TSContract.assert(typeof a === 'number', 'Not a number!'); | |
}) | |
@TSContract.Out(retVal => { | |
TSContract.assert(!retVal, 'Wat?! Return?!'); | |
}) | |
doSome(a: number, withReturn: boolean = false): void { | |
console.log(a); | |
if (withReturn) { | |
return a; | |
} | |
} | |
} | |
TSContract.setupContractInterruptionCb((message: string) => { | |
console.error(message); | |
}); | |
// cant setup another callback | |
TSContract.setupContractInterruptionCb((message: string) => { | |
console.error(message); | |
}); | |
const ct = new ContractTest(); | |
ct.doSome(12); | |
ct.doSome('12'); | |
ct.doSome(12, true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment