From 0595eafe31e14f327f52725f4b4980177d7a3215 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 22:22:46 +0000 Subject: [PATCH] fix: resolve all oxlint errors from CI - Remove unused imports (findMatchSubGamesByMatchId, isNull, or, MatchRecord, STAGE_NAMES) - Replace all != / == with !== / === throughout match-sync and components - Remove all no-non-null-assertion violations: use Map get-or-initialize pattern, remove guarded assertions (playoffMatch.round), use 'as' cast for filter-then-map pattern - Replace .sort() with .toSorted() in 5 locations - Merge duplicate react-router import in tournament.tsx - Remove unused LoaderData type alias in tournament.tsx - Rename unused 'stage' param to '_stage' in Cs2TournamentBracket - Use ?? [] in pandascore.test.ts to eliminate non-null assertions on subGames https://claude.ai/code/session_01WUUM7uWzFoSkGcZRhnEKG6 --- .../scoring/Cs2TournamentBracket.tsx | 18 ++++++++--------- app/components/scoring/MatchSchedule.tsx | 12 +++++++---- ...-seasons.$id.events.$eventId.cs2-setup.tsx | 10 +++++++--- ...sons.$id.events.$eventId.swiss-matches.tsx | 20 ++++++++++++------- ...rts-seasons.$sportsSeasonId.tournament.tsx | 7 ++----- .../match-sync/__tests__/pandascore.test.ts | 17 ++++++++-------- app/services/match-sync/espn-schedule.ts | 4 ++-- app/services/match-sync/index.ts | 12 +++++------ app/services/match-sync/pandascore.ts | 4 ++-- 9 files changed, 57 insertions(+), 47 deletions(-) diff --git a/app/components/scoring/Cs2TournamentBracket.tsx b/app/components/scoring/Cs2TournamentBracket.tsx index 31ac74d..6843064 100644 --- a/app/components/scoring/Cs2TournamentBracket.tsx +++ b/app/components/scoring/Cs2TournamentBracket.tsx @@ -18,12 +18,6 @@ interface Cs2TournamentBracketProps { userParticipantIds?: string[]; } -const STAGE_NAMES: Record = { - 1: "Opening Stage", - 2: "Challengers Stage", - 3: "Legends Stage", -}; - const STATUS_BADGE: Record = { scheduled: "bg-muted text-muted-foreground text-xs px-2 py-0.5 rounded", in_progress: "bg-amber-500/15 text-amber-400 border border-amber-500/30 text-xs px-2 py-0.5 rounded", @@ -85,7 +79,7 @@ function MatchCard({ match }: { match: MatchWithRelations }) { ); } -function SwissStageTab({ stage, matches }: { stage: number; matches: MatchWithRelations[] }) { +function SwissStageTab({ stage: _stage, matches }: { stage: number; matches: MatchWithRelations[] }) { if (matches.length === 0) { return

No matches for this stage yet.

