UNPKG

@alterior/annotations

Version:
83 lines (76 loc) 2.96 kB
import { getParameterNames } from '@alterior/common'; import { DecoratorSite, AnnotationDecoratorOptions, Annotation, MutatorDefinition, AnnotationDecorator } from './annotations'; /** * Mutators are a way to define "mutation decorators" which in some way change the value * of the elements they are applied to, as opposed to "annotation decorators", which primarily * attach metadata. * * Create a mutator with Mutator.create(). */ export class Mutator { /** * Low-level method to ceate a new mutation decorator (mutator) based on the given function. * Use `Mutator.define()` instead. */ public static create<Args extends any[]>( mutator: (target: DecoratorSite, ...args: Args) => void, options?: AnnotationDecoratorOptions<void> ): AnnotationDecorator<Args> { return <AnnotationDecorator<any[]>> Annotation.decorator(Object.assign({}, options || {}, { factory: (target: DecoratorSite, ...args: Args) => { let paramNames: string[] | undefined; let orig = target.propertyDescriptor.value; if (typeof orig === 'function') { paramNames = getParameterNames(target.propertyDescriptor.value); } mutator(target, ...args); let replacement = target.propertyDescriptor.value; if (orig !== replacement && paramNames !== undefined && !Object.hasOwn(replacement, '__parameterNames')) { Object.defineProperty(replacement, '__parameterNames', { value: paramNames }); } } })); } /** * Define a new mutation decorator (mutator). * This should be called and returned from a * function definition. For example: * ``` function Name() { return Mutator.define({ invoke(site) { // ... } }) } ``` * * The `invoke()` function takes a DecoratorSite object which describes the particular * invocation that is being run, and importantly, access to the property descriptor * for the property being defined. If you wish to completely replace (or wrap) the * default value of the property or method you are replacing, set the `value` * property of the property descriptor with `site.propertyDescriptor.value` * * For example: * ``` export function RunTwice() { return Mutator.create( site => { let prop = site.propertyDescriptor; let original = prop.value; let replacement = function(...args) { original.apply(this, args); original.apply(this, args); } prop.value = replacement; } ) * ``` */ public static define(definition: MutatorDefinition) { return this.create(definition.invoke, definition.options)(); } }