mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
Enable Biome rules 3
This commit is contained in:
@@ -3,10 +3,7 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as v from "valibot";
|
||||
import type { CustomTheme } from "~/db/tables-json";
|
||||
import {
|
||||
CUSTOM_THEME_VARS,
|
||||
type CustomThemeVar,
|
||||
} from "~/features/theme/theme-constants";
|
||||
import { CUSTOM_THEME_VARS } from "~/features/theme/theme-constants";
|
||||
import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
|
||||
import {
|
||||
ACCENT_CHROMA_MULTIPLIERS,
|
||||
@@ -308,9 +305,9 @@ export function CustomThemeSelector({
|
||||
|
||||
const handleReset = () => {
|
||||
setThemeInput(DEFAULT_THEME_INPUT);
|
||||
CUSTOM_THEME_VARS.forEach((varDef: CustomThemeVar) => {
|
||||
for (const varDef of CUSTOM_THEME_VARS) {
|
||||
document.documentElement.style.removeProperty(varDef);
|
||||
});
|
||||
}
|
||||
onReset();
|
||||
};
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ export function ObjectiveTimeline({
|
||||
|
||||
/** One tick every 25 up to the top of the data, none below zero so the control gutter stays clean. */
|
||||
function countAxisTicks(max: number) {
|
||||
const ticks = [];
|
||||
const ticks: Array<{ value: number }> = [];
|
||||
for (let value = 0; value <= max; value += COUNT_TICK_STEP) {
|
||||
ticks.push({ value });
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import { JSON_COLUMNS } from "./json-columns";
|
||||
|
||||
describe("JSON_COLUMNS", () => {
|
||||
test("matches the JSONColumnType declarations in tables.ts", () => {
|
||||
expect([...JSON_COLUMNS].sort()).toEqual(jsonColumnsFromTablesSource());
|
||||
expect([...JSON_COLUMNS].sort(alphabetically)).toEqual(
|
||||
jsonColumnsFromTablesSource(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +19,7 @@ function jsonColumnsFromTablesSource() {
|
||||
const [, interfaceName, body] = match;
|
||||
if (interfaceName === "DB") continue;
|
||||
|
||||
const columns = [];
|
||||
const columns: string[] = [];
|
||||
for (const line of body.split("\n")) {
|
||||
const columnMatch = line.match(
|
||||
/^\s*(\w+)\??:\s*.*JSONColumnType(?:Nullable)?</,
|
||||
@@ -30,7 +32,7 @@ function jsonColumnsFromTablesSource() {
|
||||
}
|
||||
|
||||
const dbInterfaceBody = source.slice(source.indexOf("export interface DB {"));
|
||||
const entries = [];
|
||||
const entries: string[] = [];
|
||||
for (const match of dbInterfaceBody.matchAll(/^\t(\w+): (\w+);/gm)) {
|
||||
const [, tableName, interfaceName] = match;
|
||||
for (const column of jsonColumnsByInterface.get(interfaceName) ?? []) {
|
||||
@@ -38,5 +40,7 @@ function jsonColumnsFromTablesSource() {
|
||||
}
|
||||
}
|
||||
|
||||
return entries.sort();
|
||||
return entries.sort(alphabetically);
|
||||
}
|
||||
|
||||
const alphabetically = (a: string, b: string) => a.localeCompare(b);
|
||||
|
||||
@@ -467,7 +467,7 @@ async function registerTeams({
|
||||
registeredAt?: Date;
|
||||
mapPool?: () => MapPool;
|
||||
}) {
|
||||
const teams = [];
|
||||
const teams: Awaited<ReturnType<typeof TournamentTeamFactory.create>>[] = [];
|
||||
for (const [i, roster] of rosters.entries()) {
|
||||
teams.push(
|
||||
await TournamentTeamFactory.create(
|
||||
|
||||
@@ -191,21 +191,9 @@ function calculateGrid({
|
||||
subsUsed: number;
|
||||
}) {
|
||||
const result: ("N/A" | ShotCellData)[][] = [];
|
||||
for (
|
||||
let issAPIndex = 0;
|
||||
issAPIndex < AP_VALUES_TO_SHOW.length;
|
||||
issAPIndex++
|
||||
) {
|
||||
const issAP = AP_VALUES_TO_SHOW[issAPIndex];
|
||||
|
||||
for (const issAP of AP_VALUES_TO_SHOW) {
|
||||
const row: ("N/A" | ShotCellData)[] = [];
|
||||
for (
|
||||
let ismAPIndex = 0;
|
||||
ismAPIndex < AP_VALUES_TO_SHOW.length;
|
||||
ismAPIndex++
|
||||
) {
|
||||
const ismAP = AP_VALUES_TO_SHOW[ismAPIndex];
|
||||
|
||||
for (const ismAP of AP_VALUES_TO_SHOW) {
|
||||
if (!apsArePossible(issAP, ismAP)) {
|
||||
row.push("N/A" as const);
|
||||
continue;
|
||||
|
||||
@@ -1196,7 +1196,10 @@ function subDefenseGraphOptions({
|
||||
.map((d) => damageToKey(d)),
|
||||
);
|
||||
|
||||
const result = [];
|
||||
const result: Array<{
|
||||
label: string;
|
||||
data: Array<{ primary: number; secondary: number }>;
|
||||
}> = [];
|
||||
|
||||
for (const key of distanceKeys) {
|
||||
const distance = key.split(",")[0];
|
||||
|
||||
@@ -45,7 +45,7 @@ export function FiltersBar() {
|
||||
};
|
||||
|
||||
const modesFormatted = () => {
|
||||
const parts = [];
|
||||
const parts: string[] = [];
|
||||
if (filters.modes.length < modesShortWithSpecial.length) {
|
||||
parts.push(filters.modes.join(", "));
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export function FiltersBar() {
|
||||
};
|
||||
|
||||
const eventTypeFormatted = () => {
|
||||
const parts = [];
|
||||
const parts: string[] = [];
|
||||
if (filters.games.length < gamesShort.length) {
|
||||
parts.push(filters.games.join(", "));
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export function FiltersBar() {
|
||||
};
|
||||
|
||||
const tagsFormatted = () => {
|
||||
const parts = [];
|
||||
const parts: string[] = [];
|
||||
if (filters.tagsIncluded.length > 0) {
|
||||
parts.push(`+${filters.tagsIncluded.length}`);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export function FiltersBar() {
|
||||
};
|
||||
|
||||
const organizersFormatted = () => {
|
||||
const parts = [];
|
||||
const parts: string[] = [];
|
||||
if (filters.orgsIncluded.length > 0) {
|
||||
parts.push(`+${filters.orgsIncluded.length}`);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export function FiltersBar() {
|
||||
};
|
||||
|
||||
const timeAndSizeFormatted = () => {
|
||||
const parts = [];
|
||||
const parts: string[] = [];
|
||||
if (filters.preferredStartTime !== "ANY") {
|
||||
parts.push(
|
||||
t(
|
||||
|
||||
@@ -73,6 +73,7 @@ function parseEntryFile(fileName: string): ChangelogGraphicEntry {
|
||||
`Invalid frontmatter in changelog entry "${fileName}": ${
|
||||
error instanceof v.ValiError ? error.message : String(error)
|
||||
}`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,9 @@ describe("ChatRoomResolver.resolve", () => {
|
||||
const room = await resolveOrThrow(await groupChatRoomId(group.id));
|
||||
|
||||
expect(room.type).toBe("SQ_GROUP");
|
||||
expect(room.participantUserIds.sort()).toEqual(memberUserIds.sort());
|
||||
expect(room.participantUserIds.sort(byId)).toEqual(
|
||||
memberUserIds.sort(byId),
|
||||
);
|
||||
expect(room.url).toBe("/q/looking");
|
||||
// a group chat resolves no observers of its own, site staff aside
|
||||
expect(hasPermission(room, "OBSERVE", { id: outsiderId() })).toBe(false);
|
||||
@@ -136,8 +138,8 @@ describe("ChatRoomResolver.resolve", () => {
|
||||
const room = await resolveOrThrow(match.chatRoomId!);
|
||||
|
||||
expect(room.type).toBe("SQ_MATCH");
|
||||
expect(room.participantUserIds.sort()).toEqual(
|
||||
[...alphaUserIds, ...bravoUserIds].sort(),
|
||||
expect(room.participantUserIds.sort(byId)).toEqual(
|
||||
[...alphaUserIds, ...bravoUserIds].sort(byId),
|
||||
);
|
||||
expect(room.titleParams).toEqual({ matchId: String(match.id) });
|
||||
expect(room.url).toContain(String(match.id));
|
||||
@@ -155,8 +157,8 @@ describe("ChatRoomResolver.resolve", () => {
|
||||
const room = await resolveOrThrow(chatRoomId);
|
||||
|
||||
expect(room.type).toBe("TOURNAMENT_MATCH");
|
||||
expect(room.participantUserIds.sort()).toEqual(
|
||||
[...teamAlphaUserIds, ...teamBravoUserIds].sort(),
|
||||
expect(room.participantUserIds.sort(byId)).toEqual(
|
||||
[...teamAlphaUserIds, ...teamBravoUserIds].sort(byId),
|
||||
);
|
||||
expect(room.titleParams.matchId).toBe(String(matchId));
|
||||
expect(room.titleParams.tournamentName).toEqual(expect.any(String));
|
||||
@@ -187,7 +189,9 @@ describe("ChatRoomResolver.resolve", () => {
|
||||
const room = await resolveOrThrow(await teamChatRoomId(team.id));
|
||||
|
||||
expect(room.type).toBe("TOURNAMENT_TEAM");
|
||||
expect(room.participantUserIds.sort()).toEqual(memberUserIds.sort());
|
||||
expect(room.participantUserIds.sort(byId)).toEqual(
|
||||
memberUserIds.sort(byId),
|
||||
);
|
||||
expect(room.titleParams.teamName).toEqual(expect.any(String));
|
||||
expect(room.permissions.OBSERVE).toContain(authorId);
|
||||
});
|
||||
@@ -199,8 +203,8 @@ describe("ChatRoomResolver.resolve", () => {
|
||||
const room = await resolveOrThrow(chatRoomId);
|
||||
|
||||
expect(room.type).toBe("SCRIM");
|
||||
expect(room.participantUserIds.sort()).toEqual(
|
||||
[...postUserIds, ...requestUserIds].sort(),
|
||||
expect(room.participantUserIds.sort(byId)).toEqual(
|
||||
[...postUserIds, ...requestUserIds].sort(byId),
|
||||
);
|
||||
expect(room.titleParams.startsAt).toBe(
|
||||
String(dateToDatabaseTimestamp(startsAt)),
|
||||
@@ -223,11 +227,18 @@ describe("ChatRoomResolver.findAllByUserId", () => {
|
||||
|
||||
const rooms = await ChatRoomResolver.findAllByUserId(alphaUserIds[0]);
|
||||
|
||||
expect(rooms.map((room) => [room.roomId, room.type]).sort()).toEqual(
|
||||
expect(
|
||||
rooms
|
||||
.map((room) => ({ roomId: room.roomId, type: room.type }))
|
||||
.sort(byRoomId),
|
||||
).toEqual(
|
||||
[
|
||||
[match.chatRoomId!, "SQ_MATCH"],
|
||||
[await groupChatRoomId(match.alphaGroup.id), "SQ_GROUP"],
|
||||
].sort(),
|
||||
{ roomId: match.chatRoomId!, type: "SQ_MATCH" },
|
||||
{
|
||||
roomId: await groupChatRoomId(match.alphaGroup.id),
|
||||
type: "SQ_GROUP",
|
||||
},
|
||||
].sort(byRoomId),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -501,3 +512,8 @@ const teamChatRoomId = async (teamId: number) => {
|
||||
|
||||
return team.chatRoomId!;
|
||||
};
|
||||
|
||||
const byId = (a: number, b: number) => a - b;
|
||||
|
||||
const byRoomId = (a: { roomId: number }, b: { roomId: number }) =>
|
||||
a.roomId - b.roomId;
|
||||
|
||||
@@ -165,13 +165,13 @@ function TrajectoryChart({
|
||||
|
||||
const groundY = yScale(0);
|
||||
|
||||
const xTicks = [];
|
||||
const xTicks: number[] = [];
|
||||
const xStep = Math.ceil(maxRange / 5);
|
||||
for (let x = 0; x <= maxRange; x += xStep) {
|
||||
xTicks.push(x);
|
||||
}
|
||||
|
||||
const yTicks = [];
|
||||
const yTicks: number[] = [];
|
||||
const yStep = Math.ceil((maxY - minY) / 4);
|
||||
for (let y = Math.ceil(minY); y <= maxY; y += yStep) {
|
||||
yTicks.push(y);
|
||||
|
||||
@@ -443,9 +443,7 @@ function isExcessiveCombo(combo: DamageCombo): boolean {
|
||||
);
|
||||
const totalDamage = combo.totalDamage;
|
||||
|
||||
for (let i = 0; i < flatDamages.length; i++) {
|
||||
const damage = flatDamages[i];
|
||||
|
||||
for (const damage of flatDamages) {
|
||||
const reducedDamage = totalDamage - damage;
|
||||
if (reducedDamage >= LETHAL_DAMAGE) {
|
||||
return true;
|
||||
|
||||
@@ -117,11 +117,13 @@ function ImageExportDialogContent({
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
import("@zumer/snapdom").then(({ preCache }) => {
|
||||
if (cancelled || !frameRef.current) return;
|
||||
import("@zumer/snapdom")
|
||||
.then(({ preCache }) => {
|
||||
if (cancelled || !frameRef.current) return;
|
||||
|
||||
preCache(frameRef.current).catch(() => {});
|
||||
});
|
||||
return preCache(frameRef.current);
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { MapPoolObject, ReadonlyMapPoolObject } from "./types";
|
||||
export function mapPoolToSerializedString(
|
||||
mapPool: ReadonlyMapPoolObject,
|
||||
): string {
|
||||
const serializedModes = [];
|
||||
const serializedModes: string[] = [];
|
||||
|
||||
for (const mode of modesShort) {
|
||||
const stages = mapPool[mode];
|
||||
|
||||
@@ -42,11 +42,12 @@ export class MapPool {
|
||||
return this.asSerialized;
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noAssignInExpressions: biome migration
|
||||
return (this.asSerialized =
|
||||
this.asSerialized =
|
||||
typeof this.source === "string"
|
||||
? this.source
|
||||
: mapPoolToSerializedString(this.source));
|
||||
: mapPoolToSerializedString(this.source);
|
||||
|
||||
return this.asSerialized;
|
||||
}
|
||||
|
||||
get parsed(): ReadonlyMapPoolObject {
|
||||
@@ -54,11 +55,12 @@ export class MapPool {
|
||||
return this.asObject;
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noAssignInExpressions: biome migration
|
||||
return (this.asObject =
|
||||
this.asObject =
|
||||
typeof this.source === "string"
|
||||
? serializedStringToMapPool(this.source)
|
||||
: this.source);
|
||||
: this.source;
|
||||
|
||||
return this.asObject;
|
||||
}
|
||||
|
||||
get dbList(): DbMapPoolList {
|
||||
|
||||
@@ -257,7 +257,9 @@ describe("notify()", () => {
|
||||
expect(user10Notifications).toHaveLength(2);
|
||||
expect(user11Notifications).toHaveLength(2);
|
||||
|
||||
const types = user10Notifications.map((n) => n.type).sort();
|
||||
const types = user10Notifications
|
||||
.map((n) => n.type)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
expect(types).toEqual(["SCRIM_CANCELED", "SCRIM_SCHEDULED"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@ async function loadAtlasLazy(name: string): Promise<() => GlyphSet | null> {
|
||||
const meta = JSON.parse(readFileSync(json, "utf8")) as AtlasMeta;
|
||||
const image = await readImage(png);
|
||||
let set: GlyphSet | null = null;
|
||||
return () => (set ??= loadGlyphSet(image, meta));
|
||||
return () => {
|
||||
set ??= loadGlyphSet(image, meta);
|
||||
return set;
|
||||
};
|
||||
}
|
||||
|
||||
/** Planner stage signatures; the (CPU) tile slicing runs on first access. */
|
||||
@@ -36,7 +39,10 @@ async function loadPlannerStagesLazy(): Promise<() => PlannerStage[] | null> {
|
||||
const manifest = JSON.parse(readFileSync(json, "utf8")) as PlannerManifest;
|
||||
const atlas = await readImage(png);
|
||||
let stages: PlannerStage[] | null = null;
|
||||
return () => (stages ??= loadPlannerStages(atlas, manifest));
|
||||
return () => {
|
||||
stages ??= loadPlannerStages(atlas, manifest);
|
||||
return stages;
|
||||
};
|
||||
}
|
||||
|
||||
/** Requires loadOpenCV() to have resolved. */
|
||||
|
||||
@@ -129,6 +129,7 @@ type MutuallyAssignable<A, B> = [A] extends [B]
|
||||
|
||||
// `true satisfies …` fails to compile the moment a schema and its core
|
||||
// interface disagree in either direction.
|
||||
// biome-ignore-start lint/suspicious/noUnusedExpressions: type-level assertions, no runtime effect
|
||||
true satisfies MutuallyAssignable<
|
||||
v.InferOutput<typeof scannerMatchPlayerSchema>,
|
||||
ScannerMatchPlayer
|
||||
@@ -149,3 +150,4 @@ true satisfies MutuallyAssignable<
|
||||
v.InferOutput<typeof scannerMatchSchema>,
|
||||
ScannerMatch
|
||||
>;
|
||||
// biome-ignore-end lint/suspicious/noUnusedExpressions: type-level assertions, no runtime effect
|
||||
|
||||
@@ -17,7 +17,7 @@ import { createScoreboardDetector } from "../core/detectors/scoreboard/index";
|
||||
import { createScoreboardBattleLogReplayDetector } from "../core/detectors/scoreboard-battle-log-replay/index";
|
||||
import { createScoreboardOwnDetector } from "../core/detectors/scoreboard-own/index";
|
||||
import type { DetectedEvent, Detector } from "../core/detectors/types";
|
||||
import { hueDistance, hueOf } from "../core/ink-color";
|
||||
import { hueDistance, hueOf, type InkRgb } from "../core/ink-color";
|
||||
import {
|
||||
type Fixture,
|
||||
isFieldSkipped,
|
||||
@@ -143,7 +143,7 @@ test("cast fixture pair: team ink hues identify sides across camera swaps", asyn
|
||||
].map((name) => fixtures.find((fixture) => fixture.name === name));
|
||||
assert.ok(pair[0] && pair[1], "cast fixture pair missing");
|
||||
|
||||
const colors = [];
|
||||
const colors: Array<readonly [InkRgb, InkRgb]> = [];
|
||||
for (const fixture of pair) {
|
||||
const { events } = await runDetectorOnFixture(detector, fixture!);
|
||||
const teamColor = (
|
||||
|
||||
@@ -115,8 +115,10 @@ async function analyzeFrame(
|
||||
// ship back the exact analyzed pixels (lossless, capture resolution) so the
|
||||
// UI never re-grabs a later frame — encoded at most once per frame
|
||||
let encoded: Promise<Blob> | null = null;
|
||||
const frameBlob = () =>
|
||||
(encoded ??= canvas.convertToBlob({ type: "image/png" }));
|
||||
const frameBlob = () => {
|
||||
encoded ??= canvas.convertToBlob({ type: "image/png" });
|
||||
return encoded;
|
||||
};
|
||||
|
||||
try {
|
||||
for (const detector of detectors) {
|
||||
|
||||
@@ -792,7 +792,7 @@ export function insertLike({
|
||||
.execute();
|
||||
} catch (error) {
|
||||
if (errorIsSqliteForeignKeyConstraintFailure(error)) {
|
||||
throw new SendouQError(error.message);
|
||||
throw new SendouQError(error.message, { cause: error });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -829,7 +829,7 @@ export function insertSuggestion({
|
||||
.execute();
|
||||
} catch (error) {
|
||||
if (errorIsSqliteForeignKeyConstraintFailure(error)) {
|
||||
throw new SendouQError(error.message);
|
||||
throw new SendouQError(error.message, { cause: error });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import * as SkillRepository from "~/features/mmr/SkillRepository.server";
|
||||
import type { TieredSkill } from "~/features/mmr/tiered.server";
|
||||
|
||||
export class SendouQError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
this.name = "SendouQError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
const PREP_TIME_MINUTES = 6.5;
|
||||
const MINUTES_PER_GAME = 6.5;
|
||||
|
||||
type GameMarker = {
|
||||
gameNumber: number;
|
||||
percentage: number;
|
||||
gameStartMinute: number;
|
||||
maxMinute: number;
|
||||
};
|
||||
|
||||
/** Acceptable max duration of a match in minutes (preparation time + game time). */
|
||||
export function totalMatchTime(maxGamesCount: number): number {
|
||||
return PREP_TIME_MINUTES + MINUTES_PER_GAME * maxGamesCount;
|
||||
@@ -15,14 +22,9 @@ export function progressPercentage(
|
||||
}
|
||||
|
||||
/** Position of each game on the match timeline as a percentage. */
|
||||
export function gameMarkers(maxGamesCount: number): Array<{
|
||||
gameNumber: number;
|
||||
percentage: number;
|
||||
gameStartMinute: number;
|
||||
maxMinute: number;
|
||||
}> {
|
||||
export function gameMarkers(maxGamesCount: number): GameMarker[] {
|
||||
const totalMinutes = totalMatchTime(maxGamesCount);
|
||||
const markers = [];
|
||||
const markers: GameMarker[] = [];
|
||||
|
||||
for (let i = 1; i <= maxGamesCount; i++) {
|
||||
const gameStartMinute = PREP_TIME_MINUTES + MINUTES_PER_GAME * (i - 1);
|
||||
|
||||
@@ -207,10 +207,10 @@ export function validatedBrackets(
|
||||
try {
|
||||
parsed = toOutputBracketFormat(brackets);
|
||||
} catch (e) {
|
||||
if ((e as { badBracketIdx: number }).badBracketIdx) {
|
||||
if (e instanceof BadBracketError) {
|
||||
return {
|
||||
type: "PLACEMENTS_PARSE_ERROR",
|
||||
bracketIdx: (e as { badBracketIdx: number }).badBracketIdx,
|
||||
bracketIdx: e.bracketIdx,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -372,6 +372,15 @@ export function bracketsToValidationError(
|
||||
return null;
|
||||
}
|
||||
|
||||
class BadBracketError extends Error {
|
||||
readonly bracketIdx: number;
|
||||
|
||||
constructor(bracketIdx: number) {
|
||||
super(`Bracket at index ${bracketIdx} has invalid placements`);
|
||||
this.bracketIdx = bracketIdx;
|
||||
}
|
||||
}
|
||||
|
||||
function toOutputBracketFormat(brackets: InputBracket[]): ParsedBracket[] {
|
||||
const result = brackets.map((bracket, bracketIdx) => {
|
||||
return {
|
||||
@@ -395,10 +404,10 @@ function toOutputBracketFormat(brackets: InputBracket[]): ParsedBracket[] {
|
||||
sourceBracket?.type === "swiss" &&
|
||||
sourceBracket?.settings?.advanceThreshold;
|
||||
if (!isSwissWithEarlyAdvance) {
|
||||
throw { badBracketIdx: bracketIdx };
|
||||
throw new BadBracketError(bracketIdx);
|
||||
}
|
||||
} else if (parsed === null) {
|
||||
throw { badBracketIdx: bracketIdx };
|
||||
throw new BadBracketError(bracketIdx);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -185,7 +185,7 @@ export function pairUp(players: SwissPairingTeam[]) {
|
||||
const scoreSums = [
|
||||
...new Set(
|
||||
scoreGroups.flatMap((s, i, a) => {
|
||||
const sums = [];
|
||||
const sums: number[] = [];
|
||||
for (let j = i; j < a.length; j++) {
|
||||
sums.push(s + a[j]);
|
||||
}
|
||||
@@ -243,8 +243,7 @@ function generateWeightedPairs({
|
||||
for (let i = 0; i < playerArray.length; i++) {
|
||||
const curr = playerArray[i];
|
||||
const next = playerArray.slice(i + 1);
|
||||
for (let j = 0; j < next.length; j++) {
|
||||
const opp = next[j];
|
||||
for (const opp of next) {
|
||||
let wt =
|
||||
75 - 75 / (scoreGroups.indexOf(Math.min(curr.score, opp.score)) + 2);
|
||||
wt +=
|
||||
|
||||
@@ -29,7 +29,7 @@ export const tournamentCtxTeam = (
|
||||
};
|
||||
|
||||
const nTeams = (n: number, startingId: number) => {
|
||||
const teams = [];
|
||||
const teams: TournamentData["ctx"]["teams"] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
teams.push(tournamentCtxTeam(i + 1, tournamentCtxTeam(i + startingId)));
|
||||
}
|
||||
|
||||
@@ -246,8 +246,8 @@ describe("startLooking", () => {
|
||||
|
||||
const roomsChangedUserIds = await startLooking(team.id);
|
||||
|
||||
expect(roomsChangedUserIds.sort()).toEqual(
|
||||
[users.id(1), users.id(2)].sort(),
|
||||
expect(roomsChangedUserIds.sort(byId)).toEqual(
|
||||
[users.id(1), users.id(2)].sort(byId),
|
||||
);
|
||||
|
||||
const chatRoomId = await chatRoomIdOf(team.id);
|
||||
@@ -390,8 +390,8 @@ describe("mergeTeams", () => {
|
||||
maxGroupSize: 4,
|
||||
});
|
||||
|
||||
expect(roomsChangedUserIds.sort()).toEqual(
|
||||
[users.id(1), users.id(2)].sort(),
|
||||
expect(roomsChangedUserIds.sort(byId)).toEqual(
|
||||
[users.id(1), users.id(2)].sort(byId),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -664,3 +664,5 @@ describe("findAllSubsByTournamentId", () => {
|
||||
expect(subs).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
const byId = (a: number, b: number) => a - b;
|
||||
|
||||
@@ -336,8 +336,8 @@ describe("TournamentTeamRepository", () => {
|
||||
TournamentTeamRepository.deleteById(team.id),
|
||||
);
|
||||
|
||||
expect(roomsChangedUserIds.sort()).toEqual(
|
||||
[ownerId(), memberId()].sort(),
|
||||
expect(roomsChangedUserIds.sort(byId)).toEqual(
|
||||
[ownerId(), memberId()].sort(byId),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -393,3 +393,5 @@ describe("TournamentTeamRepository", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const byId = (a: number, b: number) => a - b;
|
||||
|
||||
@@ -323,7 +323,9 @@ describe("UserRepository", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const teams = [];
|
||||
const teams: Awaited<
|
||||
ReturnType<typeof TournamentTeamFactory.create>
|
||||
>[] = [];
|
||||
for (const user of [topUser, topMate, lowUser, lowMate]) {
|
||||
teams.push(
|
||||
await TournamentTeamFactory.create(
|
||||
@@ -586,7 +588,9 @@ describe("UserRepository", () => {
|
||||
const tomorrow = await UserRepository.findAllPatronsForFooter();
|
||||
|
||||
expect(ids(today)).not.toEqual(ids(tomorrow));
|
||||
expect(ids(today).sort()).toEqual(ids(tomorrow).sort());
|
||||
expect(ids(today).sort((a, b) => a - b)).toEqual(
|
||||
ids(tomorrow).sort((a, b) => a - b),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -383,7 +383,7 @@ function YearSelect({
|
||||
const selectableYears = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const result = [];
|
||||
const result: number[] = [];
|
||||
for (let year = currentYear; year >= RESULTS_FIRST_YEAR; year--) {
|
||||
result.push(year);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as LFGRepository from "~/features/lfg/LFGRepository.server";
|
||||
import * as LiveStreamRepository from "~/features/live-streams/LiveStreamRepository.server";
|
||||
import { BANNED_MAPS } from "~/features/match-profile/banned-maps";
|
||||
import * as MatchProfileRepository from "~/features/match-profile/MatchProfileRepository.server";
|
||||
import type { TierName } from "~/features/mmr/mmr-constants";
|
||||
import { ordinalToSp } from "~/features/mmr/mmr-utils";
|
||||
import { userSkills as _userSkills } from "~/features/mmr/tiered.server";
|
||||
import * as TeamRepository from "~/features/team/TeamRepository.server";
|
||||
@@ -54,7 +55,12 @@ export const WIDGET_LOADERS = {
|
||||
return null;
|
||||
}
|
||||
|
||||
let peakData = null;
|
||||
let peakData: {
|
||||
peakSp: number;
|
||||
tierName: TierName;
|
||||
isPlus: boolean;
|
||||
season: number;
|
||||
} | null = null;
|
||||
let maxOrdinal = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (const season of seasonsParticipatedIn) {
|
||||
|
||||
@@ -330,7 +330,7 @@ function generateWithInput(
|
||||
|
||||
function isNotFollowingModeOrder(stage: StageValidatorInput) {
|
||||
let currentIndex = 0;
|
||||
for (let i = 0; i < mapList.length; i++) {
|
||||
for (const _ of mapList) {
|
||||
currentIndex++;
|
||||
if (currentIndex === input.modesIncluded.length) currentIndex = 0;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@ describe("NotifySeasonEndRoutine", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(notifiedUserIds().sort()).toEqual([users.id(1), users.id(2)].sort());
|
||||
expect(notifiedUserIds().sort((a, b) => a - b)).toEqual(
|
||||
[users.id(1), users.id(2)].sort((a, b) => a - b),
|
||||
);
|
||||
});
|
||||
|
||||
test("does NOT notify a participant whose skill is approximate", async () => {
|
||||
|
||||
@@ -95,7 +95,12 @@ export function wrappedAction<T extends AnySchema>({
|
||||
if (thrown instanceof Response) {
|
||||
if (thrown.status === 302) return thrown;
|
||||
|
||||
throw new Error(`Response thrown with status code: ${thrown.status}`);
|
||||
throw new Error(
|
||||
`Response thrown with status code: ${thrown.status}`,
|
||||
{
|
||||
cause: thrown,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw thrown;
|
||||
@@ -145,7 +150,12 @@ export function wrappedLoader<T>({
|
||||
return data as T;
|
||||
} catch (thrown) {
|
||||
if (thrown instanceof Response) {
|
||||
throw new Error(`Response thrown with status code: ${thrown.status}`);
|
||||
throw new Error(
|
||||
`Response thrown with status code: ${thrown.status}`,
|
||||
{
|
||||
cause: thrown,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw thrown;
|
||||
|
||||
@@ -168,7 +168,7 @@ export function nullPaddedDatesOfMonth({ month, year }: MonthYear) {
|
||||
}
|
||||
|
||||
function datesOfMonth({ month, year }: MonthYear) {
|
||||
const dates = [];
|
||||
const dates: Date[] = [];
|
||||
const date = new Date(Date.UTC(year, month, 1));
|
||||
while (date.getUTCMonth() === month) {
|
||||
dates.push(new Date(date));
|
||||
|
||||
@@ -3,9 +3,8 @@ function cyrb128(str: string) {
|
||||
let h2 = 3144134277;
|
||||
let h3 = 1013904242;
|
||||
let h4 = 2773480762;
|
||||
// biome-ignore lint/suspicious/noImplicitAnyLet: biome migration
|
||||
for (let i = 0, k; i < str.length; i++) {
|
||||
k = str.charCodeAt(i);
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const k = str.charCodeAt(i);
|
||||
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
|
||||
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
|
||||
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
|
||||
|
||||
24
biome.json
24
biome.json
@@ -34,7 +34,12 @@
|
||||
"level": "error"
|
||||
},
|
||||
"noSkippedTests": "error",
|
||||
"noParametersOnlyUsedInRecursion": "error"
|
||||
"noParametersOnlyUsedInRecursion": "error",
|
||||
"useArraySortCompare": "error",
|
||||
"noEvolvingTypes": "error",
|
||||
"noReturnAssign": "error",
|
||||
"noNestedPromises": "error",
|
||||
"noUnusedExpressions": "error"
|
||||
},
|
||||
"correctness": {
|
||||
"noUndeclaredVariables": "error",
|
||||
@@ -113,6 +118,10 @@
|
||||
"noSubstr": "error",
|
||||
"useUnifiedTypeSignatures": "error",
|
||||
"useReadonlyClassProperties": "error",
|
||||
"useForOf": "error",
|
||||
"useErrorCause": "error",
|
||||
"useThrowOnlyError": "error",
|
||||
"noNamespace": "error",
|
||||
"useFilenamingConvention": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
@@ -145,7 +154,8 @@
|
||||
"fix": "safe",
|
||||
"level": "error"
|
||||
},
|
||||
"noUselessCatchBinding": "error"
|
||||
"noUselessCatchBinding": "error",
|
||||
"noForEach": "error"
|
||||
},
|
||||
"performance": {
|
||||
"noSyncScripts": "error"
|
||||
@@ -182,6 +192,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["types/**/*.d.ts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noNamespace": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["app/**", "!app/modules/search-params/**"],
|
||||
"plugins": ["./biome-plugins/no-raw-search-params.grit"]
|
||||
|
||||
@@ -417,7 +417,9 @@ async function organizedTournament(
|
||||
startTimes: [dateToDatabaseTimestamp(addHours(new Date(), 2))],
|
||||
});
|
||||
|
||||
const teams = [];
|
||||
const teams: Awaited<
|
||||
ReturnType<typeof factories.TournamentTeamFactory.create>
|
||||
>[] = [];
|
||||
for (let teamNth = 0; teamNth < teamCount; teamNth++) {
|
||||
const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
|
||||
teams.push(
|
||||
|
||||
@@ -172,7 +172,9 @@ export async function createTeams(
|
||||
tournamentId: number,
|
||||
seeds: TeamSeed[],
|
||||
) {
|
||||
const teams = [];
|
||||
const teams: Awaited<
|
||||
ReturnType<typeof factories.TournamentTeamFactory.create>
|
||||
>[] = [];
|
||||
for (const [i, seed] of seeds.entries()) {
|
||||
const presetMembers = seed.members ?? [];
|
||||
const rosterSize = seed.rosterSize ?? ROSTER_SIZE;
|
||||
|
||||
@@ -102,7 +102,8 @@ async function seedLeaderboards(factories: Factories) {
|
||||
const alphaUserIds = [ADMIN_ID, ...mates.map((mate) => mate.id)];
|
||||
const bravoUserIds = enemies.map((enemy) => enemy.id);
|
||||
|
||||
const matches = [];
|
||||
const matches: Awaited<ReturnType<typeof factories.SQMatchFactory.create>>[] =
|
||||
[];
|
||||
for (let i = 0; i < MATCHES_COUNT_NEEDED_FOR_LEADERBOARD; i++) {
|
||||
matches.push(
|
||||
await factories.SQMatchFactory.create(
|
||||
|
||||
@@ -55,7 +55,7 @@ export class UserBuildsPage {
|
||||
await this.locators.changeSortingButton.click();
|
||||
|
||||
const dialog = this.page.getByRole("dialog");
|
||||
for (let i = 0; i < DEFAULT_BUILD_SORT.length; i++) {
|
||||
for (const _ of DEFAULT_BUILD_SORT) {
|
||||
await dialog.getByTestId("delete-sorting-button").click();
|
||||
}
|
||||
await dialog.getByRole("combobox").selectOption(sort);
|
||||
|
||||
@@ -349,7 +349,9 @@ test.describe("Tournament", () => {
|
||||
});
|
||||
|
||||
const captains = await factories.UserFactory.createMany(SEEDED_TEAM_COUNT);
|
||||
const teams = [];
|
||||
const teams: Awaited<
|
||||
ReturnType<typeof factories.TournamentTeamFactory.create>
|
||||
>[] = [];
|
||||
for (const [i, captain] of captains.entries()) {
|
||||
teams.push(
|
||||
await factories.TournamentTeamFactory.create({
|
||||
|
||||
@@ -73,6 +73,16 @@ const SEASONS: Season[] = [
|
||||
},
|
||||
];
|
||||
|
||||
type MigratedDivision = Season["divisions"][number] & {
|
||||
tier: number | null;
|
||||
castTwitchAccounts: string[] | null;
|
||||
castedMatchesInfo: CastedMatchesInfo | null;
|
||||
groupStageIdx: number;
|
||||
playoffsIdx: number;
|
||||
groupStageBracket: any;
|
||||
playoffsBracket: any;
|
||||
};
|
||||
|
||||
async function migrateSeason(trx: Transaction<any>, season: Season) {
|
||||
const signup = await trx
|
||||
.selectFrom("Tournament")
|
||||
@@ -83,7 +93,7 @@ async function migrateSeason(trx: Transaction<any>, season: Season) {
|
||||
// databases without the production league data (dev, tests)
|
||||
if (!signup) return;
|
||||
|
||||
const divisions = [];
|
||||
const divisions: MigratedDivision[] = [];
|
||||
for (const [idx, division] of season.divisions.entries()) {
|
||||
const row = await trx
|
||||
.selectFrom("Tournament")
|
||||
|
||||
@@ -59,8 +59,7 @@ self.addEventListener("notificationclick", (event) => {
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: "window" }).then((windowClients) => {
|
||||
// Check if there is already a window/tab open with the target URL
|
||||
for (let i = 0; i < windowClients.length; i++) {
|
||||
const client = windowClients[i];
|
||||
for (const client of windowClients) {
|
||||
// If so, just focus it.
|
||||
if (client.url === targetUrl && "focus" in client) {
|
||||
return client.focus();
|
||||
|
||||
@@ -22,8 +22,10 @@ invariant(
|
||||
async function loadTournament() {
|
||||
try {
|
||||
return await tournamentFromDB(tournamentId);
|
||||
} catch {
|
||||
throw new Error(`Tournament with id ${tournamentId} not found`);
|
||||
} catch (error) {
|
||||
throw new Error(`Tournament with id ${tournamentId} not found`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ for (const file of fileNames) {
|
||||
let otherLanguageContent: Record<string, string>;
|
||||
try {
|
||||
otherLanguageContent = JSON.parse(otherRawContent);
|
||||
} catch {
|
||||
throw new Error(`failed to parse ${lang}/${file}`);
|
||||
} catch (error) {
|
||||
throw new Error(`failed to parse ${lang}/${file}`, { cause: error });
|
||||
}
|
||||
|
||||
const otherLanguageContentKeys = getKeysWithoutSuffix(
|
||||
@@ -276,7 +276,7 @@ function MDOverviewTable({
|
||||
(name) => name !== "weapons.json" && name !== "gear.json",
|
||||
);
|
||||
|
||||
const rows = [];
|
||||
const rows: string[] = [];
|
||||
|
||||
rows.push(
|
||||
`| Language | Total | ${relevantFiles.map(MD.inlineCode).join(" | ")} |`,
|
||||
@@ -285,7 +285,7 @@ function MDOverviewTable({
|
||||
rows.push(`| :-- | :-: | ${relevantFiles.map(() => ":-:").join(" | ")} |`);
|
||||
|
||||
for (const [lang, missingKeysObj] of Object.entries(missingTranslations)) {
|
||||
const cells = [];
|
||||
const cells: string[] = [];
|
||||
|
||||
cells.push(MD.strong(lang));
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ async function main() {
|
||||
fs.rmSync(stagedPath, { force: true });
|
||||
throw new Error(
|
||||
`Snapshot failed verification, no checkout was touched. The archive is kept at ${archivePath}\n${(error as Error).message}`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ function parseArgs(argv: string[]): {
|
||||
let duration: number | undefined;
|
||||
let outPath: string | undefined;
|
||||
let collectTelemetry = false;
|
||||
// biome-ignore lint/style/useForOf: the index advances inside the loop to consume flag values
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i]!;
|
||||
if (arg === "--fps") fps = Number(argv[++i]);
|
||||
|
||||
Reference in New Issue
Block a user