feat(decorate-with): decorateClass() (#6136)
Generalization of `decorateMethodsWith` which also works for accessors. The suffix `With` is not part of the name because it's not fluent (unlike for `@decorateWith(decorator)`). `decorateMethodsWith` is now a deprecated alias for this new implementation.
This commit is contained in:
parent
8ce1b4bf71
commit
b9ff3db9b0
@ -13,15 +13,19 @@ class Foo {
|
||||
}
|
||||
```
|
||||
|
||||
### `decorateMethodsWith(class, map)`
|
||||
### `decorateClass(class, map)`
|
||||
|
||||
Decorates a number of methods directly, without using the decorator syntax:
|
||||
Decorates a number of accessors and methods directly, without using the decorator syntax:
|
||||
|
||||
```js
|
||||
import { decorateMethodsWith } from '@vates/decorate-with'
|
||||
import { decorateClass } from '@vates/decorate-with'
|
||||
|
||||
class Foo {
|
||||
bar() {
|
||||
get bar() {
|
||||
// body
|
||||
}
|
||||
|
||||
set bar(value) {
|
||||
// body
|
||||
}
|
||||
|
||||
@ -30,22 +34,28 @@ class Foo {
|
||||
}
|
||||
}
|
||||
|
||||
decorateMethodsWith(Foo, {
|
||||
// without arguments
|
||||
bar: lodash.curry,
|
||||
decorateClass(Foo, {
|
||||
// getter and/or setter
|
||||
bar: {
|
||||
// without arguments
|
||||
get: lodash.memoize,
|
||||
|
||||
// with arguments
|
||||
baz: [lodash.debounce, 150],
|
||||
// with arguments
|
||||
set: [lodash.debounce, 150],
|
||||
},
|
||||
|
||||
// method (with or without arguments)
|
||||
baz: lodash.curry,
|
||||
})
|
||||
```
|
||||
|
||||
The decorated class is returned, so you can export it directly.
|
||||
|
||||
To apply multiple transforms to a method, you can either call `decorateMethodsWith` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
||||
To apply multiple transforms to an accessor/method, you can either call `decorateClass` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
||||
|
||||
```js
|
||||
decorateMethodsWith(Foo, {
|
||||
bar: compose([
|
||||
decorateClass(Foo, {
|
||||
baz: compose([
|
||||
[lodash.debounce, 150]
|
||||
lodash.curry,
|
||||
])
|
||||
@ -69,4 +79,8 @@ class Foo {
|
||||
}
|
||||
```
|
||||
|
||||
Because it's a normal function, it can also be used with `decorateMethodsWith`, with `compose` or even by itself.
|
||||
Because it's a normal function, it can also be used with `decorateClass`, with `compose` or even by itself.
|
||||
|
||||
### `decorateMethodsWith(class, map)`
|
||||
|
||||
> Deprecated alias for [`decorateClass(class, map)`](#decorateclassclass-map).
|
||||
|
@ -31,15 +31,19 @@ class Foo {
|
||||
}
|
||||
```
|
||||
|
||||
### `decorateMethodsWith(class, map)`
|
||||
### `decorateClass(class, map)`
|
||||
|
||||
Decorates a number of methods directly, without using the decorator syntax:
|
||||
Decorates a number of accessors and methods directly, without using the decorator syntax:
|
||||
|
||||
```js
|
||||
import { decorateMethodsWith } from '@vates/decorate-with'
|
||||
import { decorateClass } from '@vates/decorate-with'
|
||||
|
||||
class Foo {
|
||||
bar() {
|
||||
get bar() {
|
||||
// body
|
||||
}
|
||||
|
||||
set bar(value) {
|
||||
// body
|
||||
}
|
||||
|
||||
@ -48,22 +52,28 @@ class Foo {
|
||||
}
|
||||
}
|
||||
|
||||
decorateMethodsWith(Foo, {
|
||||
// without arguments
|
||||
bar: lodash.curry,
|
||||
decorateClass(Foo, {
|
||||
// getter and/or setter
|
||||
bar: {
|
||||
// without arguments
|
||||
get: lodash.memoize,
|
||||
|
||||
// with arguments
|
||||
baz: [lodash.debounce, 150],
|
||||
// with arguments
|
||||
set: [lodash.debounce, 150],
|
||||
},
|
||||
|
||||
// method (with or without arguments)
|
||||
baz: lodash.curry,
|
||||
})
|
||||
```
|
||||
|
||||
The decorated class is returned, so you can export it directly.
|
||||
|
||||
To apply multiple transforms to a method, you can either call `decorateMethodsWith` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
||||
To apply multiple transforms to an accessor/method, you can either call `decorateClass` multiple times or use [`@vates/compose`](https://www.npmjs.com/package/@vates/compose):
|
||||
|
||||
```js
|
||||
decorateMethodsWith(Foo, {
|
||||
bar: compose([
|
||||
decorateClass(Foo, {
|
||||
baz: compose([
|
||||
[lodash.debounce, 150]
|
||||
lodash.curry,
|
||||
])
|
||||
@ -87,7 +97,11 @@ class Foo {
|
||||
}
|
||||
```
|
||||
|
||||
Because it's a normal function, it can also be used with `decorateMethodsWith`, with `compose` or even by itself.
|
||||
Because it's a normal function, it can also be used with `decorateClass`, with `compose` or even by itself.
|
||||
|
||||
### `decorateMethodsWith(class, map)`
|
||||
|
||||
> Deprecated alias for [`decorateClass(class, map)`](#decorateclassclass-map).
|
||||
|
||||
## Contributions
|
||||
|
||||
|
@ -9,14 +9,27 @@ exports.decorateWith = function decorateWith(fn, ...args) {
|
||||
|
||||
const { getOwnPropertyDescriptor, defineProperty } = Object
|
||||
|
||||
exports.decorateMethodsWith = function decorateMethodsWith(klass, map) {
|
||||
function applyDecorator(decorator, value) {
|
||||
return typeof decorator === 'function' ? decorator(value) : decorator[0](value, ...decorator.slice(1))
|
||||
}
|
||||
|
||||
exports.decorateClass = exports.decorateMethodsWith = function decorateClass(klass, map) {
|
||||
const { prototype } = klass
|
||||
for (const name of Object.keys(map)) {
|
||||
const descriptor = getOwnPropertyDescriptor(prototype, name)
|
||||
const { value } = descriptor
|
||||
|
||||
const decorator = map[name]
|
||||
descriptor.value = typeof decorator === 'function' ? decorator(value) : decorator[0](value, ...decorator.slice(1))
|
||||
const descriptor = getOwnPropertyDescriptor(prototype, name)
|
||||
if (typeof decorator === 'function' || Array.isArray(decorator)) {
|
||||
descriptor.value = applyDecorator(decorator, descriptor.value)
|
||||
} else {
|
||||
const { get, set } = decorator
|
||||
if (get !== undefined) {
|
||||
descriptor.get = applyDecorator(get, descriptor.get)
|
||||
}
|
||||
if (set !== undefined) {
|
||||
descriptor.set = applyDecorator(set, descriptor.set)
|
||||
}
|
||||
}
|
||||
|
||||
defineProperty(prototype, name, descriptor)
|
||||
}
|
||||
return klass
|
||||
|
@ -3,7 +3,9 @@
|
||||
const assert = require('assert')
|
||||
const { describe, it } = require('tap').mocha
|
||||
|
||||
const { decorateWith, decorateMethodsWith, perInstance } = require('./')
|
||||
const { decorateClass, decorateWith, decorateMethodsWith, perInstance } = require('./')
|
||||
|
||||
const identity = _ => _
|
||||
|
||||
describe('decorateWith', () => {
|
||||
it('works', () => {
|
||||
@ -31,11 +33,14 @@ describe('decorateWith', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('decorateMethodsWith', () => {
|
||||
describe('decorateClass', () => {
|
||||
it('works', () => {
|
||||
class C {
|
||||
foo() {}
|
||||
bar() {}
|
||||
get baz() {}
|
||||
// eslint-disable-next-line accessor-pairs
|
||||
set qux(_) {}
|
||||
}
|
||||
|
||||
const expectedArgs = [Math.random(), Math.random()]
|
||||
@ -45,27 +50,74 @@ describe('decorateMethodsWith', () => {
|
||||
|
||||
const newFoo = () => {}
|
||||
const newBar = () => {}
|
||||
const newGetBaz = () => {}
|
||||
const newSetQux = _ => {}
|
||||
|
||||
decorateMethodsWith(C, {
|
||||
foo(method) {
|
||||
decorateClass(C, {
|
||||
foo(fn) {
|
||||
assert.strictEqual(arguments.length, 1)
|
||||
assert.strictEqual(method, P.foo)
|
||||
assert.strictEqual(fn, P.foo)
|
||||
return newFoo
|
||||
},
|
||||
bar: [
|
||||
function (method, ...args) {
|
||||
assert.strictEqual(method, P.bar)
|
||||
function (fn, ...args) {
|
||||
assert.strictEqual(fn, P.bar)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
return newBar
|
||||
},
|
||||
...expectedArgs,
|
||||
],
|
||||
baz: {
|
||||
get(fn) {
|
||||
assert.strictEqual(arguments.length, 1)
|
||||
assert.strictEqual(fn, descriptors.baz.get)
|
||||
return newGetBaz
|
||||
},
|
||||
},
|
||||
qux: {
|
||||
set: [
|
||||
function (fn, ...args) {
|
||||
assert.strictEqual(fn, descriptors.qux.set)
|
||||
assert.deepStrictEqual(args, expectedArgs)
|
||||
return newSetQux
|
||||
},
|
||||
...expectedArgs,
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const newDescriptors = Object.getOwnPropertyDescriptors(P)
|
||||
assert.deepStrictEqual(newDescriptors.foo, { ...descriptors.foo, value: newFoo })
|
||||
assert.deepStrictEqual(newDescriptors.bar, { ...descriptors.bar, value: newBar })
|
||||
assert.deepStrictEqual(newDescriptors.baz, { ...descriptors.baz, get: newGetBaz })
|
||||
assert.deepStrictEqual(newDescriptors.qux, { ...descriptors.qux, set: newSetQux })
|
||||
})
|
||||
|
||||
it('throws if using an accessor decorator for a method', function () {
|
||||
assert.throws(() =>
|
||||
decorateClass(
|
||||
class {
|
||||
foo() {}
|
||||
},
|
||||
{ foo: { get: identity, set: identity } }
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
it('throws if using a method decorator for an accessor', function () {
|
||||
assert.throws(() =>
|
||||
decorateClass(
|
||||
class {
|
||||
get foo() {}
|
||||
},
|
||||
{ foo: identity }
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('decorateMethodsWith is an alias of decorateClass', function () {
|
||||
assert.strictEqual(decorateMethodsWith, decorateClass)
|
||||
})
|
||||
|
||||
describe('perInstance', () => {
|
||||
|
@ -32,6 +32,7 @@
|
||||
>
|
||||
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
||||
|
||||
- @vates/decorate-with major
|
||||
- xen-api major
|
||||
- @xen-orchestra/xapi minor
|
||||
- @xen-orchestra/fs major
|
||||
|
Loading…
Reference in New Issue
Block a user