Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 133 additions & 3 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
},
"ignorePatterns": [
"src/server/gatekeeper/**",
"tests/pathfinding/playground/**"
"tests/pathfinding/playground/**",
".claude/**"
],
"plugins": ["eslint", "typescript", "oxc", "import"],
"categories": {
"correctness": "off",
"correctness": "error",
"suspicious": "off",
"pedantic": "off",
"perf": "off",
Expand All @@ -22,9 +24,19 @@
},
"rules": {
"typescript/no-explicit-any": "off",
"typescript/no-floating-promises": "off",
"typescript/restrict-template-expressions": "off",
"typescript/unbound-method": "off",
"typescript/no-base-to-string": "off",
"typescript/no-misused-spread": "off",
"typescript/require-array-sort-compare": "off",
"typescript/no-redundant-type-constituents": "off",
"typescript/await-thenable": "off",
"typescript/no-useless-default-assignment": "off",
"typescript/prefer-nullish-coalescing": "error",
"eqeqeq": "error",
"no-case-declarations": "error",
"import/no-duplicates": "error",
"no-unused-vars": [
"error",
{
Expand All @@ -36,5 +48,123 @@
}
}
]
}
},
"jsPlugins": [{ "name": "eslint-js", "specifier": "oxlint-plugin-eslint" }],
"overrides": [
{
"files": [
"packages/engine/src/**/*.ts",
"packages/engine-api/src/**/*.ts",
"packages/engine-lib/src/**/*.ts"
],
"excludeFiles": ["packages/engine-lib/src/DetMath.ts"],
"rules": {
"eslint/no-restricted-properties": [
"error",
{
"object": "Math",
"property": "exp",
"message": "Math.exp differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "expm1",
"message": "Math.expm1 differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "log",
"message": "Math.log differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "log1p",
"message": "Math.log1p differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "log2",
"message": "Math.log2 differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "log10",
"message": "Math.log10 differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "pow",
"message": "Math.pow differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "sin",
"message": "Math.sin differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "cos",
"message": "Math.cos differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "tan",
"message": "Math.tan differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "asin",
"message": "Math.asin differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "acos",
"message": "Math.acos differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "atan",
"message": "Math.atan differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "atan2",
"message": "Math.atan2 differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "sinh",
"message": "Math.sinh differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "cosh",
"message": "Math.cosh differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "tanh",
"message": "Math.tanh differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "cbrt",
"message": "Math.cbrt differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
},
{
"object": "Math",
"property": "hypot",
"message": "Math.hypot differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
}
],
"eslint-js/no-restricted-syntax": [
"error",
{
"selector": "BinaryExpression[operator='**']:not([right.type='Literal'][right.value=2])",
"message": "`**` with a non-2 exponent differs between JS engines; use packages/engine-lib/src/DetMath.ts to keep the simulation deterministic."
}
]
}
}
]
}
2 changes: 2 additions & 0 deletions src/client/HostLobbyModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import { CopyButton } from "./components/CopyButton";
import "./components/GameConfigSettings";
import "./components/InputCard";
import "./components/InsufficientCurrencyDialog";
// The type import below is erased at runtime; keep this to register the dialog.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./components/ListLobbyDialog";
import { ListLobbyOptions } from "./components/ListLobbyDialog";
import "./components/LobbyPlayerView";
Expand Down
2 changes: 2 additions & 0 deletions src/client/InGameModal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// The type-only use below is erased at runtime; keep this to register the dialog.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./components/ConfirmDialog";
import { ConfirmDialog } from "./components/ConfirmDialog";
import { gamePortal } from "./GamePortal";
Expand Down
2 changes: 2 additions & 0 deletions src/client/LangSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { assetUrl } from "@openfront/shared/AssetUrls";
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { desktopSteamLocale } from "./DesktopShell";
// The type-only use below is erased at runtime; keep this to register the modal.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./LanguageModal";
import { LanguageModal } from "./LanguageModal";
import { formatDebugTranslation } from "./Utils";
Expand Down
7 changes: 5 additions & 2 deletions src/client/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import {
} from "./DesktopShell";
import "./FeaturedStream";
import { loadGameClient, prefetchGameClient } from "./GameClientLoader";
import "./GameModeSelector";
import {
GameModeSelector,
joinIsGateable,
Expand All @@ -83,6 +82,8 @@ import type { HomepagePromos } from "./HomepagePromos";
import type { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { showInGameAlert, showInGameConfirm } from "./InGameModal";
import type { JoinLobbyModal } from "./JoinLobbyModal";
// The LangSelector class is used as a type; retain this for registration.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./LangSelector";
import { LangSelector } from "./LangSelector";
import { initLayout } from "./Layout";
Expand Down Expand Up @@ -152,8 +153,10 @@ import {
// Before the first userMeResponse, for the modals that load after it.
import "./UserMeBroadcast";
import { classicReplayHref } from "./replay/ReplayEntry";
import "./UsernameInput";
import { parseReplayViewerHash } from "./replay/ReplayViewerRoute";
// The UsernameInput class is used as a type; retain this for registration.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./UsernameInput";
import { UsernameInput } from "./UsernameInput";
import { UserSettings } from "./UserSettings";
import "./components/BannedModal";
Expand Down
1 change: 0 additions & 1 deletion src/client/SinglePlayerModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import { BaseModal } from "./components/BaseModal";
import "./components/GameConfigSettings";
import "./components/SavedGamesPanel";
import { MEDAL_ORDER, medalIcon } from "./components/map/Medals";
import {
type LoadSavedGameDetail,
Expand Down
1 change: 0 additions & 1 deletion src/client/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import type { InlineCheckout } from "./components/InlineCheckout";
import "./components/NotLoggedInWarning";
import "./components/PackContentsDialog";
import { ProgressiveList } from "./components/ProgressiveList";
import "./components/PurchaseButton";
import { alignPurchaseRows } from "./components/PurchaseButton";
import "./components/TribesPanel";
import { modalHeader } from "./components/ui/ModalHeader";
Expand Down
4 changes: 4 additions & 0 deletions src/client/UserSettingModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import type { MapLayer } from "@openfront/engine-api/game/MapFiles";
import { html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js";
import { formatKeyForDisplay, translateText } from "../client/Utils";
// This type is erased at runtime; retain the import to register the setting.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./components/baseComponents/setting/SettingKeybind";
import { SettingKeybind } from "./components/baseComponents/setting/SettingKeybind";
import "./components/baseComponents/setting/SettingNumber";
// This type is erased at runtime; retain the import to register the setting.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "./components/baseComponents/setting/SettingSelect";
import { SettingSelect } from "./components/baseComponents/setting/SettingSelect";
import "./components/baseComponents/setting/SettingSlider";
Expand Down
1 change: 0 additions & 1 deletion src/client/components/NavAccountMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
import { steamSDK } from "../SteamSDK";
import { playerProfileUrl } from "../utilities/PlayerProfileUrl";
import { copyToClipboard, showToast, translateText } from "../Utils";
import "./LevelBadge";
import { levelBadgeLabel } from "./LevelBadge";
import { xpBar, xpProgressText } from "./XpBar";

Expand Down
1 change: 0 additions & 1 deletion src/client/components/ProfileCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { customElement, property, state } from "lit/decorators.js";
import { clampPrestige, levelFraction } from "../Progression";
import { prefersReducedMotion } from "../utilities/ReducedMotion";
import { translateText } from "../Utils";
import "./LevelBadge";
import { levelBadgeAccent } from "./LevelBadge";
import { splitAccountUsername, usernameText } from "./ui/UsernameText";
import { formatXp, xpProgressText } from "./XpBar";
Expand Down
1 change: 0 additions & 1 deletion src/client/replay/ReplayViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import "../hud/layers/PlayerInfoOverlay";
import type { PlayerInfoOverlay } from "../hud/layers/PlayerInfoOverlay";
import "../hud/layers/PlayerStats";
import type { PlayerStats } from "../hud/layers/PlayerStats";
import "../hud/layers/SettingsModal";
import {
ShowSettingsModalEvent,
type SettingsModal,
Expand Down
4 changes: 2 additions & 2 deletions tests/ColumnPicker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ describe("ColumnPicker", () => {
selection = (event as CustomEvent<ColumnId[]>).detail;
});
// Registry order: index 0 is clan, 1 is playerType, 2 is tiles, 3 is gold.
(popup?.querySelectorAll("input")[3] as HTMLInputElement).click();
(popup!.querySelectorAll("input")[3] as HTMLInputElement).click();
expect(selection).toEqual(["tiles", "gold"]);

(popup?.querySelectorAll("input")[0] as HTMLInputElement).click();
(popup!.querySelectorAll("input")[0] as HTMLInputElement).click();
expect(selection).toEqual(["clan", "tiles"]);

sidebar.remove();
Expand Down
1 change: 0 additions & 1 deletion tests/DesktopStatusBar.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ClientEnv } from "../src/client/ClientEnv";
import "../src/client/components/DesktopStatusBar";
import { barSource } from "../src/client/components/DesktopStatusBar";
import {
backendUnreachableConfirmed,
Expand Down
4 changes: 1 addition & 3 deletions tests/ReachabilityGating.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
} from "@openfront/shared/WireSchemas";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ClientEnv } from "../src/client/ClientEnv";
// This named import also registers <game-mode-selector> as a side effect.
import { shouldBlockMultiplayerAction } from "../src/client/GameModeSelector";
import {
attemptInFlight,
Expand Down Expand Up @@ -42,9 +43,6 @@ vi.mock("../src/client/LazyModals", async (importOriginal) => ({
whenModalLoaded: (_tag: string, open: () => void) => open(),
}));

// Registers <game-mode-selector> as a side effect.
import "../src/client/GameModeSelector";

/**
* OPE-439. The server-list heartbeat already knows whether the API answers;
* this is the half that turns that into something the player can see, on the
Expand Down
2 changes: 1 addition & 1 deletion tests/client/EffectsGrid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ describe("EffectsGrid", () => {
await card.updateComplete;
card.querySelector<HTMLButtonElement>("[data-cosmetic-main]")!.click();

expect((onMessage.mock.lastCall?.[0] as CustomEvent).detail.message).toBe(
expect((onMessage.mock.lastCall![0] as CustomEvent).detail.message).toBe(
"Selected Localized Wake",
);

Expand Down
2 changes: 1 addition & 1 deletion tests/client/InventoryModal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ describe("InventoryModal", () => {
it("announces flag, crown and skin selections too", async () => {
const onMessage = vi.fn();
const lastMessage = () =>
(onMessage.mock.lastCall?.[0] as CustomEvent).detail.message;
(onMessage.mock.lastCall![0] as CustomEvent).detail.message;
window.addEventListener("show-message", onMessage);
try {
await showTab(modal, "skins");
Expand Down
2 changes: 2 additions & 0 deletions tests/client/ListLobbyDialog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
MIN_HOSTED_LOBBY_PLAYERS,
} from "@openfront/shared/WireSchemas";
import { describe, expect, it } from "vitest";
// The class is used as a type below, so this import is needed for registration.
// oxlint-disable-next-line import/no-duplicates -- Required custom-element registration.
import "../../src/client/components/ListLobbyDialog";
import { ListLobbyDialog } from "../../src/client/components/ListLobbyDialog";

Expand Down
5 changes: 2 additions & 3 deletions tests/client/NewsModal.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
// Side-effect import registers <news-modal>; a type-only import would be
// elided and leave the element inert.
import "../../src/client/NewsModal";
// The named import registers <news-modal> as a side effect; a type-only import
// would be elided and leave the element inert.
import { NewsModal } from "../../src/client/NewsModal";

beforeEach(() => {
Expand Down
4 changes: 2 additions & 2 deletions tests/client/clan/ClanModal.rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,12 +553,12 @@ describe("ClanModal — rendering", () => {
it("uses the compact card variant, not the header size", async () => {
const card = await showMyClan(makeClan({ hardBalance: "25" }));
const cardSize = (
card?.querySelector("plutonium-icon") as HTMLElement & { size: number }
card!.querySelector("plutonium-icon") as HTMLElement & { size: number }
).size;

const header = await openDetail(makeClan({ hardBalance: "25" }));
const headerSize = (
header?.querySelector("plutonium-icon") as HTMLElement & {
header!.querySelector("plutonium-icon") as HTMLElement & {
size: number;
}
).size;
Expand Down
1 change: 0 additions & 1 deletion tests/client/graphics/layers/ChatModalShortcut.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { EventBus } from "@openfront/shared/EventBus";
import { beforeEach, describe, expect, it, vi } from "vitest";
import "../../../../src/client/hud/layers/ChatModal";
import {
ShowPlayerChatEvent,
type ChatModal,
Expand Down
1 change: 0 additions & 1 deletion tests/client/graphics/layers/EmojiTable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { AllPlayers } from "@openfront/engine-api/game/GameTypes";
import { flattenedEmojiTable } from "@openfront/engine-api/Schemas";
import { EventBus } from "@openfront/shared/EventBus";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import "../../../../src/client/hud/layers/EmojiTable";
import {
type EmojiTable,
ShowPlayerEmojiMenuEvent,
Expand Down
3 changes: 1 addition & 2 deletions tests/client/graphics/layers/SpawnTimer.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { GameMode, GameType } from "@openfront/engine-api/game/GameTypes";
import { EventBus } from "@openfront/shared/EventBus";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import "../../../../src/client/hud/layers/SpawnTimer";
import type { SpawnTimer } from "../../../../src/client/hud/layers/SpawnTimer";
import { SpawnBarVisibleEvent } from "../../../../src/client/hud/layers/SpawnTimer";
import type { SpawnTimer } from "../../../../src/client/hud/layers/SpawnTimer";
import type { GameView } from "../../../../src/client/view";

function makePlayer(team: string | null, tiles: number) {
Expand Down
Loading