Skip to content
This repository was archived by the owner on Jan 8, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/typeguards/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
3 changes: 3 additions & 0 deletions packages/typeguards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `@discordjs/typeguards`

> The Type Guards module for Discord.js
21 changes: 21 additions & 0 deletions packages/typeguards/__tests__/button.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MessageButtonOptions } from 'discord.js';
import { isLinkButtonOptions } from '../src/lib/button';

test('isLinkButtonOptions', () => {
const buttonData: MessageButtonOptions = {
style: 'LINK',
url: 'test',
};

expect(isLinkButtonOptions(buttonData)).toBe(true);

const invalidData: MessageButtonOptions = {
style: 'PRIMARY',
customId: '1234',
};

expect(isLinkButtonOptions(invalidData)).toBe(false);

// @ts-ignore
expect(() => isLinkButtonOptions({})).toThrowError();
});
80 changes: 80 additions & 0 deletions packages/typeguards/__tests__/command.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ApplicationCommandData, ApplicationCommandOptionData } from 'discord.js';
import {
isChatInputCommandData,
isContextMenuCommandData,
optionDataSupportsChoices,
optionDataSupportsSubOptions,
} from '../src/lib/command';
import { validateType } from '../src/lib/util/enum';

test('validateType', () => {
expect(() => validateType({})).toThrowError();
});

test('isContextMenuCommand', () => {
const nonTypedData: ApplicationCommandData = {
name: 'test',
description: 'test',
};

expect(isContextMenuCommandData(nonTypedData)).toBe(false);
expect(isChatInputCommandData(nonTypedData)).toBe(true);

const typedData: ApplicationCommandData = {
...nonTypedData,
type: 'CHAT_INPUT',
};

expect(isContextMenuCommandData(typedData)).toBe(false);

const invalidMessageData: ApplicationCommandData = {
name: 'test',
type: 'MESSAGE',
};

expect(isContextMenuCommandData(invalidMessageData)).toBe(true);
expect(isChatInputCommandData(invalidMessageData)).toBe(false);

const invalidUserData: ApplicationCommandData = {
name: 'test',
type: 'USER',
};

expect(isContextMenuCommandData(invalidUserData)).toBe(true);
});

test('optionsDataSupportsChoices', () => {
const subCommandData: ApplicationCommandOptionData = {
name: 'test',
type: 'SUB_COMMAND',
description: 'test',
};

expect(optionDataSupportsChoices(subCommandData)).toBe(false);

const stringData: ApplicationCommandOptionData = {
name: 'test',
type: 'STRING',
description: 'test',
};

expect(optionDataSupportsChoices(stringData)).toBe(true);
});

test('optionsDataSupportsSubOptions', () => {
const subCommandData: ApplicationCommandOptionData = {
name: 'test',
type: 'SUB_COMMAND',
description: 'test',
};

expect(optionDataSupportsSubOptions(subCommandData)).toBe(true);

const stringData: ApplicationCommandOptionData = {
name: 'test',
type: 'STRING',
description: 'test',
};

expect(optionDataSupportsSubOptions(stringData)).toBe(false);
});
50 changes: 50 additions & 0 deletions packages/typeguards/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"$schema": "http://json.schemastore.org/package",
"name": "@discordjs/typeguards",
"version": "0.1.0-canary.0",
"description": "Typeguard helpers for Discord.js types.",
"contributors": [
"Suneet Tipirneni <suneettipirneni@icloud.com>",
"NotSugden <email>"
],
"license": "Apache-2.0",
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "tsc --build --force"
},
"repository": {
"type": "git",
"url": "git+https://github.com/discordjs/discord.js-modules.git"
},
"bugs": {
"url": "https://github.com/discordjs/discord.js-modules/issues"
},
"homepage": "https://github.com/discordjs/discord.js-modules/tree/main/packages/typeguards",
"keywords": [
"discord",
"typeguards",
"typescript",
"discordapp",
"discordjs"
],
"main": "dist/index.js",
"directories": {
"lib": "src",
"test": "__tests__"
},
"files": [
"dist"
],
"dependencies": {
"discord.js": "^13.1.0"
Comment thread
suneettipirneni marked this conversation as resolved.
Outdated
},
"devDependencies": {
"@types/node-fetch": "^2.5.10"
},
"engines": {
"node": ">=16.0.0"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions packages/typeguards/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/command';
17 changes: 17 additions & 0 deletions packages/typeguards/src/lib/button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MessageButtonOptions, Constants, LinkButtonOptions } from 'discord.js';
import { isPartOfEnum } from './util/enum';

