diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..6be85b2f6
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+*.[tj]s
diff --git a/eslint.config.js b/eslint.config.js
index 05c378288..b0f93af1d 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,5 +1,6 @@
import { includeIgnoreFile } from "@eslint/compat";
import pluginJs from "@eslint/js";
+import stylisticTs from "@stylistic/eslint-plugin";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import globals from "globals";
import path from "node:path";
@@ -46,8 +47,12 @@ export default [
},
},
{
+ plugins: {
+ "@stylistic/ts": stylisticTs,
+ },
rules: {
// Enable rules
+ "@stylistic/ts/indent": ["error", 2],
"@typescript-eslint/consistent-type-definitions": [
"error",
"type",
@@ -64,6 +69,7 @@ export default [
"@typescript-eslint/prefer-literal-enum-member": "error",
"@typescript-eslint/prefer-nullish-coalescing": "error",
eqeqeq: "error",
+ indent: "off", // @stylistic/ts/indent
"sort-keys": "error",
},
},
diff --git a/package-lock.json b/package-lock.json
index 07eac317f..2612dafad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,6 +42,7 @@
"@datastructures-js/priority-queue": "^6.3.3",
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
+ "@stylistic/eslint-plugin": "^5.2.3",
"@swc/jest": "^0.2.39",
"@total-typescript/ts-reset": "^0.6.1",
"@types/benchmark": "^2.1.5",
@@ -6359,6 +6360,54 @@
"node": ">=18.0.0"
}
},
+ "node_modules/@stylistic/eslint-plugin": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.2.3.tgz",
+ "integrity": "sha512-oY7GVkJGVMI5benlBDCaRrSC1qPasafyv5dOBLLv5MTilMGnErKhO6ziEfodDDIZbo5QxPUNW360VudJOFODMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/types": "^8.38.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "estraverse": "^5.3.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=9.0.0"
+ }
+ },
+ "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/types": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
+ "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@swc/core": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.3.tgz",
diff --git a/package.json b/package.json
index 1b82703dd..0d1603dea 100644
--- a/package.json
+++ b/package.json
@@ -29,9 +29,10 @@
"@datastructures-js/priority-queue": "^6.3.3",
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
+ "@stylistic/eslint-plugin": "^5.2.3",
"@swc/jest": "^0.2.39",
- "@types/benchmark": "^2.1.5",
"@total-typescript/ts-reset": "^0.6.1",
+ "@types/benchmark": "^2.1.5",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3",
"@types/express": "^4.17.23",
diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts
index 05905b922..d684939b6 100644
--- a/src/client/HostLobbyModal.ts
+++ b/src/client/HostLobbyModal.ts
@@ -296,8 +296,8 @@ export class HostLobbyModal extends LitElement {
${typeof o === "string"
? translateText(`public_lobby.teams_${o}`)
: translateText("public_lobby.teams", {
- num: o,
- })}
+ num: o,
+ })}
`,
@@ -441,9 +441,9 @@ export class HostLobbyModal extends LitElement {
style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;"
>
${renderUnitTypeOptions({
- disabledUnits: this.disabledUnits,
- toggleUnit: this.toggleUnit.bind(this),
- })}
+ disabledUnits: this.disabledUnits,
+ toggleUnit: this.toggleUnit.bind(this),
+ })}
diff --git a/src/client/LangSelector.ts b/src/client/LangSelector.ts
index 2e6c5d51a..ed27a1328 100644
--- a/src/client/LangSelector.ts
+++ b/src/client/LangSelector.ts
@@ -276,16 +276,16 @@ export class LangSelector extends LitElement {
this.languageList.find((l) => l.code === this.currentLang) ??
(this.currentLang === "debug"
? {
- code: "debug",
- native: "Debug",
- en: "Debug",
- svg: "xx",
- }
+ code: "debug",
+ native: "Debug",
+ en: "Debug",
+ svg: "xx",
+ }
: {
- native: "English",
- en: "English",
- svg: "uk_us_flag",
- });
+ native: "English",
+ en: "English",
+ svg: "uk_us_flag",
+ });
return html`
diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts
index 6ab99d2cd..6397fe0a9 100644
--- a/src/client/PublicLobby.ts
+++ b/src/client/PublicLobby.ts
@@ -156,8 +156,8 @@ export class PublicLobby extends LitElement {
? typeof teamCount === "string"
? translateText(`public_lobby.teams_${teamCount}`)
: translateText("public_lobby.teams", {
- num: teamCount ?? 0,
- })
+ num: teamCount ?? 0,
+ })
: translateText("game_mode.ffa")}
(this.settingsMode = "basic")}
>
${translateText("user_setting.tab_basic")}
@@ -253,8 +253,8 @@ export class UserSettingModal extends LitElement {
diff --git a/src/client/graphics/layers/SettingsModal.ts b/src/client/graphics/layers/SettingsModal.ts
index a8e6b359b..69b53ec9f 100644
--- a/src/client/graphics/layers/SettingsModal.ts
+++ b/src/client/graphics/layers/SettingsModal.ts
@@ -374,8 +374,8 @@ export class SettingsModal extends LitElement implements Layer {
${this.userSettings.performanceOverlay()
? translateText("user_setting.performance_overlay_enabled")
: translateText(
- "user_setting.performance_overlay_disabled",
- )}
+ "user_setting.performance_overlay_disabled",
+ )}
diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts
index 83499e0b9..5c6169ab4 100644
--- a/src/client/graphics/layers/StructureIconsLayer.ts
+++ b/src/client/graphics/layers/StructureIconsLayer.ts
@@ -353,12 +353,12 @@ export class StructureIconsLayer implements Layer {
const shape = STRUCTURE_SHAPES[structureType];
const texture = shape
? this.createIcon(
- unit.owner(),
- structureType,
- isConstruction,
- shape,
- renderIcon,
- )
+ unit.owner(),
+ structureType,
+ isConstruction,
+ shape,
+ renderIcon,
+ )
: PIXI.Texture.EMPTY;
this.textureCache.set(cacheKey, texture);
diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts
index 9a794aac7..186232380 100644
--- a/src/core/GameRunner.ts
+++ b/src/core/GameRunner.ts
@@ -57,13 +57,13 @@ export async function createGameRunner(
const nations = gameStart.config.disableNPCs
? []
: gameMap.manifest.nations.map(
- (n) =>
- new Nation(
- new Cell(n.coordinates[0], n.coordinates[1]),
- n.strength,
- new PlayerInfo(n.name, PlayerType.FakeHuman, null, random.nextID()),
- ),
- );
+ (n) =>
+ new Nation(
+ new Cell(n.coordinates[0], n.coordinates[1]),
+ n.strength,
+ new PlayerInfo(n.name, PlayerType.FakeHuman, null, random.nextID()),
+ ),
+ );
const game: Game = createGame(
humans,
diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts
index 0f41f7e86..b70bbae13 100644
--- a/src/core/execution/FakeHumanExecution.ts
+++ b/src/core/execution/FakeHumanExecution.ts
@@ -455,8 +455,8 @@ export class FakeHumanExecution implements Execution {
const tiles =
type === UnitType.Port
? Array.from(this.player.borderTiles()).filter((t) =>
- this.mg.isOceanShore(t),
- )
+ this.mg.isOceanShore(t),
+ )
: Array.from(this.player.tiles());
if (tiles.length === 0) return null;
return this.random.randElement(tiles);
diff --git a/src/core/execution/RailroadExecution.ts b/src/core/execution/RailroadExecution.ts
index 268e39ba0..133bef25a 100644
--- a/src/core/execution/RailroadExecution.ts
+++ b/src/core/execution/RailroadExecution.ts
@@ -43,9 +43,9 @@ export class RailroadExecution implements Execution {
railType:
tiles.length > 0
? this.computeExtremityDirection(
- tiles[tiles.length - 1],
- tiles[tiles.length - 2],
- )
+ tiles[tiles.length - 1],
+ tiles[tiles.length - 2],
+ )
: RailType.VERTICAL,
});
}
diff --git a/src/core/pathfinding/AStar.ts b/src/core/pathfinding/AStar.ts
index 4056c684f..63fbf907f 100644
--- a/src/core/pathfinding/AStar.ts
+++ b/src/core/pathfinding/AStar.ts
@@ -11,19 +11,19 @@ export enum PathFindResultType {
}
export type AStarResult =
| {
- type: PathFindResultType.NextTile;
- node: NodeType;
- }
+ type: PathFindResultType.NextTile;
+ node: NodeType;
+ }
| {
- type: PathFindResultType.Pending;
- }
+ type: PathFindResultType.Pending;
+ }
| {
- type: PathFindResultType.Completed;
- node: NodeType;
- }
+ type: PathFindResultType.Completed;
+ node: NodeType;
+ }
| {
- type: PathFindResultType.PathNotFound;
- };
+ type: PathFindResultType.PathNotFound;
+ };
export type Point = {
x: number;
diff --git a/src/server/jwt.ts b/src/server/jwt.ts
index 98ebb518f..72c5d36da 100644
--- a/src/server/jwt.ts
+++ b/src/server/jwt.ts
@@ -11,9 +11,9 @@ import { PersistentIdSchema } from "../core/Schemas";
type TokenVerificationResult =
| {
- persistentId: string;
- claims: TokenPayload | null;
- }
+ persistentId: string;
+ claims: TokenPayload | null;
+ }
| false;
export async function verifyClientToken(
diff --git a/src/server/worker/websocket/handler/message/PreJoinHandler.ts b/src/server/worker/websocket/handler/message/PreJoinHandler.ts
index 1d09f4529..c08ae2e7e 100644
--- a/src/server/worker/websocket/handler/message/PreJoinHandler.ts
+++ b/src/server/worker/websocket/handler/message/PreJoinHandler.ts
@@ -63,14 +63,14 @@ async function handleJoinMessage(
): Promise<
| undefined
| {
- success: true;
- }
+ success: true;
+ }
| {
- success: false;
- code: 1002;
- description: string;
- error?: string;
- reason:
+ success: false;
+ code: 1002;
+ description: string;
+ error?: string;
+ reason:
| "ClientJoinMessageSchema"
| "Flag invalid"
| "Flag restricted"
@@ -80,20 +80,20 @@ async function handleJoinMessage(
| "Pattern restricted"
| "Pattern unlisted"
| "Unauthorized";
- }
+ }
| {
- success: false;
- code: 1011;
- reason: "Internal server error";
- error: string;
- description: string;
- }
+ success: false;
+ code: 1011;
+ reason: "Internal server error";
+ error: string;
+ description: string;
+ }
> {
const forwarded = req.headers["x-forwarded-for"];
const ip = Array.isArray(forwarded)
? forwarded[0]
: // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- forwarded || req.socket.remoteAddress || "unknown";
+ forwarded || req.socket.remoteAddress || "unknown";
try {
// Parse and handle client messages
diff --git a/webpack.config.js b/webpack.config.js
index 0820e8cd0..ca0708e92 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -111,13 +111,13 @@ export default async (env, argv) => {
// Add optimization for HTML
minify: isProduction
? {
- collapseWhitespace: true,
- removeComments: true,
- removeRedundantAttributes: true,
- removeScriptTypeAttributes: true,
- removeStyleLinkTypeAttributes: true,
- useShortDoctype: true,
- }
+ collapseWhitespace: true,
+ removeComments: true,
+ removeRedundantAttributes: true,
+ removeScriptTypeAttributes: true,
+ removeStyleLinkTypeAttributes: true,
+ useShortDoctype: true,
+ }
: false,
}),
new webpack.DefinePlugin({
@@ -160,91 +160,91 @@ export default async (env, argv) => {
devServer: isProduction
? {}
: {
- devMiddleware: { writeToDisk: true },
- static: {
- directory: path.join(__dirname, "static"),
- },
- historyApiFallback: true,
- compress: true,
- port: 9000,
- proxy: [
- // WebSocket proxies
- {
- context: ["/socket"],
- target: "ws://localhost:3000",
- ws: true,
- changeOrigin: true,
- logLevel: "debug",
- },
- // Worker WebSocket proxies - using direct paths without /socket suffix
- {
- context: ["/w0"],
- target: "ws://localhost:3001",
- ws: true,
- secure: false,
- changeOrigin: true,
- logLevel: "debug",
- },
- {
- context: ["/w1"],
- target: "ws://localhost:3002",
- ws: true,
- secure: false,
- changeOrigin: true,
- logLevel: "debug",
- },
- {
- context: ["/w2"],
- target: "ws://localhost:3003",
- ws: true,
- secure: false,
- changeOrigin: true,
- logLevel: "debug",
- },
- // Worker proxies for HTTP requests
- {
- context: ["/w0"],
- target: "http://localhost:3001",
- pathRewrite: { "^/w0": "" },
- secure: false,
- changeOrigin: true,
- logLevel: "debug",
- },
- {
- context: ["/w1"],
- target: "http://localhost:3002",
- pathRewrite: { "^/w1": "" },
- secure: false,
- changeOrigin: true,
- logLevel: "debug",
- },
- {
- context: ["/w2"],
- target: "http://localhost:3003",
- pathRewrite: { "^/w2": "" },
- secure: false,
- changeOrigin: true,
- logLevel: "debug",
- },
- // Original API endpoints
- {
- context: [
- "/api/env",
- "/api/game",
- "/api/public_lobbies",
- "/api/join_game",
- "/api/start_game",
- "/api/create_game",
- "/api/archive_singleplayer_game",
- "/api/auth/callback",
- "/api/auth/discord",
- "/api/kick_player",
- ],
- target: "http://localhost:3000",
- secure: false,
- changeOrigin: true,
- },
- ],
+ devMiddleware: { writeToDisk: true },
+ static: {
+ directory: path.join(__dirname, "static"),
},
+ historyApiFallback: true,
+ compress: true,
+ port: 9000,
+ proxy: [
+ // WebSocket proxies
+ {
+ context: ["/socket"],
+ target: "ws://localhost:3000",
+ ws: true,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ // Worker WebSocket proxies - using direct paths without /socket suffix
+ {
+ context: ["/w0"],
+ target: "ws://localhost:3001",
+ ws: true,
+ secure: false,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ {
+ context: ["/w1"],
+ target: "ws://localhost:3002",
+ ws: true,
+ secure: false,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ {
+ context: ["/w2"],
+ target: "ws://localhost:3003",
+ ws: true,
+ secure: false,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ // Worker proxies for HTTP requests
+ {
+ context: ["/w0"],
+ target: "http://localhost:3001",
+ pathRewrite: { "^/w0": "" },
+ secure: false,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ {
+ context: ["/w1"],
+ target: "http://localhost:3002",
+ pathRewrite: { "^/w1": "" },
+ secure: false,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ {
+ context: ["/w2"],
+ target: "http://localhost:3003",
+ pathRewrite: { "^/w2": "" },
+ secure: false,
+ changeOrigin: true,
+ logLevel: "debug",
+ },
+ // Original API endpoints
+ {
+ context: [
+ "/api/env",
+ "/api/game",
+ "/api/public_lobbies",
+ "/api/join_game",
+ "/api/start_game",
+ "/api/create_game",
+ "/api/archive_singleplayer_game",
+ "/api/auth/callback",
+ "/api/auth/discord",
+ "/api/kick_player",
+ ],
+ target: "http://localhost:3000",
+ secure: false,
+ changeOrigin: true,
+ },
+ ],
+ },
};
};