@alterior/annotations
Version:
Create and interact with Typescript metadata decorators
83 lines (76 loc) • 2.96 kB
text/typescript
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)();
}
}