const { MessageButtonStyles } = Constants;

/**
* Verifies if the given message button options support URL's or not.
Comment thread
suneettipirneni marked this conversation as resolved.
Outdated
* @param messageButtonOptions The message button options to check.
* @returns True if the option supports URL's, false otherwise.
Comment thread
suneettipirneni marked this conversation as resolved.
Outdated
*/
export function isLinkButtonOptions(buttonData: MessageButtonOptions): buttonData is LinkButtonOptions {
if (!('style' in buttonData)) {
throw new TypeError('INVALID_TYPE');
}

return isPartOfEnum(buttonData.style, MessageButtonStyles, ['LINK']);
}
66 changes: 66 additions & 0 deletions packages/typeguards/src/lib/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
ApplicationCommandData,
UserApplicationCommandData,
MessageApplicationCommandData,
ApplicationCommandChoicesData,
ApplicationCommandOptionData,
ApplicationCommandSubCommandData,
ApplicationCommandSubGroupData,
ChatInputApplicationCommandData,
Constants,
} from 'discord.js';
import { isPartOfEnum, validateType } from './util/enum';

const { ApplicationCommandOptionTypes, ApplicationCommandTypes } = Constants;

/**
* Whether or not the command supports a description or options.
* @param commandData The application command data to check.
* @returns True if the command is a context menu command, false otherwise.
*/
export function isContextMenuCommandData(
commandData: ApplicationCommandData,
): commandData is UserApplicationCommandData | MessageApplicationCommandData {
if (!commandData.type) {
return false;
}

validateType(commandData);

return isPartOfEnum(commandData.type, ApplicationCommandTypes, ['MESSAGE', 'USER']);
}

/**
* Whether or not the command supports a description or options.
* @param commandData The application command data to check.
* @returns True if the command is a chat input command, false otherwise.
*/
export function isChatInputCommandData(
commandData: ApplicationCommandData,
): commandData is ChatInputApplicationCommandData {
return !isContextMenuCommandData(commandData);
}

/**
* Verifies if the given command option data supports choices or not.
* @param commandOptionData The command option data to check.
* @returns True if the option supports choices, false otherwise.
*/
export function optionDataSupportsChoices(
optionData: ApplicationCommandOptionData,
): optionData is ApplicationCommandChoicesData {
validateType(optionData);
return isPartOfEnum(optionData.type, ApplicationCommandOptionTypes, ['INTEGER', 'STRING', 'NUMBER']);
}

/**
* Verifies if the given command option data supports choices or not.
* @param {ApplicationCommandOptionData} commandOptionData The command option data to check.
* @returns {boolean} True if the option supports choices, false otherwise.
*/
export function optionDataSupportsSubOptions(
optionData: ApplicationCommandOptionData,
): optionData is ApplicationCommandSubGroupData | ApplicationCommandSubCommandData {
validateType(optionData);
return isPartOfEnum(optionData.type, ApplicationCommandOptionTypes, ['SUB_COMMAND', 'SUB_COMMAND_GROUP']);
}
23 changes: 23 additions & 0 deletions packages/typeguards/src/lib/util/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Resolves a given type to an enum equivalent value, and
* checks if it's part of the given enum type.
* @param type The type to resolve
* @param object The enum to resolve to
* @param fields The enum fields to check
* @returns Whether the type is part of the enum or not.
*/
export function isPartOfEnum(type: string | number, object: Record<string, unknown>, fields: string[]): boolean {
const resolvedType = typeof type === 'number' ? type : object[type];
return fields.some((field) => object[field] === resolvedType);
}

/**
* Throws a type error if the object has no `type field`.
* @param object The object to type check.
* @private
*/
export function validateType(object: any) {
if (!('type' in object)) {
throw new TypeError('INVALID_TYPE');
}
}
11 changes: 11 additions & 0 deletions packages/typeguards/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"sourceRoot": "./",
"rootDir": "./src",
"outDir": "dist",
"lib": ["ESNext"]
},
"include": ["src"],
"references": [{ "path": "../core" }]
}
Loading