; } @@ -94,11 +88,15 @@ function SwissStageTab({ stage, matches }: { stage: number; matches: MatchWithRe const rounds = new Map(); for (const m of matches) { const r = m.matchRound ?? 0; - if (!rounds.has(r)) rounds.set(r, []); - rounds.get(r)!.push(m); + let roundGroup = rounds.get(r); + if (!roundGroup) { + roundGroup = []; + rounds.set(r, roundGroup); + } + roundGroup.push(m); } - const sortedRounds = [...rounds.entries()].sort(([a], [b]) => a - b); + const sortedRounds = [...rounds.entries()].toSorted(([a], [b]) => a - b); return (
diff --git a/app/components/scoring/MatchSchedule.tsx b/app/components/scoring/MatchSchedule.tsx index ed6edf2..3da0cae 100644 --- a/app/components/scoring/MatchSchedule.tsx +++ b/app/components/scoring/MatchSchedule.tsx @@ -74,7 +74,7 @@ function MatchRow({ match }: { match: MatchWithRelations }) {
)} - {match.matchday != null && ( + {match.matchday !== null && (
Matchday {match.matchday}
)} @@ -94,11 +94,15 @@ function MatchRow({ match }: { match: MatchWithRelations }) { function groupByMatchday(matches: MatchWithRelations[]) { const groups = new Map(); for (const m of matches) { - const key = m.matchday != null + const key = m.matchday !== null ? `Matchday ${m.matchday}` : m.scheduledAt?.toISOString().slice(0, 10) ?? "TBD"; - if (!groups.has(key)) groups.set(key, []); - groups.get(key)!.push(m); + let group = groups.get(key); + if (!group) { + group = []; + groups.set(key, group); + } + group.push(m); } return groups; } diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.cs2-setup.tsx b/app/routes/admin.sports-seasons.$id.events.$eventId.cs2-setup.tsx index ed9e90b..db4e759 100644 --- a/app/routes/admin.sports-seasons.$id.events.$eventId.cs2-setup.tsx +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.cs2-setup.tsx @@ -381,8 +381,12 @@ export default function AdminCs2Setup() { const rounds = new Map(); for (const m of stageMatches) { const r = m.matchRound ?? 0; - if (!rounds.has(r)) rounds.set(r, []); - rounds.get(r)!.push(m); + let roundGroup = rounds.get(r); + if (!roundGroup) { + roundGroup = []; + rounds.set(r, roundGroup); + } + roundGroup.push(m); } return ( @@ -391,7 +395,7 @@ export default function AdminCs2Setup() { Stage {stage} — {STAGE_NAMES[stage] ?? ''}
- {[...rounds.entries()].sort(([a], [b]) => a - b).map(([round, matches]) => ( + {[...rounds.entries()].toSorted(([a], [b]) => a - b).map(([round, matches]) => (

Round {round} diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.swiss-matches.tsx b/app/routes/admin.sports-seasons.$id.events.$eventId.swiss-matches.tsx index 0bea166..7b13e2b 100644 --- a/app/routes/admin.sports-seasons.$id.events.$eventId.swiss-matches.tsx +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.swiss-matches.tsx @@ -10,7 +10,6 @@ import { updateSeasonMatch, deleteSeasonMatch, upsertMatchSubGame, - findMatchSubGamesByMatchId, } from '~/models/season-match'; import type { MatchStatus } from '~/models/season-match'; import { Button } from '~/components/ui/button'; @@ -158,10 +157,17 @@ export default function AdminSwissMatches() { for (const m of matches) { const stage = m.matchStage ?? 0; const round = m.matchRound ?? 0; - if (!byStage.has(stage)) byStage.set(stage, new Map()); - const stageMap = byStage.get(stage)!; - if (!stageMap.has(round)) stageMap.set(round, []); - stageMap.get(round)!.push(m); + let stageMap = byStage.get(stage); + if (!stageMap) { + stageMap = new Map(); + byStage.set(stage, stageMap); + } + let roundList = stageMap.get(round); + if (!roundList) { + roundList = []; + stageMap.set(round, roundList); + } + roundList.push(m); } return ( @@ -242,13 +248,13 @@ export default function AdminSwissMatches() { {matches.length === 0 ? (

No matches yet.

) : ( - [...byStage.entries()].sort(([a], [b]) => a - b).map(([stage, rounds]) => ( + [...byStage.entries()].toSorted(([a], [b]) => a - b).map(([stage, rounds]) => ( Stage {stage} — {STAGE_NAMES[stage] ?? ''} - {[...rounds.entries()].sort(([a], [b]) => a - b).map(([round, roundMatches]) => ( + {[...rounds.entries()].toSorted(([a], [b]) => a - b).map(([round, roundMatches]) => (

Round {round}

diff --git a/app/routes/sports-seasons.$sportsSeasonId.tournament.tsx b/app/routes/sports-seasons.$sportsSeasonId.tournament.tsx index aa02b49..9f589be 100644 --- a/app/routes/sports-seasons.$sportsSeasonId.tournament.tsx +++ b/app/routes/sports-seasons.$sportsSeasonId.tournament.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { useRevalidator } from "react-router"; +import { Link, useRevalidator } from "react-router"; import { eq, inArray } from "drizzle-orm"; import type { Route } from "./+types/sports-seasons.$sportsSeasonId.tournament"; @@ -11,7 +11,6 @@ import { getBracketTemplate, getOrderedRoundsFromMatches } from "~/lib/bracket-t import { Cs2TournamentBracket } from "~/components/scoring/Cs2TournamentBracket"; import { MatchSchedule } from "~/components/scoring/MatchSchedule"; import { ArrowLeft } from "lucide-react"; -import { Link } from "react-router"; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [{ title: `${data?.sportsSeason?.name ?? "Tournament"} — Schedule & Results` }]; @@ -64,7 +63,7 @@ export async function loader({ params }: Route.LoaderArgs) { const hasLiveMatches = seasonMatches.some((m) => m.status === "in_progress") || - playoffMatches.some((m) => !m.isComplete && (m.participant1Id != null || m.participant2Id != null)); + playoffMatches.some((m) => !m.isComplete && (m.participant1Id !== null || m.participant2Id !== null)); return { sportsSeason, @@ -77,8 +76,6 @@ export async function loader({ params }: Route.LoaderArgs) { }; } -type LoaderData = Awaited>; - export default function TournamentPage({ loaderData }: Route.ComponentProps) { const { sportsSeason, diff --git a/app/services/match-sync/__tests__/pandascore.test.ts b/app/services/match-sync/__tests__/pandascore.test.ts index c982fbe..d95cee4 100644 --- a/app/services/match-sync/__tests__/pandascore.test.ts +++ b/app/services/match-sync/__tests__/pandascore.test.ts @@ -116,15 +116,16 @@ describe("PandaScoreMatchSyncAdapter", () => { const [m] = await new PandaScoreMatchSyncAdapter().fetchMatches("9999"); expect(m.subGames).toHaveLength(3); - expect(m.subGames![0].gameNumber).toBe(1); - expect(m.subGames![0].gameLabel).toBe("Mirage"); - expect(m.subGames![0].team1Score).toBe(16); - expect(m.subGames![0].team2Score).toBe(14); - expect(m.subGames![0].winnerExternalId).toBe("1"); - expect(m.subGames![0].status).toBe("complete"); + const subGames = m.subGames ?? []; + expect(subGames[0].gameNumber).toBe(1); + expect(subGames[0].gameLabel).toBe("Mirage"); + expect(subGames[0].team1Score).toBe(16); + expect(subGames[0].team2Score).toBe(14); + expect(subGames[0].winnerExternalId).toBe("1"); + expect(subGames[0].status).toBe("complete"); - expect(m.subGames![1].gameLabel).toBe("Inferno"); - expect(m.subGames![1].winnerExternalId).toBe("2"); + expect(subGames[1].gameLabel).toBe("Inferno"); + expect(subGames[1].winnerExternalId).toBe("2"); }); it("maps status values correctly", async () => { diff --git a/app/services/match-sync/espn-schedule.ts b/app/services/match-sync/espn-schedule.ts index 72771b2..cfef8ec 100644 --- a/app/services/match-sync/espn-schedule.ts +++ b/app/services/match-sync/espn-schedule.ts @@ -29,7 +29,7 @@ function mapStatus(name: string | undefined, completed: boolean | undefined): Ma } function parseScore(s: string | undefined): number | null { - if (s == null || s === "") return null; + if (s === undefined || s === "") return null; const n = parseInt(s, 10); return Number.isFinite(n) ? n : null; } @@ -44,7 +44,7 @@ export class EspnScheduleAdapter implements MatchSyncAdapter { // externalSeasonId is "YYYY" year string for ESPN sports async fetchMatches(externalSeasonId: string): Promise { - const seasonTypeParam = this.seasonType != null ? `&seasontype=${this.seasonType}` : ""; + const seasonTypeParam = this.seasonType !== undefined ? `&seasontype=${this.seasonType}` : ""; const url = `https://site.api.espn.com/apis/site/v2/sports/${this.sport}/scoreboard?limit=1000&dates=${externalSeasonId}${seasonTypeParam}`; const resp = await fetch(url); if (!resp.ok) { diff --git a/app/services/match-sync/index.ts b/app/services/match-sync/index.ts index b8ac3c8..8435753 100644 --- a/app/services/match-sync/index.ts +++ b/app/services/match-sync/index.ts @@ -1,5 +1,5 @@ import { database } from "~/database/context"; -import { eq, isNull, or } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import * as schema from "~/database/schema"; import { findParticipantsBySportsSeasonId, updateParticipant } from "~/models/season-participant"; import { upsertSeasonMatchBulk, upsertMatchSubGame } from "~/models/season-match"; @@ -9,7 +9,7 @@ import { findMatchingTeamName } from "~/lib/normalize-team-name"; import { getBracketTemplate } from "~/lib/bracket-templates"; import { PandaScoreMatchSyncAdapter } from "./pandascore"; import { EspnScheduleAdapter } from "./espn-schedule"; -import type { MatchSyncAdapter, MatchSyncResult, MatchRecord } from "./types"; +import type { MatchSyncAdapter, MatchSyncResult } from "./types"; import { logger } from "~/lib/logger"; function getMatchSyncAdapter(simulatorType: string): MatchSyncAdapter { @@ -61,7 +61,7 @@ export async function syncMatches(sportsSeasonId: string): Promise p.name); const participantByExternalId = new Map( - participants.filter((p) => p.externalId).map((p) => [p.externalId!, p]) + participants.filter((p) => p.externalId).map((p) => [p.externalId as string, p]) ); const participantByName = new Map(participants.map((p) => [p.name, p])); @@ -86,8 +86,8 @@ export async function syncMatches(sportsSeasonId: string): Promise m.matchStage != null); - const bracketMatches = fetchedMatches.filter((m) => m.matchStage == null); + const swissMatches = fetchedMatches.filter((m) => m.matchStage !== null && m.matchStage !== undefined); + const bracketMatches = fetchedMatches.filter((m) => m.matchStage === null || m.matchStage === undefined); const unmatchedTeams: MatchSyncResult["unmatchedTeams"] = []; const errors: MatchSyncResult["errors"] = []; @@ -235,7 +235,7 @@ export async function syncMatches(sportsSeasonId: string): Promise r.name === playoffMatch!.round + (r) => r.name === playoffMatch.round )?.isScoring; await processMatchResult({ diff --git a/app/services/match-sync/pandascore.ts b/app/services/match-sync/pandascore.ts index 6458f81..69d277f 100644 --- a/app/services/match-sync/pandascore.ts +++ b/app/services/match-sync/pandascore.ts @@ -103,7 +103,7 @@ export class PandaScoreMatchSyncAdapter implements MatchSyncAdapter { const subGames: SubGameRecord[] = (m.games ?? []).map((g): SubGameRecord => { const t1score = g.teams?.find((t) => t.team.id === team1?.id)?.score ?? null; const t2score = g.teams?.find((t) => t.team.id === team2?.id)?.score ?? null; - const winnerExternalId = g.winner?.id != null ? String(g.winner.id) : null; + const winnerExternalId = g.winner !== null && g.winner !== undefined ? String(g.winner.id) : null; return { externalGameId: String(g.id), gameNumber: g.position, @@ -123,7 +123,7 @@ export class PandaScoreMatchSyncAdapter implements MatchSyncAdapter { team2Name: team2?.name ?? "", team1Score: r1?.score ?? null, team2Score: r2?.score ?? null, - winnerExternalId: m.winner_id != null ? String(m.winner_id) : null, + winnerExternalId: m.winner_id !== null && m.winner_id !== undefined ? String(m.winner_id) : null, status: mapStatus(m.status), scheduledAt: m.scheduled_at ? new Date(m.scheduled_at) : null, startedAt: m.begin_at ? new Date(m.begin_at) : null,