Skip to content
This repository has been archived by the owner on Feb 11, 2021. It is now read-only.

Commit

Permalink
feat: allow ApplyOptions to take a function (#24)
Browse files Browse the repository at this point in the history
Co-authored-by: Jeroen Claassens <[email protected]>
  • Loading branch information
PyroTechniac and favna authored Jul 2, 2020
1 parent 2966580 commit cbcabdc
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 163 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"padded-blocks": "off",
"space-before-function-paren": "off",
"arrow-parens": "off",
"@typescript-eslint/no-throw-literal": "off",
"function-paren-newline": "off",

"@typescript-eslint/naming-convention": [
"error",
Expand Down
23 changes: 11 additions & 12 deletions src/core-decorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Permissions, PermissionsResolvable, Piece, PieceOptions, TextChannel } from '@klasa/core';
import type { Constructor, KlasaMessage } from 'klasa';
import { createClassDecorator, createFunctionInhibitor, Fallback } from './utils';
import type { Constructor, KlasaClient, KlasaMessage } from 'klasa';
import { createClassDecorator, createFunctionInhibitor, createProxy, Fallback } from './utils';

/**
* Decorator function that applies given options to any Klasa piece
Expand All @@ -15,16 +15,15 @@ import { createClassDecorator, createFunctionInhibitor, Fallback } from './utils
* @since 1.0.0
* @param options The options to pass to the piece constructor
*/
export function ApplyOptions<T extends PieceOptions>(options: T): ClassDecorator {
return createClassDecorator(
(target: Constructor<Piece>) =>
new Proxy(target, {
construct: (ctor, [store, file, directory, baseOptions = {}]) => new ctor(store, file, directory, { ...baseOptions, ...options }),
get: (target, prop) => {
const value = Reflect.get(target, prop);
return typeof value === 'function' ? (...args: readonly unknown[]) => value.apply(target, args) : value;
}
})
export function ApplyOptions<T extends PieceOptions>(optionsOrFn: T | ((client: KlasaClient) => T)): ClassDecorator {
return createClassDecorator((target: Constructor<Piece>) =>
createProxy(target, {
construct: (ctor, [store, file, directory, baseOptions = {}]) =>
new ctor(store, file, directory, {
...baseOptions,
...(typeof optionsOrFn === 'function' ? optionsOrFn(store.client) : optionsOrFn)
})
})
);
}

Expand Down
35 changes: 25 additions & 10 deletions src/starlight-decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
*/

import type { Constructor } from '@klasa/core';
import type { Command, CommandOptions, CommandStore, CustomUsageArgument } from 'klasa';
import { createClassDecorator } from './utils';
import type { Command, CustomUsageArgument } from 'klasa';
import { createClassDecorator, createProxy } from './utils';

/**
* Applies a set of custom resolvers to a command through a decorator
Expand All @@ -46,16 +46,31 @@ import { createClassDecorator } from './utils';
* @param resolvers Array of custom resolvers to apply to a command
*/
export function CreateResolvers(resolvers: [string, CustomUsageArgument][]): ClassDecorator {
return createClassDecorator(
(target: Constructor<Command>) =>
class extends target {
public constructor(store: CommandStore, directory: string, files: readonly string[], options: CommandOptions) {
super(store, directory, files, options);

for (const resolver of resolvers) this.createCustomResolver(...resolver);
}
return createClassDecorator((target: Constructor<Command>) =>
createProxy(target, {
construct: (ctor, [store, directory, files, options]): Command => {
const command = new ctor(store, directory, files, options);
for (const resolver of resolvers) command.createCustomResolver(...resolver);
return command;
}
})
);
}

/**
* Applies a single custom resolver to a command through a decorator
*
* ```ts
* CreateResolver('key', (arg, _possible, message, [action]) => {
* if (action === 'show' || arg) return arg || '';
* throw message.language.get('COMMAND_CONF_NOKEY');
* })
* ```
* @param name Name of the custom argument resolver
* @param resolverFn Function describing how to resolve the argument
*/
export function CreateResolver(name: string, resolverFn: CustomUsageArgument): ClassDecorator {
return CreateResolvers([[name, resolverFn]]);
}

// TODO: Add SetRoute decorator when KDH has been updated to support @klasa/core and klasa >= 0.6.0
3 changes: 2 additions & 1 deletion src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"compilerOptions": {
"rootDir": "./",
"outDir": "../dist",
"composite": true
"composite": true,
"importsNotUsedAsValues": "error"
},
"include": ["."]
}
12 changes: 12 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,15 @@ export function createFunctionInhibitor(inhibitor: Inhibitor, fallback: Fallback
} as unknown) as undefined;
});
}

