Enable Biome rules 3

This commit is contained in:
Kalle
2026-09-06 14:24:21 +03:00
parent 17a1e27048
commit e5196ca4d5
47 changed files with 218 additions and 120 deletions

View File

@@ -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();
};

View File

@@ -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 });
}

View File

@@ -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);

View File

@@ -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(

View File

@@ -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;

View File

@@ -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];

View File

@@ -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(

View File

@@ -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 },
);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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];

View File

@@ -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 {

View File

@@ -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"]);
});

View File

@@ -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. */

View File

@@ -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

View File

@@ -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 = (

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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";
}
}

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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 +=

View File

@@ -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)));
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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),
);
});
});
});

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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 () => {

View File

@@ -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;

View File

@@ -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));

View File

@@ -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);

View File

@@ -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"]

View File

@@ -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(

View File

@@ -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;

View File

@@ -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(

View File

@@ -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);

View File

@@ -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({

View File

@@ -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")

View File

@@ -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();

View File

@@ -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,
});
}
}

View File

@@ -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));

View File

@@ -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 },
);
}

View File

@@ -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]);