Skip to content

Commit

Permalink
Add decorator TC39 support
Browse files Browse the repository at this point in the history
  • Loading branch information
garethj2 committed Apr 25, 2024
1 parent 8ab449b commit 407913d
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 11 deletions.
57 changes: 57 additions & 0 deletions spec/util/memoize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import { memoize, getMemoizeCache, MemoizeCache } from '../../src/util/memoize'

/* eslint @typescript-eslint/no-explicit-any: "off" */

describe('memoize', () => {
describe('memoize class getter methods', () => {
class MathUtil {
Expand Down Expand Up @@ -46,6 +48,33 @@ describe('memoize', () => {

expect(util.increment).toBe(2)
})

describe('memoize using TC39 decorators', () => {
it('returns the same value', () => {
class MathUtil {
index = 0

get increment(): number {
return ++this.index
}
}

const getter = Object.getOwnPropertyDescriptor(
MathUtil.prototype,
'increment'
)?.get

const obj = memoize()(getter, {
name: 'increment',
kind: 'getter',
} as unknown as ClassMethodDecoratorContext) as any

const util = new MathUtil()

expect(obj.get.call(util)).toBe(1)
expect(obj.get.call(util)).toBe(1)
})
}) // TC39
}) // getters

describe('memoize class methods', () => {
Expand Down Expand Up @@ -82,6 +111,34 @@ describe('memoize', () => {
expect(util.add(2, 2)).toBe(4)
expect(util.counter).toBe(2)
})

describe('memoize using TC39 decorators', () => {
it('adds two numbers together and increments the counter', () => {
class StrUtil {
counter = 0

add(a: number, b: number): number {
this.counter++
return a + b
}
}

const obj = memoize()(StrUtil.prototype.add, {
name: 'add',
kind: 'method',
} as ClassMethodDecoratorContext) as any

const util = new StrUtil()

let result = obj.value.call(util, 1, 2)
expect(result).toBe(3)
expect(util.counter).toBe(1)

result = obj.value.call(util, 1, 2)
expect(result).toBe(3)
expect(util.counter).toBe(1)
})
}) // TC39
}) // methods

describe('memoize class setter methods', () => {
Expand Down
46 changes: 35 additions & 11 deletions src/util/memoize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,34 +199,58 @@ export function getMemoizeCache(obj: any): MemoizeCache | undefined {
*/
export function memoize(): (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
context: string | ClassMemberDecoratorContext,
descriptor?: PropertyDescriptor
) => void {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
context: string | ClassMemberDecoratorContext,
descriptor?: PropertyDescriptor
): PropertyDescriptor {
// The original getter.
const get = descriptor?.get
const value = descriptor?.value
let propKey = ''
let get: any
let value: any

if (typeof context === 'string') {
propKey = context
get = descriptor?.get
value = descriptor?.value
} else if (
context?.kind &&
context?.name &&
typeof context.name === 'string'
) {
// Support newer decorator standard (TC39). Found some issues
// with certain build processes where we need to dynamically
// support both standards.
// https://github.com/tc39/proposal-decorators
propKey = context.name
switch (context.kind) {
case 'getter':
get = target
break
case 'method':
value = target
break
}
}

if (typeof get === 'function') {
return {
get(): any {
const cache = getCache(this)

return cache.has(propertyKey)
? cache.get(propertyKey)
: cache.set(propertyKey, get.call(this))
return cache.has(propKey)
? cache.get(propKey)
: cache.set(propKey, get.call(this))
},
}
} else if (typeof value === 'function') {
return {
value(...args: any[]): any {
const cache = getCache(this)

const key = JSON.stringify({ propertyKey, args })
const key = JSON.stringify({ propKey, args })

return cache.has(key)
? cache.get(key)
Expand Down

0 comments on commit 407913d

Please sign in to comment.