@effect-ts/system
Version:
Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.
243 lines (210 loc) • 6.44 kB
text/typescript
// ets_tracing: off
// forked from https://github.com/Alorel/typescript-lazy-get-decorator
type DecoratorReturn = PropertyDescriptor | NewDescriptor
function decorateNew(
inp: NewDescriptor,
setProto: boolean,
makeNonConfigurable: boolean,
resultSelector: ResultSelectorFn
): NewDescriptor {
const out: NewDescriptor = Object.assign({}, inp)
if (out.descriptor) {
out.descriptor = Object.assign({}, out.descriptor)
}
const actualDesc: PropertyDescriptor = <any>(
(out.descriptor || /* istanbul ignore next */ out)
)
const originalMethod = validateAndExtractMethodFromDescriptor(actualDesc)
const isStatic = inp.placement === "static"
actualDesc.get = function (this: any): any {
return getterCommon(
isStatic ? this : Object.getPrototypeOf(this),
out.key,
isStatic,
!!actualDesc.enumerable,
originalMethod,
this,
// eslint-disable-next-line prefer-rest-params
arguments,
setProto,
makeNonConfigurable,
resultSelector
)
}
return out
}
function decorateLegacy(
target: any,
key: PropertyKey,
descriptor: PropertyDescriptor,
setProto: boolean,
makeNonConfigurable: boolean,
//tslint:enable:bool-param-default
resultSelector: ResultSelectorFn
): PropertyDescriptor {
/* istanbul ignore if */
if (!descriptor) {
descriptor = <any>Object.getOwnPropertyDescriptor(target, key)
if (!descriptor) {
const e = new Error("@LazyGetter is unable to determine the property descriptor")
;(<any>e).$target = target
;(<any>e).$key = key
throw e
}
}
const originalMethod = validateAndExtractMethodFromDescriptor(descriptor)
return Object.assign({}, descriptor, {
get(this: any): any {
return getterCommon(
target,
key,
Object.getPrototypeOf(target) === Function.prototype,
!!descriptor.enumerable,
originalMethod,
this,
// eslint-disable-next-line prefer-rest-params
arguments,
setProto,
makeNonConfigurable,
resultSelector
)
}
})
}
/** Signifies that the modified property descriptor can be reset to its original state */
interface ResettableDescriptor {
/**
* Restore the property descriptor on the given class instance or prototype and re-apply the lazy getter.
* @param on The class instance or prototype
*/
reset(on: any): void
}
/** ES7 proposal descriptor, tweaked for Babel */
interface NewDescriptor extends PropertyDescriptor {
descriptor?: PropertyDescriptor
key: PropertyKey
kind: string
placement: string
}
/** A filter function that must return true for the value to cached */
type ResultSelectorFn = (v: any) => boolean
function defaultFilter(): boolean {
return true
}
function validateAndExtractMethodFromDescriptor(desc: PropertyDescriptor): Function {
const originalMethod = <Function>desc.get
if (!originalMethod) {
throw new Error("@LazyGetter can only decorate getters!")
} else if (!desc.configurable) {
throw new Error("@LazyGetter target must be configurable")
}
return originalMethod
}
function getterCommon( //tslint:disable-line:parameters-max-number
target: any,
key: PropertyKey,
isStatic: boolean,
enumerable: boolean,
originalMethod: Function,
thisArg: any,
args: IArguments,
setProto: boolean,
makeNonConfigurable: boolean,
resultSelector: ResultSelectorFn
): any {
const value = originalMethod.apply(thisArg, <any>args)
if (resultSelector(value)) {
const newDescriptor: PropertyDescriptor = {
configurable: !makeNonConfigurable,
enumerable,
value
}
if (isStatic || setProto) {
Object.defineProperty(target, key, newDescriptor)
}
if (!isStatic) {
Object.defineProperty(thisArg, key, newDescriptor)
}
}
return value
}
/**
* Evaluate the getter function and cache the result
* @param [setProto=false] Set the value on the class prototype as well. Only applies to non-static getters.
* @param [makeNonConfigurable=false] Set to true to make the resolved property non-configurable
* @param [resultSelector] A filter function that must return true for the value to cached
* @return A decorator function
*/
function LazyGetter(
setProto = false,
makeNonConfigurable = false,
resultSelector: ResultSelectorFn = defaultFilter
): MethodDecorator & ResettableDescriptor {
let desc: PropertyDescriptor
let prop: PropertyKey
let args: IArguments = <any>null
let isLegacy: boolean
function decorator(
targetOrDesc: any,
key: PropertyKey,
descriptor: PropertyDescriptor
): DecoratorReturn {
// eslint-disable-next-line prefer-rest-params
args = arguments
if (key === undefined) {
if (typeof desc === "undefined") {
isLegacy = false
prop = (<NewDescriptor>targetOrDesc).key
desc = Object.assign(
{},
(<NewDescriptor>targetOrDesc).descriptor ||
/* istanbul ignore next */ targetOrDesc
)
}
return decorateNew(targetOrDesc, setProto, makeNonConfigurable, resultSelector)
} else {
if (typeof desc === "undefined") {
isLegacy = true
prop = key
desc = Object.assign(
{},
descriptor ||
/* istanbul ignore next */ Object.getOwnPropertyDescriptor(
targetOrDesc,
key
)
)
}
return decorateLegacy(
targetOrDesc,
key,
descriptor,
setProto,
makeNonConfigurable,
resultSelector
)
}
}
decorator.reset = setProto
? thrower
: (on: any): void => {
if (!on) {
throw new Error("Unable to restore descriptor on an undefined target")
}
if (!desc) {
throw new Error(
"Unable to restore descriptor. Did you remember to apply your decorator to a method?"
)
}
// Restore descriptor to its original state
Object.defineProperty(on, prop, desc)
// eslint-disable-next-line prefer-spread
const ret: any = decorator.apply(null, <any>args)
Object.defineProperty(on, prop, isLegacy ? ret : ret.descriptor || ret)
}
return decorator
}
function thrower(): never {
throw new Error("This decoration modifies the class prototype and cannot be reset.")
}
export { LazyGetter, ResultSelectorFn }