From 7bb319fcad1f63a185667407e11fc0f66ea6542e Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:45:21 -0400 Subject: [PATCH] Enable the `prefer-destructuring` eslint rule (#1858) ## Description: Enable the `prefer-destructuring` eslint rule. ## 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 | 4 ++++ src/client/ClientGameRunner.ts | 2 +- src/client/Main.ts | 2 +- src/client/TerritoryPatternsModal.ts | 2 +- src/client/Utils.ts | 2 +- .../components/baseComponents/setting/SettingKeybind.ts | 2 +- src/client/graphics/NameBoxCalculator.ts | 4 ++-- src/client/graphics/SpriteLoader.ts | 2 +- src/client/graphics/fx/ConquestFx.ts | 4 ++-- src/client/graphics/layers/FxLayer.ts | 8 ++++---- src/client/graphics/layers/NameLayer.ts | 2 +- src/client/graphics/layers/StructureIconsLayer.ts | 4 ++-- src/client/graphics/layers/TerritoryLayer.ts | 4 ++-- src/client/graphics/layers/UILayer.ts | 2 +- src/client/utilities/RenderUnitTypeOptions.ts | 2 +- src/core/PseudoRandom.ts | 2 +- src/core/execution/ConstructionExecution.ts | 2 +- src/core/execution/FakeHumanExecution.ts | 8 ++++---- src/core/execution/PortExecution.ts | 2 +- src/core/execution/RailroadExecution.ts | 2 +- src/core/game/PlayerImpl.ts | 2 +- src/core/game/UnitGrid.ts | 2 +- src/core/pathfinding/SerialAStar.ts | 2 +- src/core/utilities/Line.ts | 4 ++-- src/server/Master.ts | 2 +- src/server/Worker.ts | 2 +- .../worker/websocket/handler/message/PreJoinHandler.ts | 3 +-- tests/InputHandler.test.ts | 2 +- tests/Warship.test.ts | 2 +- 29 files changed, 43 insertions(+), 40 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 5cedff2f8..b4e1bf857 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -118,6 +118,10 @@ export default [ "object-shorthand": ["error", "always"], "no-undef": "error", "no-unused-vars": "off", // @typescript-eslint/no-unused-vars + "prefer-destructuring": ["error", { + array: false, + object: true, + }], "quote-props": ["error", "consistent-as-needed"], "sort-imports": "error", "space-before-blocks": ["error", "always"], diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index fb52dfdcd..c5204a532 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -288,7 +288,7 @@ export class ClientGameRunner { this.saveGame(gu.updates[GameUpdateType.Win][0]); } }); - const worker = this.worker; + const { worker } = this; const keepWorkerAlive = () => { if (this.isActive) { worker.sendHeartbeat(); diff --git a/src/client/Main.ts b/src/client/Main.ts index 3c0fa501a..1378d20e8 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -613,7 +613,7 @@ function hasAllowedFlare( const allowed = config.allowedFlares(); if (allowed === undefined) return true; if (userMeResponse === false) return false; - const flares = userMeResponse.player.flares; + const { flares } = userMeResponse.player; if (flares === undefined) return false; return allowed.length === 0 || allowed.some((f) => flares.includes(f)); } diff --git a/src/client/TerritoryPatternsModal.ts b/src/client/TerritoryPatternsModal.ts index 6715c85c0..f1a111821 100644 --- a/src/client/TerritoryPatternsModal.ts +++ b/src/client/TerritoryPatternsModal.ts @@ -389,7 +389,7 @@ export function generatePreviewDataUrl( // Create an image const imageData = ctx.createImageData(width, height); - const data = imageData.data; + const { data } = imageData; let i = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { diff --git a/src/client/Utils.ts b/src/client/Utils.ts index 1e03fadd9..128594a7f 100644 --- a/src/client/Utils.ts +++ b/src/client/Utils.ts @@ -115,7 +115,7 @@ export const translateText = ( let message = langSelector.translations[key]; if (!message && langSelector.defaultTranslations) { - const defaultTranslations = langSelector.defaultTranslations; + const { defaultTranslations } = langSelector; if (defaultTranslations && defaultTranslations[key]) { message = defaultTranslations[key]; } diff --git a/src/client/components/baseComponents/setting/SettingKeybind.ts b/src/client/components/baseComponents/setting/SettingKeybind.ts index 049306613..2be0d500b 100644 --- a/src/client/components/baseComponents/setting/SettingKeybind.ts +++ b/src/client/components/baseComponents/setting/SettingKeybind.ts @@ -74,7 +74,7 @@ export class SettingKeybind extends LitElement { if (!this.listening) return; e.preventDefault(); - const code = e.code; + const { code } = e; this.value = code; diff --git a/src/client/graphics/NameBoxCalculator.ts b/src/client/graphics/NameBoxCalculator.ts index 94bec5d2c..c15807f78 100644 --- a/src/client/graphics/NameBoxCalculator.ts +++ b/src/client/graphics/NameBoxCalculator.ts @@ -85,8 +85,8 @@ export function createGrid( .fill(null as unknown as boolean[]) .map(() => Array(height).fill(false)); - for (let x = scaledBoundingBox.min.x; x <= scaledBoundingBox.max.x; x++) { - for (let y = scaledBoundingBox.min.y; y <= scaledBoundingBox.max.y; y++) { + for (let { x } = scaledBoundingBox.min; x <= scaledBoundingBox.max.x; x++) { + for (let { y } = scaledBoundingBox.min; y <= scaledBoundingBox.max.y; y++) { const cell = new Cell(x * scalingFactor, y * scalingFactor); if (game.isOnMap(cell)) { const tile = game.ref(cell.x, cell.y); diff --git a/src/client/graphics/SpriteLoader.ts b/src/client/graphics/SpriteLoader.ts index 05da1d49f..51b7054d6 100644 --- a/src/client/graphics/SpriteLoader.ts +++ b/src/client/graphics/SpriteLoader.ts @@ -124,7 +124,7 @@ export const colorizeCanvas = ( ctx.drawImage(source, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; + const { data } = imageData; const colorARgb = colorA.toRgb(); const colorBRgb = colorB.toRgb(); diff --git a/src/client/graphics/fx/ConquestFx.ts b/src/client/graphics/fx/ConquestFx.ts index e10d134d0..272ccbd3f 100644 --- a/src/client/graphics/fx/ConquestFx.ts +++ b/src/client/graphics/fx/ConquestFx.ts @@ -18,8 +18,8 @@ export function conquestFxFactory( ): Fx[] { const conquestFx: Fx[] = []; const conquered = game.player(conquest.conqueredId); - const x = conquered.nameLocation().x; - const y = conquered.nameLocation().y; + const { x } = conquered.nameLocation(); + const { y } = conquered.nameLocation(); const swordAnimation = new SpriteFx( animatedSpriteLoader, diff --git a/src/client/graphics/layers/FxLayer.ts b/src/client/graphics/layers/FxLayer.ts index a6c1a04b7..982c204bb 100644 --- a/src/client/graphics/layers/FxLayer.ts +++ b/src/client/graphics/layers/FxLayer.ts @@ -71,11 +71,11 @@ export class FxLayer implements Layer { // Only display text fx for the current player return; } - const tile = bonus.tile; + const { tile } = bonus; const x = this.game.x(tile); let y = this.game.y(tile); - const gold = bonus.gold; - const troops = bonus.troops; + const { gold } = bonus; + const { troops } = bonus; if (gold > 0) { const shortened = renderNumber(gold, 0); @@ -149,7 +149,7 @@ export class FxLayer implements Layer { } onRailroadEvent(railroad: RailroadUpdate) { - const railTiles = railroad.railTiles; + const { railTiles } = railroad; for (const rail of railTiles) { // No need for pseudorandom, this is fx const chanceFx = Math.floor(Math.random() * 3); diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index aff18436d..bbb0e8477 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -233,7 +233,7 @@ export class NameLayer implements Layer { }; if (player.cosmetics.flag) { - const flag = player.cosmetics.flag; + const { flag } = player.cosmetics; if (flag !== undefined && flag !== null && flag.startsWith("!")) { const flagWrapper = document.createElement("div"); applyFlagStyles(flagWrapper); diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts index 7b8ffd1d2..7ceec7fd1 100644 --- a/src/client/graphics/layers/StructureIconsLayer.ts +++ b/src/client/graphics/layers/StructureIconsLayer.ts @@ -532,7 +532,7 @@ export class StructureIconsLayer implements Layer { const screenPos = this.transformHandler.worldToScreenCoordinates(worldPos); const { type, stage } = options; - const scale = this.transformHandler.scale; + const { scale } = this.transformHandler; const spritesEnabled = this.game .config() .userSettings() @@ -612,7 +612,7 @@ export class StructureIconsLayer implements Layer { const screenPos = this.transformHandler.worldToScreenCoordinates(worldPos); screenPos.x = Math.round(screenPos.x); - const scale = this.transformHandler.scale; + const { scale } = this.transformHandler; screenPos.y = Math.round( scale >= ZOOM_THRESHOLD && this.game.config().userSettings()?.structureSprites() diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index 3f82884c0..42a9f0969 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -393,7 +393,7 @@ export class TerritoryLayer implements Layer { break; } - const tile = entry.tile; + const { tile } = entry; this.paintTerritory(tile); for (const neighbor of this.game.neighbors(tile)) { this.paintTerritory(neighbor, true); @@ -455,7 +455,7 @@ export class TerritoryLayer implements Layer { } } else { // Interior tiles - const pattern = owner.cosmetics.pattern; + const { pattern } = owner.cosmetics; const patternsEnabled = this.cachedTerritoryPatternsEnabled ?? false; // Alternative view only shows borders. diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 8a6c26099..9f1d8c75b 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -263,7 +263,7 @@ export class UILayer implements Layer { * Draw health bar for a unit */ public drawHealthBar(unit: UnitView) { - const maxHealth = this.game.unitInfo(unit.type()).maxHealth; + const { maxHealth } = this.game.unitInfo(unit.type()); if (maxHealth === undefined || this.context === null) { return; } diff --git a/src/client/utilities/RenderUnitTypeOptions.ts b/src/client/utilities/RenderUnitTypeOptions.ts index 24d4d5f7f..02569e654 100644 --- a/src/client/utilities/RenderUnitTypeOptions.ts +++ b/src/client/utilities/RenderUnitTypeOptions.ts @@ -36,7 +36,7 @@ export function renderUnitTypeOptions({ type="checkbox" .checked=${disabledUnits.includes(type)} @change=${(e: Event) => { - const checked = (e.target as HTMLInputElement).checked; + const { checked } = (e.target as HTMLInputElement); toggleUnit(type, checked); }} /> diff --git a/src/core/PseudoRandom.ts b/src/core/PseudoRandom.ts index 824e4ad85..ee07a7640 100644 --- a/src/core/PseudoRandom.ts +++ b/src/core/PseudoRandom.ts @@ -41,7 +41,7 @@ export class PseudoRandom { // Selects a random element from a set. randFromSet(set: Set): T { - const size = set.size; + const { size } = set; if (size === 0) { throw new Error("set must not be empty"); } diff --git a/src/core/execution/ConstructionExecution.ts b/src/core/execution/ConstructionExecution.ts index 9ee7a27d0..226af4e9d 100644 --- a/src/core/execution/ConstructionExecution.ts +++ b/src/core/execution/ConstructionExecution.ts @@ -99,7 +99,7 @@ export class ConstructionExecution implements Execution { } private completeConstruction() { - const player = this.player; + const { player } = this; switch (this.constructionType) { case UnitType.AtomBomb: case UnitType.HydrogenBomb: diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index a70893937..55f488e40 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -67,7 +67,7 @@ export class FakeHumanExecution implements Execution { } private updateRelationsFromEmbargos() { - const player = this.player; + const { player } = this; if (player === null) return; const others = this.mg.players().filter((p) => p.id() !== player.id()); @@ -90,7 +90,7 @@ export class FakeHumanExecution implements Execution { } private handleEmbargoesToHostileNations() { - const player = this.player; + const { player } = this; if (player === null) return; const others = this.mg.players().filter((p) => p.id() !== player.id()); @@ -248,7 +248,7 @@ export class FakeHumanExecution implements Execution { if (other.isTraitor()) { return false; } - const difficulty = this.mg.config().gameConfig().difficulty; + const { difficulty } = this.mg.config().gameConfig(); if ( difficulty === Difficulty.Hard || difficulty === Difficulty.Impossible @@ -491,7 +491,7 @@ export class FakeHumanExecution implements Execution { private structureSpawnTileValue(type: UnitType): (tile: TileRef) => number { if (this.player === null) throw new Error("not initialized"); const borderTiles = this.player.borderTiles(); - const mg = this.mg; + const { mg } = this; const otherUnits = this.player.units(type); // Prefer spacing structures out of atom bomb range const borderSpacing = this.mg.config().nukeMagnitudes(UnitType.AtomBomb).outer; diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 54de6ace0..0ed2548b7 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -27,7 +27,7 @@ export class PortExecution implements Execution { throw new Error("Not initialized"); } if (this.port === null) { - const tile = this.tile; + const { tile } = this; const spawn = this.player.canBuild(UnitType.Port, tile); if (spawn === false) { console.warn( diff --git a/src/core/execution/RailroadExecution.ts b/src/core/execution/RailroadExecution.ts index 436178b26..7bcfa8fa3 100644 --- a/src/core/execution/RailroadExecution.ts +++ b/src/core/execution/RailroadExecution.ts @@ -21,7 +21,7 @@ export class RailroadExecution implements Execution { /* eslint-disable sort-keys */ init(mg: Game, ticks: number): void { this.mg = mg; - const tiles = this.railRoad.tiles; + const { tiles } = this.railRoad; // Inverse direction computation for the first tile this.railTiles.push({ tile: tiles[0], diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index ad0dc676c..5d12c95c6 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -846,7 +846,7 @@ export class PlayerImpl implements Player { if (existing.length === 0) { return false; } - const unit = existing[0].unit; + const { unit } = existing[0]; if (!this.canUpgradeUnit(unit.type())) { return false; } diff --git a/src/core/game/UnitGrid.ts b/src/core/game/UnitGrid.ts index ceeef2ede..2f17d9ee9 100644 --- a/src/core/game/UnitGrid.ts +++ b/src/core/game/UnitGrid.ts @@ -94,7 +94,7 @@ export class UnitGrid { private getCellsInRange(tile: TileRef, range: number) { const x = this.gm.x(tile); const y = this.gm.y(tile); - const cellSize = this.cellSize; + const { cellSize } = this; const [gridX, gridY] = this.getGridCoords(x, y); const startGridX = Math.max( 0, diff --git a/src/core/pathfinding/SerialAStar.ts b/src/core/pathfinding/SerialAStar.ts index e6590d59f..ee7a25bde 100644 --- a/src/core/pathfinding/SerialAStar.ts +++ b/src/core/pathfinding/SerialAStar.ts @@ -75,7 +75,7 @@ export class SerialAStar implements AStar { if (this.completed) return PathFindResultType.Completed; this.maxTries -= 1; - let iterations = this.iterations; + let { iterations } = this; while (!this.fwdOpenSet.isEmpty() && !this.bwdOpenSet.isEmpty()) { iterations--; diff --git a/src/core/utilities/Line.ts b/src/core/utilities/Line.ts index 9f100b91a..219751ffb 100644 --- a/src/core/utilities/Line.ts +++ b/src/core/utilities/Line.ts @@ -27,8 +27,8 @@ export class BezenhamLine { if (this.p1.x === this.p2.x && this.p1.y === this.p2.y) { return true; } - const x = this.p1.x; - const y = this.p1.y; + const { x } = this.p1; + const { y } = this.p1; const err2 = 2 * this.error; if (err2 > -this.dy) { diff --git a/src/server/Master.ts b/src/server/Master.ts index f110e286d..7c860b355 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -90,7 +90,7 @@ export async function startMaster() { cluster.on("message", (worker, message) => { if (message.type === "WORKER_READY") { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const workerId = message.workerId; + const { workerId } = message; readyWorkers.add(workerId); log.info( `Worker ${workerId} is ready. (${readyWorkers.size}/${config.numWorkers()} ready)`, diff --git a/src/server/Worker.ts b/src/server/Worker.ts index 82f85d5de..43ffe9d7f 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -89,7 +89,7 @@ export async function startWorker() { app.post( "/api/create_game/:id", gatekeeper.httpHandler(LimiterType.Post, async (req, res) => { - const id = req.params.id; + const { id } = req.params; const creatorClientID = (() => { if (typeof req.query.creatorClientID !== "string") return undefined; diff --git a/src/server/worker/websocket/handler/message/PreJoinHandler.ts b/src/server/worker/websocket/handler/message/PreJoinHandler.ts index 466d0b3b4..c4896942d 100644 --- a/src/server/worker/websocket/handler/message/PreJoinHandler.ts +++ b/src/server/worker/websocket/handler/message/PreJoinHandler.ts @@ -169,8 +169,7 @@ async function handleJoinMessage( success: false, }; } - roles = result.player.roles; - flares = result.player.flares; + ({ roles, flares } = result.player); if (allowedFlares !== undefined) { // Login is required diff --git a/tests/InputHandler.test.ts b/tests/InputHandler.test.ts index 82050eaa0..c63fcd23c 100644 --- a/tests/InputHandler.test.ts +++ b/tests/InputHandler.test.ts @@ -79,7 +79,7 @@ describe("InputHandler AutoUpgrade", () => { }), ); - const calls = mockEmit.mock.calls; + const { calls } = mockEmit.mock; const lastCall = calls[calls.length - 1]; expect(lastCall[0]).not.toBeInstanceOf(AutoUpgradeEvent); }); diff --git a/tests/Warship.test.ts b/tests/Warship.test.ts index a4536ffe3..fbfc26e80 100644 --- a/tests/Warship.test.ts +++ b/tests/Warship.test.ts @@ -38,7 +38,7 @@ describe("Warship", () => { }); test("Warship heals only if player has port", async () => { - const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth; + const { maxHealth } = game.config().unitInfo(UnitType.Warship); if (typeof maxHealth !== "number") { expect(typeof maxHealth).toBe("number"); throw new Error("unreachable");