From 52f64db6d36870a593e6a413ab6294acdbbb8da9 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Wed, 2 Apr 2025 21:06:08 -0400 Subject: [PATCH] Fix various ESLint violations (#402) ## Description: Fixes a number of ESLint violations. Although I have tested these changes through the local dev server, I don't have a high confidence that the testing is sufficient, as I am new to this codebase. This change would benefit from heightened scrutiny. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: fake.neo --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com> --- eslint.config.js | 3 - resources/lang/en.json | 1 + src/client/ClientGameRunner.ts | 12 ++- src/client/GoogleAdElement.ts | 4 +- src/client/HostLobbyModal.ts | 2 + src/client/InputHandler.ts | 2 +- src/client/LangSelector.ts | 68 +++++++------- src/client/LocalPersistantStats.ts | 94 ++++++++++---------- src/client/Main.ts | 4 +- src/client/graphics/GameRenderer.ts | 1 - src/client/graphics/layers/StructureLayer.ts | 2 +- src/client/graphics/layers/WinModal.ts | 7 +- src/core/game/Game.ts | 4 +- src/core/game/GameImpl.ts | 4 +- src/core/game/PlayerImpl.ts | 2 +- src/global.d.ts | 4 - src/server/GameServer.ts | 14 +-- src/server/Gatekeeper.ts | 10 +-- src/server/MasterMetrics.ts | 6 +- src/server/StructuredLog.ts | 8 +- tsconfig.json | 1 + 21 files changed, 127 insertions(+), 126 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 575e2122c..8b14cd1f5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -21,16 +21,13 @@ export default [ { rules: { // Disable rules that would fail. The failures should be fixed, and the entries here removed. - "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-empty-object-type": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-require-imports": "off", "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": "off", "no-case-declarations": "off", "no-useless-escape": "off", - "prefer-const": "off", }, }, ]; diff --git a/resources/lang/en.json b/resources/lang/en.json index 9b03fbd14..274a8f18d 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -1,5 +1,6 @@ { "main": { + "title": "OpenFront (ALPHA)", "join_discord": "Join the Discord!", "create_lobby": "Create Lobby", "join_lobby": "Join Lobby", diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 04cef96e4..534b693a5 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -25,7 +25,7 @@ import { loadTerrainMap } from "../core/game/TerrainMapLoader"; import { UserSettings } from "../core/game/UserSettings"; import { WorkerClient } from "../core/worker/WorkerClient"; import { InputHandler, MouseMoveEvent, MouseUpEvent } from "./InputHandler"; -import { LocalPersistantStats } from "./LocalPersistantStats"; +import { endGame, startGame, startTime } from "./LocalPersistantStats"; import { getPersistentIDFromCookie } from "./Main"; import { SendAttackIntentEvent, @@ -36,6 +36,7 @@ import { import { createCanvas } from "./Utils"; import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; +export // Is this function needed? function distSortUnitWorld(tile: TileRef, game: GameView) { return (a: Unit | UnitView, b: Unit | UnitView) => { return ( @@ -69,10 +70,7 @@ export function joinLobby( ); const userSettings: UserSettings = new UserSettings(); - LocalPersistantStats.startGame( - lobbyConfig.gameID, - lobbyConfig.gameStartInfo?.config, - ); + startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config); const transport = new Transport(lobbyConfig, eventBus); @@ -198,13 +196,13 @@ export class ClientGameRunner { players, // Not saving turns locally [], - LocalPersistantStats.startTime(), + startTime(), Date.now(), winner, update.winnerType, update.allPlayersStats, ); - LocalPersistantStats.endGame(record); + endGame(record); } public start() { diff --git a/src/client/GoogleAdElement.ts b/src/client/GoogleAdElement.ts index 608c49322..6ae2b0eaa 100644 --- a/src/client/GoogleAdElement.ts +++ b/src/client/GoogleAdElement.ts @@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators.js"; declare global { interface Window { - adsbygoogle: any[]; + adsbygoogle: unknown[]; } } @@ -89,7 +89,7 @@ const isElectron = () => { if ( typeof window !== "undefined" && typeof window.process === "object" && - // @ts-ignore + // @ts-expect-error hidden window.process.type === "renderer" ) { return true; diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index 4bb979a62..1c8faa7fe 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -432,6 +432,7 @@ export class HostLobbyModal extends LitElement { } as GameConfig), }, ); + return response; } private getRandomMap(): GameMapType { @@ -460,6 +461,7 @@ export class HostLobbyModal extends LitElement { }, }, ); + return response; } private async copyToClipboard() { diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index e20e462fa..806bc6cf8 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -93,7 +93,7 @@ export class InputHandler { private alternateView = false; - private moveInterval: any = null; + private moveInterval: NodeJS.Timeout = null; private activeKeys = new Set(); private readonly PAN_SPEED = 5; diff --git a/src/client/LangSelector.ts b/src/client/LangSelector.ts index 2c2c10573..648a2a909 100644 --- a/src/client/LangSelector.ts +++ b/src/client/LangSelector.ts @@ -20,10 +20,12 @@ const translations = { es: esTranslations, }; +type Translation = Partial<(typeof translations)[keyof typeof translations]>; + @customElement("lang-selector") export class LangSelector extends LitElement { - @state() public translations: any = {}; - @state() private defaultTranslations: any = {}; + @state() public translations: Translation = {}; + @state() private defaultTranslations = {}; @state() private currentLang: string = "en"; createRenderRoot() { @@ -44,10 +46,10 @@ export class LangSelector extends LitElement { this.translations = await this.loadLanguage(userLang); this.currentLang = userLang; - this.applyTranslation(this.translations); + this.applyTranslation(); } - private async loadLanguage(lang: string): Promise { + private async loadLanguage(lang: string): Promise { try { const translation = translations[lang as keyof typeof translations]; if (!translation) throw new Error(`Language file not found: ${lang}`); @@ -58,7 +60,7 @@ export class LangSelector extends LitElement { } } - private applyTranslation(translations: any) { + private applyTranslation() { const components = [ "single-player-modal", "host-lobby-modal", @@ -75,24 +77,14 @@ export class LangSelector extends LitElement { "public-lobby", ]; - document.title = translations.main?.title || document.title; + const main = this.translations.main; + if (main && "title" in main) { + document.title = main.title; + } document.querySelectorAll("[data-i18n]").forEach((element) => { const key = element.getAttribute("data-i18n"); - const keys = key.split("."); - let text = translations; - for (const k of keys) { - text = text?.[k]; - if (!text) break; - } - if (!text && this.defaultTranslations) { - let fallback = this.defaultTranslations; - for (const k of keys) { - fallback = fallback?.[k]; - if (!fallback) break; - } - text = fallback; - } + const text = this.translateText(key); if (text) { element.innerHTML = text; } else { @@ -101,7 +93,7 @@ export class LangSelector extends LitElement { }); components.forEach((tagName) => { - const el = document.querySelector(tagName) as any; + const el = document.querySelector(tagName) as LitElement; if (el && typeof el.requestUpdate === "function") { el.requestUpdate(); } else { @@ -117,19 +109,13 @@ export class LangSelector extends LitElement { params: Record = {}, ): string { const keys = key.split("."); - let text: any = this.translations; - - for (const k of keys) { - text = text?.[k]; - if (!text) break; + let text = findTranslation(keys, this.translations); + if (!text && this.defaultTranslations) { + text = findTranslation(keys, this.defaultTranslations); } - if (!text && this.defaultTranslations) { - text = this.defaultTranslations; - for (const k of keys) { - text = text?.[k]; - if (!text) return key; - } + if (text == null || typeof text !== "string") { + return null; } for (const [param, value] of Object.entries(params)) { @@ -143,7 +129,7 @@ export class LangSelector extends LitElement { localStorage.setItem("lang", lang); this.translations = await this.loadLanguage(lang); this.currentLang = lang; - this.applyTranslation(this.translations); + this.applyTranslation(); } render() { @@ -178,3 +164,19 @@ export class LangSelector extends LitElement { `; } } + +function findTranslation( + keys: string[], + translations: Translation, +): string | null { + let ptr: unknown = translations; + for (const k of keys) { + ptr = ptr?.[k]; + if (!ptr) break; + } + if (ptr && typeof ptr === "string") { + return ptr; + } else { + return null; + } +} diff --git a/src/client/LocalPersistantStats.ts b/src/client/LocalPersistantStats.ts index 0733acc80..d659332c1 100644 --- a/src/client/LocalPersistantStats.ts +++ b/src/client/LocalPersistantStats.ts @@ -9,53 +9,51 @@ export interface LocalStatsData { }; } -export namespace LocalPersistantStats { - let _startTime: number; +let _startTime: number; - function getStats(): LocalStatsData { - const statsStr = localStorage.getItem("game-records"); - return statsStr ? JSON.parse(statsStr) : {}; - } - - function save(stats: LocalStatsData) { - // To execute asynchronously - setTimeout( - () => localStorage.setItem("game-records", JSON.stringify(stats)), - 0, - ); - } - - // The user can quit the game anytime so better save the lobby as soon as the - // game starts. - export function startGame(id: GameID, lobby: GameConfig) { - if (typeof localStorage === "undefined") { - return; - } - - _startTime = Date.now(); - const stats = getStats(); - stats[id] = { lobby }; - save(stats); - } - - export function startTime() { - return _startTime; - } - - export function endGame(gameRecord: GameRecord) { - if (typeof localStorage === "undefined") { - return; - } - - const stats = getStats(); - const gameStat = stats[gameRecord.id]; - - if (!gameStat) { - consolex.log("LocalPersistantStats: game not found"); - return; - } - - gameStat.gameRecord = gameRecord; - save(stats); - } +function getStats(): LocalStatsData { + const statsStr = localStorage.getItem("game-records"); + return statsStr ? JSON.parse(statsStr) : {}; +} + +function save(stats: LocalStatsData) { + // To execute asynchronously + setTimeout( + () => localStorage.setItem("game-records", JSON.stringify(stats)), + 0, + ); +} + +// The user can quit the game anytime so better save the lobby as soon as the +// game starts. +export function startGame(id: GameID, lobby: GameConfig) { + if (typeof localStorage === "undefined") { + return; + } + + _startTime = Date.now(); + const stats = getStats(); + stats[id] = { lobby }; + save(stats); +} + +export function startTime() { + return _startTime; +} + +export function endGame(gameRecord: GameRecord) { + if (typeof localStorage === "undefined") { + return; + } + + const stats = getStats(); + const gameStat = stats[gameRecord.id]; + + if (!gameStat) { + consolex.log("LocalPersistantStats: game not found"); + return; + } + + gameStat.gameRecord = gameRecord; + save(stats); } diff --git a/src/client/Main.ts b/src/client/Main.ts index 70fb1af84..a301232ce 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -84,7 +84,7 @@ class Client { "google-ad", ) as NodeListOf; - window.addEventListener("beforeunload", (event) => { + window.addEventListener("beforeunload", () => { consolex.log("Browser is closing"); if (this.gameStop != null) { this.gameStop(); @@ -213,7 +213,7 @@ class Client { ); } - private async handleLeaveLobby(event: CustomEvent) { + private async handleLeaveLobby(/* event: CustomEvent */) { if (this.gameStop == null) { return; } diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 42284b5fe..6af009d13 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -40,7 +40,6 @@ export function createRenderer( const startingModal = document.querySelector( "game-starting-modal", ) as GameStartingModal; - startingModal instanceof GameStartingModal; startingModal.hide(); // TODO maybe append this to dcoument instead of querying for them? diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts index d0d5a24b3..c931a2959 100644 --- a/src/client/graphics/layers/StructureLayer.ts +++ b/src/client/graphics/layers/StructureLayer.ts @@ -216,7 +216,7 @@ export class StructureLayer implements Layer { private handleUnitRendering(unit: UnitView) { const unitType = unit.constructionType() ?? unit.type(); - let iconType = unitType; + const iconType = unitType; if (!this.isUnitTypeSupported(unitType)) return; const config = this.unitConfigs[unitType]; diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index be4fb5394..6f38b380a 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -12,11 +12,12 @@ import { Layer } from "./Layer"; // Add this at the top of your file declare global { interface Window { - adsbygoogle: any[]; + adsbygoogle: unknown[]; } } + // Add this at the top of your file -declare let adsbygoogle: any[]; +declare let adsbygoogle: unknown[]; @customElement("win-modal") export class WinModal extends LitElement implements Layer { @@ -257,7 +258,7 @@ export class WinModal extends LitElement implements Layer { }); } - renderLayer(context: CanvasRenderingContext2D) {} + renderLayer(/* context: CanvasRenderingContext2D */) {} shouldTransform(): boolean { return false; diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 415ac9f7f..1c316ed5b 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -137,8 +137,8 @@ export class Cell { private strRepr: string; constructor( - public readonly x, - public readonly y, + public readonly x: number, + public readonly y: number, ) { this.strRepr = `Cell[${this.x},${this.y}]`; } diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 7712de084..fd0bb9301 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -141,7 +141,7 @@ export class GameImpl implements Game { } addUpdate(update: GameUpdate) { - (this.updates[update.type] as any[]).push(update); + (this.updates[update.type] as GameUpdate[]).push(update); } nextUnitID(): number { @@ -383,7 +383,7 @@ export class GameImpl implements Game { } playerByClientID(id: ClientID): Player | null { - for (const [pID, player] of this._players) { + for (const [, player] of this._players) { if (player.clientID() == id) { return player; } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index ddd47dec8..9606e205b 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -1004,7 +1004,7 @@ export class PlayerImpl implements Player { // It's a probability list, so if an element appears twice it's because it's // twice more likely to be picked later. tradingPorts(port: Unit): Unit[] { - let ports = this.mg + const ports = this.mg .players() .filter((p) => p != port.owner() && p.canTrade(port.owner())) .flatMap((p) => p.units(UnitType.Port)) diff --git a/src/global.d.ts b/src/global.d.ts index 013efe572..d0380a773 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -32,7 +32,3 @@ declare module "*.html" { const content: string; export default content; } -declare module "*.json" { - const value: any; - export default value; -} diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 72e07bf99..13bb64460 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -456,7 +456,7 @@ export class GameServer { const lastHashTurn = this.turns.length - 10; - let { mostCommonHash, outOfSyncClients } = + const { mostCommonHash, outOfSyncClients } = this.findOutOfSyncClients(lastHashTurn); if (outOfSyncClients.length == 0) { @@ -464,11 +464,6 @@ export class GameServer { return; } - if (outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)) { - // If half clients out of sync assume all are out of sync. - outOfSyncClients = this.activeClients; - } - const serverDesync = ServerDesyncSchema.safeParse({ type: "desync", turn: lastHashTurn, @@ -519,7 +514,7 @@ export class GameServer { } // Create a list of clients whose hash doesn't match the most common one - const outOfSyncClients: Client[] = []; + let outOfSyncClients: Client[] = []; for (const client of this.activeClients) { if (client.hashes.has(turnNumber)) { @@ -530,6 +525,11 @@ export class GameServer { } } + // If half clients out of sync assume all are out of sync. + if (outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)) { + outOfSyncClients = this.activeClients; + } + return { mostCommonHash, outOfSyncClients, diff --git a/src/server/Gatekeeper.ts b/src/server/Gatekeeper.ts index 1d0223948..3025a3b22 100644 --- a/src/server/Gatekeeper.ts +++ b/src/server/Gatekeeper.ts @@ -16,7 +16,7 @@ export interface Gatekeeper { // The wrapper for request handlers with optional rate limiting httpHandler: ( limiterType: LimiterType, - fn: (req: Request, res: Response, next: NextFunction) => Promise, + fn: (req: Request, res: Response, next: NextFunction) => Promise, ) => (req: Request, res: Response, next: NextFunction) => Promise; // The wrapper for WebSocket message handlers with rate limiting @@ -67,8 +67,8 @@ async function getGatekeeper(): Promise { // Use dynamic import for ES modules // Using a type assertion to avoid TypeScript errors for optional modules const module = await import( - "./gatekeeper/RealGatekeeper.js" as any - ).catch(() => import("./gatekeeper/RealGatekeeper.js" as any)); + "./gatekeeper/RealGatekeeper.js" as string + ).catch(() => import("./gatekeeper/RealGatekeeper.js" as string)); if (!module || !module.RealGatekeeper) { console.log( @@ -95,7 +95,7 @@ export class GatekeeperWrapper implements Gatekeeper { httpHandler( limiterType: LimiterType, - fn: (req: Request, res: Response, next: NextFunction) => Promise, + fn: (req: Request, res: Response, next: NextFunction) => Promise, ) { return async (req: Request, res: Response, next: NextFunction) => { try { @@ -129,7 +129,7 @@ export class NoOpGatekeeper implements Gatekeeper { // Simple pass-through with no rate limiting httpHandler( limiterType: LimiterType, - fn: (req: Request, res: Response, next: NextFunction) => Promise, + fn: (req: Request, res: Response, next: NextFunction) => Promise, ) { return async (req: Request, res: Response, next: NextFunction) => { try { diff --git a/src/server/MasterMetrics.ts b/src/server/MasterMetrics.ts index e598d3b26..5b6d3c07a 100644 --- a/src/server/MasterMetrics.ts +++ b/src/server/MasterMetrics.ts @@ -50,7 +50,7 @@ export function setupMetricsServer() { } else if (line.trim() && !line.startsWith("#")) { // Add worker label to each metric line and collect for later const processedLine = line.replace( - /^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9\.e+-]+.*)/, + /^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9.e+-]+.*)/, (match, metricName, existingLabels, valueAndRest) => { if (existingLabels) { return `${metricName}{${existingLabels},worker="master"}${valueAndRest}`; @@ -108,7 +108,7 @@ export function setupMetricsServer() { // Process and collect actual metric values try { const processedLine = line.replace( - /^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9\.e+-]+.*)/, + /^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9.e+-]+.*)/, (match, metricName, existingLabels, valueAndRest) => { if (existingLabels) { return `${metricName}{${existingLabels},worker="worker-${i}"}${valueAndRest}`; @@ -122,7 +122,7 @@ export function setupMetricsServer() { if (processedLine !== line) { allMetricValues.push(processedLine); } else if ( - line.match(/^[a-z][a-z0-9_]*(?:{[^}]*})?\s+[0-9\.e+-]+.*/) + line.match(/^[a-z][a-z0-9_]*(?:{[^}]*})?\s+[0-9.e+-]+.*/) ) { // This looks like a metric line but didn't match our regex, try a more general approach const parts = line.split(/({|\s+)/); diff --git a/src/server/StructuredLog.ts b/src/server/StructuredLog.ts index 6fcb7e283..f15bfda94 100644 --- a/src/server/StructuredLog.ts +++ b/src/server/StructuredLog.ts @@ -3,7 +3,13 @@ import { ClientID, GameID, LogSeverity } from "../core/Schemas"; export interface slogMsg { logKey: string; msg: string; - data?: any; + data?: { + stack?: unknown; + clientID?: unknown; + clientIP?: unknown; + gameID?: unknown; + isRejoin?: unknown; + }; severity?: LogSeverity; gameID?: GameID; clientID?: ClientID; diff --git a/tsconfig.json b/tsconfig.json index 9e80b0532..043a34236 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "experimentalDecorators": true, + "resolveJsonModule": true, "useDefineForClassFields": false }, "include": [