setup linter

This commit is contained in:
Snowflake107 2021-06-22 16:09:05 +05:45
parent 821e3d1053
commit b485ed8a9f
16 changed files with 94 additions and 53 deletions

7
.eslintignore Normal file
View file

@ -0,0 +1,7 @@
example/
node_modules/
lib/
.github/
docs/
*.d.ts

20
.eslintrc.json Normal file
View file

@ -0,0 +1,20 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/ban-ts-comment": "error"
}
}

View file

@ -172,7 +172,7 @@ client.on("interaction", async (interaction) => {
requestedBy: interaction.user, requestedBy: interaction.user,
searchEngine: interaction.commandName === "soundcloud" ? QueryType.SOUNDCLOUD_SEARCH : QueryType.AUTO searchEngine: interaction.commandName === "soundcloud" ? QueryType.SOUNDCLOUD_SEARCH : QueryType.AUTO
}) })
.catch(() => {}); .catch(Util.noop);
if (!searchResult || !searchResult.tracks.length) return void interaction.followUp({ content: "No results were found!" }); if (!searchResult || !searchResult.tracks.length) return void interaction.followUp({ content: "No results were found!" });
const queue = await player.createQueue(interaction.guild, { const queue = await player.createQueue(interaction.guild, {

View file

@ -10,10 +10,12 @@
"scripts": { "scripts": {
"dev": "cd example/test && ts-node index.ts", "dev": "cd example/test && ts-node index.ts",
"build": "rimraf lib && tsc", "build": "rimraf lib && tsc",
"build:check": "tsc --noEmit --incremental false",
"format": "prettier --write \"src/**/*.ts\" \"example/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"example/**/*.ts\"",
"lint": "tslint -p tsconfig.json",
"docs": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml --output docs/docs.json", "docs": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml --output docs/docs.json",
"docs:test": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml" "docs:test": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix"
}, },
"funding": "https://github.com/Androz2091/discord-player?sponsor=1", "funding": "https://github.com/Androz2091/discord-player?sponsor=1",
"contributors": [ "contributors": [
@ -68,15 +70,16 @@
"@discordjs/opus": "^0.5.3", "@discordjs/opus": "^0.5.3",
"@types/node": "^15.12.2", "@types/node": "^15.12.2",
"@types/ws": "^7.4.4", "@types/ws": "^7.4.4",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"discord-api-types": "^0.18.1", "discord-api-types": "^0.18.1",
"discord.js": "^13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201", "discord.js": "^13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201",
"discord.js-docgen": "discordjs/docgen#ts-patch", "discord.js-docgen": "discordjs/docgen#ts-patch",
"eslint": "^7.29.0",
"jsdoc-babel": "^0.5.0", "jsdoc-babel": "^0.5.0",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"tslint": "^6.1.3", "typescript": "^4.3.4"
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.3.2"
} }
} }

View file

@ -8,6 +8,7 @@ import { QueryResolver } from "./utils/QueryResolver";
import YouTube from "youtube-sr"; import YouTube from "youtube-sr";
import { Util } from "./utils/Util"; import { Util } from "./utils/Util";
import Spotify from "spotify-url-info"; import Spotify from "spotify-url-info";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import { Client as SoundCloud } from "soundcloud-scraper"; import { Client as SoundCloud } from "soundcloud-scraper";
import { Playlist } from "./Structures/Playlist"; import { Playlist } from "./Structures/Playlist";
@ -49,7 +50,7 @@ class Player extends EventEmitter<PlayerEvents> {
this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this)); this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this));
if (this.options?.autoRegisterExtractor) { if (this.options?.autoRegisterExtractor) {
let nv: any; let nv: any; // eslint-disable-line @typescript-eslint/no-explicit-any
if ((nv = Util.require("@discord-player/extractor"))) { if ((nv = Util.require("@discord-player/extractor"))) {
["Attachment", "Facebook", "Reverbnation", "Vimeo"].forEach((ext) => void this.use(ext, nv[ext])); ["Attachment", "Facebook", "Reverbnation", "Vimeo"].forEach((ext) => void this.use(ext, nv[ext]));
@ -139,7 +140,7 @@ class Player extends EventEmitter<PlayerEvents> {
try { try {
prev.destroy(); prev.destroy();
} catch {} } catch {} // eslint-disable-line no-empty
this.queues.delete(guild.id); this.queues.delete(guild.id);
return prev; return prev;
@ -162,6 +163,7 @@ class Player extends EventEmitter<PlayerEvents> {
options.requestedBy = this.client.users.resolve(options.requestedBy); options.requestedBy = this.client.users.resolve(options.requestedBy);
if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO; if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_, extractor] of this.extractors) { for (const [_, extractor] of this.extractors) {
if (!extractor.validate(query)) continue; if (!extractor.validate(query)) continue;
const data = await extractor.handle(query); const data = await extractor.handle(query);
@ -194,11 +196,11 @@ class Player extends EventEmitter<PlayerEvents> {
case QueryType.YOUTUBE_SEARCH: { case QueryType.YOUTUBE_SEARCH: {
const videos = await YouTube.search(query, { const videos = await YouTube.search(query, {
type: "video" type: "video"
}).catch(() => {}); }).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!videos) return { playlist: null, tracks: [] }; if (!videos) return { playlist: null, tracks: [] };
const tracks = videos.map((m) => { const tracks = videos.map((m) => {
(m as any).source = "youtube"; (m as any).source = "youtube"; // eslint-disable-line @typescript-eslint/no-explicit-any
return new Track(this, { return new Track(this, {
title: m.title, title: m.title,
description: m.description, description: m.description,
@ -216,12 +218,13 @@ class Player extends EventEmitter<PlayerEvents> {
} }
case QueryType.SOUNDCLOUD_TRACK: case QueryType.SOUNDCLOUD_TRACK:
case QueryType.SOUNDCLOUD_SEARCH: { case QueryType.SOUNDCLOUD_SEARCH: {
const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => {}); // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function
const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(Util.noop);
if (!result || !result.length) return { playlist: null, tracks: [] }; if (!result || !result.length) return { playlist: null, tracks: [] };
const res: Track[] = []; const res: Track[] = [];
for (const r of result) { for (const r of result) {
const trackInfo = await soundcloud.getSongInfo(r.url).catch(() => {}); const trackInfo = await soundcloud.getSongInfo(r.url).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!trackInfo) continue; if (!trackInfo) continue;
const track = new Track(this, { const track = new Track(this, {
@ -243,7 +246,7 @@ class Player extends EventEmitter<PlayerEvents> {
return { playlist: null, tracks: res }; return { playlist: null, tracks: res };
} }
case QueryType.SPOTIFY_SONG: { case QueryType.SPOTIFY_SONG: {
const spotifyData = await Spotify.getData(query).catch(() => {}); const spotifyData = await Spotify.getData(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!spotifyData) return { playlist: null, tracks: [] }; if (!spotifyData) return { playlist: null, tracks: [] };
const spotifyTrack = new Track(this, { const spotifyTrack = new Track(this, {
title: spotifyData.name, title: spotifyData.name,
@ -264,7 +267,7 @@ class Player extends EventEmitter<PlayerEvents> {
} }
case QueryType.SPOTIFY_PLAYLIST: case QueryType.SPOTIFY_PLAYLIST:
case QueryType.SPOTIFY_ALBUM: { case QueryType.SPOTIFY_ALBUM: {
const spotifyPlaylist = await Spotify.getData(query).catch(() => {}); const spotifyPlaylist = await Spotify.getData(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!spotifyPlaylist) return { playlist: null, tracks: [] }; if (!spotifyPlaylist) return { playlist: null, tracks: [] };
const playlist = new Playlist(this, { const playlist = new Playlist(this, {
@ -290,6 +293,7 @@ class Player extends EventEmitter<PlayerEvents> {
}); });
if (spotifyPlaylist.type !== "playlist") { if (spotifyPlaylist.type !== "playlist") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => { playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => {
const data = new Track(this, { const data = new Track(this, {
title: m.name ?? "", title: m.name ?? "",
@ -307,6 +311,7 @@ class Player extends EventEmitter<PlayerEvents> {
return data; return data;
}) as Track[]; }) as Track[];
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => { playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => {
const data = new Track(this, { const data = new Track(this, {
title: m.track.name ?? "", title: m.track.name ?? "",
@ -328,7 +333,7 @@ class Player extends EventEmitter<PlayerEvents> {
return { playlist: playlist, tracks: playlist.tracks }; return { playlist: playlist, tracks: playlist.tracks };
} }
case QueryType.SOUNDCLOUD_PLAYLIST: { case QueryType.SOUNDCLOUD_PLAYLIST: {
const data = await SoundCloud.getPlaylist(query).catch(() => {}); const data = await SoundCloud.getPlaylist(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!data) return { playlist: null, tracks: [] }; if (!data) return { playlist: null, tracks: [] };
const res = new Playlist(this, { const res = new Playlist(this, {
@ -367,11 +372,11 @@ class Player extends EventEmitter<PlayerEvents> {
return { playlist: res, tracks: res.tracks }; return { playlist: res, tracks: res.tracks };
} }
case QueryType.YOUTUBE_PLAYLIST: { case QueryType.YOUTUBE_PLAYLIST: {
const ytpl = await YouTube.getPlaylist(query).catch(() => {}); const ytpl = await YouTube.getPlaylist(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!ytpl) return { playlist: null, tracks: [] }; if (!ytpl) return { playlist: null, tracks: [] };
// @todo: better way of handling large playlists // @todo: better way of handling large playlists
await ytpl.fetch().catch(() => {}); await ytpl.fetch().catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
const playlist: Playlist = new Playlist(this, { const playlist: Playlist = new Playlist(this, {
title: ytpl.title, title: ytpl.title,
@ -420,6 +425,7 @@ class Player extends EventEmitter<PlayerEvents> {
* @param {boolean} [force=false] Overwrite existing extractor with this name (if available) * @param {boolean} [force=false] Overwrite existing extractor with this name (if available)
* @returns {ExtractorModel} * @returns {ExtractorModel}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
use(extractorName: string, extractor: ExtractorModel | any, force = false): ExtractorModel { use(extractorName: string, extractor: ExtractorModel | any, force = false): ExtractorModel {
if (!extractorName) throw new Error("Cannot use unknown extractor!"); if (!extractorName) throw new Error("Cannot use unknown extractor!");
if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName); if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName);

View file

@ -2,13 +2,14 @@ import { ExtractorModelData } from "../types/types";
class ExtractorModel { class ExtractorModel {
name: string; name: string;
private _raw: any; private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/** /**
* Model for raw Discord Player extractors * Model for raw Discord Player extractors
* @param {string} extractorName Name of the extractor * @param {string} extractorName Name of the extractor
* @param {object} data Extractor object * @param {object} data Extractor object
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(extractorName: string, data: any) { constructor(extractorName: string, data: any) {
/** /**
* The extractor name * The extractor name
@ -37,6 +38,7 @@ class ExtractorModel {
return { return {
playlist: data.playlist ?? null, playlist: data.playlist ?? null,
data: data:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data.info?.map((m: any) => ({ data.info?.map((m: any) => ({
title: m.title, title: m.title,
duration: m.duration, duration: m.duration,

View file

@ -16,7 +16,7 @@ class Playlist {
}; };
public id: string; public id: string;
public url: string; public url: string;
public readonly rawPlaylist?: any; public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/** /**
* Playlist constructor * Playlist constructor

View file

@ -2,7 +2,7 @@ import { Collection, Guild, StageChannel, VoiceChannel } from "discord.js";
import { Player } from "../Player"; import { Player } from "../Player";
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher"; import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
import Track from "./Track"; import Track from "./Track";
import { FiltersName, PlayerOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types"; import { PlayerOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types";
import ytdl from "discord-ytdl-core"; import ytdl from "discord-ytdl-core";
import { AudioResource, StreamType } from "@discordjs/voice"; import { AudioResource, StreamType } from "@discordjs/voice";
import { Util } from "../utils/Util"; import { Util } from "../utils/Util";
@ -19,9 +19,9 @@ class Queue<T = unknown> {
public playing = false; public playing = false;
public metadata?: T = null; public metadata?: T = null;
public repeatMode: QueueRepeatMode = 0; public repeatMode: QueueRepeatMode = 0;
private _streamTime: number = 0; private _streamTime = 0;
public _cooldownsTimeout = new Collection<string, NodeJS.Timeout>(); public _cooldownsTimeout = new Collection<string, NodeJS.Timeout>();
private _activeFilters: any[] = []; private _activeFilters: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any
private _filtersUpdate = false; private _filtersUpdate = false;
public destroyed = false; public destroyed = false;
@ -134,7 +134,7 @@ class Queue<T = unknown> {
}); });
this.connection = connection; this.connection = connection;
if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch(() => {}); if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
this.connection.on("error", (err) => this.player.emit("connectionError", this, err)); this.connection.on("error", (err) => this.player.emit("connectionError", this, err));
this.connection.on("debug", (msg) => this.player.emit("debug", this, msg)); this.connection.on("debug", (msg) => this.player.emit("debug", this, msg));
@ -332,7 +332,7 @@ class Queue<T = unknown> {
}); });
} }
const _filters: any[] = []; const _filters: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any
for (const filter in filters) { for (const filter in filters) {
if (filters[filter as keyof QueueFilters] === true) _filters.push(filter); if (filters[filter as keyof QueueFilters] === true) _filters.push(filter);
@ -466,7 +466,7 @@ class Queue<T = unknown> {
const info = await ytdl const info = await ytdl
.getInfo(track.url) .getInfo(track.url)
.then((x) => x.related_videos[0]) .then((x) => x.related_videos[0])
.catch(() => {}); .catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function
if (!info) { if (!info) {
if (this.options.leaveOnEnd) this.destroy(); if (this.options.leaveOnEnd) this.destroy();
return void this.player.emit("queueEnd", this); return void this.player.emit("queueEnd", this);

View file

@ -18,10 +18,10 @@ import Track from "../Structures/Track";
import { Util } from "../utils/Util"; import { Util } from "../utils/Util";
export interface VoiceEvents { export interface VoiceEvents {
error: (error: AudioPlayerError) => any; error: (error: AudioPlayerError) => any; // eslint-disable-line @typescript-eslint/no-explicit-any
debug: (message: string) => any; debug: (message: string) => any; // eslint-disable-line @typescript-eslint/no-explicit-any
start: () => any; start: () => any; // eslint-disable-line @typescript-eslint/no-explicit-any
finish: () => any; finish: () => any; // eslint-disable-line @typescript-eslint/no-explicit-any
} }
class StreamDispatcher extends EventEmitter<VoiceEvents> { class StreamDispatcher extends EventEmitter<VoiceEvents> {
@ -29,7 +29,7 @@ class StreamDispatcher extends EventEmitter<VoiceEvents> {
public readonly audioPlayer: AudioPlayer; public readonly audioPlayer: AudioPlayer;
public readonly channel: VoiceChannel | StageChannel; public readonly channel: VoiceChannel | StageChannel;
public audioResource?: AudioResource<Track>; public audioResource?: AudioResource<Track>;
private readyLock: boolean = false; private readyLock = false;
/** /**
* Creates new connection object * Creates new connection object
@ -108,6 +108,7 @@ class StreamDispatcher extends EventEmitter<VoiceEvents> {
* @param {object} [ops={}] Options * @param {object} [ops={}] Options
* @returns {AudioResource} * @returns {AudioResource}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any }) { createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any }) {
this.audioResource = createAudioResource(src, { this.audioResource = createAudioResource(src, {
inputType: ops?.type ?? StreamType.Arbitrary, inputType: ops?.type ?? StreamType.Arbitrary,
@ -134,7 +135,7 @@ class StreamDispatcher extends EventEmitter<VoiceEvents> {
try { try {
this.audioPlayer.stop(true); this.audioPlayer.stop(true);
this.voiceConnection.destroy(); this.voiceConnection.destroy();
} catch {} } catch {} // eslint-disable-line no-empty
} }
/** /**

View file

@ -52,7 +52,7 @@ class VoiceUtils {
let conn = joinVoiceChannel({ let conn = joinVoiceChannel({
guildId: channel.guild.id, guildId: channel.guild.id,
channelId: channel.id, channelId: channel.id,
adapterCreator: (channel.guild as any).voiceAdapterCreator, adapterCreator: (channel.guild as any).voiceAdapterCreator, // eslint-disable-line @typescript-eslint/no-explicit-any
selfDeaf: Boolean(options.deaf) selfDeaf: Boolean(options.deaf)
}); });

View file

@ -84,9 +84,9 @@ export interface RawTrackData {
requestedBy: User; requestedBy: User;
playlist?: Playlist; playlist?: Playlist;
source?: TrackSource; source?: TrackSource;
engine?: any; engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
live?: boolean; live?: boolean;
raw?: any; raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
} }
/** /**
@ -182,7 +182,7 @@ export interface ExtractorModelData {
}; };
id: string; id: string;
url: string; url: string;
rawPlaylist?: any; rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}; };
data: { data: {
title: string; title: string;
@ -304,6 +304,7 @@ export enum QueryType {
* @param {Track} track The track * @param {Track} track The track
*/ */
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface PlayerEvents { export interface PlayerEvents {
botDisconnect: (queue: Queue) => any; botDisconnect: (queue: Queue) => any;
channelEmpty: (queue: Queue) => any; channelEmpty: (queue: Queue) => any;
@ -317,6 +318,8 @@ export interface PlayerEvents {
trackStart: (queue: Queue, track: Track) => any; trackStart: (queue: Queue, track: Track) => any;
} }
/* eslint-enable @typescript-eslint/no-explicit-any */
/** /**
* @typedef {object} PlayOptions * @typedef {object} PlayOptions
* @property {boolean} [filtersUpdate=false] If this play was triggered for filters update * @property {boolean} [filtersUpdate=false] If this play was triggered for filters update
@ -384,7 +387,7 @@ export interface PlaylistInitData {
}; };
id: string; id: string;
url: string; url: string;
rawPlaylist?: any; rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
} }
/** /**

View file

@ -90,7 +90,7 @@ const FilterList = {
}, },
toString() { toString() {
return this.names.map((m) => (this as any)[m]).join(","); return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
}, },
create(filter?: FiltersName[]): string { create(filter?: FiltersName[]): string {

View file

@ -1,6 +1,7 @@
import { validateID, validateURL } from "ytdl-core"; import { validateID, validateURL } from "ytdl-core";
import { YouTube } from "youtube-sr"; import { YouTube } from "youtube-sr";
import { QueryType } from "../types/types"; import { QueryType } from "../types/types";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import { validateURL as SoundcloudValidateURL } from "soundcloud-scraper"; import { validateURL as SoundcloudValidateURL } from "soundcloud-scraper";

View file

@ -14,7 +14,7 @@ class Util {
* @param {object} durObj The duration object * @param {object} durObj The duration object
* @returns {string} * @returns {string}
*/ */
static durationString(durObj: object) { static durationString(durObj: Record<string, number>) {
return Object.values(durObj) return Object.values(durObj)
.map((m) => (isNaN(m) ? 0 : m)) .map((m) => (isNaN(m) ? 0 : m))
.join(":"); .join(":");
@ -58,6 +58,7 @@ class Util {
* @param {any[]} arr The array * @param {any[]} arr The array
* @returns {any} * @returns {any}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static last<T = any>(arr: T[]): T { static last<T = any>(arr: T[]): T {
if (!Array.isArray(arr)) return; if (!Array.isArray(arr)) return;
return arr[arr.length - 1]; return arr[arr.length - 1];
@ -93,6 +94,10 @@ class Util {
static wait(time: number) { static wait(time: number) {
return new Promise((r) => setTimeout(r, time).unref()); return new Promise((r) => setTimeout(r, time).unref());
} }
static get noop() {
return () => {}; // eslint-disable-line @typescript-eslint/no-empty-function
}
} }
export { Util }; export { Util };

8
tsconfig.eslint.json Normal file
View file

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": [
"**/*.ts",
"**/*.js"
],
"exclude": []
}

View file

@ -1,15 +0,0 @@
{
"defaultSeverity": "error",
"extends": ["tslint:recommended", "tslint-config-prettier"],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"object-literal-sort-keys": false,
"interface-name": false,
"no-empty": false,
"no-console": false,
"radix": false
},
"rulesDirectory": []
}