From ef51adda6ce600818ab265ca3edf3cfcd5981d6b Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:25:02 -0400 Subject: [PATCH] Enable the `@typescript-eslint/no-explicit-any` eslint rule (#1830) ## Description: Enable the `@typescript-eslint/no-explicit-any` eslint rule. Fixes #1789 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced --- eslint.config.js | 12 ++++++++++ src/client/LangSelector.ts | 8 +++++++ src/client/LanguageModal.ts | 1 + src/client/Main.ts | 1 + src/client/Utils.ts | 2 ++ src/client/graphics/layers/EventsDisplay.ts | 4 ++-- .../graphics/layers/PlayerActionHandler.ts | 4 ++-- src/client/graphics/layers/RadialMenu.ts | 22 +++++++++++-------- src/core/EventBus.ts | 1 + src/core/Util.ts | 1 + src/core/worker/Worker.worker.ts | 2 +- src/server/Cloudflare.ts | 1 + src/server/GameServer.ts | 1 + src/server/Master.ts | 1 + src/server/Worker.ts | 1 + .../handler/message/PostJoinHandler.ts | 2 +- 16 files changed, 49 insertions(+), 15 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 0f85fec76..6c312e996 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -75,6 +75,7 @@ export default [ "type", ], "@typescript-eslint/no-duplicate-enum-values": "error", + "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-inferrable-types": "error", "@typescript-eslint/no-mixed-enums": "error", "@typescript-eslint/no-require-imports": "error", @@ -127,9 +128,20 @@ export default [ files: [ "**/*.config.{js,ts,jsx,tsx}", "**/*.test.{js,ts,jsx,tsx}", + "tests/**/*.{js,ts,jsx,tsx}", + ], + rules: { + // Disabled rules for tests, configs + "@typescript-eslint/no-explicit-any": "off", + "sort-keys": "off", + }, + }, + { + files: [ "src/client/**/*.{js,ts,jsx,tsx}", ], rules: { + // Disabled rules for frontend "sort-keys": "off", }, }, diff --git a/src/client/LangSelector.ts b/src/client/LangSelector.ts index ed27a1328..b55c78955 100644 --- a/src/client/LangSelector.ts +++ b/src/client/LangSelector.ts @@ -36,12 +36,14 @@ export class LangSelector extends LitElement { @state() public translations: Record | undefined; @state() public defaultTranslations: Record | undefined; @state() public currentLang = "en"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @state() private languageList: any[] = []; @state() private showModal = false; @state() private debugMode = false; private debugKeyPressed = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any private languageMap: Record = { ar, bg, @@ -130,6 +132,7 @@ export class LangSelector extends LitElement { private async loadLanguageList() { try { const data = this.languageMap; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let list: any[] = []; const browserLang = new Intl.Locale(navigator.language).language; @@ -146,6 +149,7 @@ export class LangSelector extends LitElement { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any let debugLang: any = null; if (this.debugKeyPressed) { debugLang = { @@ -177,6 +181,7 @@ export class LangSelector extends LitElement { list.sort((a, b) => a.en.localeCompare(b.en)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const finalList: any[] = []; if (currentLangEntry) finalList.push(currentLangEntry); if (englishEntry) finalList.push(englishEntry); @@ -236,7 +241,9 @@ export class LangSelector extends LitElement { components.forEach((tag) => { document.querySelectorAll(tag).forEach((el) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (typeof (el as any).requestUpdate === "function") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (el as any).requestUpdate(); } }); @@ -317,6 +324,7 @@ export class LangSelector extends LitElement { } function flattenTranslations( + // eslint-disable-next-line @typescript-eslint/no-explicit-any obj: Record, parentKey = "", result: Record = {}, diff --git a/src/client/LanguageModal.ts b/src/client/LanguageModal.ts index 41dcc5c97..236715d03 100644 --- a/src/client/LanguageModal.ts +++ b/src/client/LanguageModal.ts @@ -5,6 +5,7 @@ import { translateText } from "../client/Utils"; @customElement("language-modal") export class LanguageModal extends LitElement { @property({ type: Boolean }) visible = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @property({ type: Array }) languageList: any[] = []; @property({ type: String }) currentLang = "en"; diff --git a/src/client/Main.ts b/src/client/Main.ts index 55b7c75b0..67a2015c8 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -56,6 +56,7 @@ declare global { spaAddAds: (ads: Array<{ type: string; selectorId: string }>) => void; destroyUnits: (adType: string) => void; settings?: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any slots?: any; }; spaNewPage: (url: string) => void; diff --git a/src/client/Utils.ts b/src/client/Utils.ts index 118474bec..184329f63 100644 --- a/src/client/Utils.ts +++ b/src/client/Utils.ts @@ -57,6 +57,7 @@ export function generateCryptoRandomUUID(): string { // Fallback using crypto.getRandomValues if (crypto !== undefined && "getRandomValues" in crypto) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return (([1e7] as any) + -1e3 + -4e3 + -8e3 + -1e11).replace( /[018]/g, (c: number): string => @@ -83,6 +84,7 @@ export const translateText = ( key: string, params: Record = {}, ): string => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const self = translateText as any; self.formatterCache ??= new Map(); self.lastLang ??= null; diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 850f1c393..bf6bc5134 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; import { DirectiveResult } from "lit/directive.js"; import { unsafeHTML, UnsafeHTMLDirective } from "lit/directives/unsafe-html.js"; @@ -96,7 +96,7 @@ export class EventsDisplay extends LitElement implements Layer { ]); private renderButton(options: { - content: any; // Can be string, TemplateResult, or other renderable content + content: string | TemplateResult | DirectiveResult; onClick?: () => void; className?: string; disabled?: boolean; diff --git a/src/client/graphics/layers/PlayerActionHandler.ts b/src/client/graphics/layers/PlayerActionHandler.ts index 1bba3300e..e134e35a2 100644 --- a/src/client/graphics/layers/PlayerActionHandler.ts +++ b/src/client/graphics/layers/PlayerActionHandler.ts @@ -97,8 +97,8 @@ export class PlayerActionHandler { this.eventBus.emit(new SendEmojiIntentEvent(targetPlayer, emojiIndex)); } - handleQuickChat(recipient: PlayerView, chatKey: string, params: any = {}) { - this.eventBus.emit(new SendQuickChatEvent(recipient, chatKey, params)); + handleQuickChat(recipient: PlayerView, chatKey: string, target?: PlayerID) { + this.eventBus.emit(new SendQuickChatEvent(recipient, chatKey, target)); } handleDeleteUnit(unitId: number) { diff --git a/src/client/graphics/layers/RadialMenu.ts b/src/client/graphics/layers/RadialMenu.ts index 731aa336a..8d65dead5 100644 --- a/src/client/graphics/layers/RadialMenu.ts +++ b/src/client/graphics/layers/RadialMenu.ts @@ -266,7 +266,7 @@ export class RadialMenu implements Layer { menuGroup.style("opacity", 0).style("transform", "scale(0.5)"); } - this.menuGroups.set(level, menuGroup as any); + this.menuGroups.set(level, menuGroup); const offset = -Math.PI / items.length; @@ -307,7 +307,7 @@ export class RadialMenu implements Layer { SVGGElement, unknown >, - arc: d3.Arc>, + arc: d3.Arc>, level: number, ) { arcs @@ -348,7 +348,7 @@ export class RadialMenu implements Layer { arcs.each((d) => { const pathId = d.data.id; const path = d3.select(`path[data-id="${pathId}"]`); - this.menuPaths.set(pathId, path as any); + this.menuPaths.set(pathId, path as never); if ( pathId === this.selectedItemId && @@ -388,7 +388,9 @@ export class RadialMenu implements Layer { >, level: number, ) { - const onHover = (d: d3.PieArcDatum, path: any) => { + const onHover = (d: d3.PieArcDatum, path: d3.Selection< + d3.BaseType, unknown, HTMLElement, unknown + >) => { const disabled = this.params === null || d.data.disabled(this.params); if (d.data.tooltipItems && d.data.tooltipItems.length > 0) { this.showTooltip(d.data.tooltipItems); @@ -407,7 +409,9 @@ export class RadialMenu implements Layer { path.attr("stroke-width", "3"); }; - const onMouseOut = (d: d3.PieArcDatum, path: any) => { + const onMouseOut = (d: d3.PieArcDatum, path: d3.Selection< + d3.BaseType, unknown, HTMLElement, unknown + >) => { const disabled = this.params === null || d.data.disabled(this.params); if (this.submenuHoverTimeout !== null) { window.clearTimeout(this.submenuHoverTimeout); @@ -518,7 +522,7 @@ export class RadialMenu implements Layer { SVGGElement, unknown >, - arc: d3.Arc>, + arc: d3.Arc>, ) { arcs .append("g") @@ -553,7 +557,7 @@ export class RadialMenu implements Layer { .attr("opacity", disabled ? 0.5 : 1); } - this.menuIcons.set(contentId, content as any); + this.menuIcons.set(contentId, content as never); }); } @@ -735,8 +739,8 @@ export class RadialMenu implements Layer { }); } - private animateExistingMenu( - previousMenu: d3.Selection, + private animateExistingMenu( + previousMenu: d3.Selection, ) { previousMenu .transition() diff --git a/src/core/EventBus.ts b/src/core/EventBus.ts index 3ca0c895b..d696ee013 100644 --- a/src/core/EventBus.ts +++ b/src/core/EventBus.ts @@ -1,6 +1,7 @@ export type GameEvent = object; export type EventConstructor = new ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any ...args: any[] ) => T; diff --git a/src/core/Util.ts b/src/core/Util.ts index 543d8b442..30a4ee97b 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -285,6 +285,7 @@ export const flattenedEmojiTable: string[] = emojiTable.flat(); /** * JSON.stringify replacer function that converts bigint values to strings. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function replacer(_key: string, value: any): any { return typeof value === "bigint" ? value.toString() : value; } diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 866b70834..bafda59bb 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -13,7 +13,7 @@ import { WorkerMessage, } from "./WorkerMessages"; -const ctx: Worker = self as any; +const ctx: Worker = self as unknown as Worker; let gameRunner: Promise | null = null; const mapLoader = new FetchGameMapLoader(`/maps`, version); diff --git a/src/server/Cloudflare.ts b/src/server/Cloudflare.ts index 949d5f3fc..1e5025e8f 100644 --- a/src/server/Cloudflare.ts +++ b/src/server/Cloudflare.ts @@ -64,6 +64,7 @@ export class Cloudflare { private async makeRequest( url: string, method = "GET", + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any, ): Promise { const response = await fetch(url, { diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 9566ff3a7..71d737cb8 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -226,6 +226,7 @@ export class GameServer { ); }); client.ws.on("error", (error: Error) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") { client.ws.close(1002, "WS_ERR_UNEXPECTED_RSV_1"); } diff --git a/src/server/Master.ts b/src/server/Master.ts index e5d0f6688..c13014214 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -121,6 +121,7 @@ export async function startMaster() { // Handle worker crashes cluster.on("exit", (worker, code, signal) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const workerId = (worker as any).process?.env?.WORKER_ID; if (!workerId) { log.error(`worker crashed could not find id`); diff --git a/src/server/Worker.ts b/src/server/Worker.ts index a0ef684c1..754f16cec 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -309,6 +309,7 @@ export async function startWorker() { ); ws.on("error", (error: Error) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") { ws.close(1002, "WS_ERR_UNEXPECTED_RSV_1"); } diff --git a/src/server/worker/websocket/handler/message/PostJoinHandler.ts b/src/server/worker/websocket/handler/message/PostJoinHandler.ts index 25ba36c47..ada06af0b 100644 --- a/src/server/worker/websocket/handler/message/PostJoinHandler.ts +++ b/src/server/worker/websocket/handler/message/PostJoinHandler.ts @@ -102,7 +102,7 @@ export async function postJoinMessageHandler( break; } default: { - log.warn(`Unknown message type: ${(clientMsg as any).type}`, { + log.warn(`Unknown message type: ${clientMsg.type}`, { clientID: client.clientID, }); break;