diff --git a/app/components/elements/Toast.tsx b/app/components/elements/Toast.tsx index f2f34d135..b5b4d1422 100644 --- a/app/components/elements/Toast.tsx +++ b/app/components/elements/Toast.tsx @@ -19,7 +19,7 @@ interface QueuedToast { class ToastQueue { private toasts: QueuedToast[] = []; - private listeners = new Set<() => void>(); + private readonly listeners = new Set<() => void>(); private counter = 0; add(content: SendouToast, options?: { timeout?: number }) { diff --git a/app/features/build-analyzer/core/stats.ts b/app/features/build-analyzer/core/stats.ts index 7e1dfed9c..89e4e4416 100644 --- a/app/features/build-analyzer/core/stats.ts +++ b/app/features/build-analyzer/core/stats.ts @@ -201,7 +201,7 @@ function specialPoint({ const { effect } = abilityPointsToEffects({ abilityPoints: apFromMap({ - abilityPoints: abilityPoints, + abilityPoints, ability: SPECIAL_POINT_ABILITY, }), key: "IncreaseRt_Special", @@ -252,7 +252,7 @@ function specialLost( const { baseEffect, effect } = abilityPointsToEffects({ abilityPoints: apFromMap({ - abilityPoints: abilityPoints, + abilityPoints, ability: SPECIAL_SAVED_AFTER_DEATH_ABILITY, }), key: "SpecialGaugeRt_Restart", diff --git a/app/features/events/events-hooks.browser.test.tsx b/app/features/events/events-hooks.browser.test.tsx index 5c183fcc5..a87535775 100644 --- a/app/features/events/events-hooks.browser.test.tsx +++ b/app/features/events/events-hooks.browser.test.tsx @@ -13,7 +13,10 @@ import type { ServerEvent } from "./events-types"; class FakeEventSource { static instances: FakeEventSource[] = []; - private listeners = new Map void>>(); + private readonly listeners = new Map< + string, + Set<(event: MessageEvent) => void> + >(); url: string; diff --git a/app/features/map-list-generator/core/map-pool.ts b/app/features/map-list-generator/core/map-pool.ts index 5eaa9f6b6..17bccf6fa 100644 --- a/app/features/map-list-generator/core/map-pool.ts +++ b/app/features/map-list-generator/core/map-pool.ts @@ -15,7 +15,7 @@ export type DbMapPoolList = Array< >; export class MapPool { - private source: string | ReadonlyMapPoolObject; + private readonly source: string | ReadonlyMapPoolObject; private asSerialized?: string; private asObject?: ReadonlyMapPoolObject; diff --git a/app/features/map-planner/components/Planner.tsx b/app/features/map-planner/components/Planner.tsx index 6e13729be..36391a743 100644 --- a/app/features/map-planner/components/Planner.tsx +++ b/app/features/map-planner/components/Planner.tsx @@ -287,11 +287,11 @@ export default function Planner() { type: "image", x: point[0], y: point[1], - isLocked: isLocked, + isLocked, id: shapeId, meta: meta ?? {}, props: { - assetId: assetId, + assetId, w: size[0], h: size[1], }, diff --git a/app/features/params/components/WeaponParamsTable.tsx b/app/features/params/components/WeaponParamsTable.tsx index 2ba57c149..e1ab728c0 100644 --- a/app/features/params/components/WeaponParamsTable.tsx +++ b/app/features/params/components/WeaponParamsTable.tsx @@ -408,7 +408,7 @@ function DamageRateInfoSection({ damageMultipliers[String(weaponId)]?.find((m) => m.target === target); return ( - + <> ); })} - + ); } diff --git a/app/features/scanner/core/detectors/scheduler.ts b/app/features/scanner/core/detectors/scheduler.ts index ade301e51..51719035f 100644 --- a/app/features/scanner/core/detectors/scheduler.ts +++ b/app/features/scanner/core/detectors/scheduler.ts @@ -102,8 +102,8 @@ interface DetectorState { } export class DetectorScheduler { - #options: SchedulerOptions; - #states = new Map(); + readonly #options: SchedulerOptions; + readonly #states = new Map(); #maxT = Number.NEGATIVE_INFINITY; #lastActivityT = Number.NEGATIVE_INFINITY; #matchOpenUntilT = Number.NEGATIVE_INFINITY; diff --git a/app/features/scanner/core/timeline/index.ts b/app/features/scanner/core/timeline/index.ts index bfd993e5e..2109a6750 100644 --- a/app/features/scanner/core/timeline/index.ts +++ b/app/features/scanner/core/timeline/index.ts @@ -77,8 +77,8 @@ export type TimelineAction = | { action: "dropped"; reason: "low-confidence" }; export class TimelineBuilder { - #events: DetectedEvent[] = []; - #options: TimelineOptions; + readonly #events: DetectedEvent[] = []; + readonly #options: TimelineOptions; constructor(options: Partial = {}) { this.#options = { ...DEFAULT_TIMELINE_OPTIONS, ...options }; diff --git a/app/features/scanner/core/timeline/same-scoreboard.ts b/app/features/scanner/core/timeline/same-scoreboard.ts index 7c13a38ca..90ff64c0b 100644 --- a/app/features/scanner/core/timeline/same-scoreboard.ts +++ b/app/features/scanner/core/timeline/same-scoreboard.ts @@ -81,10 +81,12 @@ export function sameScoreboardMatch(a: unknown, b: unknown): boolean { // are distinctive per match — near-zero overlap means a different board const pa = paints(da); const pb = paints(db); - if (pa.length >= PAINT_MIN_READ && pb.length >= PAINT_MIN_READ) { - if (multisetOverlap(pa, pb) < Math.min(pa.length, pb.length) / 2) - return false; - } + if ( + pa.length >= PAINT_MIN_READ && + pb.length >= PAINT_MIN_READ && + multisetOverlap(pa, pb) < Math.min(pa.length, pb.length) / 2 + ) + return false; return true; } diff --git a/app/features/scanner/worker/client.ts b/app/features/scanner/worker/client.ts index 48eed2ddd..276561d1b 100644 --- a/app/features/scanner/worker/client.ts +++ b/app/features/scanner/worker/client.ts @@ -43,13 +43,13 @@ interface QueuedFrame { } export class AnalyzerClient { - #worker: Worker; + readonly #worker: Worker; #ready = false; #busy = false; - #onResult: ResultHandler; - #onError: ErrorHandler; - #onDone: DoneHandler | undefined; - #readyPromise: Promise; + readonly #onResult: ResultHandler; + readonly #onError: ErrorHandler; + readonly #onDone: DoneHandler | undefined; + readonly #readyPromise: Promise; #rejectReady: ((error: Error) => void) | undefined; #idleWaiters: (() => void)[] = []; #chunk: PendingChunk | null = null; diff --git a/app/features/sendouq-match/core/skills.server.ts b/app/features/sendouq-match/core/skills.server.ts index 579da8abb..77147f1de 100644 --- a/app/features/sendouq-match/core/skills.server.ts +++ b/app/features/sendouq-match/core/skills.server.ts @@ -41,7 +41,7 @@ export async function calculateMatchSkills({ for (const [index, userId] of winner.entries()) { newSkills.push({ - groupMatchId: groupMatchId, + groupMatchId, identifier: null, mu: winnerTeamNew[index].mu, season, @@ -52,7 +52,7 @@ export async function calculateMatchSkills({ for (const [index, userId] of loser.entries()) { newSkills.push({ - groupMatchId: groupMatchId, + groupMatchId, identifier: null, mu: loserTeamNew[index].mu, season, @@ -75,7 +75,7 @@ export async function calculateMatchSkills({ ); newSkills.push({ - groupMatchId: groupMatchId, + groupMatchId, identifier: winnerTeamIdentifier, mu: winnerGroupNew.mu, season, @@ -83,7 +83,7 @@ export async function calculateMatchSkills({ userId: null, }); newSkills.push({ - groupMatchId: groupMatchId, + groupMatchId, identifier: loserTeamIdentifier, mu: loserGroupNew.mu, season, diff --git a/app/features/sendouq-streams/core/streams.server.ts b/app/features/sendouq-streams/core/streams.server.ts index 5ab3082fb..af1751a33 100644 --- a/app/features/sendouq-streams/core/streams.server.ts +++ b/app/features/sendouq-streams/core/streams.server.ts @@ -22,7 +22,7 @@ export function cachedStreams() { return cachified({ key: SENDOUQ_STREAMS_KEY, - cache: cache, + cache, ttl: ttl(IN_MILLISECONDS.HALF_HOUR), async getFreshValue() { return streamedMatches({ diff --git a/app/features/sendouq/core/SendouQ.server.ts b/app/features/sendouq/core/SendouQ.server.ts index c7f662962..efd0560a2 100644 --- a/app/features/sendouq/core/SendouQ.server.ts +++ b/app/features/sendouq/core/SendouQ.server.ts @@ -57,10 +57,10 @@ const SECONDS_TILL_STALE = class SendouQClass { groups; - #recentMatches; - #isAccurateTiers; - #userSkills; - #intervals; + readonly #recentMatches; + readonly #isAccurateTiers; + readonly #userSkills; + readonly #intervals; usersInQueue; constructor( diff --git a/app/features/top-search/actions/xsearch.player.$id.server.ts b/app/features/top-search/actions/xsearch.player.$id.server.ts index 542a6cf3c..a4a3eb495 100644 --- a/app/features/top-search/actions/xsearch.player.$id.server.ts +++ b/app/features/top-search/actions/xsearch.player.$id.server.ts @@ -30,7 +30,7 @@ export const action = async ({ params }: ActionFunctionArgs) => { ); logger.info("Unlinking player", { - id: id, + id, userId: user.id, }); diff --git a/app/features/tournament-bracket/core/Bracket/SwissBracket.ts b/app/features/tournament-bracket/core/Bracket/SwissBracket.ts index a9516270a..34c4715e9 100644 --- a/app/features/tournament-bracket/core/Bracket/SwissBracket.ts +++ b/app/features/tournament-bracket/core/Bracket/SwissBracket.ts @@ -274,7 +274,7 @@ export class SwissBracket extends Bracket { teamId: winner.id, setWins: 1, setLosses: 0, - mapWins: mapWins, + mapWins, mapLosses: 0, }); } diff --git a/app/features/tournament-bracket/core/Tournament.ts b/app/features/tournament-bracket/core/Tournament.ts index 38db29ea2..75eb20731 100644 --- a/app/features/tournament-bracket/core/Tournament.ts +++ b/app/features/tournament-bracket/core/Tournament.ts @@ -114,13 +114,13 @@ export class Tournament { ctx; /** See {@link TournamentArgs.participatedUsers}, null when this view did not get them. */ readonly participatedUserIds: number[] | null; - private args; - private data; - private _brackets: Array = []; + private readonly args; + private readonly data; + private readonly _brackets: Array = []; private _allBrackets: Bracket[] | undefined; private _derivedMeta: BracketDerivedMeta[] | undefined; private _bracketsMeta: BracketMeta[] | undefined; - private bracketIdxsBeingBuilt = new Set(); + private readonly bracketIdxsBeingBuilt = new Set(); constructor(args: TournamentArgs) { const { data, ctx, bracketsMeta, brackets } = args; diff --git a/app/features/tournament-bracket/core/engine/create/builder.ts b/app/features/tournament-bracket/core/engine/create/builder.ts index a36e6d13b..4e5dcf692 100644 --- a/app/features/tournament-bracket/core/engine/create/builder.ts +++ b/app/features/tournament-bracket/core/engine/create/builder.ts @@ -63,7 +63,7 @@ export class StageCreator { slots: ParticipantSlot[], ): void { const groupId = this.insertGroup({ - stageId: stageId, + stageId, number, }); @@ -87,7 +87,7 @@ export class StageCreator { slotsB: ParticipantSlot[], ): void { const groupId = this.insertGroup({ - stageId: stageId, + stageId, number, }); @@ -105,7 +105,7 @@ export class StageCreator { ): StandardBracketResults { const roundCount = helpers.getUpperBracketRoundCount(slots.length); const groupId = this.insertGroup({ - stageId: stageId, + stageId, number, }); @@ -139,7 +139,7 @@ export class StageCreator { const ordered = ordering[method](losers[losersId++]); const groupId = this.insertGroup({ - stageId: stageId, + stageId, number, }); @@ -179,7 +179,7 @@ export class StageCreator { duels: Duel[], ): void { const groupId = this.insertGroup({ - stageId: stageId, + stageId, number, }); @@ -196,8 +196,8 @@ export class StageCreator { ): void { const roundId = this.insertRound({ number: roundNumber, - stageId: stageId, - groupId: groupId, + stageId, + groupId, }); for (let i = 0; i < matchCount; i++) { @@ -225,9 +225,9 @@ export class StageCreator { this.insertMatch({ number: matchNumber, - stageId: stageId, - groupId: groupId, - roundId: roundId, + stageId, + groupId, + roundId, opponent1, opponent2, winnerSide: null, @@ -235,11 +235,10 @@ export class StageCreator { } /** No ordering for major rounds (the first round must be ordered beforehand), LB minor rounds use the given method. */ - getCurrentDuels(previousDuels: Duel[], currentDuelCount: number): Duel[]; getCurrentDuels( previousDuels: Duel[], currentDuelCount: number, - major: true, + major?: true, ): Duel[]; getCurrentDuels( previousDuels: Duel[], diff --git a/app/features/tournament-bracket/core/engine/create/helpers.test.ts b/app/features/tournament-bracket/core/engine/create/helpers.test.ts index d47484e30..855913a27 100644 --- a/app/features/tournament-bracket/core/engine/create/helpers.test.ts +++ b/app/features/tournament-bracket/core/engine/create/helpers.test.ts @@ -339,9 +339,9 @@ function assertRoundRobin(input: number[], output: [number, number][][]): void { const matchPerRound = Math.floor(n / 2); const roundCount = n % 2 === 0 ? n - 1 : n; - if (output.length !== roundCount) throw Error("Round count is wrong"); + if (output.length !== roundCount) throw new Error("Round count is wrong"); if (!output.every((round) => round.length === matchPerRound)) - throw Error("Not every round has the good number of matches"); + throw new Error("Not every round has the good number of matches"); const checkAllOpponents = Object.fromEntries( input.map((element) => [element, new Set()]), @@ -351,22 +351,22 @@ function assertRoundRobin(input: number[], output: [number, number][][]): void { const checkUnique = new Set(); for (const match of round) { - if (match.length !== 2) throw Error("One match is not a pair"); + if (match.length !== 2) throw new Error("One match is not a pair"); if (checkUnique.has(match[0])) - throw Error("This team is already playing"); + throw new Error("This team is already playing"); checkUnique.add(match[0]); if (checkUnique.has(match[1])) - throw Error("This team is already playing"); + throw new Error("This team is already playing"); checkUnique.add(match[1]); if (checkAllOpponents[match[0]].has(match[1])) - throw Error("The team has already matched this team"); + throw new Error("The team has already matched this team"); checkAllOpponents[match[0]].add(match[1]); if (checkAllOpponents[match[1]].has(match[0])) - throw Error("The team has already matched this team"); + throw new Error("The team has already matched this team"); checkAllOpponents[match[1]].add(match[0]); } } @@ -380,9 +380,9 @@ function assertAbDivisionRoundRobin( const roundCount = Math.max(divisionA.length, divisionB.length); const matchesPerRound = Math.min(divisionA.length, divisionB.length); - if (output.length !== roundCount) throw Error("Round count is wrong"); + if (output.length !== roundCount) throw new Error("Round count is wrong"); if (!output.every((round) => round.length === matchesPerRound)) - throw Error("Not every round has the good number of matches"); + throw new Error("Not every round has the good number of matches"); const aSet = new Set(divisionA); const bSet = new Set(divisionB); @@ -392,26 +392,28 @@ function assertAbDivisionRoundRobin( const playingInRound = new Set(); for (const match of round) { - if (match.length !== 2) throw Error("One match is not a pair"); + if (match.length !== 2) throw new Error("One match is not a pair"); const [a, b] = match; - if (!aSet.has(a)) throw Error(`${a} is not a division A participant`); - if (!bSet.has(b)) throw Error(`${b} is not a division B participant`); + if (!aSet.has(a)) throw new Error(`${a} is not a division A participant`); + if (!bSet.has(b)) throw new Error(`${b} is not a division B participant`); - if (playingInRound.has(a)) throw Error("This team is already playing"); + if (playingInRound.has(a)) + throw new Error("This team is already playing"); playingInRound.add(a); - if (playingInRound.has(b)) throw Error("This team is already playing"); + if (playingInRound.has(b)) + throw new Error("This team is already playing"); playingInRound.add(b); const pairingKey = `${a}-${b}`; if (seenPairings.has(pairingKey)) - throw Error("The teams have already been paired"); + throw new Error("The teams have already been paired"); seenPairings.add(pairingKey); } } if (seenPairings.size !== divisionA.length * divisionB.length) - throw Error("Not every A vs B pairing was generated"); + throw new Error("Not every A vs B pairing was generated"); } diff --git a/app/features/tournament-bracket/core/engine/create/helpers.ts b/app/features/tournament-bracket/core/engine/create/helpers.ts index 1d9a2a7e6..536db7429 100644 --- a/app/features/tournament-bracket/core/engine/create/helpers.ts +++ b/app/features/tournament-bracket/core/engine/create/helpers.ts @@ -80,11 +80,12 @@ export function makeAbDivisionGroups( divisionB: T[], groupCount: number, ): { a: T[]; b: T[] }[] { - if (groupCount <= 0) throw Error("Group count must be strictly positive."); + if (groupCount <= 0) + throw new Error("Group count must be strictly positive."); if (divisionA.length !== divisionB.length) { if (groupCount !== 1) - throw Error( + throw new Error( "Uneven A/B divisions are only supported with a single group.", ); @@ -92,7 +93,7 @@ export function makeAbDivisionGroups( } if (divisionA.length % groupCount !== 0) - throw Error("Pool size must be divisible by group count."); + throw new Error("Pool size must be divisible by group count."); const aOrdered = ordering["groups.seed_optimized"](divisionA, groupCount); const bOrdered = ordering["groups.seed_optimized"](divisionB, groupCount); @@ -152,14 +153,16 @@ export function ensureValidSize( participantCount: number, ): void { if (participantCount < 2) - throw Error("Impossible to create a stage with less than 2 participants."); + throw new Error( + "Impossible to create a stage with less than 2 participants.", + ); if (stageType === "round_robin") { return; } if (!Number.isInteger(Math.log2(participantCount))) - throw Error( + throw new Error( "The library only supports a participant count which is a power of two.", ); } diff --git a/app/features/tournament-bracket/core/engine/create/index.ts b/app/features/tournament-bracket/core/engine/create/index.ts index 69d4fc76a..bb8e56ed2 100644 --- a/app/features/tournament-bracket/core/engine/create/index.ts +++ b/app/features/tournament-bracket/core/engine/create/index.ts @@ -50,7 +50,7 @@ export function createResolved(input: ResolvedCreateBracketInput): BracketData { createDoubleElimination(creator); break; default: - throw Error("Unknown stage type."); + throw new Error("Unknown stage type."); } return creator.data; @@ -65,7 +65,8 @@ function attachRoundMaps( const resolveRound = (roundId: number) => { const round = roundsById.get(roundId); - if (!round) throw Error(`No round found for map list round id ${roundId}`); + if (!round) + throw new Error(`No round found for map list round id ${roundId}`); return round; }; @@ -75,7 +76,7 @@ function attachRoundMaps( data.round.map((round) => round.number), ).size; if (mapsInput.length !== distinctRoundNumberCount) { - throw Error("Invalid map list count"); + throw new Error("Invalid map list count"); } const mapsByRoundNumber = new Map( @@ -87,7 +88,8 @@ function attachRoundMaps( for (const round of data.round) { const maps = mapsByRoundNumber.get(round.number); - if (!maps) throw Error(`No maps found for round number ${round.number}`); + if (!maps) + throw new Error(`No maps found for round number ${round.number}`); round.maps = { ...maps }; } @@ -95,7 +97,7 @@ function attachRoundMaps( } if (mapsInput.length !== data.round.length) { - throw Error("Invalid map list count"); + throw new Error("Invalid map list count"); } for (const input of mapsInput) { @@ -103,7 +105,7 @@ function attachRoundMaps( } for (const round of data.round) { - if (!round.maps) throw Error(`Round id ${round.id} is missing maps`); + if (!round.maps) throw new Error(`Round id ${round.id} is missing maps`); } } diff --git a/app/features/tournament-bracket/core/engine/create/round-robin.ts b/app/features/tournament-bracket/core/engine/create/round-robin.ts index 68c233bcd..8a9e689c5 100644 --- a/app/features/tournament-bracket/core/engine/create/round-robin.ts +++ b/app/features/tournament-bracket/core/engine/create/round-robin.ts @@ -37,10 +37,10 @@ function getRoundRobinGroups(creator: StageCreator): ParticipantSlot[][] { creator.settings.groupCount === undefined || !Number.isInteger(creator.settings.groupCount) ) - throw Error("You must specify a group count for round-robin stages."); + throw new Error("You must specify a group count for round-robin stages."); if (creator.settings.groupCount <= 0) - throw Error("You must provide a strictly positive group count."); + throw new Error("You must provide a strictly positive group count."); const slots = creator.getSlots(); const ordered = ordering["groups.seed_optimized"]( @@ -59,19 +59,21 @@ function getAbDivisionGroups(creator: StageCreator): { creator.settings.groupCount === undefined || !Number.isInteger(creator.settings.groupCount) ) - throw Error("You must specify a group count for round-robin stages."); + throw new Error("You must specify a group count for round-robin stages."); if (creator.settings.groupCount <= 0) - throw Error("You must provide a strictly positive group count."); + throw new Error("You must provide a strictly positive group count."); const abDivisions = creator.input.abDivisions; if (!abDivisions) - throw Error("abDivisions must be provided when hasAbDivisions is enabled."); + throw new Error( + "abDivisions must be provided when hasAbDivisions is enabled.", + ); const slots = creator.getSlots(); if (abDivisions.length !== slots.length) - throw Error("abDivisions length must match the seeding length."); + throw new Error("abDivisions length must match the seeding length."); const divisionA: ParticipantSlot[] = []; const divisionB: ParticipantSlot[] = []; @@ -79,13 +81,13 @@ function getAbDivisionGroups(creator: StageCreator): { for (let i = 0; i < slots.length; i++) { const slot = slots[i]; if (slot === null) - throw Error("BYEs are not supported with A/B divisions."); + throw new Error("BYEs are not supported with A/B divisions."); const division = abDivisions[i]; if (division === 0) divisionA.push(slot); else if (division === 1) divisionB.push(slot); else - throw Error( + throw new Error( `Participant at seed ${i + 1} is missing an A/B division assignment.`, ); } diff --git a/app/features/tournament-bracket/core/engine/create/swiss.ts b/app/features/tournament-bracket/core/engine/create/swiss.ts index facdeff44..2cb0e10aa 100644 --- a/app/features/tournament-bracket/core/engine/create/swiss.ts +++ b/app/features/tournament-bracket/core/engine/create/swiss.ts @@ -86,7 +86,7 @@ function firstRoundMatches({ id: matchId++, groupId: groupIdx, stageId: 0, - roundId: roundId, + roundId, number: i + 1, opponent1: { id: upper, @@ -103,7 +103,7 @@ function firstRoundMatches({ id: matchId++, groupId: groupIdx, stageId: 0, - roundId: roundId, + roundId, number: upperHalf.length + 1, opponent1: { id: bye, diff --git a/app/features/tournament-bracket/core/engine/propagation/dropped-teams.ts b/app/features/tournament-bracket/core/engine/propagation/dropped-teams.ts index 241106aa1..02aba3bbf 100644 --- a/app/features/tournament-bracket/core/engine/propagation/dropped-teams.ts +++ b/app/features/tournament-bracket/core/engine/propagation/dropped-teams.ts @@ -32,7 +32,7 @@ export function endDroppedTeamMatches( })(); const stored = store.matchById(match.id); - if (!stored) throw Error("Match not found."); + if (!stored) throw new Error("Match not found."); propagator.updateMatch( stored, diff --git a/app/features/tournament-bracket/core/engine/propagation/reset-result.ts b/app/features/tournament-bracket/core/engine/propagation/reset-result.ts index 781572fd7..4daf6cd70 100644 --- a/app/features/tournament-bracket/core/engine/propagation/reset-result.ts +++ b/app/features/tournament-bracket/core/engine/propagation/reset-result.ts @@ -17,13 +17,13 @@ export function resetMatchResults( const propagator = new Propagator(store); const stored = store.matchById(matchId); - if (!stored) throw Error("Match not found."); + if (!stored) throw new Error("Match not found."); const stage = store.stageById(stored.stageId); - if (!stage) throw Error("Stage not found."); + if (!stage) throw new Error("Stage not found."); const group = store.groupById(stored.groupId); - if (!group) throw Error("Group not found."); + if (!group) throw new Error("Group not found."); const { roundNumber, roundCount } = propagator.getRoundPositionalInfo( stored.roundId, @@ -48,7 +48,7 @@ export function resetMatchResults( !isMatchByeCompleted(match), ) ) - throw Error("The match is locked."); + throw new Error("The match is locked."); helpers.clearWinner(stored); store.markMatchChanged(stored); diff --git a/app/features/tournament-bracket/core/engine/propagation/set.ts b/app/features/tournament-bracket/core/engine/propagation/set.ts index c8cd949bd..dbf067ebc 100644 --- a/app/features/tournament-bracket/core/engine/propagation/set.ts +++ b/app/features/tournament-bracket/core/engine/propagation/set.ts @@ -123,5 +123,5 @@ function sideOfTeam(match: MatchData, teamId: number): 0 | 1 { if (match.opponent1?.id === teamId) return 0; if (match.opponent2?.id === teamId) return 1; - throw Error(`Team id ${teamId} is not in the match`); + throw new Error(`Team id ${teamId} is not in the match`); } diff --git a/app/features/tournament-bracket/core/engine/propagation/store.ts b/app/features/tournament-bracket/core/engine/propagation/store.ts index e7b11411f..9cdf3d93a 100644 --- a/app/features/tournament-bracket/core/engine/propagation/store.ts +++ b/app/features/tournament-bracket/core/engine/propagation/store.ts @@ -82,7 +82,7 @@ export class Store { /** Records that a match row of this store was mutated. */ markMatchChanged(match: MatchData): void { if (this.matchesById.get(match.id) !== match) - throw Error("Match is not a row of this store."); + throw new Error("Match is not a row of this store."); this.changedMatchIds.add(match.id); } diff --git a/app/features/tournament-bracket/core/engine/propagation/traversal.ts b/app/features/tournament-bracket/core/engine/propagation/traversal.ts index 97e38455d..07a10363b 100644 --- a/app/features/tournament-bracket/core/engine/propagation/traversal.ts +++ b/app/features/tournament-bracket/core/engine/propagation/traversal.ts @@ -31,10 +31,10 @@ export class Propagator { ); const stage = this.store.stageById(match.stageId); - if (!stage) throw Error("Stage not found."); + if (!stage) throw new Error("Stage not found."); const group = this.store.groupById(match.groupId); - if (!group) throw Error("Group not found."); + if (!group) throw new Error("Group not found."); const matchLocation = helpers.getMatchLocation(stage.type, group.number); @@ -48,10 +48,10 @@ export class Propagator { force?: boolean, ): void { if (!force && matchStatus(this.store.data, stored.id) === "PENDING") - throw Error("The match is locked."); + throw new Error("The match is locked."); const stage = this.store.stageById(stored.stageId); - if (!stage) throw Error("Stage not found."); + if (!stage) throw new Error("Stage not found."); const resultChanged = helpers.setMatchResults( stored, @@ -137,7 +137,7 @@ export class Propagator { winnerSide?: Side, ): void { if (matchLocation === "final_group") { - if (!nextMatches[0]) throw Error("First next match is null."); + if (!nextMatches[0]) throw new Error("First next match is null."); setNextOpponent(nextMatches[0], "opponent1", match, "opponent1"); setNextOpponent(nextMatches[0], "opponent2", match, "opponent2"); this.store.markMatchChanged(nextMatches[0]); @@ -157,7 +157,7 @@ export class Propagator { } if (nextMatches.length !== 2) return; - if (!nextMatches[1]) throw Error("Second next match is null."); + if (!nextMatches[1]) throw new Error("Second next match is null."); // Second match is the consolation final (SE) or a loser bracket match (DE). if (matchLocation === "single_bracket") { @@ -193,7 +193,7 @@ export class Propagator { getRoundPositionalInfo(roundId: number): RoundPositionalInfo { const round = this.store.roundById(roundId); - if (!round) throw Error("Round not found."); + if (!round) throw new Error("Round not found."); return { roundNumber: round.number, @@ -228,7 +228,7 @@ export class Propagator { case "final_group": return this.getNextMatchesFinal(match, roundNumber, roundCount); default: - throw Error("Unknown bracket kind."); + throw new Error("Unknown bracket kind."); } } @@ -378,7 +378,7 @@ export class Propagator { /** The only bracket in single elimination, the winner bracket in double elimination. */ private getUpperBracket(stageId: number): GroupData { const winnerBracket = this.store.groupByNumber(stageId, 1); - if (!winnerBracket) throw Error("Winner bracket not found."); + if (!winnerBracket) throw new Error("Winner bracket not found."); return winnerBracket; } @@ -386,7 +386,7 @@ export class Propagator { private participantCount(stageId: number): number { const upperBracket = this.getUpperBracket(stageId); const firstRound = this.store.roundByNumber(upperBracket.id, 1); - if (!firstRound) throw Error("First round not found."); + if (!firstRound) throw new Error("First round not found."); return this.store.matchCountInRound(firstRound.id) * 2; } @@ -424,11 +424,11 @@ export class Propagator { ): MatchData { const round = this.store.roundByNumber(groupId, roundNumber); - if (!round) throw Error("Round not found."); + if (!round) throw new Error("Round not found."); const match = this.store.matchByNumber(round.id, matchNumber); - if (!match) throw Error("Match not found."); + if (!match) throw new Error("Match not found."); return match; } diff --git a/app/features/tournament-bracket/core/toMapList.ts b/app/features/tournament-bracket/core/toMapList.ts index 8be6e3eea..1ca1be847 100644 --- a/app/features/tournament-bracket/core/toMapList.ts +++ b/app/features/tournament-bracket/core/toMapList.ts @@ -142,9 +142,9 @@ function sortRounds( doubleEliminationGroupRank(b.groupId); if (rankDiff !== 0) return rankDiff; } - if (type === "single_elimination") { - // finals and 3rd place match last - if (a.groupId !== b.groupId) return a.groupId - b.groupId; + // finals and 3rd place match last + if (type === "single_elimination" && a.groupId !== b.groupId) { + return a.groupId - b.groupId; } return a.number - b.number; diff --git a/app/features/tournament-bracket/routes/to.$id.brackets.finalize.tsx b/app/features/tournament-bracket/routes/to.$id.brackets.finalize.tsx index 4bd128d38..5a1164248 100644 --- a/app/features/tournament-bracket/routes/to.$id.brackets.finalize.tsx +++ b/app/features/tournament-bracket/routes/to.$id.brackets.finalize.tsx @@ -207,7 +207,7 @@ function NewBadgeReceiversSelector({ tournament.minMembersPerTeam === newOwnerStanding.members.length; newReceivers.push({ - badgeId: badgeId, + badgeId, tournamentTeamId: newOwnerTournamentTeamId, userIds: defaultSelected ? newOwnerStanding.members.map((m) => m.userId) diff --git a/app/features/tournament/tournament-utils.ts b/app/features/tournament/tournament-utils.ts index c1b45a3f6..cac429c33 100644 --- a/app/features/tournament/tournament-utils.ts +++ b/app/features/tournament/tournament-utils.ts @@ -435,7 +435,7 @@ export function getBracketProgressionLabel( while (j < prefix.length && j < name.length && prefix[j] === name[j]) { j++; } - prefix = prefix.substring(0, j); + prefix = prefix.slice(0, j); if (prefix === "") break; } diff --git a/app/features/trophies/components/Trophy.tsx b/app/features/trophies/components/Trophy.tsx index 8937e716e..f91c5ec98 100644 --- a/app/features/trophies/components/Trophy.tsx +++ b/app/features/trophies/components/Trophy.tsx @@ -127,7 +127,7 @@ export function Trophy({ try { viewer.setState(JSON.parse(modelState)); - } catch (_) { + } catch { setError(true); return; } diff --git a/app/features/user-page/core/widgets/widget-form-schemas.ts b/app/features/user-page/core/widgets/widget-form-schemas.ts index ee7dfac69..195c4a7b6 100644 --- a/app/features/user-page/core/widgets/widget-form-schemas.ts +++ b/app/features/user-page/core/widgets/widget-form-schemas.ts @@ -206,7 +206,7 @@ function pastedTierListUrlToSearchParams(value: string) { if (!value.includes("/tier-list-maker")) return value; try { - return new URL(value, "https://sendou.ink").search.substring(1); + return new URL(value, "https://sendou.ink").search.slice(1); } catch { return value; } diff --git a/app/features/vods/vods-schemas.ts b/app/features/vods/vods-schemas.ts index c1be22d23..1378163ce 100644 --- a/app/features/vods/vods-schemas.ts +++ b/app/features/vods/vods-schemas.ts @@ -38,7 +38,7 @@ const videoMatchSchema = v.object({ "Invalid time format. Use HH:MM:SS or MM:SS", ), ), - stageId: stageId, + stageId, mode: modeShort, weapons: v.array(weaponSplId), }); diff --git a/app/hooks/useAutoRerender.ts b/app/hooks/useAutoRerender.ts index 3290fbdd3..721aa86cc 100644 --- a/app/hooks/useAutoRerender.ts +++ b/app/hooks/useAutoRerender.ts @@ -58,8 +58,7 @@ function getTickStore(everyMs: number, alignToMs: number): TickStore { * Rerenders periodically, returning the latest tick's `Date`: consume it so React Compiler can't memoize the * rerender away. Components on the same interval and `alignTo` share one timer, ticking at exactly `alignTo + N*interval`. */ -export function useAutoRerender(): Date; -export function useAutoRerender(every: "second" | "ten seconds"): Date; +export function useAutoRerender(every?: "second" | "ten seconds"): Date; export function useAutoRerender( every: "minute", options: AutoRerenderOptions, diff --git a/app/modules/i18n/resources.server.ts b/app/modules/i18n/resources.server.ts index ee6a938c1..2a8d83fb7 100644 --- a/app/modules/i18n/resources.server.ts +++ b/app/modules/i18n/resources.server.ts @@ -496,34 +496,34 @@ export const resources = { welcome: welcomeEsUs, }, en: { - gear: gear, - faq: faq, - forms: forms, - friends: friends, - weapons: weapons, + gear, + faq, + forms, + friends, + weapons, schedule: scheduleEn, scrims: scrimsEn, - settings: settings, - common: common, + settings, + common, "game-badges": gameBadges, "game-misc": gameMisc, - tournament: tournament, - front: front, - user: user, - q: q, - art: art, - builds: builds, - lfg: lfg, - vods: vods, - calendar: calendar, - org: org, - params: params, - badges: badges, - contributions: contributions, - team: team, - trophies: trophies, + tournament, + front, + user, + q, + art, + builds, + lfg, + vods, + calendar, + org, + params, + badges, + contributions, + team, + trophies, "tier-list-maker": tierListMaker, - analyzer: analyzer, + analyzer, welcome: welcomeEn, }, ko: { diff --git a/app/modules/virtualizer/core.ts b/app/modules/virtualizer/core.ts index 59d5f6bb9..559e18ba0 100644 --- a/app/modules/virtualizer/core.ts +++ b/app/modules/virtualizer/core.ts @@ -25,11 +25,11 @@ export interface VirtualItem { */ export class VirtualizerCore { private count: number; - private estimatedSize: number; - private gap: number; - private overscan: number; - private measuredSizes = new Map(); - private offsets: number[] = []; + private readonly estimatedSize: number; + private readonly gap: number; + private readonly overscan: number; + private readonly measuredSizes = new Map(); + private readonly offsets: number[] = []; /** Entries of `offsets` before this index are up to date. */ private validOffsets = 0; diff --git a/app/routines/routine.server.ts b/app/routines/routine.server.ts index 39ff15901..f09c765f9 100644 --- a/app/routines/routine.server.ts +++ b/app/routines/routine.server.ts @@ -2,7 +2,7 @@ import { logger } from "../utils/logger"; export class Routine { readonly name; - private func; + private readonly func; constructor({ name, diff --git a/app/utils/compression.ts b/app/utils/compression.ts index e00031083..503d35e6c 100644 --- a/app/utils/compression.ts +++ b/app/utils/compression.ts @@ -15,7 +15,7 @@ export function compressToBase64( const base64 = btoa(binary); if (!options?.urlSafe) return base64; - return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]+$/, ""); } /** diff --git a/app/utils/oklch-gamut.ts b/app/utils/oklch-gamut.ts index 308083b03..74c4fa762 100644 --- a/app/utils/oklch-gamut.ts +++ b/app/utils/oklch-gamut.ts @@ -103,7 +103,7 @@ function compute_max_saturation(a: number, b: number): number { const f1 = wl * l_dS + wm * m_dS + ws * s_dS; const f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2; - S = S - (f * f1) / (f1 * f1 - 0.5 * f * f2); + S -= (f * f1) / (f1 * f1 - 0.5 * f * f2); } return S; diff --git a/biome.json b/biome.json index fe68e703b..ab867a183 100644 --- a/biome.json +++ b/biome.json @@ -99,6 +99,20 @@ "useSymbolDescription": "error", "useGroupedAccessorPairs": "error", "useSpreadOverApply": "error", + "useCollapsedIf": { + "fix": "safe", + "level": "error" + }, + "useConsistentObjectDefinitions": { + "fix": "safe", + "level": "error" + }, + "useThrowNewError": "error", + "useShorthandAssign": "error", + "useFragmentSyntax": "error", + "noSubstr": "error", + "useUnifiedTypeSignatures": "error", + "useReadonlyClassProperties": "error", "useFilenamingConvention": { "level": "error", "options": { @@ -122,7 +136,16 @@ "useWhile": { "fix": "safe", "level": "error" - } + }, + "noUselessStringConcat": { + "fix": "safe", + "level": "error" + }, + "noDivRegex": { + "fix": "safe", + "level": "error" + }, + "noUselessCatchBinding": "error" }, "performance": { "noSyncScripts": "error" diff --git a/scripts/scanner/build-glyph-atlas.ts b/scripts/scanner/build-glyph-atlas.ts index 1db909ef5..acfe0fda5 100644 --- a/scripts/scanner/build-glyph-atlas.ts +++ b/scripts/scanner/build-glyph-atlas.ts @@ -111,7 +111,7 @@ const NAME_GREEK = "ια"; // ι: "Rιppιng_H", α: "◇Dαrz™" (special-symb * Scoreboard-names only (not death-tag) until attested, like NAME_GREEK. */ const NAME_SYMBOLS = - "′‘’‚‛…″“”„←→↑↓⇒⇔˜€∞√∀⊂⊃∴∵∂№♭♀♂◎◇◆△▲▽▼†※™" + "『』【】〈〉《》〔〕々〆〇〃~"; + "′‘’‚‛…″“”„←→↑↓⇒⇔˜€∞√∀⊂⊃∴∵∂№♭♀♂◎◇◆△▲▽▼†※™『』【】〈〉《》〔〕々〆〇〃~"; /** * Render the key char but emit it as the value: in-game names show "•" as a