/** @hidden */
// eslint-disable-next-line @typescript-eslint/ban-types
export function createProxy<T extends object>(target: T, handler: Omit<ProxyHandler<T>, 'get'>): T {
return new Proxy(target, {
...handler,
get: (target, property) => {
const value = Reflect.get(target, property);
return typeof value === 'function' ? (...args: readonly unknown[]) => value.apply(target, args) : value;
}
});
}
30 changes: 16 additions & 14 deletions tests/core-decorators/apply-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import { client, MockCommandStore } from '@mocks/MockInstances';
import { ApplyOptions } from '@src/core-decorators';
import { Command, CommandOptions } from 'klasa';

test('ApplyOptions Decorator', () => {
@ApplyOptions<CommandOptions>({
name: 'test',
cooldown: 10
})
class TestPiece extends Command {
public getName() {
return this.name;
describe('ApplyOptions', () => {
test('GIVEN options object THEN sets options', () => {
@ApplyOptions<CommandOptions>({
name: 'test',
cooldown: 10
})
class TestPiece extends Command {
public getName() {
return this.name;
}
}
}

const instance = new TestPiece(new MockCommandStore('name', client), __dirname, [__filename]);
const instance = new TestPiece(new MockCommandStore('name', client), __dirname, [__filename]);

expect(instance.name).toBe('test');
expect(instance.cooldown).toBe(10);
expect(instance.guarded).toBe(false);
expect(Object.getOwnPropertyDescriptor(instance.constructor.prototype, 'getName')).toBeDefined();
expect(instance.name).toBe('test');
expect(instance.cooldowns.time).toBe(10);
expect(instance.guarded).toBe(false);
expect(Object.getOwnPropertyDescriptor(instance.constructor.prototype, 'getName')).toBeDefined();
});
});
2 changes: 1 addition & 1 deletion tests/mocks/MockInstances.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Command, CommandStore, KlasaClient } from 'klasa';

export const client = new KlasaClient();
export const client = new KlasaClient({ language: 'en-GB' });

export class Message {
public content: string;
Expand Down
53 changes: 53 additions & 0 deletions tests/starlight-decorators/create-resolver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Cache } from '@klasa/cache';
import { client, MockCommandStore } from '@mocks/MockInstances';
import { ApplyOptions } from '@src/core-decorators';
import { CreateResolver } from '@src/starlight-decorators';
import { Command, CommandOptions, CustomUsageArgument } from 'klasa';

describe('CreateResolver Decorator', () => {
const mockCommandStore = new MockCommandStore('name', client);
const receivedResolvers = new Cache<string, CustomUsageArgument>([
[
'key',
(arg, _possible, message, [action]) => {
if (action === 'show' || arg) return arg || '';
throw message.language.get('COMMAND_CONF_NOKEY');
}
]
]);

test('Applies Resolver to a command', () => {
@CreateResolver('key', (arg, _possible, message, [action]) => {
if (action === 'show' || arg) return arg || '';
throw message.language.get('COMMAND_CONF_NOKEY');
})
class TestCommand extends Command {}

const instance = new TestCommand(mockCommandStore, __dirname, [__filename]);

expect(instance.usage.customResolvers.firstValue).toEqual(expect.any(Function));
expect(instance.usage.customResolvers.firstKey).toEqual(receivedResolvers.firstKey);

expect(instance.usage.customResolvers.size).toEqual(1);
});

test('Is compatible with @ApplyOptions', () => {
@ApplyOptions<CommandOptions>({
name: 'test',
cooldown: 10
})
@CreateResolver('key', (arg, _possible, message, [action]) => {
if (action === 'show' || arg) return arg || '';
throw message.language.get('COMMAND_CONF_NOKEY');
})
class TestCommand extends Command {}

const instance = new TestCommand(mockCommandStore, __dirname, [__filename]);

expect(instance.name).toEqual('test');
expect(instance.usage.customResolvers.firstValue).toEqual(expect.any(Function));
expect(instance.usage.customResolvers.firstKey).toEqual(receivedResolvers.firstKey);

expect(instance.usage.customResolvers.size).toEqual(1);
});
});
4 changes: 1 addition & 3 deletions tests/starlight-decorators/create-resolvers.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Cache } from '@klasa/cache';
import { client, MockCommandStore } from '@mocks/MockInstances';
import { ApplyOptions } from '@src/core-decorators';
import { CreateResolvers } from '@src/starlight-decorators';
import { Command, CommandOptions, CustomUsageArgument } from 'klasa';
import { ApplyOptions } from '@src/core-decorators';

describe('CreateResolvers Decorator', () => {
const mockCommandStore = new MockCommandStore('name', client);
Expand Down Expand Up @@ -74,8 +74,6 @@ describe('CreateResolvers Decorator', () => {
const instance = new TestCommand(mockCommandStore, __dirname, [__filename]);

expect(instance.name).toEqual('test');
expect(instance.cooldown).toEqual(10);

expect(instance.usage.customResolvers.firstValue).toEqual(expect.any(Function));
expect(instance.usage.customResolvers.firstKey).toEqual(receivedResolvers.firstKey);

Expand Down
Loading

0 comments on commit cbcabdc

Please sign in to comment.