TypeScript source code review & clean-up (all 10 modules)#427
Merged
Conversation
- card.ts: deserializeCard validates input via isCardLike before casting; isTrump guards Suit.None trumpSuit (joker-pair declarations) - ai.ts: trumpInfo required (always present when AI invoked); suitDistribution typed as Partial<Record<Suit,number>>; suitVoids narrowed to Set<PlayableSuit> - trumpDeclaration.ts: detectPossibleDeclarations returns DeclarationOpportunity[]; card.suit check uses !== Suit.None instead of truthiness - Ripple fix: add PlayableSuit import + cast at all suitVoids call sites; remove now-redundant trumpInfo || fallbacks in teammateAnalysis; add trumpInfo to test helper createAIGameContext
- cardSorting: type getSuitOrderValue/getRankOrderValue with Suit/Rank enums instead of string; remove always-true card field truthiness guards - suitHelpers: remove dead joker ternary (both branches were identical); document that no distinct Unicode emoji exists for big/small joker - cardAutoSelection: remove redundant inner && trumpInfo check (already guarded by the outer condition on line 92) - translationHelpers: fix dead assignment (both ternary branches identical); narrow TranslationFunction from any to minimal typed interface - gameInitialization: replace biased sort-shuffle with Fisher-Yates for uniform bot name index distribution - gameStatePersistence: fix infinite retry loop in getItem (was passing retries instead of retries+1, so count never incremented)
- cardValue: remove misleading || 0 fallback on exhaustive Record<Rank, number> (suppressed bugs silently; direct indexing is correct and safe) - tractorLogic: type getSuitOffset param as Suit instead of string; update switch cases from raw string literals to Suit.Spades/Hearts/Clubs/Diamonds - multiComboAnalysis: remove dead else branch in groupCardsBySuitOrTrump (non-trump cards always have a valid suit; the fallback to Suit.None was unreachable dead code) - comboDetection: extract getLeadingSuit as exported module-level function; remove always-true card.suit guard; delete two in-file local duplicates - playValidation: remove third local copy of getLeadingSuit; import the single source of truth from comboDetection instead
…o run.sh, and clean up workspace script paths
Bugs fixed:
- useGameState: fix stale-closure on restore path — restoredState declared
before completed-trick block; currentTrick cleared inline instead of via
setTimeout(handleTrickResultComplete) which captured null gameState
- useGameStatePersistence: settings={} default created new object ref each
render causing useMemo/triggerAutoSave to recompute every render; fixed by
destructuring individual boolean fields with primitive defaults
- useProgressiveDealing: replace 3× magic string "dealing" with GamePhase.Dealing
Dead code removed:
- useGameState: remove initGame no-op and its call-site in GameScreenController
- useGameState: remove redundant inner if(result.completedTrick) guard (already
asserted by outer condition)
- useGameStatePersistence: remove unused checkForSavedGame, useGameRestoration,
RestorationData interface, and imports (hasSavedGame, getSavedGameMetadata,
PersistedGameState); move MIN_SAVE_INTERVAL to module scope
Style / type safety:
- useAITurns: waitingPlayerId PlayerId|null instead of "" as PlayerId cast;
de-duplicate trickComplete expression; remove redundant sub-field deps from
effect dependency array
- useTranslation: remove unnecessary ?. and || fallback on changeLanguage (×5)
- useTrickResults: fix wrong comment on onTrickResultCompleteRef; tighten
auto-hide effect deps to [showTrickResult]
Dead code removed: - aiGameContext: remove isOpponentWinning from TrickWinnerAnalysis (never read anywhere; trivially derivable as !isTeammateWinning) - aiGameContext: remove isLeadWinning from TrickWinnerAnalysis (never read anywhere in src/ or tests) - aiGameContext: remove dead trumpInfo fallback in getRemainingUnseenCards — context.trumpInfo is required (hardened in module 1); or-fallback was dead - aiGameContext: remove unreachable if(!trumpInfo) guard directly below Type/style fixes: - aiGameContext: un-export 4 internal-only helpers (isPlayerOnAttackingTeam, getCurrentAttackingPoints, calculateCardsRemaining, calculatePointPressure) that have no external callers; keep analyzeTrickWinner and getTrickPosition exported (used in tests) - aiGameContext: add post-switch return to getTrickPosition to satisfy tsc (switch on number is not exhaustively provable); remove dead default case - aiCardMemory: remove redundant if(suitKey !== undefined) guard — card.suit is typed as Suit enum, never undefined Test updates: - Remove isOpponentWinning/isLeadWinning from TrickWinnerAnalysis fixtures - Replace isOpponentWinning assertions with !isTeammateWinning equivalents
- [BUG] validCombosDecision.ts: Fix dead filter body in attemptToBeatWithThreshold
- strongBeatingCombos.filter used block-body arrow without return, so the .some()
result was always discarded (implicit undefined → falsy). Every combo was excluded,
making the threshold-gated beat path completely unreachable. Fixed with expression body.
- [BUG] multiComboFollowingStrategy.ts: Avoid mutating playerHand via in-place .sort()
- selectCrossSuitDisposal called playerHand.sort() which reorders the caller's actual
hand array. Changed to [...playerHand].sort() to copy before sorting.
- [TYPE] teammateAnalysis.ts: Remove unnecessary 'as PlayerId' casts (x2)
- gameState.players[idx]?.id yields string|undefined. The existing && nextPlayerId
guard already narrows it; casting it to PlayerId earlier was redundant and unsafe.
playerMemories is Record<string, PlayerMemory> so no cast needed at the lookup site.
- [STYLE/DEAD] routingLogic.ts: Make trumpInfo required in fallbackSelection
- trumpInfo was optional but always provided by routeToDecision. The else branch
returning [playerHand[0]] was unreachable dead code. Made required, removed branch.
- [STYLE] suitAvailabilityAnalysis.ts: Add block scope to case const declarations
- Wrapped ComboType.Pair and ComboType.Tractor case bodies in { } to eliminate
'lexical declaration in case clause' lint warnings.
- [TYPE] candidateLeadDetection.ts: Rename local 'isTrump' to 'isTrumpSuit'
- const isTrump = suit === Suit.None shadowed the imported isTrump function
within createMetadata. Renamed to isTrumpSuit to eliminate the hazard.
- [PERF] candidateLeadDetection.ts: Hoist createMemoryContext out of for-loop
- getAllUnbeatableCardsInSuit recreated an identical MemoryContext on every
combo iteration. gameState is constant across iterations so hoisting is safe.
- [TYPE] leadingContext.ts + leadingScoring.ts: Tighten voidSuits to Set<PlayableSuit>
- LeadingContext.teammate.voidSuits and opponents.voidSuits were typed Set<Suit>
despite never containing Suit.None (trump voids tracked separately via isTrumpVoid).
- Changed interface fields and constructors to Set<PlayableSuit>.
- Added as PlayableSuit casts at the opponentVoids.add() site and the two
scoreNonTrumpLead voidSuits.has() call sites.
- Added PlayableSuit to leadingScoring.ts import.
…mpDeclaration/
- [DEAD] trumpDeclarationStrategy.ts: Remove always-true shouldDeclare variable
- const shouldDeclare = true followed by shouldDeclare ? x : undefined made the
false branch unreachable. Inlined shouldDeclare: true and declaration directly.
- [TYPE] trumpDeclarationStrategy.ts: Remove redundant if (card.rank) guard
- rank is a required Rank enum field — always truthy. Guard removed in
calculateSuitStrength.
- [STYLE] trumpDeclarationStrategy.ts: Copy options before .sort() in evaluateAllPossibleTrumpSuits
- options.sort() mutated the array stored in the suitGroups Map in-place.
Changed to [...options].sort().
- [TYPE] kittySwapStrategy.ts: Remove redundant pair.cards[0].rank && guard
- rank is a required Rank enum field — the leading truthy check before
bigRanks.includes() was always true. Guard removed in applyBasicExclusions.
- [TYPE] kittySwapStrategy.ts: Remove redundant if (card.suit) guard
- card.suit is a required Suit enum field — always truthy. Guard removed in
selectFromDisposableCards.
- [STYLE] kittySwapStrategy.ts: Fix module header comment rank > 7 -> rank >= 5
- bigRanks array starts at Rank.Five, not Rank.Eight. Header now matches code.
- [DEAD] llmGamePrompt.ts: Remove 4 no-op .replace() calls in buildLLMSystemPrompt
- STATIC_LLM_GAME_RULES contains none of the searched placeholder strings.
All 4 regex replacements were dead. Trump values are already injected via
buildUserPromptTemplate args. Now returns STATIC_LLM_GAME_RULES directly.
- [TYPE] llmPromptTemplates.ts + llmGamePrompt.ts: Remove unused handCardsCount
- handCardsCount was declared in UserPromptTemplateArgs and populated at the
call site (handCards.length) but never read inside buildUserPromptTemplate
or any block builder. Removed from interface and call site.
- [TYPE] llmAIStrategy.ts: Remove redundant config.applyToPlayers && guard
- applyToPlayers is a required string[] field initialized in DEFAULT_LLM_CONFIG.
The leading truthy guard before .includes() was never needed.
- [STYLE] llmGamePrompt.ts: Refactor allCategories mutation to declarative concat
- localBuildHandDisplay spread categoryOrder into a mutable array then pushed
extras. Replaced with filter + spread concat pattern.
…les/
- [STYLE] index.ts: Replace console.warn/console.error with gameLogger
- getStoredLanguage used console.warn; changeLanguageCustom used console.error.
Both replaced with gameLogger.warn/gameLogger.error per codebase convention.
Added gameLogger import.
- [STYLE] index.ts: Remove redundant 'as Language' cast in getStoredLanguage
- The condition (stored === 'en' || stored === 'zh') already narrows stored
to Language. The explicit cast was redundant; removed to let TS narrow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Systematic quality audit of all ~16,250 lines across 60
.tsfiles insrc/. Changes are purely non-functional: dead code removal, type guard fixes, style improvements, and one logic bug fix.Modules
src/types/AIGameContexthardening,PlayableSuitconstraintsrc/utils/src/game/src/hooks/src/ai/coresrc/ai/following/src/ai/leading/PlayableSuittighteningsrc/ai/kittySwap/+trumpDeclaration/src/ai/llm/.replace()calls, unused interface field, redundant guardsrc/locales/gameLoggerconvention, redundant castVerification
npm run qualitycheck)